mirror of
https://github.com/mattermost/focalboard.git
synced 2025-03-23 20:52:42 +02:00
Logger for FocalBoard server (#466)
- structured, asynchronous logging - supports discreet log levels, including custom levels - supports output to console, files, and all common log aggregators. - supports JSON, plain text and GELF formats - lazy formatting and writing
This commit is contained in:
parent
fe6b0d04b3
commit
417de9f837
config.jsonlogging.json
server
webapp
@ -18,5 +18,6 @@
|
||||
"localOnly": false,
|
||||
"enableLocalMode": true,
|
||||
"localModeSocketLocation": "/var/tmp/focalboard_local.socket",
|
||||
"authMode": "native"
|
||||
"authMode": "native",
|
||||
"logging_file": "logging.json"
|
||||
}
|
||||
|
45
logging.json
Normal file
45
logging.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"console": {
|
||||
"type": "console",
|
||||
"options": {
|
||||
"out": "stdout"
|
||||
},
|
||||
"format": "plain",
|
||||
"format_options": {
|
||||
"min_level_len": 5,
|
||||
"min_msg_len": 40,
|
||||
"enable_color": true
|
||||
},
|
||||
"levels": [
|
||||
{"id": 5, "name": "debug"},
|
||||
{"id": 4, "name": "info", "color": 36},
|
||||
{"id": 3, "name": "warn"},
|
||||
{"id": 2, "name": "error", "color": 31},
|
||||
{"id": 1, "name": "fatal", "stacktrace": true},
|
||||
{"id": 0, "name": "panic", "stacktrace": true},
|
||||
{"id": 500, "name": "telemetry", "color":34}
|
||||
],
|
||||
"maxqueuesize": 1000
|
||||
},
|
||||
"file": {
|
||||
"type": "file",
|
||||
"options": {
|
||||
"filename": "focalboard-server.log",
|
||||
"max_size": 1000000,
|
||||
"max_age": 1,
|
||||
"max_backups": 10,
|
||||
"compress": true
|
||||
},
|
||||
"format": "json",
|
||||
"levels": [
|
||||
{"id": 5, "name": "debug"},
|
||||
{"id": 4, "name": "info"},
|
||||
{"id": 3, "name": "warn"},
|
||||
{"id": 2, "name": "error"},
|
||||
{"id": 1, "name": "fatal", "stacktrace": true},
|
||||
{"id": 0, "name": "panic", "stacktrace": true},
|
||||
{"id": 500, "name": "telemetry"}
|
||||
],
|
||||
"maxqueuesize": 1000
|
||||
}
|
||||
}
|
@ -3,11 +3,11 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
)
|
||||
|
||||
type AdminSetPasswordData struct {
|
||||
@ -20,29 +20,29 @@ func (a *API) handleAdminSetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
var requestData AdminSetPasswordData
|
||||
err = json.Unmarshal(requestBody, &requestData)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(requestData.Password, "") {
|
||||
errorResponse(w, http.StatusBadRequest, "password is required", err)
|
||||
a.errorResponse(w, http.StatusBadRequest, "password is required", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.app().UpdateUserPassword(username, requestData.Password)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("AdminSetPassword, username: %s", username)
|
||||
a.logger.Debug("AdminSetPassword, username: %s", mlog.String("username", username))
|
||||
|
||||
jsonStringResponse(w, http.StatusOK, "{}")
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@ -16,6 +15,7 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/focalboard/server/app"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/store"
|
||||
"github.com/mattermost/focalboard/server/utils"
|
||||
)
|
||||
@ -38,13 +38,15 @@ type API struct {
|
||||
authService string
|
||||
singleUserToken string
|
||||
MattermostAuth bool
|
||||
logger *mlog.Logger
|
||||
}
|
||||
|
||||
func NewAPI(appBuilder func() *app.App, singleUserToken string, authService string) *API {
|
||||
func NewAPI(appBuilder func() *app.App, singleUserToken string, authService string, logger *mlog.Logger) *API {
|
||||
return &API{
|
||||
appBuilder: appBuilder,
|
||||
singleUserToken: singleUserToken,
|
||||
authService: authService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,8 +95,8 @@ func (a *API) RegisterAdminRoutes(r *mux.Router) {
|
||||
func (a *API) requireCSRFToken(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !a.checkCSRFToken(r) {
|
||||
log.Println("checkCSRFToken FAILED")
|
||||
errorResponse(w, http.StatusBadRequest, "", nil)
|
||||
a.logger.Error("checkCSRFToken FAILED")
|
||||
a.errorResponse(w, http.StatusBadRequest, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -122,7 +124,7 @@ func (a *API) hasValidReadTokenForBlock(r *http.Request, container store.Contain
|
||||
|
||||
isValid, err := a.app().IsValidReadToken(container, blockID, readToken)
|
||||
if err != nil {
|
||||
log.Printf("IsValidReadToken ERROR: %v", err)
|
||||
a.logger.Error("IsValidReadToken ERROR", mlog.Err(err))
|
||||
return false
|
||||
}
|
||||
|
||||
@ -224,21 +226,25 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
blockType := query.Get("type")
|
||||
container, err := a.getContainer(r)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
blocks, err := a.app().GetBlocks(*container, parentID, blockType)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
// log.Printf("GetBlocks parentID: %s, type: %s, %d result(s)", parentID, blockType, len(blocks))
|
||||
a.logger.Debug("GetBlocks",
|
||||
mlog.String("parentID", parentID),
|
||||
mlog.String("blockType", blockType),
|
||||
mlog.Int("block_count", len(blocks)),
|
||||
)
|
||||
|
||||
json, err := json.Marshal(blocks)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -292,13 +298,13 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
container, err := a.getContainer(r)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -306,7 +312,7 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err = json.Unmarshal(requestBody, &blocks)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -314,19 +320,19 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
// Error checking
|
||||
if len(block.Type) < 1 {
|
||||
message := fmt.Sprintf("missing type for block id %s", block.ID)
|
||||
errorResponse(w, http.StatusBadRequest, message, nil)
|
||||
a.errorResponse(w, http.StatusBadRequest, message, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if block.CreateAt < 1 {
|
||||
message := fmt.Sprintf("invalid createAt for block id %s", block.ID)
|
||||
errorResponse(w, http.StatusBadRequest, message, nil)
|
||||
a.errorResponse(w, http.StatusBadRequest, message, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if block.UpdateAt < 1 {
|
||||
message := fmt.Sprintf("invalid UpdateAt for block id %s", block.ID)
|
||||
errorResponse(w, http.StatusBadRequest, message, nil)
|
||||
a.errorResponse(w, http.StatusBadRequest, message, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -335,11 +341,11 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err = a.app().InsertBlocks(*container, blocks)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("POST Blocks %d block(s)", len(blocks))
|
||||
a.logger.Debug("POST Blocks", mlog.Int("block_count", len(blocks)))
|
||||
jsonStringResponse(w, http.StatusOK, "{}")
|
||||
}
|
||||
|
||||
@ -374,13 +380,13 @@ func (a *API) handleGetUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
user, err := a.app().GetUser(userID)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
userData, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -424,14 +430,14 @@ func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
user, err = a.app().GetUser(session.UserID)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
userData, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -476,18 +482,18 @@ func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
container, err := a.getContainer(r)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.app().DeleteBlock(*container, blockID, userID)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("DELETE Block %s", blockID)
|
||||
a.logger.Debug("DELETE Block", mlog.String("blockID", blockID))
|
||||
jsonStringResponse(w, http.StatusOK, "{}")
|
||||
}
|
||||
|
||||
@ -536,7 +542,7 @@ func (a *API) handleGetSubTree(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
container, err := a.getContainerAllowingReadTokenForBlock(r, blockID)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -547,21 +553,25 @@ func (a *API) handleGetSubTree(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if levels != 2 && levels != 3 {
|
||||
log.Printf(`ERROR Invalid levels: %d`, levels)
|
||||
errorResponse(w, http.StatusBadRequest, "invalid levels", nil)
|
||||
a.logger.Error("Invalid levels", mlog.Int64("levels", levels))
|
||||
a.errorResponse(w, http.StatusBadRequest, "invalid levels", nil)
|
||||
return
|
||||
}
|
||||
|
||||
blocks, err := a.app().GetSubTree(*container, blockID, int(levels))
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("GetSubTree (%v) blockID: %s, %d result(s)", levels, blockID, len(blocks))
|
||||
a.logger.Debug("GetSubTree",
|
||||
mlog.Int64("levels", levels),
|
||||
mlog.String("blockID", blockID),
|
||||
mlog.Int("block_count", len(blocks)),
|
||||
)
|
||||
json, err := json.Marshal(blocks)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -600,7 +610,7 @@ func (a *API) handleExport(w http.ResponseWriter, r *http.Request) {
|
||||
rootID := query.Get("root_id")
|
||||
container, err := a.getContainer(r)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -611,13 +621,13 @@ func (a *API) handleExport(w http.ResponseWriter, r *http.Request) {
|
||||
blocks, err = a.app().GetBlocksWithRootID(*container, rootID)
|
||||
}
|
||||
|
||||
log.Printf("%d raw block(s)", len(blocks))
|
||||
a.logger.Debug("raw blocks", mlog.Int("block_count", len(blocks)))
|
||||
blocks = filterOrphanBlocks(blocks)
|
||||
log.Printf("EXPORT %d filtered block(s)", len(blocks))
|
||||
a.logger.Debug("EXPORT filtered blocks", mlog.Int("block_count", len(blocks)))
|
||||
|
||||
json, err := json.Marshal(blocks)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -702,13 +712,13 @@ func (a *API) handleImport(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
container, err := a.getContainer(r)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -716,7 +726,7 @@ func (a *API) handleImport(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err = json.Unmarshal(requestBody, &blocks)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -724,11 +734,11 @@ func (a *API) handleImport(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err = a.app().InsertBlocks(*container, blocks)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("IMPORT Blocks %d block(s)", len(blocks))
|
||||
a.logger.Debug("IMPORT Blocks", mlog.Int("block_count", len(blocks)))
|
||||
jsonStringResponse(w, http.StatusOK, "{}")
|
||||
}
|
||||
|
||||
@ -770,23 +780,23 @@ func (a *API) handleGetSharing(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
container, err := a.getContainer(r)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
sharing, err := a.app().GetSharing(*container, rootID)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
sharingData, err := json.Marshal(sharing)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("GET sharing %s", rootID)
|
||||
a.logger.Debug("GET sharing", mlog.String("rootID", rootID))
|
||||
jsonBytesResponse(w, http.StatusOK, sharingData)
|
||||
}
|
||||
|
||||
@ -827,13 +837,13 @@ func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
container, err := a.getContainer(r)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -841,7 +851,7 @@ func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err = json.Unmarshal(requestBody, &sharing)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -856,11 +866,11 @@ func (a *API) handlePostSharing(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err = a.app().UpsertSharing(*container, sharing)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("POST sharing %s", sharing.ID)
|
||||
a.logger.Debug("POST sharing", mlog.String("sharingID", sharing.ID))
|
||||
jsonStringResponse(w, http.StatusOK, "{}")
|
||||
}
|
||||
|
||||
@ -902,29 +912,29 @@ func (a *API) handleGetWorkspace(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
session := ctx.Value("session").(*model.Session)
|
||||
if !a.app().DoesUserHaveWorkspaceAccess(session.UserID, workspaceID) {
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
workspace, err = a.app().GetWorkspace(workspaceID)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
}
|
||||
if workspace == nil {
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
workspace, err = a.app().GetRootWorkspace()
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
workspaceData, err := json.Marshal(workspace)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -957,7 +967,7 @@ func (a *API) handlePostWorkspaceRegenerateSignupToken(w http.ResponseWriter, r
|
||||
|
||||
workspace, err := a.app().GetRootWorkspace()
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -965,7 +975,7 @@ func (a *API) handlePostWorkspaceRegenerateSignupToken(w http.ResponseWriter, r
|
||||
|
||||
err = a.app().UpsertWorkspaceSignupToken(*workspace)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1018,7 +1028,7 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
|
||||
// Caller must have access to the root block's container
|
||||
_, err := a.getContainerAllowingReadTokenForBlock(r, rootID)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1033,7 +1043,7 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
fileReader, err := a.app().GetFileReader(workspaceID, rootID, filename)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
defer fileReader.Close()
|
||||
@ -1092,7 +1102,7 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
|
||||
// Caller must have access to the root block's container
|
||||
_, err := a.getContainerAllowingReadTokenForBlock(r, rootID)
|
||||
if err != nil {
|
||||
noContainerErrorResponse(w, err)
|
||||
a.noContainerErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1106,14 +1116,17 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
fileId, err := a.app().SaveFile(file, workspaceID, rootID, handle.Filename)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("uploadFile, filename: %s, fileId: %s", handle.Filename, fileId)
|
||||
a.logger.Debug("uploadFile",
|
||||
mlog.String("filename", handle.Filename),
|
||||
mlog.String("fileID", fileId),
|
||||
)
|
||||
data, err := json.Marshal(FileUploadResponse{FileID: fileId})
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1122,6 +1135,39 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Response helpers
|
||||
|
||||
func (a *API) errorResponse(w http.ResponseWriter, code int, message string, sourceError error) {
|
||||
a.logger.Error("API ERROR",
|
||||
mlog.Int("code", code),
|
||||
mlog.Err(sourceError),
|
||||
)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, err := json.Marshal(model.ErrorResponse{Error: message, ErrorCode: code})
|
||||
if err != nil {
|
||||
data = []byte("{}")
|
||||
}
|
||||
w.WriteHeader(code)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func (a *API) errorResponseWithCode(w http.ResponseWriter, statusCode int, errorCode int, message string, sourceError error) {
|
||||
a.logger.Error("API ERROR",
|
||||
mlog.Int("status", statusCode),
|
||||
mlog.Int("code", errorCode),
|
||||
mlog.Err(sourceError),
|
||||
)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, err := json.Marshal(model.ErrorResponse{Error: message, ErrorCode: errorCode})
|
||||
if err != nil {
|
||||
data = []byte("{}")
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func (a *API) noContainerErrorResponse(w http.ResponseWriter, sourceError error) {
|
||||
a.errorResponseWithCode(w, http.StatusBadRequest, ERROR_NO_WORKSPACE_CODE, ERROR_NO_WORKSPACE_MESSAGE, sourceError)
|
||||
}
|
||||
|
||||
func jsonStringResponse(w http.ResponseWriter, code int, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
@ -1134,32 +1180,6 @@ func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) {
|
||||
w.Write(json)
|
||||
}
|
||||
|
||||
func errorResponse(w http.ResponseWriter, code int, message string, sourceError error) {
|
||||
log.Printf("API ERROR %d, err: %v\n", code, sourceError)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, err := json.Marshal(model.ErrorResponse{Error: message, ErrorCode: code})
|
||||
if err != nil {
|
||||
data = []byte("{}")
|
||||
}
|
||||
w.WriteHeader(code)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func errorResponseWithCode(w http.ResponseWriter, statusCode int, errorCode int, message string, sourceError error) {
|
||||
log.Printf("API ERROR status %d, errorCode: %d, err: %v\n", statusCode, errorCode, sourceError)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, err := json.Marshal(model.ErrorResponse{Error: message, ErrorCode: errorCode})
|
||||
if err != nil {
|
||||
data = []byte("{}")
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func noContainerErrorResponse(w http.ResponseWriter, sourceError error) {
|
||||
errorResponseWithCode(w, http.StatusBadRequest, ERROR_NO_WORKSPACE_CODE, ERROR_NO_WORKSPACE_MESSAGE, sourceError)
|
||||
}
|
||||
|
||||
func addUserID(rw http.ResponseWriter, req *http.Request, next http.Handler) {
|
||||
ctx := context.WithValue(req.Context(), "userid", req.Header.Get("userid"))
|
||||
req = req.WithContext(ctx)
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -15,6 +14,7 @@ import (
|
||||
serverContext "github.com/mattermost/focalboard/server/context"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/auth"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
)
|
||||
|
||||
// LoginRequest is a login request
|
||||
@ -154,32 +154,32 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if len(a.singleUserToken) > 0 {
|
||||
// Not permitted in single-user mode
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
var loginData LoginRequest
|
||||
err = json.Unmarshal(requestBody, &loginData)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
if loginData.Type == "normal" {
|
||||
token, err := a.app().Login(loginData.Username, loginData.Email, loginData.Password, loginData.MfaToken)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusUnauthorized, "incorrect login", err)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "incorrect login", err)
|
||||
return
|
||||
}
|
||||
json, err := json.Marshal(LoginResponse{Token: token})
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
errorResponse(w, http.StatusBadRequest, "invalid login type", nil)
|
||||
a.errorResponse(w, http.StatusBadRequest, "invalid login type", nil)
|
||||
}
|
||||
|
||||
func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
@ -217,20 +217,20 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if len(a.singleUserToken) > 0 {
|
||||
// Not permitted in single-user mode
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
var registerData RegisterRequest
|
||||
err = json.Unmarshal(requestBody, ®isterData)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -238,35 +238,35 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
if len(registerData.Token) > 0 {
|
||||
workspace, err := a.app().GetRootWorkspace()
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
if registerData.Token != workspace.SignupToken {
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// No signup token, check if no active users
|
||||
userCount, err := a.app().GetRegisteredUserCount()
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
if userCount > 0 {
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = registerData.IsValid(); err != nil {
|
||||
errorResponse(w, http.StatusBadRequest, err.Error(), err)
|
||||
a.errorResponse(w, http.StatusBadRequest, err.Error(), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = a.app().RegisterUser(registerData.Username, registerData.Email, registerData.Password)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusBadRequest, err.Error(), err)
|
||||
a.errorResponse(w, http.StatusBadRequest, err.Error(), err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -309,7 +309,7 @@ func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if len(a.singleUserToken) > 0 {
|
||||
// Not permitted in single-user mode
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -318,23 +318,23 @@ func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
requestBody, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
var requestData ChangePasswordRequest
|
||||
if err := json.Unmarshal(requestBody, &requestData); err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = requestData.IsValid(); err != nil {
|
||||
errorResponse(w, http.StatusBadRequest, err.Error(), err)
|
||||
a.errorResponse(w, http.StatusBadRequest, err.Error(), err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = a.app().ChangePassword(userID, requestData.OldPassword, requestData.NewPassword); err != nil {
|
||||
errorResponse(w, http.StatusBadRequest, err.Error(), err)
|
||||
a.errorResponse(w, http.StatusBadRequest, err.Error(), err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -349,10 +349,10 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
token, _ := auth.ParseAuthTokenFromRequest(r)
|
||||
|
||||
log.Printf(`Single User: %v`, len(a.singleUserToken) > 0)
|
||||
a.logger.Debug(`attachSession`, mlog.Bool("single_user", len(a.singleUserToken) > 0))
|
||||
if len(a.singleUserToken) > 0 {
|
||||
if required && (token != a.singleUserToken) {
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
@ -391,7 +391,7 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)
|
||||
session, err := a.app().GetSession(token)
|
||||
if err != nil {
|
||||
if required {
|
||||
errorResponse(w, http.StatusUnauthorized, "", err)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -401,8 +401,12 @@ func (a *API) attachSession(handler func(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
authService := session.AuthService
|
||||
if authService != a.authService {
|
||||
log.Printf(`Session '%s' authService mismatch '%s' instead of '%s'`, session.ID, authService, a.authService)
|
||||
errorResponse(w, http.StatusUnauthorized, "", err)
|
||||
a.logger.Error(`Session authService mismatch`,
|
||||
mlog.String("sessionID", session.ID),
|
||||
mlog.String("want", a.authService),
|
||||
mlog.String("got", authService),
|
||||
)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -416,7 +420,7 @@ func (a *API) adminRequired(handler func(w http.ResponseWriter, r *http.Request)
|
||||
// Currently, admin APIs require local unix connections
|
||||
conn := serverContext.GetContextConn(r)
|
||||
if _, isUnix := conn.(*net.UnixConn); !isUnix {
|
||||
errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
a.errorResponse(w, http.StatusUnauthorized, "", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,11 @@ package app
|
||||
import (
|
||||
"github.com/mattermost/focalboard/server/auth"
|
||||
"github.com/mattermost/focalboard/server/services/config"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/store"
|
||||
"github.com/mattermost/focalboard/server/services/webhook"
|
||||
"github.com/mattermost/focalboard/server/ws"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/shared/filestore"
|
||||
)
|
||||
|
||||
@ -16,6 +18,7 @@ type App struct {
|
||||
wsServer *ws.Server
|
||||
filesBackend filestore.FileBackend
|
||||
webhook *webhook.Client
|
||||
logger *mlog.Logger
|
||||
}
|
||||
|
||||
func New(
|
||||
@ -25,6 +28,7 @@ func New(
|
||||
wsServer *ws.Server,
|
||||
filesBackend filestore.FileBackend,
|
||||
webhook *webhook.Client,
|
||||
logger *mlog.Logger,
|
||||
) *App {
|
||||
return &App{
|
||||
config: config,
|
||||
@ -33,5 +37,6 @@ func New(
|
||||
wsServer: wsServer,
|
||||
filesBackend: filesBackend,
|
||||
webhook: webhook,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/auth"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/store"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -80,7 +79,7 @@ func (a *App) Login(username, email, password, mfaToken string) (string, error)
|
||||
}
|
||||
|
||||
if !auth.ComparePassword(user.Password, password) {
|
||||
log.Printf("Invalid password for userID: %s\n", user.ID)
|
||||
a.logger.Debug("Invalid password for user", mlog.String("userID", user.ID))
|
||||
return "", errors.New("invalid username or password")
|
||||
}
|
||||
|
||||
@ -175,7 +174,7 @@ func (a *App) ChangePassword(userID, oldPassword, newPassword string) error {
|
||||
}
|
||||
|
||||
if !auth.ComparePassword(user.Password, oldPassword) {
|
||||
log.Printf("Invalid password for userID: %s\n", user.ID)
|
||||
a.logger.Debug("Invalid password for user", mlog.String("userID", user.ID))
|
||||
return errors.New("invalid username or password")
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/utils"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/shared/filestore"
|
||||
)
|
||||
|
||||
@ -46,9 +47,9 @@ func (a *App) GetFileReader(workspaceID, rootID, filename string) (filestore.Rea
|
||||
if oldExists {
|
||||
err := a.filesBackend.MoveFile(filename, filePath)
|
||||
if err != nil {
|
||||
log.Printf("ERROR moving old file from '%s' to '%s'", filename, filePath)
|
||||
a.logger.Error("ERROR moving file", mlog.String("old", filename), mlog.String("new", filePath))
|
||||
} else {
|
||||
log.Printf("Moved old file from '%s' to '%s'", filename, filePath)
|
||||
a.logger.Debug("Moved file", mlog.String("old", filename), mlog.String("new", filePath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,11 @@ import (
|
||||
|
||||
"github.com/mattermost/focalboard/server/auth"
|
||||
"github.com/mattermost/focalboard/server/services/config"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/store/mockstore"
|
||||
"github.com/mattermost/focalboard/server/services/webhook"
|
||||
"github.com/mattermost/focalboard/server/ws"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/shared/filestore/mocks"
|
||||
)
|
||||
|
||||
@ -27,10 +29,12 @@ func SetupTestHelper(t *testing.T) *TestHelper {
|
||||
cfg := config.Configuration{}
|
||||
store := mockstore.NewMockStore(ctrl)
|
||||
auth := auth.New(&cfg, store)
|
||||
logger := mlog.NewLogger()
|
||||
logger.Configure("", cfg.LoggingEscapedJson)
|
||||
sessionToken := "TESTTOKEN"
|
||||
wsserver := ws.NewServer(auth, sessionToken, false)
|
||||
webhook := webhook.NewClient(&cfg)
|
||||
app2 := New(&cfg, store, auth, wsserver, &mocks.FileBackend{}, webhook)
|
||||
wsserver := ws.NewServer(auth, sessionToken, false, logger)
|
||||
webhook := webhook.NewClient(&cfg, logger)
|
||||
app2 := New(&cfg, store, auth, wsserver, &mocks.FileBackend{}, webhook, logger)
|
||||
|
||||
return &TestHelper{
|
||||
App: app2,
|
||||
|
@ -2,9 +2,9 @@ package app
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/utils"
|
||||
)
|
||||
|
||||
@ -18,16 +18,16 @@ func (a *App) GetRootWorkspace() (*model.Workspace, error) {
|
||||
}
|
||||
err := a.store.UpsertWorkspaceSignupToken(*workspace)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to initialize workspace", err)
|
||||
a.logger.Fatal("Unable to initialize workspace", mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
workspace, err = a.store.GetWorkspace(workspaceID)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to get initialized workspace", err)
|
||||
a.logger.Fatal("Unable to get initialized workspace", mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("initialized workspace")
|
||||
a.logger.Info("initialized workspace")
|
||||
}
|
||||
|
||||
return workspace, nil
|
||||
|
@ -12,6 +12,7 @@ require (
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/lib/pq v1.10.0
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattermost/logr/v2 v2.0.0-20210525034931-179e4b3c986d
|
||||
github.com/mattermost/mattermost-server/v5 v5.3.2-0.20210524045451-a4f7df6f6e3c
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
@ -25,7 +26,7 @@ require (
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tidwall/gjson v1.7.3 // indirect
|
||||
go.uber.org/zap v1.16.0
|
||||
go.uber.org/zap v1.16.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 // indirect
|
||||
|
@ -606,6 +606,8 @@ github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ
|
||||
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
|
||||
github.com/mattermost/logr v1.0.13 h1:6F/fM3csvH6Oy5sUpJuW7YyZSzZZAhJm5VcgKMxA2P8=
|
||||
github.com/mattermost/logr v1.0.13/go.mod h1:Mt4DPu1NXMe6JxPdwCC0XBoxXmN9eXOIRPoZarU2PXs=
|
||||
github.com/mattermost/logr/v2 v2.0.0-20210525034931-179e4b3c986d h1:MfG19tMusEOb0k/UBrLQFYhHnLOWt8vcuGDzDQ1fvJw=
|
||||
github.com/mattermost/logr/v2 v2.0.0-20210525034931-179e4b3c986d/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
|
||||
github.com/mattermost/mattermost-server/v5 v5.3.2-0.20210524045451-a4f7df6f6e3c h1:p0C9yt6UYyTExEeHjBPBUCwCMAyTWvwAEc2/plNuZL4=
|
||||
github.com/mattermost/mattermost-server/v5 v5.3.2-0.20210524045451-a4f7df6f6e3c/go.mod h1:6CqGEG0Vnhrl23h8LB+lcOIT8KIUhzbJ7qhXlV7Ek9U=
|
||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
|
||||
|
@ -1,7 +1,6 @@
|
||||
package integrationtests
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
@ -9,6 +8,7 @@ import (
|
||||
"github.com/mattermost/focalboard/server/client"
|
||||
"github.com/mattermost/focalboard/server/server"
|
||||
"github.com/mattermost/focalboard/server/services/config"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
@ -27,22 +27,47 @@ func getTestConfig() *config.Configuration {
|
||||
connectionString = ":memory:"
|
||||
}
|
||||
|
||||
logging := []byte(`
|
||||
{
|
||||
"testing": {
|
||||
"type": "console",
|
||||
"options": {
|
||||
"out": "stdout"
|
||||
},
|
||||
"format": "plain",
|
||||
"format_options": {
|
||||
"delim": " "
|
||||
},
|
||||
"levels": [
|
||||
{"id": 5, "name": "debug"},
|
||||
{"id": 4, "name": "info"},
|
||||
{"id": 3, "name": "warn"},
|
||||
{"id": 2, "name": "error", "stacktrace": true},
|
||||
{"id": 1, "name": "fatal", "stacktrace": true},
|
||||
{"id": 0, "name": "panic", "stacktrace": true}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
return &config.Configuration{
|
||||
ServerRoot: "http://localhost:8888",
|
||||
Port: 8888,
|
||||
DBType: dbType,
|
||||
DBConfigString: connectionString,
|
||||
DBTablePrefix: "test_",
|
||||
WebPath: "./pack",
|
||||
FilesDriver: "local",
|
||||
FilesPath: "./files",
|
||||
ServerRoot: "http://localhost:8888",
|
||||
Port: 8888,
|
||||
DBType: dbType,
|
||||
DBConfigString: connectionString,
|
||||
DBTablePrefix: "test_",
|
||||
WebPath: "./pack",
|
||||
FilesDriver: "local",
|
||||
FilesPath: "./files",
|
||||
LoggingEscapedJson: string(logging),
|
||||
}
|
||||
}
|
||||
|
||||
func SetupTestHelper() *TestHelper {
|
||||
sessionToken := "TESTTOKEN"
|
||||
th := &TestHelper{}
|
||||
srv, err := server.New(getTestConfig(), sessionToken)
|
||||
logger := mlog.NewLogger()
|
||||
logger.Configure("", getTestConfig().LoggingEscapedJson)
|
||||
srv, err := server.New(getTestConfig(), sessionToken, logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -61,10 +86,10 @@ func (th *TestHelper) InitBasic() *TestHelper {
|
||||
|
||||
for {
|
||||
URL := th.Server.Config().ServerRoot
|
||||
log.Printf("Polling server at %v", URL)
|
||||
th.Server.Logger().Info("Polling server", mlog.String("url", URL))
|
||||
resp, err := http.Get(URL)
|
||||
if err != nil {
|
||||
log.Println("Polling failed:", err)
|
||||
th.Server.Logger().Error("Polling failed", mlog.Err(err))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
@ -72,12 +97,12 @@ func (th *TestHelper) InitBasic() *TestHelper {
|
||||
|
||||
// Currently returns 404
|
||||
// if resp.StatusCode != http.StatusOK {
|
||||
// log.Println("Not OK:", resp.StatusCode)
|
||||
// th.Server.Logger().Error("Not OK", mlog.Int("statusCode", resp.StatusCode))
|
||||
// continue
|
||||
// }
|
||||
|
||||
// Reached this point: server is up and running!
|
||||
log.Println("Server ping OK, statusCode:", resp.StatusCode)
|
||||
th.Server.Logger().Info("Server ping OK", mlog.Int("statusCode", resp.StatusCode))
|
||||
|
||||
break
|
||||
}
|
||||
@ -86,6 +111,8 @@ func (th *TestHelper) InitBasic() *TestHelper {
|
||||
}
|
||||
|
||||
func (th *TestHelper) TearDown() {
|
||||
defer th.Server.Logger().Shutdown()
|
||||
|
||||
err := th.Server.Shutdown()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -38,6 +38,9 @@ import (
|
||||
"github.com/mattermost/focalboard/server/server"
|
||||
"github.com/mattermost/focalboard/server/services/config"
|
||||
)
|
||||
import (
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
)
|
||||
|
||||
// Active server used with shared code (dll)
|
||||
var pServer *server.Server
|
||||
@ -58,13 +61,13 @@ func isProcessRunning(pid int) bool {
|
||||
}
|
||||
|
||||
// monitorPid is used to keep the server lifetime in sync with another (client app) process
|
||||
func monitorPid(pid int) {
|
||||
log.Printf("Monitoring PID: %d", pid)
|
||||
func monitorPid(pid int, logger *mlog.Logger) {
|
||||
logger.Info("Monitoring PID", mlog.Int("pid", pid))
|
||||
|
||||
go func() {
|
||||
for {
|
||||
if !isProcessRunning(pid) {
|
||||
log.Printf("Monitored process not found, exiting.")
|
||||
logger.Info("Monitored process not found, exiting.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -73,18 +76,17 @@ func monitorPid(pid int) {
|
||||
}()
|
||||
}
|
||||
|
||||
func logInfo() {
|
||||
log.Println("Focalboard Server")
|
||||
log.Println("Version: " + model.CurrentVersion)
|
||||
log.Println("Edition: " + model.Edition)
|
||||
log.Println("Build Number: " + model.BuildNumber)
|
||||
log.Println("Build Date: " + model.BuildDate)
|
||||
log.Println("Build Hash: " + model.BuildHash)
|
||||
func logInfo(logger *mlog.Logger) {
|
||||
logger.Info("FocalBoard Server",
|
||||
mlog.String("version", model.CurrentVersion),
|
||||
mlog.String("edition", model.Edition),
|
||||
mlog.String("build_number", model.BuildNumber),
|
||||
mlog.String("build_date", model.BuildDate),
|
||||
mlog.String("build_hash", model.BuildHash),
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
logInfo()
|
||||
|
||||
// config.json file
|
||||
config, err := config.ReadConfigFile()
|
||||
if err != nil {
|
||||
@ -92,6 +94,21 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
logger := mlog.NewLogger()
|
||||
err = logger.Configure(config.LoggingFile, config.LoggingEscapedJson)
|
||||
if err != nil {
|
||||
log.Fatal("Error in config file for logger: ", err)
|
||||
return
|
||||
}
|
||||
defer logger.Shutdown()
|
||||
|
||||
if logger.HasTargets() {
|
||||
restore := logger.RedirectStdLog(mlog.Info, mlog.String("src", "stdlog"))
|
||||
defer restore()
|
||||
}
|
||||
|
||||
logInfo(logger)
|
||||
|
||||
// Command line args
|
||||
pMonitorPid := flag.Int("monitorpid", -1, "a process ID")
|
||||
pPort := flag.Int("port", config.Port, "the port number")
|
||||
@ -109,42 +126,42 @@ func main() {
|
||||
if singleUser {
|
||||
singleUserToken = os.Getenv("FOCALBOARD_SINGLE_USER_TOKEN")
|
||||
if len(singleUserToken) < 1 {
|
||||
log.Fatal("The FOCALBOARD_SINGLE_USER_TOKEN environment variable must be set for single user mode ")
|
||||
logger.Fatal("The FOCALBOARD_SINGLE_USER_TOKEN environment variable must be set for single user mode ")
|
||||
return
|
||||
}
|
||||
log.Printf("Single user mode")
|
||||
logger.Info("Single user mode")
|
||||
}
|
||||
|
||||
if pMonitorPid != nil && *pMonitorPid > 0 {
|
||||
monitorPid(*pMonitorPid)
|
||||
monitorPid(*pMonitorPid, logger)
|
||||
}
|
||||
|
||||
// Override config from commandline
|
||||
|
||||
if pDBType != nil && len(*pDBType) > 0 {
|
||||
config.DBType = *pDBType
|
||||
log.Printf("DBType from commandline: %s", *pDBType)
|
||||
logger.Info("DBType from commandline", mlog.String("DBType", *pDBType))
|
||||
}
|
||||
|
||||
if pDBConfig != nil && len(*pDBConfig) > 0 {
|
||||
config.DBConfigString = *pDBConfig
|
||||
// Don't echo, as the confix string may contain passwords
|
||||
log.Printf("DBConfigString overriden from commandline")
|
||||
logger.Info("DBConfigString overriden from commandline")
|
||||
}
|
||||
|
||||
if pPort != nil && *pPort > 0 && *pPort != config.Port {
|
||||
// Override port
|
||||
log.Printf("Port from commandline: %d", *pPort)
|
||||
logger.Info("Port from commandline", mlog.Int("port", *pPort))
|
||||
config.Port = *pPort
|
||||
}
|
||||
|
||||
server, err := server.New(config, singleUserToken)
|
||||
server, err := server.New(config, singleUserToken, logger)
|
||||
if err != nil {
|
||||
log.Fatal("server.New ERROR: ", err)
|
||||
logger.Fatal("server.New ERROR", mlog.Err(err))
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
log.Fatal("server.Start ERROR: ", err)
|
||||
logger.Fatal("server.Start ERROR", mlog.Err(err))
|
||||
}
|
||||
|
||||
// Setting up signal capturing
|
||||
@ -176,8 +193,6 @@ func StopServer() {
|
||||
}
|
||||
|
||||
func startServer(webPath string, filesPath string, port int, singleUserToken, dbConfigString string) {
|
||||
logInfo()
|
||||
|
||||
if pServer != nil {
|
||||
stopServer()
|
||||
pServer = nil
|
||||
@ -190,6 +205,15 @@ func startServer(webPath string, filesPath string, port int, singleUserToken, db
|
||||
return
|
||||
}
|
||||
|
||||
logger := mlog.NewLogger()
|
||||
err = logger.Configure(config.LoggingFile, config.LoggingEscapedJson)
|
||||
if err != nil {
|
||||
log.Fatal("Error in config file for logger: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
logInfo(logger)
|
||||
|
||||
if len(filesPath) > 0 {
|
||||
config.FilesPath = filesPath
|
||||
}
|
||||
@ -206,13 +230,13 @@ func startServer(webPath string, filesPath string, port int, singleUserToken, db
|
||||
config.DBConfigString = dbConfigString
|
||||
}
|
||||
|
||||
pServer, err = server.New(config, singleUserToken)
|
||||
pServer, err = server.New(config, singleUserToken, logger)
|
||||
if err != nil {
|
||||
log.Fatal("server.New ERROR: ", err)
|
||||
logger.Fatal("server.New ERROR", mlog.Err(err))
|
||||
}
|
||||
|
||||
if err := pServer.Start(); err != nil {
|
||||
log.Fatal("server.Start ERROR: ", err)
|
||||
logger.Fatal("server.Start ERROR", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,6 +247,8 @@ func stopServer() {
|
||||
|
||||
err := pServer.Shutdown()
|
||||
if err != nil {
|
||||
log.Fatal("server.Shutdown ERROR: ", err)
|
||||
pServer.Logger().Error("server.Shutdown ERROR", mlog.Err(err))
|
||||
}
|
||||
pServer.Logger().Shutdown()
|
||||
pServer = nil
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
@ -21,6 +19,7 @@ import (
|
||||
"github.com/mattermost/focalboard/server/context"
|
||||
appModel "github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/config"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/prometheus"
|
||||
"github.com/mattermost/focalboard/server/services/scheduler"
|
||||
"github.com/mattermost/focalboard/server/services/store"
|
||||
@ -30,9 +29,10 @@ import (
|
||||
"github.com/mattermost/focalboard/server/services/webhook"
|
||||
"github.com/mattermost/focalboard/server/web"
|
||||
"github.com/mattermost/focalboard/server/ws"
|
||||
"github.com/oklog/run"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/shared/filestore"
|
||||
"github.com/mattermost/mattermost-server/v5/utils"
|
||||
"github.com/oklog/run"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -49,7 +49,7 @@ type Server struct {
|
||||
store store.Store
|
||||
filesBackend filestore.FileBackend
|
||||
telemetry *telemetry.Service
|
||||
logger *zap.Logger
|
||||
logger *mlog.Logger
|
||||
cleanUpSessionsTask *scheduler.ScheduledTask
|
||||
promServer *prometheus.Service
|
||||
promInstrumentor *prometheus.Instrumentor
|
||||
@ -60,16 +60,11 @@ type Server struct {
|
||||
appBuilder func() *app.App
|
||||
}
|
||||
|
||||
func New(cfg *config.Configuration, singleUserToken string) (*Server, error) {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func New(cfg *config.Configuration, singleUserToken string, logger *mlog.Logger) (*Server, error) {
|
||||
var db store.Store
|
||||
db, err = sqlstore.New(cfg.DBType, cfg.DBConfigString, cfg.DBTablePrefix)
|
||||
db, err := sqlstore.New(cfg.DBType, cfg.DBConfigString, cfg.DBTablePrefix, logger)
|
||||
if err != nil {
|
||||
log.Print("Unable to start the database", err)
|
||||
logger.Error("Unable to start the database", mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
if cfg.AuthMode == "mattermost" {
|
||||
@ -83,7 +78,7 @@ func New(cfg *config.Configuration, singleUserToken string) (*Server, error) {
|
||||
|
||||
authenticator := auth.New(cfg, db)
|
||||
|
||||
wsServer := ws.NewServer(authenticator, singleUserToken, cfg.AuthMode == "mattermost")
|
||||
wsServer := ws.NewServer(authenticator, singleUserToken, cfg.AuthMode == "mattermost", logger)
|
||||
|
||||
filesBackendSettings := filestore.FileBackendSettings{}
|
||||
filesBackendSettings.DriverName = cfg.FilesDriver
|
||||
@ -101,15 +96,15 @@ func New(cfg *config.Configuration, singleUserToken string) (*Server, error) {
|
||||
|
||||
filesBackend, appErr := filestore.NewFileBackend(filesBackendSettings)
|
||||
if appErr != nil {
|
||||
log.Print("Unable to initialize the files storage")
|
||||
logger.Error("Unable to initialize the files storage", mlog.Err(appErr))
|
||||
|
||||
return nil, errors.New("unable to initialize the files storage")
|
||||
}
|
||||
|
||||
webhookClient := webhook.NewClient(cfg)
|
||||
webhookClient := webhook.NewClient(cfg, logger)
|
||||
|
||||
appBuilder := func() *app.App { return app.New(cfg, db, authenticator, wsServer, filesBackend, webhookClient) }
|
||||
focalboardAPI := api.NewAPI(appBuilder, singleUserToken, cfg.AuthMode)
|
||||
appBuilder := func() *app.App { return app.New(cfg, db, authenticator, wsServer, filesBackend, webhookClient, logger) }
|
||||
focalboardAPI := api.NewAPI(appBuilder, singleUserToken, cfg.AuthMode, logger)
|
||||
|
||||
// Local router for admin APIs
|
||||
localRouter := mux.NewRouter()
|
||||
@ -117,11 +112,11 @@ func New(cfg *config.Configuration, singleUserToken string) (*Server, error) {
|
||||
|
||||
// Init workspace
|
||||
if _, err = appBuilder().GetRootWorkspace(); err != nil {
|
||||
log.Print("Unable to get root workspace", err)
|
||||
logger.Error("Unable to get root workspace", mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
webServer := web.NewServer(cfg.WebPath, cfg.ServerRoot, cfg.Port, cfg.UseSSL, cfg.LocalOnly)
|
||||
webServer := web.NewServer(cfg.WebPath, cfg.ServerRoot, cfg.Port, cfg.UseSSL, cfg.LocalOnly, logger)
|
||||
webServer.AddRoutes(wsServer)
|
||||
webServer.AddRoutes(focalboardAPI)
|
||||
|
||||
@ -160,7 +155,7 @@ func New(cfg *config.Configuration, singleUserToken string) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
telemetryService := telemetry.New(telemetryID, zap.NewStdLog(logger))
|
||||
telemetryService := telemetry.New(telemetryID, logger.StdLogger(mlog.Telemetry))
|
||||
telemetryService.RegisterTracker("server", func() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"version": appModel.CurrentVersion,
|
||||
@ -226,7 +221,7 @@ func (s *Server) Start() error {
|
||||
}
|
||||
|
||||
if err := s.store.CleanUpSessions(secondsAgo); err != nil {
|
||||
s.logger.Error("Unable to clean up the sessions", zap.Error(err))
|
||||
s.logger.Error("Unable to clean up the sessions", mlog.Err(err))
|
||||
}
|
||||
}, cleanupSessionTaskFrequency)
|
||||
|
||||
@ -267,7 +262,7 @@ func (s *Server) Shutdown() error {
|
||||
}
|
||||
|
||||
if err := s.telemetry.Shutdown(); err != nil {
|
||||
s.logger.Warn("Error occurred when shutting down telemetry", zap.Error(err))
|
||||
s.logger.Warn("Error occurred when shutting down telemetry", mlog.Err(err))
|
||||
}
|
||||
|
||||
defer s.logger.Info("Server.Shutdown")
|
||||
@ -279,6 +274,10 @@ func (s *Server) Config() *config.Configuration {
|
||||
return s.config
|
||||
}
|
||||
|
||||
func (s *Server) Logger() *mlog.Logger {
|
||||
return s.logger
|
||||
}
|
||||
|
||||
// Local server
|
||||
|
||||
func (s *Server) startLocalModeServer() error {
|
||||
@ -289,7 +288,7 @@ func (s *Server) startLocalModeServer() error {
|
||||
|
||||
// TODO: Close and delete socket file on shutdown
|
||||
if err := syscall.Unlink(s.config.LocalModeSocketLocation); err != nil {
|
||||
log.Print("Unable to unlink socket.", err)
|
||||
s.logger.Error("Unable to unlink socket.", mlog.Err(err))
|
||||
}
|
||||
|
||||
socket := s.config.LocalModeSocketLocation
|
||||
@ -302,10 +301,10 @@ func (s *Server) startLocalModeServer() error {
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Println("Starting unix socket server")
|
||||
s.logger.Info("Starting unix socket server")
|
||||
err = s.localModeServer.Serve(unixListener)
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("Error starting unix socket server: %v", err)
|
||||
s.logger.Error("Error starting unix socket server", mlog.Err(err))
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -48,6 +48,9 @@ type Configuration struct {
|
||||
LocalModeSocketLocation string `json:"localModeSocketLocation" mapstructure:"localModeSocketLocation"`
|
||||
|
||||
AuthMode string `json:"authMode" mapstructure:"authMode"`
|
||||
|
||||
LoggingFile string `json:"logging_file" mapstructure:"logging_file"`
|
||||
LoggingEscapedJson string `json:"logging_escaped_json" mapstructure:"logging_escaped_json"`
|
||||
}
|
||||
|
||||
// ReadConfigFile read the configuration from the filesystem.
|
||||
|
40
server/services/mlog/levels.go
Normal file
40
server/services/mlog/levels.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import "github.com/mattermost/logr/v2"
|
||||
|
||||
// Standard levels
|
||||
var (
|
||||
Panic = logr.Panic // ID = 0
|
||||
Fatal = logr.Fatal // ID = 1
|
||||
Error = logr.Error // ID = 2
|
||||
Warn = logr.Warn // ID = 3
|
||||
Info = logr.Info // ID = 4
|
||||
Debug = logr.Debug // ID = 5
|
||||
Trace = logr.Trace // ID = 6
|
||||
StdAll = []Level{Panic, Fatal, Error, Warn, Info, Debug, Trace}
|
||||
)
|
||||
|
||||
// Register custom (discrete) levels here.
|
||||
// !!!!! Custom ID's must be between 20 and 32,768 !!!!!!
|
||||
var (
|
||||
/* Example
|
||||
// used by the audit system
|
||||
AuditAPI = Level{ID: 100, Name: "audit-api"}
|
||||
AuditContent = Level{ID: 101, Name: "audit-content"}
|
||||
AuditPerms = Level{ID: 102, Name: "audit-permissions"}
|
||||
AuditCLI = Level{ID: 103, Name: "audit-cli"}
|
||||
*/
|
||||
|
||||
// add more here ...
|
||||
Telemetry = Level{ID: 500, Name: "telemetry"}
|
||||
)
|
||||
|
||||
// Combinations for LogM (log multi)
|
||||
var (
|
||||
/* Example
|
||||
MAuditAll = []Level{AuditAPI, AuditContent, AuditPerms, AuditCLI}
|
||||
*/
|
||||
)
|
256
server/services/mlog/mlog.go
Normal file
256
server/services/mlog/mlog.go
Normal file
@ -0,0 +1,256 @@
|
||||
// Package mlog provides a simple wrapper around Logr.
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/logr/v2"
|
||||
logrcfg "github.com/mattermost/logr/v2/config"
|
||||
)
|
||||
|
||||
const (
|
||||
ShutdownTimeout = time.Second * 15
|
||||
)
|
||||
|
||||
// Type and function aliases from Logr to limit the spread of dependencies throughout Focalboard.
|
||||
type Field = logr.Field
|
||||
type Level = logr.Level
|
||||
|
||||
// Any picks the best supported field type based on type of val.
|
||||
// For best performance when passing a struct (or struct pointer),
|
||||
// implement `logr.LogWriter` on the struct, otherwise reflection
|
||||
// will be used to generate a string representation.
|
||||
var Any = logr.Any
|
||||
|
||||
// Int64 constructs a field containing a key and Int64 value.
|
||||
var Int64 = logr.Int64
|
||||
|
||||
// Int32 constructs a field containing a key and Int32 value.
|
||||
var Int32 = logr.Int32
|
||||
|
||||
// Int constructs a field containing a key and Int value.
|
||||
var Int = logr.Int
|
||||
|
||||
// Uint64 constructs a field containing a key and Uint64 value.
|
||||
var Uint64 = logr.Uint64
|
||||
|
||||
// Uint32 constructs a field containing a key and Uint32 value.
|
||||
var Uint32 = logr.Uint32
|
||||
|
||||
// Uint constructs a field containing a key and Uint value.
|
||||
var Uint = logr.Uint
|
||||
|
||||
// Float64 constructs a field containing a key and Float64 value.
|
||||
var Float64 = logr.Float64
|
||||
|
||||
// Float32 constructs a field containing a key and Float32 value.
|
||||
var Float32 = logr.Float32
|
||||
|
||||
// String constructs a field containing a key and String value.
|
||||
var String = logr.String
|
||||
|
||||
// Stringer constructs a field containing a key and a fmt.Stringer value.
|
||||
// The fmt.Stringer's `String` method is called lazily.
|
||||
var Stringer = logr.Stringer
|
||||
|
||||
// Err constructs a field containing a default key ("error") and error value.
|
||||
var Err = logr.Err
|
||||
|
||||
// NamedErr constructs a field containing a key and error value.
|
||||
var NamedErr = logr.NamedErr
|
||||
|
||||
// Bool constructs a field containing a key and bool value.
|
||||
var Bool = logr.Bool
|
||||
|
||||
// Time constructs a field containing a key and time.Time value.
|
||||
var Time = logr.Time
|
||||
|
||||
// Duration constructs a field containing a key and time.Duration value.
|
||||
var Duration = logr.Duration
|
||||
|
||||
// Millis constructs a field containing a key and timestamp value.
|
||||
// The timestamp is expected to be milliseconds since Jan 1, 1970 UTC.
|
||||
var Millis = logr.Millis
|
||||
|
||||
// Array constructs a field containing a key and array value.
|
||||
var Array = logr.Array
|
||||
|
||||
// Map constructs a field containing a key and map value.
|
||||
var Map = logr.Map
|
||||
|
||||
// LoggerConfig is a map of LogTarget configurations.
|
||||
type LoggerConfig map[string]logrcfg.TargetCfg
|
||||
|
||||
func (lc LoggerConfig) append(cfg LoggerConfig) {
|
||||
for k, v := range cfg {
|
||||
lc[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Logger provides a thin wrapper around a Logr instance. This is a struct instead of an interface
|
||||
// so that there are no allocations on the heap each interface method invocation. Normally not
|
||||
// something to be concerned about, but logging calls for disabled levels should have as little CPU
|
||||
// and memory impact as possible. Most of these wrapper calls will be inlined as well.
|
||||
type Logger struct {
|
||||
log *logr.Logger
|
||||
}
|
||||
|
||||
// NewLogger creates a new Logger instance which can be configured via `(*Logger).Configure`
|
||||
func NewLogger() *Logger {
|
||||
lgr, _ := logr.New()
|
||||
log := lgr.NewLogger()
|
||||
|
||||
return &Logger{
|
||||
log: &log,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure provides a new configuration for this logger.
|
||||
// Zero or more sources of config can be provided, with target name collisions resolved using the
|
||||
// following precedence:
|
||||
// cfgFile > cfgJson
|
||||
func (l *Logger) Configure(cfgFile string, cfgEscaped string) error {
|
||||
cfgMap := make(LoggerConfig)
|
||||
|
||||
// Add config from file
|
||||
if cfgFile != "" {
|
||||
if b, err := ioutil.ReadFile(string(cfgFile)); err != nil {
|
||||
return fmt.Errorf("error reading logger config file %s: %w", cfgFile, err)
|
||||
} else {
|
||||
var mapCfgFile LoggerConfig
|
||||
if err := json.Unmarshal(b, &mapCfgFile); err != nil {
|
||||
return fmt.Errorf("error decoding logger config file %s: %w", cfgFile, err)
|
||||
}
|
||||
cfgMap.append(mapCfgFile)
|
||||
}
|
||||
}
|
||||
|
||||
// Add config from escaped json string
|
||||
if cfgEscaped != "" {
|
||||
if b, err := decodeEscapedJSONString(string(cfgEscaped)); err != nil {
|
||||
return fmt.Errorf("error unescaping logger config as escaped json: %w", err)
|
||||
} else {
|
||||
var mapCfgEscaped LoggerConfig
|
||||
if err := json.Unmarshal(b, &mapCfgEscaped); err != nil {
|
||||
return fmt.Errorf("error decoding logger config as escaped json: %w", err)
|
||||
}
|
||||
cfgMap.append(mapCfgEscaped)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfgMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return logrcfg.ConfigureTargets(l.log.Logr(), cfgMap, nil)
|
||||
}
|
||||
|
||||
func decodeEscapedJSONString(s string) ([]byte, error) {
|
||||
type wrapper struct {
|
||||
wrap string
|
||||
}
|
||||
var wrapped wrapper
|
||||
ss := fmt.Sprintf("{\"wrap\":%s}", s)
|
||||
if err := json.Unmarshal([]byte(ss), &wrapped); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(wrapped.wrap), nil
|
||||
}
|
||||
|
||||
// With creates a new Logger with the specified fields. This is a light-weight
|
||||
// operation and can be called on demand.
|
||||
func (l *Logger) With(fields ...Field) *Logger {
|
||||
logWith := l.log.With(fields...)
|
||||
return &Logger{
|
||||
log: &logWith,
|
||||
}
|
||||
}
|
||||
|
||||
// IsLevelEnabled returns true only if at least one log target is
|
||||
// configured to emit the specified log level. Use this check when
|
||||
// gathering the log info may be expensive.
|
||||
//
|
||||
// Note, transformations and serializations done via fields are already
|
||||
// lazily evaluated and don't require this check beforehand.
|
||||
func (l *Logger) IsLevelEnabled(level Level) bool {
|
||||
return l.IsLevelEnabled(level)
|
||||
}
|
||||
|
||||
// Log emits the log record for any targets configured for the specified level.
|
||||
func (l *Logger) Log(level Level, msg string, fields ...Field) {
|
||||
l.log.Log(level, msg, fields...)
|
||||
}
|
||||
|
||||
// LogM emits the log record for any targets configured for the specified levels.
|
||||
// Equivalent to calling `Log` once for each level.
|
||||
func (l *Logger) LogM(levels []Level, msg string, fields ...Field) {
|
||||
l.log.LogM(levels, msg, fields...)
|
||||
}
|
||||
|
||||
// Convenience method equivalent to calling `Log` with the `Trace` level.
|
||||
func (l *Logger) Trace(msg string, fields ...Field) {
|
||||
l.log.Trace(msg, fields...)
|
||||
}
|
||||
|
||||
// Convenience method equivalent to calling `Log` with the `Debug` level.
|
||||
func (l *Logger) Debug(msg string, fields ...Field) {
|
||||
l.log.Debug(msg, fields...)
|
||||
}
|
||||
|
||||
// Convenience method equivalent to calling `Log` with the `Info` level.
|
||||
func (l *Logger) Info(msg string, fields ...Field) {
|
||||
l.log.Info(msg, fields...)
|
||||
}
|
||||
|
||||
// Convenience method equivalent to calling `Log` with the `Warn` level.
|
||||
func (l *Logger) Warn(msg string, fields ...Field) {
|
||||
l.log.Warn(msg, fields...)
|
||||
}
|
||||
|
||||
// Convenience method equivalent to calling `Log` with the `Error` level.
|
||||
func (l *Logger) Error(msg string, fields ...Field) {
|
||||
l.log.Error(msg, fields...)
|
||||
}
|
||||
|
||||
// Convenience method equivalent to calling `Log` with the `Fatal` level,
|
||||
// followed by `os.Exit(1)`.
|
||||
func (l *Logger) Fatal(msg string, fields ...Field) {
|
||||
l.log.Log(logr.Fatal, msg, fields...)
|
||||
l.Shutdown()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// HasTargets returns true if at least one log target has been added.
|
||||
func (l *Logger) HasTargets() bool {
|
||||
return l.log.Logr().HasTargets()
|
||||
}
|
||||
|
||||
// StdLogger creates a standard logger backed by this logger.
|
||||
// All log records are output with the specified level.
|
||||
func (l *Logger) StdLogger(level Level) *log.Logger {
|
||||
return l.log.StdLogger(level)
|
||||
}
|
||||
|
||||
// RedirectStdLog redirects output from the standard library's package-global logger
|
||||
// to this logger at the specified level and with zero or more Field's. Since this logger already
|
||||
// handles caller annotations, timestamps, etc., it automatically disables the standard
|
||||
// library's annotations and prefixing.
|
||||
// A function is returned that restores the original prefix and flags and resets the standard
|
||||
// library's output to os.Stdout.
|
||||
func (l *Logger) RedirectStdLog(level Level, fields ...Field) func() {
|
||||
return l.log.Logr().RedirectStdLog(level, fields...)
|
||||
}
|
||||
|
||||
// Shutdown shuts down the logger after making best efforts to flush any
|
||||
// remaining records.
|
||||
func (l *Logger) Shutdown() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ShutdownTimeout)
|
||||
defer cancel()
|
||||
return l.log.Logr().ShutdownWithTimeout(ctx)
|
||||
}
|
59
server/services/mlog/tlog.go
Normal file
59
server/services/mlog/tlog.go
Normal file
@ -0,0 +1,59 @@
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/logr/v2"
|
||||
"github.com/mattermost/logr/v2/formatters"
|
||||
)
|
||||
|
||||
// CreateTestLogger creates a logger for unit tests. Log records are output to `(*testing.T)Log`
|
||||
func CreateTestLogger(t *testing.T, levels ...Field) (logger *Logger) {
|
||||
logger = NewLogger()
|
||||
|
||||
filter := logr.NewCustomFilter(StdAll...)
|
||||
formatter := &formatters.Plain{}
|
||||
target := newTestingTarget(t)
|
||||
|
||||
logger.log.Logr().AddTarget(target, "test", filter, formatter, 1000)
|
||||
return logger
|
||||
}
|
||||
|
||||
// testingTarget is a simple log target that writes to the testing log.
|
||||
type testingTarget struct {
|
||||
mux sync.Mutex
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func newTestingTarget(t *testing.T) *testingTarget {
|
||||
return &testingTarget{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// Init is called once to initialize the target.
|
||||
func (tt *testingTarget) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write outputs bytes to this file target.
|
||||
func (tt *testingTarget) Write(p []byte, rec *logr.LogRec) (int, error) {
|
||||
tt.mux.Lock()
|
||||
defer tt.mux.Unlock()
|
||||
|
||||
if tt.t != nil {
|
||||
tt.t.Log(string(p))
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Shutdown is called once to free/close any resources.
|
||||
// Target queue is already drained when this is called.
|
||||
func (tt *testingTarget) Shutdown() error {
|
||||
tt.mux.Lock()
|
||||
defer tt.mux.Unlock()
|
||||
|
||||
tt.t = nil
|
||||
return nil
|
||||
}
|
@ -5,12 +5,12 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/store"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
@ -37,12 +37,12 @@ func (s *SQLStore) GetBlocksWithParentAndType(c store.Container, parentID string
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
log.Printf(`getBlocksWithParentAndType ERROR: %v`, err)
|
||||
s.logger.Error(`getBlocksWithParentAndType ERROR`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blocksFromRows(rows)
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlocksWithParent(c store.Container, parentID string) ([]model.Block, error) {
|
||||
@ -66,12 +66,12 @@ func (s *SQLStore) GetBlocksWithParent(c store.Container, parentID string) ([]mo
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
log.Printf(`getBlocksWithParent ERROR: %v`, err)
|
||||
s.logger.Error(`getBlocksWithParent ERROR`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blocksFromRows(rows)
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlocksWithRootID(c store.Container, rootID string) ([]model.Block, error) {
|
||||
@ -95,12 +95,12 @@ func (s *SQLStore) GetBlocksWithRootID(c store.Container, rootID string) ([]mode
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
log.Printf(`GetBlocksWithRootID ERROR: %v`, err)
|
||||
s.logger.Error(`GetBlocksWithRootID ERROR`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blocksFromRows(rows)
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlocksWithType(c store.Container, blockType string) ([]model.Block, error) {
|
||||
@ -124,12 +124,12 @@ func (s *SQLStore) GetBlocksWithType(c store.Container, blockType string) ([]mod
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
log.Printf(`getBlocksWithParentAndType ERROR: %v`, err)
|
||||
s.logger.Error(`getBlocksWithParentAndType ERROR`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blocksFromRows(rows)
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
// GetSubTree2 returns blocks within 2 levels of the given blockID
|
||||
@ -154,12 +154,12 @@ func (s *SQLStore) GetSubTree2(c store.Container, blockID string) ([]model.Block
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
log.Printf(`getSubTree ERROR: %v`, err)
|
||||
s.logger.Error(`getSubTree ERROR`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blocksFromRows(rows)
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
// GetSubTree3 returns blocks within 3 levels of the given blockID
|
||||
@ -192,12 +192,12 @@ func (s *SQLStore) GetSubTree3(c store.Container, blockID string) ([]model.Block
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
log.Printf(`getSubTree3 ERROR: %v`, err)
|
||||
s.logger.Error(`getSubTree3 ERROR`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blocksFromRows(rows)
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetAllBlocks(c store.Container) ([]model.Block, error) {
|
||||
@ -220,15 +220,15 @@ func (s *SQLStore) GetAllBlocks(c store.Container) ([]model.Block, error) {
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
log.Printf(`getAllBlocks ERROR: %v`, err)
|
||||
s.logger.Error(`getAllBlocks ERROR`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blocksFromRows(rows)
|
||||
return s.blocksFromRows(rows)
|
||||
}
|
||||
|
||||
func blocksFromRows(rows *sql.Rows) ([]model.Block, error) {
|
||||
func (s *SQLStore) blocksFromRows(rows *sql.Rows) ([]model.Block, error) {
|
||||
defer rows.Close()
|
||||
|
||||
results := []model.Block{}
|
||||
@ -252,7 +252,7 @@ func blocksFromRows(rows *sql.Rows) ([]model.Block, error) {
|
||||
&block.DeleteAt)
|
||||
if err != nil {
|
||||
// handle this error
|
||||
log.Printf(`ERROR blocksFromRows: %v`, err)
|
||||
s.logger.Error(`ERROR blocksFromRows`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
@ -264,7 +264,7 @@ func blocksFromRows(rows *sql.Rows) ([]model.Block, error) {
|
||||
err = json.Unmarshal([]byte(fieldsJSON), &block.Fields)
|
||||
if err != nil {
|
||||
// handle this error
|
||||
log.Printf(`ERROR blocksFromRows fields: %v`, err)
|
||||
s.logger.Error(`ERROR blocksFromRows fields`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ package sqlstore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/store"
|
||||
"github.com/mattermost/focalboard/server/services/store/sqlstore/initializations"
|
||||
)
|
||||
@ -25,7 +25,7 @@ func (s *SQLStore) InitializeTemplates() error {
|
||||
}
|
||||
|
||||
func (s *SQLStore) importInitialTemplates() error {
|
||||
log.Printf("importInitialTemplates")
|
||||
s.logger.Debug("importInitialTemplates")
|
||||
blocksJSON := initializations.MustAsset("templates.json")
|
||||
|
||||
var archive model.Archive
|
||||
@ -38,9 +38,13 @@ func (s *SQLStore) importInitialTemplates() error {
|
||||
WorkspaceID: "0",
|
||||
}
|
||||
|
||||
log.Printf("Inserting %d blocks", len(archive.Blocks))
|
||||
s.logger.Debug("Inserting blocks", mlog.Int("block_count", len(archive.Blocks)))
|
||||
for _, block := range archive.Blocks {
|
||||
// log.Printf("\t%v %v %v", block.ID, block.Type, block.Title)
|
||||
s.logger.Trace("insert block",
|
||||
mlog.String("blockID", block.ID),
|
||||
mlog.String("block_type", block.Type),
|
||||
mlog.String("block_title", block.Title),
|
||||
)
|
||||
err := s.InsertBlock(globalContainer, block)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -62,7 +66,7 @@ func (s *SQLStore) isInitializationNeeded() (bool, error) {
|
||||
var count int
|
||||
err := row.Scan(&count)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
s.logger.Fatal("isInitializationNeeded", mlog.Err(err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -15,27 +15,28 @@ const (
|
||||
|
||||
// SQLStore is a SQL database.
|
||||
type SQLStore struct {
|
||||
db *sql.DB
|
||||
dbType string
|
||||
tablePrefix string
|
||||
db *sql.DB
|
||||
dbType string
|
||||
tablePrefix string
|
||||
connectionString string
|
||||
logger *mlog.Logger
|
||||
}
|
||||
|
||||
// New creates a new SQL implementation of the store.
|
||||
func New(dbType, connectionString string, tablePrefix string) (*SQLStore, error) {
|
||||
log.Println("connectDatabase", dbType, connectionString)
|
||||
func New(dbType, connectionString string, tablePrefix string, logger *mlog.Logger) (*SQLStore, error) {
|
||||
logger.Info("connectDatabase", mlog.String("dbType", dbType), mlog.String("connStr", connectionString))
|
||||
var err error
|
||||
|
||||
db, err := sql.Open(dbType, connectionString)
|
||||
if err != nil {
|
||||
log.Print("connectDatabase: ", err)
|
||||
logger.Error("connectDatabase failed", mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
log.Printf(`Database Ping failed: %v`, err)
|
||||
logger.Error(`Database Ping failed`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
@ -45,18 +46,19 @@ func New(dbType, connectionString string, tablePrefix string) (*SQLStore, error)
|
||||
dbType: dbType,
|
||||
tablePrefix: tablePrefix,
|
||||
connectionString: connectionString,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
err = store.Migrate()
|
||||
if err != nil {
|
||||
log.Printf(`Table creation / migration failed: %v`, err)
|
||||
logger.Error(`Table creation / migration failed`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = store.InitializeTemplates()
|
||||
if err != nil {
|
||||
log.Printf(`InitializeTemplates failed: %v`, err)
|
||||
logger.Error(`InitializeTemplates failed`, mlog.Err(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/store"
|
||||
"github.com/mattermost/focalboard/server/services/store/storetests"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -20,10 +21,13 @@ func SetupTests(t *testing.T) (store.Store, func()) {
|
||||
connectionString = ":memory:"
|
||||
}
|
||||
|
||||
store, err := New(dbType, connectionString, "test_")
|
||||
logger := mlog.CreateTestLogger(t)
|
||||
|
||||
store, err := New(dbType, connectionString, "test_", logger)
|
||||
require.Nil(t, err)
|
||||
|
||||
tearDown := func() {
|
||||
defer logger.Shutdown()
|
||||
err = store.Shutdown()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ package sqlstore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
)
|
||||
@ -98,7 +98,7 @@ func (s *SQLStore) GetWorkspace(ID string) (*model.Workspace, error) {
|
||||
|
||||
err = json.Unmarshal([]byte(settingsJSON), &workspace.Settings)
|
||||
if err != nil {
|
||||
log.Printf(`ERROR GetWorkspace settings json.Unmarshal: %v`, err)
|
||||
s.logger.Error(`ERROR GetWorkspace settings json.Unmarshal`, mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,11 @@ package webhook
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/config"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
)
|
||||
|
||||
// NotifyUpdate calls webhooks
|
||||
@ -18,22 +18,24 @@ func (wh *Client) NotifyUpdate(block model.Block) {
|
||||
|
||||
json, err := json.Marshal(block)
|
||||
if err != nil {
|
||||
log.Fatal("NotifyUpdate: json.Marshal", err)
|
||||
wh.logger.Fatal("NotifyUpdate: json.Marshal", mlog.Err(err))
|
||||
}
|
||||
for _, url := range wh.config.WebhookUpdate {
|
||||
http.Post(url, "application/json", bytes.NewBuffer(json))
|
||||
log.Printf("webhook.NotifyUpdate: %s", url)
|
||||
wh.logger.Debug("webhook.NotifyUpdate", mlog.String("url", url))
|
||||
}
|
||||
}
|
||||
|
||||
// Client is a webhook client
|
||||
type Client struct {
|
||||
config *config.Configuration
|
||||
logger *mlog.Logger
|
||||
}
|
||||
|
||||
// NewClient creates a new Client
|
||||
func NewClient(config *config.Configuration) *Client {
|
||||
func NewClient(config *config.Configuration, logger *mlog.Logger) *Client {
|
||||
return &Client{
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -11,6 +10,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
)
|
||||
|
||||
// RoutedService defines the interface that is needed for any service to
|
||||
@ -29,10 +29,11 @@ type Server struct {
|
||||
port int
|
||||
ssl bool
|
||||
localOnly bool
|
||||
logger *mlog.Logger
|
||||
}
|
||||
|
||||
// NewServer creates a new instance of the webserver.
|
||||
func NewServer(rootPath string, serverRoot string, port int, ssl, localOnly bool) *Server {
|
||||
func NewServer(rootPath string, serverRoot string, port int, ssl, localOnly bool, logger *mlog.Logger) *Server {
|
||||
r := mux.NewRouter()
|
||||
|
||||
var addr string
|
||||
@ -45,7 +46,7 @@ func NewServer(rootPath string, serverRoot string, port int, ssl, localOnly bool
|
||||
baseURL := ""
|
||||
url, err := url.Parse(serverRoot)
|
||||
if err != nil {
|
||||
log.Printf("Invalid ServerRoot setting: %v\n", err)
|
||||
logger.Error("Invalid ServerRoot setting", mlog.Err(err))
|
||||
}
|
||||
baseURL = url.Path
|
||||
|
||||
@ -58,6 +59,7 @@ func NewServer(rootPath string, serverRoot string, port int, ssl, localOnly bool
|
||||
rootPath: rootPath,
|
||||
port: port,
|
||||
ssl: ssl,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
return ws
|
||||
@ -78,13 +80,13 @@ func (ws *Server) registerRoutes() {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
indexTemplate, err := template.New("index").ParseFiles(path.Join(ws.rootPath, "index.html"))
|
||||
if err != nil {
|
||||
log.Printf("Unable to serve the index.html fil, err: %v\n", err)
|
||||
ws.logger.Error("Unable to serve the index.html file", mlog.Err(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
err = indexTemplate.ExecuteTemplate(w, "index.html", map[string]string{"BaseURL": ws.baseURL})
|
||||
if err != nil {
|
||||
log.Printf("Unable to serve the index.html fil, err: %v\n", err)
|
||||
ws.logger.Error("Unable to serve the index.html file", mlog.Err(err))
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
@ -95,28 +97,28 @@ func (ws *Server) registerRoutes() {
|
||||
func (ws *Server) Start() {
|
||||
ws.registerRoutes()
|
||||
if ws.port == -1 {
|
||||
log.Print("server not bind to any port\n")
|
||||
ws.logger.Error("server not bind to any port")
|
||||
return
|
||||
}
|
||||
|
||||
isSSL := ws.ssl && fileExists("./cert/cert.pem") && fileExists("./cert/key.pem")
|
||||
if isSSL {
|
||||
log.Printf("https server started on :%d\n", ws.port)
|
||||
ws.logger.Info("https server started", mlog.Int("port", ws.port))
|
||||
go func() {
|
||||
if err := ws.ListenAndServeTLS("./cert/cert.pem", "./cert/key.pem"); err != nil {
|
||||
log.Fatalf("ListenAndServeTLS: %v", err)
|
||||
ws.logger.Fatal("ListenAndServeTLS", mlog.Err(err))
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("http server started on :%d\n", ws.port)
|
||||
ws.logger.Info("http server started", mlog.Int("port", ws.port))
|
||||
go func() {
|
||||
if err := ws.ListenAndServe(); err != http.ErrServerClosed {
|
||||
log.Fatalf("ListenAndServe: %v", err)
|
||||
ws.logger.Fatal("ListenAndServeTLS", mlog.Err(err))
|
||||
}
|
||||
log.Println("http server stopped")
|
||||
ws.logger.Info("http server stopped")
|
||||
}()
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/mattermost/focalboard/server/auth"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"github.com/mattermost/focalboard/server/services/mlog"
|
||||
"github.com/mattermost/focalboard/server/services/store"
|
||||
)
|
||||
|
||||
@ -32,6 +33,7 @@ type Server struct {
|
||||
hub Hub
|
||||
singleUserToken string
|
||||
isMattermostAuth bool
|
||||
logger *mlog.Logger
|
||||
}
|
||||
|
||||
// UpdateMsg is sent on block updates
|
||||
@ -68,7 +70,7 @@ type websocketSession struct {
|
||||
}
|
||||
|
||||
// NewServer creates a new Server.
|
||||
func NewServer(auth *auth.Auth, singleUserToken string, isMattermostAuth bool) *Server {
|
||||
func NewServer(auth *auth.Auth, singleUserToken string, isMattermostAuth bool, logger *mlog.Logger) *Server {
|
||||
return &Server{
|
||||
listeners: make(map[string][]*websocket.Conn),
|
||||
upgrader: websocket.Upgrader{
|
||||
@ -79,6 +81,7 @@ func NewServer(auth *auth.Auth, singleUserToken string, isMattermostAuth bool) *
|
||||
auth: auth,
|
||||
singleUserToken: singleUserToken,
|
||||
isMattermostAuth: isMattermostAuth,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,13 +94,13 @@ func (ws *Server) handleWebSocketOnChange(w http.ResponseWriter, r *http.Request
|
||||
// Upgrade initial GET request to a websocket
|
||||
client, err := ws.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Printf("ERROR upgrading to websocket: %v", err)
|
||||
ws.logger.Error("ERROR upgrading to websocket", mlog.Err(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure we close the connection when the function returns
|
||||
defer func() {
|
||||
log.Printf("DISCONNECT WebSocket onChange, client: %s", client.RemoteAddr())
|
||||
ws.logger.Debug("DISCONNECT WebSocket onChange", mlog.Stringer("client", client.RemoteAddr()))
|
||||
|
||||
// Remove client from listeners
|
||||
ws.removeListener(client)
|
||||
@ -119,7 +122,10 @@ func (ws *Server) handleWebSocketOnChange(w http.ResponseWriter, r *http.Request
|
||||
for {
|
||||
_, p, err := client.ReadMessage()
|
||||
if err != nil {
|
||||
log.Printf("ERROR WebSocket onChange, client: %s, err: %v", client.RemoteAddr(), err)
|
||||
ws.logger.Error("ERROR WebSocket onChange",
|
||||
mlog.Stringer("client", client.RemoteAddr()),
|
||||
mlog.Err(err),
|
||||
)
|
||||
ws.removeListener(client)
|
||||
|
||||
break
|
||||
@ -130,7 +136,7 @@ func (ws *Server) handleWebSocketOnChange(w http.ResponseWriter, r *http.Request
|
||||
err = json.Unmarshal(p, &command)
|
||||
if err != nil {
|
||||
// handle this error
|
||||
log.Printf(`ERROR webSocket parsing command JSON: %v`, string(p))
|
||||
ws.logger.Error(`ERROR webSocket parsing command`, mlog.String("json", string(p)))
|
||||
|
||||
continue
|
||||
}
|
||||
@ -139,23 +145,32 @@ func (ws *Server) handleWebSocketOnChange(w http.ResponseWriter, r *http.Request
|
||||
if ws.auth.DoesUserHaveWorkspaceAccess(userID, command.WorkspaceID) {
|
||||
wsSession.workspaceID = command.WorkspaceID
|
||||
} else {
|
||||
log.Printf(`ERROR User doesn't have permissions to read the workspace: %s`, command.WorkspaceID)
|
||||
ws.logger.Error(`ERROR User doesn't have permissions to read the workspace`, mlog.String("workspaceID", command.WorkspaceID))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch command.Action {
|
||||
case "AUTH":
|
||||
log.Printf(`Command: AUTH, client: %s`, client.RemoteAddr())
|
||||
ws.logger.Debug(`Command: AUTH`, mlog.Stringer("client", client.RemoteAddr()))
|
||||
ws.authenticateListener(&wsSession, command.WorkspaceID, command.Token)
|
||||
case "ADD":
|
||||
log.Printf(`Command: Add workspaceID: %s, blockIDs: %v, client: %s`, wsSession.workspaceID, command.BlockIDs, client.RemoteAddr())
|
||||
ws.logger.Debug(`Command: ADD`,
|
||||
mlog.String("workspaceID", wsSession.workspaceID),
|
||||
mlog.Array("blockIDs", command.BlockIDs),
|
||||
mlog.Stringer("client", client.RemoteAddr()),
|
||||
)
|
||||
ws.addListener(&wsSession, &command)
|
||||
case "REMOVE":
|
||||
log.Printf(`Command: Remove workspaceID: %s, blockID: %v, client: %s`, wsSession.workspaceID, command.BlockIDs, client.RemoteAddr())
|
||||
ws.logger.Debug(`Command: REMOVE`,
|
||||
mlog.String("workspaceID", wsSession.workspaceID),
|
||||
mlog.Array("blockIDs", command.BlockIDs),
|
||||
mlog.Stringer("client", client.RemoteAddr()),
|
||||
)
|
||||
|
||||
ws.removeListenerFromBlocks(&wsSession, &command)
|
||||
default:
|
||||
log.Printf(`ERROR webSocket command, invalid action: %v`, command.Action)
|
||||
ws.logger.Error(`ERROR webSocket command, invalid action`, mlog.String("action", command.Action))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,7 +192,7 @@ func (ws *Server) isValidSessionToken(token, workspaceID string) bool {
|
||||
func (ws *Server) authenticateListener(wsSession *websocketSession, workspaceID, token string) {
|
||||
if wsSession.isAuthenticated {
|
||||
// Do not allow multiple auth calls (for security)
|
||||
log.Printf("authenticateListener: Ignoring already authenticated session")
|
||||
ws.logger.Debug("authenticateListener: Ignoring already authenticated session", mlog.String("workspaceID", workspaceID))
|
||||
return
|
||||
}
|
||||
|
||||
@ -192,7 +207,7 @@ func (ws *Server) authenticateListener(wsSession *websocketSession, workspaceID,
|
||||
|
||||
wsSession.workspaceID = workspaceID
|
||||
wsSession.isAuthenticated = true
|
||||
log.Printf("authenticateListener: Authenticated, workspaceID: %s", workspaceID)
|
||||
ws.logger.Debug("authenticateListener: Authenticated", mlog.String("workspaceID", workspaceID))
|
||||
}
|
||||
|
||||
func (ws *Server) getAuthenticatedWorkspaceID(wsSession *websocketSession, command *WebsocketCommand) (string, error) {
|
||||
@ -203,7 +218,7 @@ func (ws *Server) getAuthenticatedWorkspaceID(wsSession *websocketSession, comma
|
||||
// If not authenticated, try to authenticate the read token against the supplied workspaceID
|
||||
workspaceID := command.WorkspaceID
|
||||
if len(workspaceID) == 0 {
|
||||
log.Printf("getAuthenticatedWorkspaceID: No workspace")
|
||||
ws.logger.Error("getAuthenticatedWorkspaceID: No workspace")
|
||||
return "", errors.New("No workspace")
|
||||
}
|
||||
|
||||
@ -234,8 +249,8 @@ func makeItemID(workspaceID, blockID string) string {
|
||||
func (ws *Server) addListener(wsSession *websocketSession, command *WebsocketCommand) {
|
||||
workspaceID, err := ws.getAuthenticatedWorkspaceID(wsSession, command)
|
||||
if err != nil {
|
||||
log.Printf("addListener: NOT AUTHENTICATED, ERROR: %v", err)
|
||||
sendError(wsSession.client, "not authenticated")
|
||||
ws.logger.Error("addListener: NOT AUTHENTICATED", mlog.Err(err))
|
||||
ws.sendError(wsSession.client, "not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
@ -272,8 +287,8 @@ func (ws *Server) removeListener(client *websocket.Conn) {
|
||||
func (ws *Server) removeListenerFromBlocks(wsSession *websocketSession, command *WebsocketCommand) {
|
||||
workspaceID, err := ws.getAuthenticatedWorkspaceID(wsSession, command)
|
||||
if err != nil {
|
||||
log.Printf("addListener: NOT AUTHENTICATED, ERROR: %v", err)
|
||||
sendError(wsSession.client, "not authenticated")
|
||||
ws.logger.Error("addListener: NOT AUTHENTICATED", mlog.Err(err))
|
||||
ws.sendError(wsSession.client, "not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
@ -300,14 +315,14 @@ func (ws *Server) removeListenerFromBlocks(wsSession *websocketSession, command
|
||||
ws.mu.Unlock()
|
||||
}
|
||||
|
||||
func sendError(conn *websocket.Conn, message string) {
|
||||
func (ws *Server) sendError(conn *websocket.Conn, message string) {
|
||||
errorMsg := ErrorMsg{
|
||||
Error: message,
|
||||
}
|
||||
|
||||
err := conn.WriteJSON(errorMsg)
|
||||
if err != nil {
|
||||
log.Printf("sendError error: %v", err)
|
||||
ws.logger.Error("sendError error", mlog.Err(err))
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
@ -372,7 +387,10 @@ func (ws *Server) BroadcastBlockChange(workspaceID string, block model.Block) {
|
||||
|
||||
for _, blockID := range blockIDsToNotify {
|
||||
listeners := ws.getListeners(workspaceID, blockID)
|
||||
log.Printf("%d listener(s) for blockID: %s", len(listeners), blockID)
|
||||
ws.logger.Debug("listener(s) for blockID",
|
||||
mlog.Int("listener_count", len(listeners)),
|
||||
mlog.String("blockID", blockID),
|
||||
)
|
||||
|
||||
message := UpdateMsg{
|
||||
Action: "UPDATE_BLOCK",
|
||||
@ -388,11 +406,15 @@ func (ws *Server) BroadcastBlockChange(workspaceID string, block model.Block) {
|
||||
|
||||
if listeners != nil {
|
||||
for _, listener := range listeners {
|
||||
log.Printf("Broadcast change, workspaceID: %s, blockID: %s, remoteAddr: %s", workspaceID, blockID, listener.RemoteAddr())
|
||||
ws.logger.Debug("Broadcast change",
|
||||
mlog.String("workspaceID", workspaceID),
|
||||
mlog.String("blockID", blockID),
|
||||
mlog.Stringer("remoteAddr", listener.RemoteAddr()),
|
||||
)
|
||||
|
||||
err := listener.WriteJSON(message)
|
||||
if err != nil {
|
||||
log.Printf("broadcast error: %v", err)
|
||||
ws.logger.Error("broadcast error", mlog.Err(err))
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
346
webapp/package-lock.json
generated
346
webapp/package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "focalboard",
|
||||
"version": "0.6.7",
|
||||
"dependencies": {
|
||||
"cypress": "^6.8.0",
|
||||
"emoji-mart": "^3.0.1",
|
||||
"imagemin-gifsicle": "^7.0.0",
|
||||
"imagemin-mozjpeg": "^9.0.0",
|
||||
@ -1312,6 +1313,7 @@
|
||||
"jest-resolve": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"jest-worker": "^26.6.2",
|
||||
"node-notifier": "^8.0.0",
|
||||
"slash": "^3.0.0",
|
||||
"source-map": "^0.6.0",
|
||||
"string-length": "^4.0.1",
|
||||
@ -3788,6 +3790,7 @@
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.1",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
@ -9511,7 +9514,350 @@
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
<<<<<<< HEAD
|
||||
"node_modules/jest-each/node_modules/pretty-format": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz",
|
||||
"integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"ansi-regex": "^5.0.0",
|
||||
"ansi-styles": "^4.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-each/node_modules/react-is": {
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
|
||||
"integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jest-environment-jsdom": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz",
|
||||
"integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"jsdom": "^16.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/@jest/types": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||
"integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^15.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/@types/istanbul-reports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
|
||||
"integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-jsdom/node_modules/jest-util": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
|
||||
"integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"is-ci": "^2.0.0",
|
||||
"micromatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-node": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz",
|
||||
"integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/fake-timers": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"jest-mock": "^26.6.2",
|
||||
"jest-util": "^26.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-node/node_modules/@jest/types": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||
"integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^15.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-node/node_modules/@types/istanbul-reports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
|
||||
"integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-node/node_modules/chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-environment-node/node_modules/jest-util": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
|
||||
"integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"is-ci": "^2.0.0",
|
||||
"micromatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-get-type": {
|
||||
"version": "26.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz",
|
||||
"integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-haste-map": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz",
|
||||
"integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/graceful-fs": "^4.1.2",
|
||||
"@types/node": "*",
|
||||
"anymatch": "^3.0.3",
|
||||
"fb-watchman": "^2.0.0",
|
||||
"fsevents": "^2.1.2",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"jest-regex-util": "^26.0.0",
|
||||
"jest-serializer": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"jest-worker": "^26.6.2",
|
||||
"micromatch": "^4.0.2",
|
||||
"sane": "^4.0.3",
|
||||
"walker": "^1.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-haste-map/node_modules/@jest/types": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||
"integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^15.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-haste-map/node_modules/@types/istanbul-reports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
|
||||
"integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-haste-map/node_modules/chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-haste-map/node_modules/jest-util": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz",
|
||||
"integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"is-ci": "^2.0.0",
|
||||
"micromatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-haste-map/node_modules/jest-worker": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
|
||||
"integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2": {
|
||||
"version": "26.6.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz",
|
||||
"integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.1.0",
|
||||
"@jest/environment": "^26.6.2",
|
||||
"@jest/source-map": "^26.6.2",
|
||||
"@jest/test-result": "^26.6.2",
|
||||
"@jest/types": "^26.6.2",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"co": "^4.6.0",
|
||||
"expect": "^26.6.2",
|
||||
"is-generator-fn": "^2.0.0",
|
||||
"jest-each": "^26.6.2",
|
||||
"jest-matcher-utils": "^26.6.2",
|
||||
"jest-message-util": "^26.6.2",
|
||||
"jest-runtime": "^26.6.3",
|
||||
"jest-snapshot": "^26.6.2",
|
||||
"jest-util": "^26.6.2",
|
||||
"pretty-format": "^26.6.2",
|
||||
"throat": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@jest/types": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz",
|
||||
"integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^15.0.0",
|
||||
"chalk": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/@types/istanbul-reports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
|
||||
"integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-jasmine2/node_modules/jest-util": {
|
||||
=======
|
||||
"node_modules/jest-haste-map": {
|
||||
>>>>>>> upstream/main
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz",
|
||||
"integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"cypress:open": "cypress open"
|
||||
},
|
||||
"dependencies": {
|
||||
"cypress": "^6.8.0",
|
||||
"emoji-mart": "^3.0.1",
|
||||
"imagemin-gifsicle": "^7.0.0",
|
||||
"imagemin-mozjpeg": "^9.0.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user