MM-36764 mlog refactor (#18118)
Refactor mlog - simplify mlog by removing redundant code - remove Zap dependency - update unit test helpers - update logging config - update auditing
Этот коммит содержится в:
3
Makefile
3
Makefile
@@ -453,8 +453,7 @@ run-server: prepackaged-binaries validate-go-version start-docker ## Starts the
|
||||
@echo Running mattermost for development
|
||||
|
||||
mkdir -p $(BUILD_WEBAPP_DIR)/dist/files
|
||||
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) 2>&1 | \
|
||||
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) logs --logrus $(RUN_IN_BACKGROUND)
|
||||
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) $(RUN_IN_BACKGROUND)
|
||||
|
||||
debug-server: start-docker ## Compile and start server using delve.
|
||||
mkdir -p $(BUILD_WEBAPP_DIR)/dist/files
|
||||
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/store/localcachelayer"
|
||||
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
"github.com/mattermost/mattermost-server/v6/web"
|
||||
"github.com/mattermost/mattermost-server/v6/wsapi"
|
||||
)
|
||||
@@ -67,6 +66,8 @@ type TestHelper struct {
|
||||
LocalClient *model.Client4
|
||||
|
||||
IncludeCacheLayer bool
|
||||
|
||||
TestLogger *mlog.Logger
|
||||
}
|
||||
|
||||
var mainHelper *testlib.MainHelper
|
||||
@@ -120,6 +121,15 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
||||
options = append(options, app.StoreOverride(dbStore))
|
||||
}
|
||||
|
||||
testLogger, _ := mlog.NewLogger()
|
||||
logCfg, _ := config.MloggerConfigFromLoggerConfig(&memoryConfig.LogSettings, nil, config.GetLogFileLocation)
|
||||
if errCfg := testLogger.ConfigureTargets(logCfg); errCfg != nil {
|
||||
panic("failed to configure test logger: " + errCfg.Error())
|
||||
}
|
||||
// lock logger config so server init cannot override it during testing.
|
||||
testLogger.LockConfiguration()
|
||||
options = append(options, app.SetLogger(testLogger))
|
||||
|
||||
s, err := app.NewServer(options...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -131,6 +141,7 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
||||
ConfigStore: configStore,
|
||||
IncludeCacheLayer: includeCache,
|
||||
Context: &request.Context{},
|
||||
TestLogger: testLogger,
|
||||
}
|
||||
|
||||
if s.SearchEngine != nil && s.SearchEngine.BleveEngine != nil && searchEngine != nil {
|
||||
@@ -336,15 +347,11 @@ func (th *TestHelper) ShutdownApp() {
|
||||
}
|
||||
|
||||
func (th *TestHelper) TearDown() {
|
||||
utils.DisableDebugLogForTest()
|
||||
if th.IncludeCacheLayer {
|
||||
// Clean all the caches
|
||||
th.App.Srv().InvalidateAllCaches()
|
||||
}
|
||||
|
||||
th.ShutdownApp()
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
var initBasicOnce sync.Once
|
||||
@@ -501,12 +508,10 @@ func (th *TestHelper) CreateBotWithClient(client *model.Client4) *model.Bot {
|
||||
Description: "bot",
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
rbot, _, err := client.CreateBot(bot)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return rbot
|
||||
}
|
||||
|
||||
@@ -527,12 +532,10 @@ func (th *TestHelper) CreateTeamWithClient(client *model.Client4) *model.Team {
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
rteam, _, err := client.CreateTeam(team)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return rteam
|
||||
}
|
||||
|
||||
@@ -548,7 +551,6 @@ func (th *TestHelper) CreateUserWithClient(client *model.Client4) *model.User {
|
||||
Password: "Pa$$word11",
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
ruser, _, err := client.CreateUser(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -559,7 +561,6 @@ func (th *TestHelper) CreateUserWithClient(client *model.Client4) *model.User {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return ruser
|
||||
}
|
||||
|
||||
@@ -651,12 +652,10 @@ func (th *TestHelper) CreateChannelWithClientAndTeam(client *model.Client4, chan
|
||||
TeamId: teamId,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
rchannel, _, err := client.CreateChannel(channel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return rchannel
|
||||
}
|
||||
|
||||
@@ -680,12 +679,10 @@ func (th *TestHelper) CreatePostWithClient(client *model.Client4, channel *model
|
||||
Message: "message_" + id,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
rpost, _, err := client.CreatePost(post)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return rpost
|
||||
}
|
||||
|
||||
@@ -698,12 +695,10 @@ func (th *TestHelper) CreatePinnedPostWithClient(client *model.Client4, channel
|
||||
IsPinned: true,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
rpost, _, err := client.CreatePost(post)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return rpost
|
||||
}
|
||||
|
||||
@@ -713,12 +708,10 @@ func (th *TestHelper) CreateMessagePostWithClient(client *model.Client4, channel
|
||||
Message: message,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
rpost, _, err := client.CreatePost(post)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return rpost
|
||||
}
|
||||
|
||||
@@ -738,13 +731,11 @@ func (th *TestHelper) CreateMessagePostNoClient(channel *model.Channel, message
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
var channel *model.Channel
|
||||
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
@@ -769,82 +760,59 @@ func (th *TestHelper) LoginSystemManager() {
|
||||
}
|
||||
|
||||
func (th *TestHelper) LoginBasicWithClient(client *model.Client4) {
|
||||
utils.DisableDebugLogForTest()
|
||||
_, _, err := client.Login(th.BasicUser.Email, th.BasicUser.Password)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) LoginBasic2WithClient(client *model.Client4) {
|
||||
utils.DisableDebugLogForTest()
|
||||
_, _, err := client.Login(th.BasicUser2.Email, th.BasicUser2.Password)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) LoginTeamAdminWithClient(client *model.Client4) {
|
||||
utils.DisableDebugLogForTest()
|
||||
_, _, err := client.Login(th.TeamAdminUser.Email, th.TeamAdminUser.Password)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) LoginSystemManagerWithClient(client *model.Client4) {
|
||||
utils.DisableDebugLogForTest()
|
||||
_, _, err := client.Login(th.SystemManagerUser.Email, th.SystemManagerUser.Password)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) LoginSystemAdminWithClient(client *model.Client4) {
|
||||
utils.DisableDebugLogForTest()
|
||||
_, _, err := client.Login(th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) UpdateActiveUser(user *model.User, active bool) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
_, err := th.App.UpdateActive(th.Context, user, active)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
_, err := th.App.JoinUserToTeam(th.Context, team, user, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
member, err := th.App.AddUserToChannel(user, channel, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
return member
|
||||
}
|
||||
|
||||
@@ -864,12 +832,10 @@ func (th *TestHelper) CreateGroup() *model.Group {
|
||||
RemoteId: "ri_" + id,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
group, err := th.App.CreateGroup(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return group
|
||||
}
|
||||
|
||||
@@ -1107,59 +1073,39 @@ func (th *TestHelper) cleanupTestFile(info *model.FileInfo) error {
|
||||
}
|
||||
|
||||
func (th *TestHelper) MakeUserChannelAdmin(user *model.User, channel *model.Channel) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
if cm, err := th.App.Srv().Store.Channel().GetMember(context.Background(), channel.Id, user.Id); err == nil {
|
||||
cm.SchemeAdmin = true
|
||||
if _, err = th.App.Srv().Store.Channel().UpdateMember(cm); err != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) UpdateUserToTeamAdmin(user *model.User, team *model.Team) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
if tm, err := th.App.Srv().Store.Team().GetMember(context.Background(), team.Id, user.Id); err == nil {
|
||||
tm.SchemeAdmin = true
|
||||
if _, err = th.App.Srv().Store.Team().UpdateMember(tm); err != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) UpdateUserToNonTeamAdmin(user *model.User, team *model.Team) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
if tm, err := th.App.Srv().Store.Team().GetMember(context.Background(), team.Id, user.Id); err == nil {
|
||||
tm.SchemeAdmin = false
|
||||
if _, err = th.App.Srv().Store.Team().UpdateMember(tm); err != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
results := make(map[string][]string)
|
||||
|
||||
for _, roleName := range []string{
|
||||
@@ -1172,24 +1118,18 @@ func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string {
|
||||
} {
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
results[roleName] = role.Permissions
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
return results
|
||||
}
|
||||
|
||||
func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
for roleName, permissions := range data {
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
@@ -1201,20 +1141,14 @@ func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) {
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
@@ -1226,7 +1160,6 @@ func (th *TestHelper) RemovePermissionFromRole(permission string, roleName strin
|
||||
}
|
||||
|
||||
if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
|
||||
utils.EnableDebugLogForTest()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1234,25 +1167,18 @@ func (th *TestHelper) RemovePermissionFromRole(permission string, roleName strin
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
for _, existingPermission := range role.Permissions {
|
||||
if existingPermission == permission {
|
||||
utils.EnableDebugLogForTest()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1261,11 +1187,8 @@ func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) SetupTeamScheme() *model.Scheme {
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock"
|
||||
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
func TestCreateChannel(t *testing.T) {
|
||||
@@ -3238,7 +3237,6 @@ func TestAutocompleteChannels(t *testing.T) {
|
||||
defer th.TearDown()
|
||||
|
||||
// A private channel to make sure private channels are not used
|
||||
utils.DisableDebugLogForTest()
|
||||
ptown, _, _ := th.Client.CreateChannel(&model.Channel{
|
||||
DisplayName: "Town",
|
||||
Name: "town",
|
||||
@@ -3251,7 +3249,6 @@ func TestAutocompleteChannels(t *testing.T) {
|
||||
Type: model.ChannelTypeOpen,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
})
|
||||
utils.EnableDebugLogForTest()
|
||||
defer func() {
|
||||
th.Client.DeleteChannel(ptown.Id)
|
||||
th.Client.DeleteChannel(tower.Id)
|
||||
@@ -3320,7 +3317,6 @@ func TestAutocompleteChannelsForSearch(t *testing.T) {
|
||||
defer th.App.PermanentDeleteUser(th.Context, u4)
|
||||
|
||||
// A private channel to make sure private channels are not used
|
||||
utils.DisableDebugLogForTest()
|
||||
ptown, _, _ := th.SystemAdminClient.CreateChannel(&model.Channel{
|
||||
DisplayName: "Town",
|
||||
Name: "town",
|
||||
@@ -3339,7 +3335,6 @@ func TestAutocompleteChannelsForSearch(t *testing.T) {
|
||||
defer func() {
|
||||
th.Client.DeleteChannel(mypriv.Id)
|
||||
}()
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
dc1, _, err := th.Client.CreateDirectChannel(th.BasicUser.Id, u1.Id)
|
||||
require.NoError(t, err)
|
||||
@@ -3450,7 +3445,6 @@ func TestAutocompleteChannelsForSearchGuestUsers(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// A private channel to make sure private channels are not used
|
||||
utils.DisableDebugLogForTest()
|
||||
town, _, _ := th.SystemAdminClient.CreateChannel(&model.Channel{
|
||||
DisplayName: "Town",
|
||||
Name: "town",
|
||||
@@ -3475,8 +3469,6 @@ func TestAutocompleteChannelsForSearchGuestUsers(t *testing.T) {
|
||||
_, _, err = th.SystemAdminClient.AddChannelMember(mypriv.Id, guest.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
dc1, _, err := th.SystemAdminClient.CreateDirectChannel(th.BasicUser.Id, guest.Id)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -468,7 +467,7 @@ func TestUpdateConfigDiffInAuditRecord(t *testing.T) {
|
||||
require.Equal(t, timeoutVal+1, *cfg.ServiceSettings.ReadTimeout)
|
||||
|
||||
// Forcing a flush before attempting to read log's content.
|
||||
err = th.Server.Log.Flush(context.Background())
|
||||
err = th.Server.Audit.Flush()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, logFile.Sync())
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
@@ -25,8 +24,6 @@ func TestMain(m *testing.M) {
|
||||
WithReadReplica: replicaFlag,
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
@@ -327,9 +327,12 @@ func TestGetLogs(t *testing.T) {
|
||||
mlog.Info(strconv.Itoa(i))
|
||||
}
|
||||
|
||||
err := th.TestLogger.Flush()
|
||||
require.NoError(t, err, "failed to flush log")
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
||||
logs, _, err := c.GetLogs(0, 10)
|
||||
require.NoError(t, err)
|
||||
logs, _, err2 := c.GetLogs(0, 10)
|
||||
require.NoError(t, err2)
|
||||
require.Len(t, logs, 10)
|
||||
|
||||
for i := 10; i < 20; i++ {
|
||||
@@ -347,8 +350,8 @@ func TestGetLogs(t *testing.T) {
|
||||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
||||
_, resp, err := th.Client.GetLogs(0, 10)
|
||||
require.Error(t, err)
|
||||
_, resp, err2 := th.Client.GetLogs(0, 10)
|
||||
require.Error(t, err2)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
|
||||
10
app/admin.go
10
app/admin.go
@@ -4,7 +4,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -13,11 +12,11 @@ import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mail"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
func (s *Server) GetLogs(page, perPage int) ([]string, *model.AppError) {
|
||||
@@ -63,11 +62,8 @@ func (s *Server) GetLogsSkipSend(page, perPage int) ([]string, *model.AppError)
|
||||
var lines []string
|
||||
|
||||
if *s.Config().LogSettings.EnableFile {
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), mlog.DefaultFlushTimeout)
|
||||
defer timeoutCancel()
|
||||
mlog.Flush(timeoutCtx)
|
||||
|
||||
logFile := utils.GetLogFileLocation(*s.Config().LogSettings.FileLocation)
|
||||
s.Log.Flush()
|
||||
logFile := config.GetLogFileLocation(*s.Config().LogSettings.FileLocation)
|
||||
file, err := os.Open(logFile)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
@@ -232,7 +232,7 @@ type AppIface interface {
|
||||
// LogAuditRec logs an audit record using default LvlAuditCLI.
|
||||
LogAuditRec(rec *audit.Record, err error)
|
||||
// LogAuditRecWithLevel logs an audit record using specified Level.
|
||||
LogAuditRecWithLevel(rec *audit.Record, level mlog.LogLevel, err error)
|
||||
LogAuditRecWithLevel(rec *audit.Record, level mlog.Level, err error)
|
||||
// MakeAuditRecord creates a audit record pre-populated with defaults.
|
||||
MakeAuditRecord(event string, initialStatus string) *audit.Record
|
||||
// MarkChanelAsUnreadFromPost will take a post and set the channel as unread from that one.
|
||||
|
||||
69
app/audit.go
69
app/audit.go
@@ -9,8 +9,6 @@ import (
|
||||
"net/http"
|
||||
"os/user"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
@@ -66,7 +64,7 @@ func (a *App) LogAuditRec(rec *audit.Record, err error) {
|
||||
}
|
||||
|
||||
// LogAuditRecWithLevel logs an audit record using specified Level.
|
||||
func (a *App) LogAuditRecWithLevel(rec *audit.Record, level mlog.LogLevel, err error) {
|
||||
func (a *App) LogAuditRecWithLevel(rec *audit.Record, level mlog.Level, err error) {
|
||||
if rec == nil {
|
||||
return
|
||||
}
|
||||
@@ -106,64 +104,27 @@ func (a *App) MakeAuditRecord(event string, initialStatus string) *audit.Record
|
||||
}
|
||||
|
||||
func (s *Server) configureAudit(adt *audit.Audit, bAllowAdvancedLogging bool) error {
|
||||
var errs error
|
||||
|
||||
adt.OnQueueFull = s.onAuditTargetQueueFull
|
||||
adt.OnError = s.onAuditError
|
||||
|
||||
// Configure target for rotating file output (E0, E10)
|
||||
if *s.Config().ExperimentalAuditSettings.FileEnabled {
|
||||
opts := audit.FileOptions{
|
||||
Filename: *s.Config().ExperimentalAuditSettings.FileName,
|
||||
MaxSize: *s.Config().ExperimentalAuditSettings.FileMaxSizeMB,
|
||||
MaxAge: *s.Config().ExperimentalAuditSettings.FileMaxAgeDays,
|
||||
MaxBackups: *s.Config().ExperimentalAuditSettings.FileMaxBackups,
|
||||
Compress: *s.Config().ExperimentalAuditSettings.FileCompress,
|
||||
}
|
||||
|
||||
maxQueueSize := *s.Config().ExperimentalAuditSettings.FileMaxQueueSize
|
||||
if maxQueueSize <= 0 {
|
||||
maxQueueSize = audit.DefMaxQueueSize
|
||||
}
|
||||
|
||||
filter := adt.MakeFilter(LevelAPI, LevelContent, LevelPerms, LevelCLI)
|
||||
formatter := adt.MakeJSONFormatter()
|
||||
formatter.DisableTimestamp = false
|
||||
target, err := audit.NewFileTarget(filter, formatter, opts, maxQueueSize)
|
||||
if err != nil {
|
||||
errs = multierror.Append(err)
|
||||
} else {
|
||||
mlog.Debug("File audit target created successfully", mlog.String("filename", opts.Filename))
|
||||
adt.AddTarget(target)
|
||||
}
|
||||
}
|
||||
|
||||
// Advanced logging for audit requires license.
|
||||
var logConfigSrc config.LogConfigSrc
|
||||
dsn := *s.Config().ExperimentalAuditSettings.AdvancedLoggingConfig
|
||||
if !bAllowAdvancedLogging || dsn == "" {
|
||||
return errs
|
||||
}
|
||||
cfg, err := config.NewLogConfigSrc(dsn, s.configStore)
|
||||
if err != nil {
|
||||
errs = multierror.Append(fmt.Errorf("invalid config for audit, %w", err))
|
||||
return errs
|
||||
}
|
||||
mlog.Debug("Loaded audit configuration", mlog.String("source", dsn))
|
||||
|
||||
for name, t := range cfg.Get() {
|
||||
if len(t.Levels) == 0 {
|
||||
t.Levels = mlog.MLvlAuditAll
|
||||
}
|
||||
target, err := mlog.NewLogrTarget(name, t)
|
||||
if bAllowAdvancedLogging && dsn != "" {
|
||||
var err error
|
||||
logConfigSrc, err = config.NewLogConfigSrc(dsn, s.configStore)
|
||||
if err != nil {
|
||||
errs = multierror.Append(err)
|
||||
continue
|
||||
}
|
||||
if target != nil {
|
||||
adt.AddTarget(target)
|
||||
return fmt.Errorf("invalid config source for audit, %w", err)
|
||||
}
|
||||
mlog.Debug("Loaded audit configuration", mlog.String("source", dsn))
|
||||
}
|
||||
return errs
|
||||
|
||||
// ExperimentalAuditSettings provides basic file audit (E0, E10); logConfigSrc provides advanced config (E20).
|
||||
cfg, err := config.MloggerConfigFromAuditConfig(s.Config().ExperimentalAuditSettings, logConfigSrc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid config for audit, %w", err)
|
||||
}
|
||||
|
||||
return adt.Configure(cfg)
|
||||
}
|
||||
|
||||
func (s *Server) onAuditTargetQueueFull(qname string, maxQSize int) bool {
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
@@ -172,12 +171,10 @@ func (th *TestHelper) CreateTeam() *model.Team {
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err error
|
||||
if team, err = th.store.Team().Save(team); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return team
|
||||
}
|
||||
|
||||
@@ -192,13 +189,11 @@ func (th *TestHelper) createChannel(team *model.Team, channelType string) *model
|
||||
CreatorId: th.BasicUser.Id,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err error
|
||||
if channel, err = th.store.Channel().Save(channel, *th.configStore.Get().TeamSettings.MaxChannelsPerTeam); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
@@ -26,8 +25,6 @@ func TestMain(m *testing.M) {
|
||||
WithReadReplica: replicaFlag,
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
var randomJPEG []byte
|
||||
@@ -59,8 +58,7 @@ func BenchmarkUploadFile(b *testing.B) {
|
||||
prepareTestImages(b)
|
||||
th := Setup(b).InitBasic()
|
||||
defer th.TearDown()
|
||||
// disable logging in the benchmark, as best we can
|
||||
th.App.Log().SetConsoleLevel(mlog.LevelError)
|
||||
|
||||
teamID := model.NewId()
|
||||
channelID := model.NewId()
|
||||
userID := model.NewId()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -27,7 +26,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/store/sqlstore"
|
||||
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
@@ -41,7 +39,8 @@ type TestHelper struct {
|
||||
BasicPost *model.Post
|
||||
|
||||
SystemAdminUser *model.User
|
||||
LogBuffer *bytes.Buffer
|
||||
LogBuffer *mlog.Buffer
|
||||
TestLogger *mlog.Logger
|
||||
IncludeCacheLayer bool
|
||||
|
||||
tempWorkspace string
|
||||
@@ -55,16 +54,16 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
||||
|
||||
configStore := config.NewTestMemoryStore()
|
||||
|
||||
config := configStore.Get()
|
||||
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||
*config.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||
*config.LogSettings.EnableSentry = false // disable error reporting during tests
|
||||
*config.AnnouncementSettings.AdminNoticesEnabled = false
|
||||
*config.AnnouncementSettings.UserNoticesEnabled = false
|
||||
configStore.Set(config)
|
||||
memoryConfig := configStore.Get()
|
||||
*memoryConfig.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||
*memoryConfig.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||
*memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||
*memoryConfig.LogSettings.EnableSentry = false // disable error reporting during tests
|
||||
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
||||
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
||||
configStore.Set(memoryConfig)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
buffer := &mlog.Buffer{}
|
||||
|
||||
var options []Option
|
||||
options = append(options, ConfigStore(configStore))
|
||||
@@ -80,7 +79,18 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
||||
} else {
|
||||
options = append(options, StoreOverride(dbStore))
|
||||
}
|
||||
options = append(options, SetLogger(mlog.NewTestingLogger(tb, buffer)))
|
||||
|
||||
testLogger, _ := mlog.NewLogger()
|
||||
logCfg, _ := config.MloggerConfigFromLoggerConfig(&memoryConfig.LogSettings, nil, config.GetLogFileLocation)
|
||||
if errCfg := testLogger.ConfigureTargets(logCfg); errCfg != nil {
|
||||
panic("failed to configure test logger: " + errCfg.Error())
|
||||
}
|
||||
if errW := mlog.AddWriterTarget(testLogger, buffer, true, mlog.StdAll...); errW != nil {
|
||||
panic("failed to add writer target to test logger: " + errW.Error())
|
||||
}
|
||||
// lock logger config so server init cannot override it during testing.
|
||||
testLogger.LockConfiguration()
|
||||
options = append(options, SetLogger(testLogger))
|
||||
|
||||
s, err := NewServer(options...)
|
||||
if err != nil {
|
||||
@@ -92,6 +102,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
||||
Context: &request.Context{},
|
||||
Server: s,
|
||||
LogBuffer: buffer,
|
||||
TestLogger: testLogger,
|
||||
IncludeCacheLayer: includeCacheLayer,
|
||||
}
|
||||
|
||||
@@ -240,12 +251,10 @@ func (th *TestHelper) CreateTeam() *model.Team {
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if team, err = th.App.CreateTeam(th.Context, team); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return team
|
||||
}
|
||||
|
||||
@@ -268,7 +277,6 @@ func (th *TestHelper) CreateUserOrGuest(guest bool) *model.User {
|
||||
EmailVerified: true,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if guest {
|
||||
if user, err = th.App.CreateGuest(th.Context, user); err != nil {
|
||||
@@ -279,7 +287,6 @@ func (th *TestHelper) CreateUserOrGuest(guest bool) *model.User {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -331,7 +338,6 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
|
||||
option(channel)
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var appErr *model.AppError
|
||||
if channel, appErr = th.App.CreateChannel(th.Context, channel, true); appErr != nil {
|
||||
panic(appErr)
|
||||
@@ -353,29 +359,24 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
var channel *model.Channel
|
||||
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateGroupChannel(user1 *model.User, user2 *model.User) *model.Channel {
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
var channel *model.Channel
|
||||
if channel, err = th.App.CreateGroupChannel([]string{th.BasicUser.Id, user1.Id, user2.Id}, th.BasicUser.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
@@ -389,12 +390,10 @@ func (th *TestHelper) CreatePost(channel *model.Channel) *model.Post {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return post
|
||||
}
|
||||
|
||||
@@ -406,47 +405,32 @@ func (th *TestHelper) CreateMessagePost(channel *model.Channel, message string)
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return post
|
||||
}
|
||||
|
||||
func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
_, err := th.App.JoinUserToTeam(th.Context, team, user, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) RemoveUserFromTeam(user *model.User, team *model.Team) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
err := th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
member, err := th.App.AddUserToChannel(user, channel, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
return member
|
||||
}
|
||||
|
||||
@@ -456,8 +440,6 @@ func (th *TestHelper) CreateRole(roleName string) *model.Role {
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateScheme() (*model.Scheme, []*model.Role) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
scheme, err := th.App.CreateScheme(&model.Scheme{
|
||||
DisplayName: "Test Scheme Display Name",
|
||||
Name: model.NewId(),
|
||||
@@ -485,9 +467,6 @@ func (th *TestHelper) CreateScheme() (*model.Scheme, []*model.Role) {
|
||||
}
|
||||
roles = append(roles, role)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
return scheme, roles
|
||||
}
|
||||
|
||||
@@ -501,18 +480,14 @@ func (th *TestHelper) CreateGroup() *model.Group {
|
||||
RemoteId: model.NewId(),
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if group, err = th.App.CreateGroup(group); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return group
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateEmoji() *model.Emoji {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
emoji, err := th.App.Srv().Store.Emoji().Save(&model.Emoji{
|
||||
CreatorId: th.BasicUser.Id,
|
||||
Name: model.NewRandomString(10),
|
||||
@@ -520,15 +495,10 @@ func (th *TestHelper) CreateEmoji() *model.Emoji {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
return emoji
|
||||
}
|
||||
|
||||
func (th *TestHelper) AddReactionToPost(post *model.Post, user *model.User, emojiName string) *model.Reaction {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
reaction, err := th.App.SaveReactionForPost(th.Context, &model.Reaction{
|
||||
UserId: user.Id,
|
||||
PostId: post.Id,
|
||||
@@ -537,9 +507,6 @@ func (th *TestHelper) AddReactionToPost(post *model.Post, user *model.User, emoj
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
return reaction
|
||||
}
|
||||
|
||||
@@ -668,11 +635,8 @@ func (th *TestHelper) SetupPluginAPI() *PluginAPI {
|
||||
}
|
||||
|
||||
func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
@@ -684,7 +648,6 @@ func (th *TestHelper) RemovePermissionFromRole(permission string, roleName strin
|
||||
}
|
||||
|
||||
if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
|
||||
utils.EnableDebugLogForTest()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -692,25 +655,18 @@ func (th *TestHelper) RemovePermissionFromRole(permission string, roleName strin
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
for _, existingPermission := range role.Permissions {
|
||||
if existingPermission == permission {
|
||||
utils.EnableDebugLogForTest()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -719,11 +675,8 @@ func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
// This function is copy of storetest/NewTestId
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
@@ -26,8 +25,6 @@ func TestMain(m *testing.M) {
|
||||
WithReadReplica: replicaFlag,
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
@@ -11251,7 +11251,7 @@ func (a *OpenTracingAppLayer) LogAuditRec(rec *audit.Record, err error) {
|
||||
a.app.LogAuditRec(rec, err)
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) LogAuditRecWithLevel(rec *audit.Record, level mlog.LogLevel, err error) {
|
||||
func (a *OpenTracingAppLayer) LogAuditRecWithLevel(rec *audit.Record, level mlog.Level, err error) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.LogAuditRecWithLevel")
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ type PluginAPI struct {
|
||||
id string
|
||||
app *App
|
||||
ctx *request.Context
|
||||
logger *mlog.SugarLogger
|
||||
logger mlog.Sugar
|
||||
manifest *model.Manifest
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewPluginAPI(a *App, c *request.Context, manifest *model.Manifest) *PluginA
|
||||
manifest: manifest,
|
||||
ctx: c,
|
||||
app: a,
|
||||
logger: a.Log().With(mlog.String("plugin_id", manifest.Id)).Sugar(),
|
||||
logger: a.Log().Sugar(mlog.String("plugin_id", manifest.Id)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -724,11 +724,14 @@ func TestPluginPanicLogs(t *testing.T) {
|
||||
}
|
||||
_, err := th.App.CreatePost(th.Context, post, th.BasicChannel, false, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
th.TestLogger.Flush()
|
||||
|
||||
// We shutdown plugins first so that the read on the log buffer is race-free.
|
||||
th.App.Srv().ShutDownPlugins()
|
||||
tearDown()
|
||||
|
||||
testlib.AssertLog(t, th.LogBuffer, mlog.LevelDebug, "panic: some text from panic")
|
||||
testlib.AssertLog(t, th.LogBuffer, mlog.LvlDebug.Name, "panic: some text from panic")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -970,7 +970,7 @@ func TestCreatePostAsUser(t *testing.T) {
|
||||
_, appErr := th.App.CreatePostAsUser(th.Context, post, "", true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
testlib.AssertLog(t, th.LogBuffer, mlog.LevelWarn, "Failed to get membership")
|
||||
testlib.AssertLog(t, th.LogBuffer, mlog.LvlWarn.Name, "Failed to get membership")
|
||||
})
|
||||
|
||||
t.Run("does not log warning for bot user not in channel", func(t *testing.T) {
|
||||
@@ -993,7 +993,7 @@ func TestCreatePostAsUser(t *testing.T) {
|
||||
_, appErr = th.App.CreatePostAsUser(th.Context, post, "", true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
testlib.AssertNoLog(t, th.LogBuffer, mlog.LevelWarn, "Failed to get membership")
|
||||
testlib.AssertNoLog(t, th.LogBuffer, mlog.LvlWarn.Name, "Failed to get membership")
|
||||
})
|
||||
|
||||
t.Run("marks channel as viewed for reply post when CRT is off", func(t *testing.T) {
|
||||
|
||||
176
app/server.go
176
app/server.go
@@ -138,7 +138,6 @@ type Server struct {
|
||||
statusCache cache.Cache
|
||||
configListenerId string
|
||||
licenseListenerId string
|
||||
logListenerId string
|
||||
clusterLeaderListenerId string
|
||||
searchConfigListenerId string
|
||||
searchLicenseListenerId string
|
||||
@@ -146,8 +145,6 @@ type Server struct {
|
||||
configStore *config.Store
|
||||
postActionCookieSecret []byte
|
||||
|
||||
advancedLogListenerCleanup func()
|
||||
|
||||
pluginCommands []*PluginCommand
|
||||
pluginCommandsLock sync.RWMutex
|
||||
|
||||
@@ -787,74 +784,79 @@ func (s *Server) DatabaseTypeAndMattermostVersion() (string, string) {
|
||||
return *s.Config().SqlSettings.DriverName, mattermostVersion.Value
|
||||
}
|
||||
|
||||
// initLogging initializes and configures the logger. This may be called more than once.
|
||||
// initLogging initializes and configures the logger(s). This may be called more than once.
|
||||
func (s *Server) initLogging() error {
|
||||
var err error
|
||||
// create the app logger if needed
|
||||
if s.Log == nil {
|
||||
s.Log = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(&s.Config().LogSettings, utils.GetLogFileLocation))
|
||||
}
|
||||
|
||||
// Use this app logger as the global logger (eventually remove all instances of global logging).
|
||||
// This is deferred because a copy is made of the logger and it must be fully configured before
|
||||
// the copy is made.
|
||||
defer mlog.InitGlobalLogger(s.Log)
|
||||
|
||||
// Redirect default Go logger to this logger.
|
||||
defer mlog.RedirectStdLog(s.Log)
|
||||
|
||||
if s.NotificationsLog == nil {
|
||||
notificationLogSettings := utils.GetLogSettingsFromNotificationsLogSettings(&s.Config().NotificationLogSettings)
|
||||
s.NotificationsLog = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(notificationLogSettings, utils.GetNotificationsLogFileLocation)).
|
||||
WithCallerSkip(1).With(mlog.String("logSource", "notifications"))
|
||||
}
|
||||
|
||||
if s.logListenerId != "" {
|
||||
s.RemoveConfigListener(s.logListenerId)
|
||||
}
|
||||
s.logListenerId = s.AddConfigListener(func(_, after *model.Config) {
|
||||
s.Log.ChangeLevels(utils.MloggerConfigFromLoggerConfig(&after.LogSettings, utils.GetLogFileLocation))
|
||||
|
||||
notificationLogSettings := utils.GetLogSettingsFromNotificationsLogSettings(&after.NotificationLogSettings)
|
||||
s.NotificationsLog.ChangeLevels(utils.MloggerConfigFromLoggerConfig(notificationLogSettings, utils.GetNotificationsLogFileLocation))
|
||||
})
|
||||
|
||||
// Configure advanced logging.
|
||||
// Advanced logging is E20 only, however logging must be initialized before the license
|
||||
// file is loaded. If no valid E20 license exists then advanced logging will be
|
||||
// shutdown once license is loaded/checked.
|
||||
if *s.Config().LogSettings.AdvancedLoggingConfig != "" {
|
||||
dsn := *s.Config().LogSettings.AdvancedLoggingConfig
|
||||
|
||||
cfg, err := config.NewLogConfigSrc(dsn, s.configStore)
|
||||
s.Log, err = mlog.NewLogger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid advanced logging config, %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Log.ConfigAdvancedLogging(cfg.Get()); err != nil {
|
||||
return fmt.Errorf("error configuring advanced logging, %w", err)
|
||||
// create notification logger if needed
|
||||
if s.NotificationsLog == nil {
|
||||
l, err := mlog.NewLogger()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.NotificationsLog = l.With(mlog.String("logSource", "notifications"))
|
||||
}
|
||||
|
||||
mlog.Info("Loaded advanced logging config", mlog.String("source", dsn))
|
||||
|
||||
listenerId := cfg.AddListener(func(_, newCfg mlog.LogTargetCfg) {
|
||||
if err := s.Log.ConfigAdvancedLogging(newCfg); err != nil {
|
||||
mlog.Error("Error re-configuring advanced logging", mlog.Err(err))
|
||||
} else {
|
||||
mlog.Info("Re-configured advanced logging")
|
||||
}
|
||||
})
|
||||
|
||||
// In case initLogging is called more than once.
|
||||
if s.advancedLogListenerCleanup != nil {
|
||||
s.advancedLogListenerCleanup()
|
||||
if err := s.configureLogger("logging", s.Log, &s.Config().LogSettings, s.configStore, config.GetLogFileLocation); err != nil {
|
||||
// if the config is locked then a unit test has already configured and locked the logger; not an error.
|
||||
if !errors.Is(err, mlog.ErrConfigurationLock) {
|
||||
// revert to default logger if the config is invalid
|
||||
mlog.InitGlobalLogger(nil)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.advancedLogListenerCleanup = func() {
|
||||
cfg.RemoveListener(listenerId)
|
||||
// Redirect default Go logger to app logger.
|
||||
s.Log.RedirectStdLog(mlog.LvlStdLog)
|
||||
|
||||
// Use the app logger as the global logger (eventually remove all instances of global logging).
|
||||
mlog.InitGlobalLogger(s.Log)
|
||||
|
||||
notificationLogSettings := config.GetLogSettingsFromNotificationsLogSettings(&s.Config().NotificationLogSettings)
|
||||
if err := s.configureLogger("notification logging", s.NotificationsLog, notificationLogSettings, s.configStore, config.GetNotificationsLogFileLocation); err != nil {
|
||||
if !errors.Is(err, mlog.ErrConfigurationLock) {
|
||||
mlog.Error("Error configuring notification logger", mlog.Err(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureLogger applies the specified configuration to a logger.
|
||||
func (s *Server) configureLogger(name string, logger *mlog.Logger, logSettings *model.LogSettings, configStore *config.Store, getPath func(string) string) error {
|
||||
// Advanced logging is E20 only, however logging must be initialized before the license
|
||||
// file is loaded. If no valid E20 license exists then advanced logging will be
|
||||
// shutdown once license is loaded/checked.
|
||||
var err error
|
||||
dsn := *logSettings.AdvancedLoggingConfig
|
||||
var logConfigSrc config.LogConfigSrc
|
||||
if dsn != "" {
|
||||
logConfigSrc, err = config.NewLogConfigSrc(dsn, configStore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid config source for %s, %w", name, err)
|
||||
}
|
||||
mlog.Info("Loaded configuration for "+name, mlog.String("source", dsn))
|
||||
}
|
||||
|
||||
cfg, err := config.MloggerConfigFromLoggerConfig(logSettings, logConfigSrc, getPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid config source for %s, %w", name, err)
|
||||
}
|
||||
|
||||
if err := logger.ConfigureTargets(cfg); err != nil {
|
||||
return fmt.Errorf("invalid config for %s, %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeUnlicensedLogTargets removes any unlicensed log target types.
|
||||
func (s *Server) removeUnlicensedLogTargets(license *model.License) {
|
||||
if license != nil && *license.Features.AdvancedLogging {
|
||||
// advanced logging enabled via license; no need to remove any targets
|
||||
@@ -864,8 +866,12 @@ func (s *Server) removeUnlicensedLogTargets(license *model.License) {
|
||||
timeoutCtx, cancelCtx := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancelCtx()
|
||||
|
||||
mlog.RemoveTargets(timeoutCtx, func(ti mlog.TargetInfo) bool {
|
||||
return ti.Type != "*target.Writer" && ti.Type != "*target.File"
|
||||
s.Log.RemoveTargets(timeoutCtx, func(ti mlog.TargetInfo) bool {
|
||||
return ti.Type != "*targets.Writer" && ti.Type != "*targets.File"
|
||||
})
|
||||
|
||||
s.NotificationsLog.RemoveTargets(timeoutCtx, func(ti mlog.TargetInfo) bool {
|
||||
return ti.Type != "*targets.Writer" && ti.Type != "*targets.File"
|
||||
})
|
||||
}
|
||||
|
||||
@@ -939,11 +945,15 @@ func (s *Server) enableLoggingMetrics() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := mlog.EnableMetrics(s.Metrics.GetLoggerMetricsCollector()); err != nil {
|
||||
mlog.Error("Failed to enable advanced logging metrics", mlog.Err(err))
|
||||
} else {
|
||||
mlog.Debug("Advanced logging metrics enabled")
|
||||
s.Log.SetMetricsCollector(s.Metrics.GetLoggerMetricsCollector(), mlog.DefaultMetricsUpdateFreqMillis)
|
||||
|
||||
// logging config needs to be reloaded when metrics collector is added or changed.
|
||||
if err := s.initLogging(); err != nil {
|
||||
mlog.Error("Error re-configuring logging for metrics")
|
||||
return
|
||||
}
|
||||
|
||||
mlog.Debug("Logging metrics enabled")
|
||||
}
|
||||
|
||||
const TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second
|
||||
@@ -1014,13 +1024,7 @@ func (s *Server) Shutdown() {
|
||||
|
||||
s.WaitForGoroutines()
|
||||
|
||||
if s.advancedLogListenerCleanup != nil {
|
||||
s.advancedLogListenerCleanup()
|
||||
s.advancedLogListenerCleanup = nil
|
||||
}
|
||||
|
||||
s.RemoveConfigListener(s.configListenerId)
|
||||
s.RemoveConfigListener(s.logListenerId)
|
||||
s.stopSearchEngine()
|
||||
|
||||
s.Audit.Shutdown()
|
||||
@@ -1058,12 +1062,6 @@ func (s *Server) Shutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), time.Second*15)
|
||||
defer timeoutCancel()
|
||||
if err := mlog.Flush(timeoutCtx); err != nil {
|
||||
mlog.Warn("Error flushing logs", mlog.Err(err))
|
||||
}
|
||||
|
||||
s.dndTaskMut.Lock()
|
||||
if s.dndTask != nil {
|
||||
s.dndTask.Cancel()
|
||||
@@ -1072,10 +1070,15 @@ func (s *Server) Shutdown() {
|
||||
|
||||
mlog.Info("Server stopped")
|
||||
|
||||
// this should just write the "server stopped" record, the rest are already flushed.
|
||||
timeoutCtx2, timeoutCancel2 := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer timeoutCancel2()
|
||||
_ = mlog.ShutdownAdvancedLogging(timeoutCtx2)
|
||||
// shutdown main and notification loggers which will flush any remaining log records.
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), time.Second*15)
|
||||
defer timeoutCancel()
|
||||
if err = s.NotificationsLog.ShutdownWithTimeout(timeoutCtx); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error shutting down notification logger: %v", err)
|
||||
}
|
||||
if err = s.Log.ShutdownWithTimeout(timeoutCtx); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error shutting down main logger: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Restart() error {
|
||||
@@ -1202,7 +1205,7 @@ func (s *Server) Start() error {
|
||||
|
||||
// If we have debugging of CORS turned on then forward messages to logs
|
||||
if debug {
|
||||
corsWrapper.Log = s.Log.StdLog(mlog.String("source", "cors"))
|
||||
corsWrapper.Log = s.Log.With(mlog.String("source", "cors")).StdLogger(mlog.LvlDebug)
|
||||
}
|
||||
|
||||
handler = corsWrapper.Handler(handler)
|
||||
@@ -1222,10 +1225,7 @@ func (s *Server) Start() error {
|
||||
s.Busy = NewBusy(s.Cluster)
|
||||
|
||||
// Creating a logger for logging errors from http.Server at error level
|
||||
errStdLog, err := s.Log.StdLogAt(mlog.LevelError, mlog.String("source", "httpserver"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
errStdLog := s.Log.With(mlog.String("source", "httpserver")).StdLogger(mlog.LvlError)
|
||||
|
||||
s.Server = &http.Server{
|
||||
Handler: handler,
|
||||
@@ -1270,7 +1270,7 @@ func (s *Server) Start() error {
|
||||
server := &http.Server{
|
||||
Addr: httpListenAddress,
|
||||
Handler: m.HTTPHandler(nil),
|
||||
ErrorLog: s.Log.StdLog(mlog.String("source", "le_forwarder_server")),
|
||||
ErrorLog: s.Log.With(mlog.String("source", "le_forwarder_server")).StdLogger(mlog.LvlError),
|
||||
}
|
||||
go server.ListenAndServe()
|
||||
} else {
|
||||
@@ -1284,7 +1284,7 @@ func (s *Server) Start() error {
|
||||
|
||||
server := &http.Server{
|
||||
Handler: http.HandlerFunc(handleHTTPRedirect),
|
||||
ErrorLog: s.Log.StdLog(mlog.String("source", "forwarder_server")),
|
||||
ErrorLog: s.Log.With(mlog.String("source", "forwarder_server")).StdLogger(mlog.LvlError),
|
||||
}
|
||||
server.Serve(redirectListener)
|
||||
}()
|
||||
@@ -2026,7 +2026,7 @@ func (a *App) getNotificationsLog() (*model.FileData, string) {
|
||||
// Getting notifications.log
|
||||
if *a.Srv().Config().NotificationLogSettings.EnableFile {
|
||||
// notifications.log
|
||||
notificationsLog := utils.GetNotificationsLogFileLocation(*a.Srv().Config().LogSettings.FileLocation)
|
||||
notificationsLog := config.GetNotificationsLogFileLocation(*a.Srv().Config().LogSettings.FileLocation)
|
||||
|
||||
notificationsLogFileData, notificationsLogFileDataErr := ioutil.ReadFile(notificationsLog)
|
||||
|
||||
@@ -2053,7 +2053,7 @@ func (a *App) getMattermostLog() (*model.FileData, string) {
|
||||
// Getting mattermost.log
|
||||
if *a.Srv().Config().LogSettings.EnableFile {
|
||||
// mattermost.log
|
||||
mattermostLog := utils.GetLogFileLocation(*a.Srv().Config().LogSettings.FileLocation)
|
||||
mattermostLog := config.GetLogFileLocation(*a.Srv().Config().LogSettings.FileLocation)
|
||||
|
||||
mattermostLogFileData, mattermostLogFileDataErr := ioutil.ReadFile(mattermostLog)
|
||||
|
||||
|
||||
@@ -516,29 +516,29 @@ func checkEndpoint(t *testing.T, client *http.Client, url string) error {
|
||||
}
|
||||
|
||||
func TestPanicLog(t *testing.T) {
|
||||
// Creating a temp file to collect logs
|
||||
tmpfile, err := ioutil.TempFile("", "mlog")
|
||||
if err != nil {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Creating a temp dir for log
|
||||
tmpDir, err := os.MkdirTemp("", "mlog-test")
|
||||
require.NoError(t, err, "cannot create tmp dir for log file")
|
||||
defer func() {
|
||||
require.NoError(t, tmpfile.Close())
|
||||
require.NoError(t, os.Remove(tmpfile.Name()))
|
||||
err2 := os.RemoveAll(tmpDir)
|
||||
assert.NoError(t, err2)
|
||||
}()
|
||||
|
||||
// This test requires Zap file target for now.
|
||||
mlog.EnableZap()
|
||||
defer mlog.DisableZap()
|
||||
|
||||
// Creating logger to log to console and temp file
|
||||
logger := mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
EnableFile: true,
|
||||
FileLocation: tmpfile.Name(),
|
||||
FileLevel: mlog.LevelInfo,
|
||||
})
|
||||
logger, _ := mlog.NewLogger()
|
||||
|
||||
logSettings := model.NewLogSettings()
|
||||
logSettings.EnableConsole = model.NewBool(true)
|
||||
logSettings.ConsoleJson = model.NewBool(true)
|
||||
logSettings.EnableFile = model.NewBool(true)
|
||||
logSettings.FileLocation = &tmpDir
|
||||
logSettings.FileLevel = &mlog.LvlInfo.Name
|
||||
|
||||
cfg, err := config.MloggerConfigFromLoggerConfig(logSettings, nil, config.GetLogFileLocation)
|
||||
require.NoError(t, err)
|
||||
err = logger.ConfigureTargets(cfg)
|
||||
require.NoError(t, err)
|
||||
logger.LockConfiguration()
|
||||
|
||||
// Creating a server with logger
|
||||
s, err := NewServer(SetLogger(logger))
|
||||
@@ -567,16 +567,22 @@ func TestPanicLog(t *testing.T) {
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
client.Get("https://localhost:" + strconv.Itoa(s.ListenAddr.Port) + "/panic")
|
||||
|
||||
err = logger.Flush()
|
||||
assert.NoError(t, err, "flush should succeed")
|
||||
s.Shutdown()
|
||||
|
||||
// Checking whether panic was logged
|
||||
var panicLogged = false
|
||||
var infoLogged = false
|
||||
|
||||
_, err = tmpfile.Seek(0, 0)
|
||||
logFile, err := os.Open(config.GetLogFileLocation(tmpDir))
|
||||
require.NoError(t, err, "cannot open log file")
|
||||
|
||||
_, err = logFile.Seek(0, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
scanner := bufio.NewScanner(tmpfile)
|
||||
scanner := bufio.NewScanner(logFile)
|
||||
for scanner.Scan() {
|
||||
if !infoLogged && strings.Contains(scanner.Text(), "inside panic handler") {
|
||||
infoLogged = true
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
"github.com/mattermost/mattermost-server/v6/store/localcachelayer"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
@@ -36,6 +35,7 @@ type TestHelper struct {
|
||||
|
||||
SystemAdminUser *model.User
|
||||
LogBuffer *bytes.Buffer
|
||||
TestLogger *mlog.Logger
|
||||
IncludeCacheLayer bool
|
||||
|
||||
tempWorkspace string
|
||||
@@ -49,15 +49,15 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
||||
|
||||
memoryStore := config.NewTestMemoryStore()
|
||||
|
||||
config := memoryStore.Get()
|
||||
memoryConfig := memoryStore.Get()
|
||||
if configSet != nil {
|
||||
configSet(config)
|
||||
configSet(memoryConfig)
|
||||
}
|
||||
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||
*config.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||
*config.LogSettings.EnableSentry = false // disable error reporting during tests
|
||||
memoryStore.Set(config)
|
||||
*memoryConfig.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||
*memoryConfig.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||
*memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||
*memoryConfig.LogSettings.EnableSentry = false // disable error reporting during tests
|
||||
memoryStore.Set(memoryConfig)
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
|
||||
@@ -74,7 +74,15 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
||||
} else {
|
||||
options = append(options, app.StoreOverride(dbStore))
|
||||
}
|
||||
options = append(options, app.SetLogger(mlog.NewTestingLogger(tb, buffer)))
|
||||
|
||||
testLogger, _ := mlog.NewLogger()
|
||||
logCfg, _ := config.MloggerConfigFromLoggerConfig(&memoryConfig.LogSettings, nil, config.GetLogFileLocation)
|
||||
if errCfg := testLogger.ConfigureTargets(logCfg); errCfg != nil {
|
||||
panic("failed to configure test logger: " + errCfg.Error())
|
||||
}
|
||||
// lock logger config so server init cannot override it during testing.
|
||||
testLogger.LockConfiguration()
|
||||
options = append(options, app.SetLogger(testLogger))
|
||||
|
||||
s, err := app.NewServer(options...)
|
||||
if err != nil {
|
||||
@@ -86,6 +94,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
||||
Context: &request.Context{},
|
||||
Server: s,
|
||||
LogBuffer: buffer,
|
||||
TestLogger: testLogger,
|
||||
IncludeCacheLayer: includeCacheLayer,
|
||||
}
|
||||
|
||||
@@ -188,12 +197,11 @@ func (th *TestHelper) createTeam() *model.Team {
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if team, err = th.App.CreateTeam(th.Context, team); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
return team
|
||||
}
|
||||
|
||||
@@ -216,7 +224,6 @@ func (th *TestHelper) createUserOrGuest(guest bool) *model.User {
|
||||
EmailVerified: true,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if guest {
|
||||
if user, err = th.App.CreateGuest(th.Context, user); err != nil {
|
||||
@@ -227,7 +234,6 @@ func (th *TestHelper) createUserOrGuest(guest bool) *model.User {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -262,7 +268,6 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
|
||||
option(channel)
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil {
|
||||
panic(err)
|
||||
@@ -284,7 +289,6 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
@@ -299,34 +303,28 @@ func (th *TestHelper) createChannelWithAnotherUser(team *model.Team, channelType
|
||||
CreatorId: userID,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
func (th *TestHelper) createDmChannel(user *model.User) *model.Channel {
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
var channel *model.Channel
|
||||
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
func (th *TestHelper) createGroupChannel(user1 *model.User, user2 *model.User) *model.Channel {
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
var channel *model.Channel
|
||||
if channel, err = th.App.CreateGroupChannel([]string{th.BasicUser.Id, user1.Id, user2.Id}, th.BasicUser.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
@@ -340,36 +338,25 @@ func (th *TestHelper) createPost(channel *model.Channel) *model.Post {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return post
|
||||
}
|
||||
|
||||
func (th *TestHelper) linkUserToTeam(user *model.User, team *model.Team) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
_, err := th.App.JoinUserToTeam(th.Context, team, user, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) addUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
member, err := th.App.AddUserToChannel(user, channel, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
return member
|
||||
}
|
||||
|
||||
@@ -401,11 +388,8 @@ func (th *TestHelper) tearDown() {
|
||||
}
|
||||
|
||||
func (th *TestHelper) removePermissionFromRole(permission string, roleName string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
@@ -417,7 +401,6 @@ func (th *TestHelper) removePermissionFromRole(permission string, roleName strin
|
||||
}
|
||||
|
||||
if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
|
||||
utils.EnableDebugLogForTest()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -425,25 +408,18 @@ func (th *TestHelper) removePermissionFromRole(permission string, roleName strin
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) addPermissionToRole(permission string, roleName string) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
for _, existingPermission := range role.Permissions {
|
||||
if existingPermission == permission {
|
||||
utils.EnableDebugLogForTest()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -452,9 +428,6 @@ func (th *TestHelper) addPermissionToRole(permission string, roleName string) {
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
utils.EnableDebugLogForTest()
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package slashcommands
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
@@ -18,8 +17,6 @@ func TestMain(m *testing.M) {
|
||||
EnableResources: true,
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
150
audit/audit.go
150
audit/audit.go
@@ -5,17 +5,12 @@ package audit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
"github.com/mattermost/logr/format"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
type Audit struct {
|
||||
lgr *logr.Logr
|
||||
logger logr.Logger
|
||||
logger *mlog.Logger
|
||||
|
||||
// OnQueueFull is called on an attempt to add an audit record to a full queue.
|
||||
// Return true to drop record, or false to block until there is room in queue.
|
||||
@@ -26,56 +21,34 @@ type Audit struct {
|
||||
}
|
||||
|
||||
func (a *Audit) Init(maxQueueSize int) {
|
||||
a.lgr = &logr.Logr{MaxQueueSize: maxQueueSize}
|
||||
a.logger = a.lgr.NewLogger()
|
||||
|
||||
a.lgr.OnQueueFull = a.onQueueFull
|
||||
a.lgr.OnTargetQueueFull = a.onTargetQueueFull
|
||||
a.lgr.OnLoggerError = a.onLoggerError
|
||||
}
|
||||
|
||||
// MakeFilter creates a filter which only allows the specified audit levels to be output.
|
||||
func (a *Audit) MakeFilter(level ...mlog.LogLevel) *logr.CustomFilter {
|
||||
filter := &logr.CustomFilter{}
|
||||
for _, l := range level {
|
||||
filter.Add(logr.Level(l))
|
||||
}
|
||||
return filter
|
||||
}
|
||||
|
||||
// MakeJSONFormatter creates a formatter that outputs JSON suitable for audit records.
|
||||
func (a *Audit) MakeJSONFormatter() *format.JSON {
|
||||
f := &format.JSON{
|
||||
DisableTimestamp: true,
|
||||
DisableMsg: true,
|
||||
DisableStacktrace: true,
|
||||
DisableLevel: true,
|
||||
ContextSorter: sortAuditFields,
|
||||
}
|
||||
return f
|
||||
a.logger, _ = mlog.NewLogger(
|
||||
mlog.MaxQueueSize(maxQueueSize),
|
||||
mlog.OnLoggerError(a.onLoggerError),
|
||||
mlog.OnQueueFull(a.onQueueFull),
|
||||
mlog.OnTargetQueueFull(a.onTargetQueueFull),
|
||||
)
|
||||
}
|
||||
|
||||
// LogRecord emits an audit record with complete info.
|
||||
func (a *Audit) LogRecord(level mlog.LogLevel, rec Record) {
|
||||
flds := logr.Fields{}
|
||||
flds[KeyAPIPath] = rec.APIPath
|
||||
flds[KeyEvent] = rec.Event
|
||||
flds[KeyStatus] = rec.Status
|
||||
flds[KeyUserID] = rec.UserID
|
||||
flds[KeySessionID] = rec.SessionID
|
||||
flds[KeyClient] = rec.Client
|
||||
flds[KeyIPAddress] = rec.IPAddress
|
||||
|
||||
for k, v := range rec.Meta {
|
||||
flds[k] = v
|
||||
func (a *Audit) LogRecord(level mlog.Level, rec Record) {
|
||||
flds := []mlog.Field{
|
||||
mlog.String(KeyAPIPath, rec.APIPath),
|
||||
mlog.String(KeyEvent, rec.Event),
|
||||
mlog.String(KeyStatus, rec.Status),
|
||||
mlog.String(KeyUserID, rec.UserID),
|
||||
mlog.String(KeySessionID, rec.SessionID),
|
||||
mlog.String(KeyClient, rec.Client),
|
||||
mlog.String(KeyIPAddress, rec.IPAddress),
|
||||
}
|
||||
|
||||
l := a.logger.WithFields(flds)
|
||||
l.Log(logr.Level(level))
|
||||
for k, v := range rec.Meta {
|
||||
flds = append(flds, mlog.Any(k, v))
|
||||
}
|
||||
a.logger.Log(level, "", flds...)
|
||||
}
|
||||
|
||||
// Log emits an audit record based on minimum required info.
|
||||
func (a *Audit) Log(level mlog.LogLevel, path string, evt string, status string, userID string, sessionID string, meta Meta) {
|
||||
func (a *Audit) Log(level mlog.Level, path string, evt string, status string, userID string, sessionID string, meta Meta) {
|
||||
a.LogRecord(level, Record{
|
||||
APIPath: path,
|
||||
Event: evt,
|
||||
@@ -86,20 +59,30 @@ func (a *Audit) Log(level mlog.LogLevel, path string, evt string, status string,
|
||||
})
|
||||
}
|
||||
|
||||
// AddTarget adds a Logr target to the list of targets each audit record will be output to.
|
||||
func (a *Audit) AddTarget(target logr.Target) {
|
||||
a.lgr.AddTarget(target)
|
||||
// Configure sets zero or more target to output audit logs to.
|
||||
func (a *Audit) Configure(cfg mlog.LoggerConfiguration) error {
|
||||
return a.logger.ConfigureTargets(cfg)
|
||||
}
|
||||
|
||||
// Shutdown cleanly stops the audit engine after making best efforts to flush all targets.
|
||||
func (a *Audit) Shutdown() {
|
||||
err := a.lgr.Shutdown()
|
||||
// Flush attempts to write all queued audit records to all targets.
|
||||
func (a *Audit) Flush() error {
|
||||
err := a.logger.Flush()
|
||||
if err != nil {
|
||||
a.onLoggerError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Audit) onQueueFull(rec *logr.LogRec, maxQueueSize int) bool {
|
||||
// Shutdown cleanly stops the audit engine after making best efforts to flush all targets.
|
||||
func (a *Audit) Shutdown() error {
|
||||
err := a.logger.Shutdown()
|
||||
if err != nil {
|
||||
a.onLoggerError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Audit) onQueueFull(rec *mlog.LogRec, maxQueueSize int) bool {
|
||||
if a.OnQueueFull != nil {
|
||||
return a.OnQueueFull("main", maxQueueSize)
|
||||
}
|
||||
@@ -107,7 +90,7 @@ func (a *Audit) onQueueFull(rec *logr.LogRec, maxQueueSize int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *Audit) onTargetQueueFull(target logr.Target, rec *logr.LogRec, maxQueueSize int) bool {
|
||||
func (a *Audit) onTargetQueueFull(target mlog.Target, rec *mlog.LogRec, maxQueueSize int) bool {
|
||||
if a.OnQueueFull != nil {
|
||||
return a.OnQueueFull(fmt.Sprintf("%v", target), maxQueueSize)
|
||||
}
|
||||
@@ -118,58 +101,7 @@ func (a *Audit) onTargetQueueFull(target logr.Target, rec *logr.LogRec, maxQueue
|
||||
func (a *Audit) onLoggerError(err error) {
|
||||
if a.OnError != nil {
|
||||
a.OnError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// sortAuditFields sorts the context fields of an audit record such that some fields
|
||||
// are prepended in order, some are appended in order, and the rest are sorted alphabetically.
|
||||
// This is done to make reading the records easier since common fields will appear in the same order.
|
||||
func sortAuditFields(fields logr.Fields) []format.ContextField {
|
||||
prependKeys := []string{KeyEvent, KeyStatus, KeyUserID, KeySessionID, KeyIPAddress}
|
||||
appendKeys := []string{KeyClusterID, KeyClient}
|
||||
|
||||
// sort alphabetically any fields not in the prepend/append lists.
|
||||
keys := make([]string, 0, len(fields))
|
||||
for k := range fields {
|
||||
if !findIn(k, prependKeys, appendKeys) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
allKeys := make([]string, 0, len(fields))
|
||||
|
||||
// add any prepends that exist in fields
|
||||
for _, k := range prependKeys {
|
||||
if _, ok := fields[k]; ok {
|
||||
allKeys = append(allKeys, k)
|
||||
}
|
||||
}
|
||||
|
||||
// sorted
|
||||
allKeys = append(allKeys, keys...)
|
||||
|
||||
// add any appends that exist in fields
|
||||
for _, k := range appendKeys {
|
||||
if _, ok := fields[k]; ok {
|
||||
allKeys = append(allKeys, k)
|
||||
}
|
||||
}
|
||||
|
||||
cfs := make([]format.ContextField, 0, len(allKeys))
|
||||
for _, k := range allKeys {
|
||||
cfs = append(cfs, format.ContextField{Key: k, Val: fields[k]})
|
||||
}
|
||||
return cfs
|
||||
}
|
||||
|
||||
func findIn(s string, arrs ...[]string) bool {
|
||||
for _, list := range arrs {
|
||||
for _, key := range list {
|
||||
if s == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
mlog.Error("Auditing error", mlog.Err(err))
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package audit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
"github.com/mattermost/logr/format"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_sortAuditFields(t *testing.T) {
|
||||
type args struct {
|
||||
fields logr.Fields
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []format.ContextField
|
||||
}{
|
||||
{name: "empty list",
|
||||
args: args{fields: logr.Fields{}},
|
||||
want: []format.ContextField{},
|
||||
},
|
||||
{name: "partial list",
|
||||
args: args{fields: logr.Fields{"zProp": "x", "xProp": "x", "yProp": "x", KeyClusterID: "x", KeyEvent: "x"}},
|
||||
want: []format.ContextField{
|
||||
{Key: KeyEvent, Val: "x"},
|
||||
{Key: "xProp", Val: "x"},
|
||||
{Key: "yProp", Val: "x"},
|
||||
{Key: "zProp", Val: "x"},
|
||||
{Key: KeyClusterID, Val: "x"},
|
||||
},
|
||||
},
|
||||
{name: "append/prepend only list",
|
||||
args: args{fields: logr.Fields{KeyClusterID: "x", KeyEvent: "x", KeySessionID: "x", KeyIPAddress: "x", KeyClient: "x",
|
||||
KeyUserID: "x", KeyStatus: "x"}},
|
||||
want: []format.ContextField{
|
||||
// prepend: KeyEvent, KeyStatus, KeyUserID, KeySessionID, KeyIPAddress
|
||||
// append: KeyClusterID, KeyClient
|
||||
{Key: KeyEvent, Val: "x"},
|
||||
{Key: KeyStatus, Val: "x"},
|
||||
{Key: KeyUserID, Val: "x"},
|
||||
{Key: KeySessionID, Val: "x"},
|
||||
{Key: KeyIPAddress, Val: "x"},
|
||||
{Key: KeyClusterID, Val: "x"},
|
||||
{Key: KeyClient, Val: "x"},
|
||||
},
|
||||
},
|
||||
{name: "sortables only list",
|
||||
args: args{fields: logr.Fields{"zProp": "x", "xProp": "x", "yProp": "x"}},
|
||||
want: []format.ContextField{
|
||||
{Key: "xProp", Val: "x"},
|
||||
{Key: "yProp", Val: "x"},
|
||||
{Key: "zProp", Val: "x"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := sortAuditFields(tt.args.fields)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package audit
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
"github.com/mattermost/logr/target"
|
||||
)
|
||||
|
||||
type FileOptions target.FileOptions
|
||||
|
||||
// NewFileTarget creates a target capable of outputting log records to a rotated file.
|
||||
func NewFileTarget(filter logr.Filter, formatter logr.Formatter, opts FileOptions, maxQSize int) (*target.File, error) {
|
||||
fopts := target.FileOptions(opts)
|
||||
err := checkFileWritable(fopts.Filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target := target.NewFileTarget(filter, formatter, fopts, maxQSize)
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func checkFileWritable(filename string) error {
|
||||
// try opening/creating the file for writing
|
||||
file, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.Close()
|
||||
return nil
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog/human"
|
||||
)
|
||||
|
||||
var LogsCmd = &cobra.Command{
|
||||
Use: "logs",
|
||||
Short: "Display logs in a human-readable format",
|
||||
RunE: logsCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
LogsCmd.Flags().Bool("logrus", false, "Use logrus for formatting.")
|
||||
RootCmd.AddCommand(LogsCmd)
|
||||
}
|
||||
|
||||
func logsCmdF(command *cobra.Command, args []string) error {
|
||||
// check stdin to see if we have a pipe
|
||||
fi, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var input io.Reader
|
||||
if fi.Size() == 0 && fi.Mode()&os.ModeNamedPipe == 0 {
|
||||
file, err := os.Open("mattermost.log")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
input = file
|
||||
} else {
|
||||
input = os.Stdin
|
||||
}
|
||||
var writer human.LogWriter
|
||||
|
||||
if flag, _ := command.Flags().GetBool("logrus"); flag {
|
||||
writer = human.NewLogrusWriter(os.Stdout)
|
||||
} else {
|
||||
writer = human.NewSimpleWriter(os.Stdout)
|
||||
}
|
||||
human.ProcessLogs(input, writer)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/api4"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
@@ -28,8 +27,6 @@ func TestMain(m *testing.M) {
|
||||
EnableResources: true,
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
api4.SetMainHelper(mainHelper)
|
||||
|
||||
@@ -57,7 +57,7 @@ func (e *logSrcEmitter) RemoveListener(id string) {
|
||||
}
|
||||
|
||||
// invokeConfigListeners synchronously notifies all listeners about the configuration change.
|
||||
func (e *logSrcEmitter) invokeConfigListeners(oldCfg, newCfg mlog.LogTargetCfg) {
|
||||
func (e *logSrcEmitter) invokeConfigListeners(oldCfg, newCfg mlog.LoggerConfiguration) {
|
||||
e.listeners.Range(func(key, value interface{}) bool {
|
||||
listener := value.(LogSrcListener)
|
||||
listener(oldCfg, newCfg)
|
||||
|
||||
@@ -56,18 +56,18 @@ func TestEmitter(t *testing.T) {
|
||||
func TestLogSrcEmitter(t *testing.T) {
|
||||
var e logSrcEmitter
|
||||
|
||||
expectedOldCfg := make(mlog.LogTargetCfg)
|
||||
expectedNewCfg := make(mlog.LogTargetCfg)
|
||||
expectedOldCfg := make(mlog.LoggerConfiguration)
|
||||
expectedNewCfg := make(mlog.LoggerConfiguration)
|
||||
|
||||
listener1 := false
|
||||
id1 := e.AddListener(func(oldCfg, newCfg mlog.LogTargetCfg) {
|
||||
id1 := e.AddListener(func(oldCfg, newCfg mlog.LoggerConfiguration) {
|
||||
assert.Equal(t, expectedOldCfg, oldCfg)
|
||||
assert.Equal(t, expectedNewCfg, newCfg)
|
||||
listener1 = true
|
||||
})
|
||||
|
||||
listener2 := false
|
||||
id2 := e.AddListener(func(oldCfg, newCfg mlog.LogTargetCfg) {
|
||||
id2 := e.AddListener(func(oldCfg, newCfg mlog.LoggerConfiguration) {
|
||||
assert.Equal(t, expectedOldCfg, oldCfg)
|
||||
assert.Equal(t, expectedNewCfg, newCfg)
|
||||
listener2 = true
|
||||
|
||||
@@ -6,7 +6,6 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -14,23 +13,17 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
type LogSrcListener func(old, new mlog.LogTargetCfg)
|
||||
type LogSrcListener func(old, new mlog.LoggerConfiguration)
|
||||
|
||||
// LogConfigSrc abstracts the Advanced Logging configuration so that implementations can
|
||||
// fetch from file, database, etc.
|
||||
type LogConfigSrc interface {
|
||||
// Get fetches the current, cached configuration.
|
||||
Get() mlog.LogTargetCfg
|
||||
Get() mlog.LoggerConfiguration
|
||||
|
||||
// Set updates the dsn specifying the source and reloads
|
||||
Set(dsn string, configStore *Store) (err error)
|
||||
|
||||
// AddListener adds a callback function to invoke when the configuration is modified.
|
||||
AddListener(listener LogSrcListener) string
|
||||
|
||||
// RemoveListener removes a callback function using an id returned from AddListener.
|
||||
RemoveListener(id string)
|
||||
|
||||
// Close cleans up resources.
|
||||
Close() error
|
||||
}
|
||||
@@ -38,6 +31,10 @@ type LogConfigSrc interface {
|
||||
// NewLogConfigSrc creates an advanced logging configuration source, backed by a
|
||||
// file, JSON string, or database.
|
||||
func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) {
|
||||
if dsn == "" {
|
||||
return nil, errors.New("dsn should not be empty")
|
||||
}
|
||||
|
||||
if configStore == nil {
|
||||
return nil, errors.New("configStore should not be nil")
|
||||
}
|
||||
@@ -63,7 +60,7 @@ func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) {
|
||||
type jsonSrc struct {
|
||||
logSrcEmitter
|
||||
mutex sync.RWMutex
|
||||
cfg mlog.LogTargetCfg
|
||||
cfg mlog.LoggerConfiguration
|
||||
}
|
||||
|
||||
func newJSONSrc(data string) (*jsonSrc, error) {
|
||||
@@ -72,7 +69,7 @@ func newJSONSrc(data string) (*jsonSrc, error) {
|
||||
}
|
||||
|
||||
// Get fetches the current, cached configuration
|
||||
func (src *jsonSrc) Get() mlog.LogTargetCfg {
|
||||
func (src *jsonSrc) Get() mlog.LoggerConfiguration {
|
||||
src.mutex.RLock()
|
||||
defer src.mutex.RUnlock()
|
||||
return src.cfg
|
||||
@@ -89,7 +86,7 @@ func (src *jsonSrc) Set(data string, _ *Store) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *jsonSrc) set(cfg mlog.LogTargetCfg) {
|
||||
func (src *jsonSrc) set(cfg mlog.LoggerConfiguration) {
|
||||
src.mutex.Lock()
|
||||
defer src.mutex.Unlock()
|
||||
|
||||
@@ -106,11 +103,9 @@ func (src *jsonSrc) Close() error {
|
||||
// fileSrc
|
||||
|
||||
type fileSrc struct {
|
||||
logSrcEmitter
|
||||
mutex sync.RWMutex
|
||||
cfg mlog.LogTargetCfg
|
||||
|
||||
path string
|
||||
cfg mlog.LoggerConfiguration
|
||||
path string
|
||||
}
|
||||
|
||||
func newFileSrc(path string, configStore *Store) (*fileSrc, error) {
|
||||
@@ -124,7 +119,7 @@ func newFileSrc(path string, configStore *Store) (*fileSrc, error) {
|
||||
}
|
||||
|
||||
// Get fetches the current, cached configuration
|
||||
func (src *fileSrc) Get() mlog.LogTargetCfg {
|
||||
func (src *fileSrc) Get() mlog.LoggerConfiguration {
|
||||
src.mutex.RLock()
|
||||
defer src.mutex.RUnlock()
|
||||
return src.cfg
|
||||
@@ -145,26 +140,14 @@ func (src *fileSrc) Set(path string, configStore *Store) error {
|
||||
}
|
||||
|
||||
src.set(cfg)
|
||||
|
||||
// If path is a real file and not just the name of a database resource then watch it for changes.
|
||||
// Absolute paths are explicit and require no resolution.
|
||||
if _, err = os.Stat(path); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
src.mutex.Lock()
|
||||
defer src.mutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *fileSrc) set(cfg mlog.LogTargetCfg) {
|
||||
func (src *fileSrc) set(cfg mlog.LoggerConfiguration) {
|
||||
src.mutex.Lock()
|
||||
defer src.mutex.Unlock()
|
||||
|
||||
old := src.cfg
|
||||
src.cfg = cfg
|
||||
src.invokeConfigListeners(old, cfg)
|
||||
}
|
||||
|
||||
// Close cleans up resources.
|
||||
@@ -172,8 +155,8 @@ func (src *fileSrc) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func logTargetCfgFromJSON(data []byte) (mlog.LogTargetCfg, error) {
|
||||
cfg := make(mlog.LogTargetCfg)
|
||||
func logTargetCfgFromJSON(data []byte) (mlog.LoggerConfiguration, error) {
|
||||
cfg := make(mlog.LoggerConfiguration)
|
||||
err := json.Unmarshal(data, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
216
config/logger.go
Обычный файл
216
config/logger.go
Обычный файл
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/utils/fileutils"
|
||||
)
|
||||
|
||||
const (
|
||||
LogRotateSize = 10000
|
||||
LogCompress = true
|
||||
LogRotateMaxAge = 0
|
||||
LogRotateMaxBackups = 0
|
||||
LogFilename = "mattermost.log"
|
||||
LogNotificationFilename = "notifications.log"
|
||||
LogMinLevelLen = 5
|
||||
LogMinMsgLen = 45
|
||||
LogDelim = " "
|
||||
LogEnableCaller = true
|
||||
)
|
||||
|
||||
type fileLocationFunc func(string) string
|
||||
|
||||
func MloggerConfigFromLoggerConfig(s *model.LogSettings, configSrc LogConfigSrc, getFileFunc fileLocationFunc) (mlog.LoggerConfiguration, error) {
|
||||
cfg := make(mlog.LoggerConfiguration)
|
||||
|
||||
var targetCfg mlog.TargetCfg
|
||||
var err error
|
||||
|
||||
// add the simple logging config
|
||||
if *s.EnableConsole {
|
||||
targetCfg, err = makeSimpleConsoleTarget(*s.ConsoleLevel, *s.ConsoleJson, *s.EnableColor)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg["_defConsole"] = targetCfg
|
||||
}
|
||||
|
||||
if *s.EnableFile {
|
||||
targetCfg, err = makeSimpleFileTarget(getFileFunc(*s.FileLocation), *s.FileLevel, *s.FileJson)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg["_defFile"] = targetCfg
|
||||
}
|
||||
|
||||
if configSrc == nil {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// add advanced logging config
|
||||
cfgAdv := configSrc.Get()
|
||||
cfg.Append(cfgAdv)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func MloggerConfigFromAuditConfig(auditSettings model.ExperimentalAuditSettings, configSrc LogConfigSrc) (mlog.LoggerConfiguration, error) {
|
||||
cfg := make(mlog.LoggerConfiguration)
|
||||
|
||||
var targetCfg mlog.TargetCfg
|
||||
var err error
|
||||
|
||||
// add the simple audit config
|
||||
if *auditSettings.FileEnabled {
|
||||
targetCfg, err = makeSimpleFileTarget(*auditSettings.FileName, "error", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// apply audit specific levels
|
||||
targetCfg.Levels = []mlog.Level{mlog.LvlAuditAPI, mlog.LvlAuditContent, mlog.LvlAuditPerms, mlog.LvlAuditCLI}
|
||||
|
||||
// apply audit specific formatting
|
||||
targetCfg.FormatOptions = json.RawMessage(`{"disable_timestamp": true, "disable_msg": true, "disable_stacktrace": true, "disable_level": true}`)
|
||||
|
||||
cfg["_defAudit"] = targetCfg
|
||||
}
|
||||
|
||||
if configSrc == nil {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// add advanced audit config
|
||||
cfgAdv := configSrc.Get()
|
||||
cfg.Append(cfgAdv)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func GetLogFileLocation(fileLocation string) string {
|
||||
if fileLocation == "" {
|
||||
fileLocation, _ = fileutils.FindDir("logs")
|
||||
}
|
||||
|
||||
return filepath.Join(fileLocation, LogFilename)
|
||||
}
|
||||
|
||||
func GetNotificationsLogFileLocation(fileLocation string) string {
|
||||
if fileLocation == "" {
|
||||
fileLocation, _ = fileutils.FindDir("logs")
|
||||
}
|
||||
|
||||
return filepath.Join(fileLocation, LogNotificationFilename)
|
||||
}
|
||||
|
||||
func GetLogSettingsFromNotificationsLogSettings(notificationLogSettings *model.NotificationLogSettings) *model.LogSettings {
|
||||
settings := &model.LogSettings{}
|
||||
settings.SetDefaults()
|
||||
settings.ConsoleJson = notificationLogSettings.ConsoleJson
|
||||
settings.ConsoleLevel = notificationLogSettings.ConsoleLevel
|
||||
settings.EnableConsole = notificationLogSettings.EnableConsole
|
||||
settings.EnableFile = notificationLogSettings.EnableFile
|
||||
settings.FileJson = notificationLogSettings.FileJson
|
||||
settings.FileLevel = notificationLogSettings.FileLevel
|
||||
settings.FileLocation = notificationLogSettings.FileLocation
|
||||
settings.AdvancedLoggingConfig = notificationLogSettings.AdvancedLoggingConfig
|
||||
settings.EnableColor = notificationLogSettings.EnableColor
|
||||
return settings
|
||||
}
|
||||
|
||||
func makeSimpleConsoleTarget(level string, outputJSON bool, color bool) (mlog.TargetCfg, error) {
|
||||
levels, err := stdLevels(level)
|
||||
if err != nil {
|
||||
return mlog.TargetCfg{}, err
|
||||
}
|
||||
|
||||
target := mlog.TargetCfg{
|
||||
Type: "console",
|
||||
Levels: levels,
|
||||
Options: json.RawMessage(`{"out": "stdout"}`),
|
||||
MaxQueueSize: 1000,
|
||||
}
|
||||
|
||||
if outputJSON {
|
||||
target.Format = "json"
|
||||
target.FormatOptions = makeJSONFormatOptions()
|
||||
} else {
|
||||
target.Format = "plain"
|
||||
target.FormatOptions = makePlainFormatOptions(color)
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func makeSimpleFileTarget(filename string, level string, json bool) (mlog.TargetCfg, error) {
|
||||
levels, err := stdLevels(level)
|
||||
if err != nil {
|
||||
return mlog.TargetCfg{}, err
|
||||
}
|
||||
|
||||
target := mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Levels: levels,
|
||||
Options: makeFileOptions(filename),
|
||||
MaxQueueSize: 1000,
|
||||
}
|
||||
|
||||
if json {
|
||||
target.Format = "json"
|
||||
target.FormatOptions = makeJSONFormatOptions()
|
||||
} else {
|
||||
target.Format = "plain"
|
||||
target.FormatOptions = makePlainFormatOptions(false)
|
||||
}
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func stdLevels(level string) ([]mlog.Level, error) {
|
||||
stdLevel, err := stringToStdLevel(level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var levels []mlog.Level
|
||||
for _, l := range mlog.StdAll {
|
||||
if l.ID <= stdLevel.ID {
|
||||
levels = append(levels, l)
|
||||
}
|
||||
}
|
||||
return levels, nil
|
||||
}
|
||||
|
||||
func stringToStdLevel(level string) (mlog.Level, error) {
|
||||
level = strings.ToLower(level)
|
||||
for _, l := range mlog.StdAll {
|
||||
if l.Name == level {
|
||||
return l, nil
|
||||
}
|
||||
}
|
||||
return mlog.Level{}, fmt.Errorf("%s is not a standard level", level)
|
||||
}
|
||||
|
||||
func makeJSONFormatOptions() json.RawMessage {
|
||||
str := fmt.Sprintf(`{"enable_caller": %t}`, LogEnableCaller)
|
||||
return json.RawMessage(str)
|
||||
}
|
||||
|
||||
func makePlainFormatOptions(enableColor bool) json.RawMessage {
|
||||
str := fmt.Sprintf(`{"delim": "%s", "min_level_len": %d, "min_msg_len": %d, "enable_color": %t, "enable_caller": %t}`,
|
||||
LogDelim, LogMinLevelLen, LogMinMsgLen, enableColor, LogEnableCaller)
|
||||
return json.RawMessage(str)
|
||||
}
|
||||
|
||||
func makeFileOptions(filename string) json.RawMessage {
|
||||
str := fmt.Sprintf(`{"filename": "%s", "max_size": %d, "max_age": %d, "max_backups": %d, "compress": %t}`,
|
||||
filename, LogRotateSize, LogRotateMaxAge, LogRotateMaxBackups, LogCompress)
|
||||
return json.RawMessage(str)
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
@@ -23,8 +22,6 @@ func TestMain(m *testing.M) {
|
||||
EnableStore: true,
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package einterfaces
|
||||
|
||||
import (
|
||||
"github.com/mattermost/logr"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
type MetricsInterface interface {
|
||||
@@ -66,7 +66,7 @@ type MetricsInterface interface {
|
||||
ObservePluginAPIDuration(pluginID, apiName string, success bool, elapsed float64)
|
||||
|
||||
ObserveEnabledUsers(users int64)
|
||||
GetLoggerMetricsCollector() logr.MetricsCollector
|
||||
GetLoggerMetricsCollector() mlog.MetricsCollector
|
||||
|
||||
IncrementRemoteClusterMsgSentCounter(remoteID string)
|
||||
IncrementRemoteClusterMsgReceivedCounter(remoteID string)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
logr "github.com/mattermost/logr"
|
||||
logr "github.com/mattermost/logr/v2"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/mattermost/mattermost-server/v6/model"
|
||||
|
||||
13
go.mod
13
go.mod
@@ -50,7 +50,7 @@ require (
|
||||
github.com/hashicorp/go-hclog v0.16.1
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
|
||||
github.com/hashicorp/go-msgpack v1.1.5 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-plugin v1.4.2
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
@@ -72,7 +72,8 @@ require (
|
||||
github.com/mattermost/gosaml2 v0.3.3
|
||||
github.com/mattermost/gziphandler v0.0.1
|
||||
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d
|
||||
github.com/mattermost/logr v1.0.13
|
||||
github.com/mattermost/logr v1.0.13 // indirect
|
||||
github.com/mattermost/logr/v2 v2.0.10
|
||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
@@ -102,7 +103,7 @@ require (
|
||||
github.com/russellhaering/goxmldsig v1.1.0
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/smartystreets/assertions v1.0.0 // indirect
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/splitio/go-client/v6 v6.1.0
|
||||
@@ -118,7 +119,7 @@ require (
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4
|
||||
github.com/wiggin77/merror v1.0.3
|
||||
github.com/wiggin77/srslog v1.0.1
|
||||
github.com/wiggin77/srslog v1.0.1 // indirect
|
||||
github.com/willf/bitset v1.1.11 // indirect
|
||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c
|
||||
github.com/yuin/goldmark v1.3.8
|
||||
@@ -126,7 +127,7 @@ require (
|
||||
go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect
|
||||
go.uber.org/atomic v1.8.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.17.0
|
||||
go.uber.org/zap v1.17.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||
@@ -138,7 +139,7 @@ require (
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/olivere/elastic.v6 v6.2.35
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
willnorris.com/go/imageproxy v0.10.0
|
||||
|
||||
14
go.sum
14
go.sum
@@ -655,6 +655,20 @@ 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.4 h1:LiqvzNNfia23hlu1zmmCDmjqmSfwWNsEtFlL4/7jf8o=
|
||||
github.com/mattermost/logr/v2 v2.0.4/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
|
||||
github.com/mattermost/logr/v2 v2.0.5 h1:qYuPn0FSDtsmxcwhGUsSDkiWCH17ZYts6BLAAgsp1ks=
|
||||
github.com/mattermost/logr/v2 v2.0.5/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
|
||||
github.com/mattermost/logr/v2 v2.0.6 h1:Ewmtqm+o6y6yDSqdMZdrP5o0P7rCKbBNJQl+FbhcWHg=
|
||||
github.com/mattermost/logr/v2 v2.0.6/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
|
||||
github.com/mattermost/logr/v2 v2.0.7 h1:rcwGa3faMWa/0k841FZhrcb8qZexfV8sk9ZVCV/8KUg=
|
||||
github.com/mattermost/logr/v2 v2.0.7/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
|
||||
github.com/mattermost/logr/v2 v2.0.8 h1:HQuie671ZtmIp0Y6gY0u34UQvnBw8rPFLeLxWOxv5VM=
|
||||
github.com/mattermost/logr/v2 v2.0.8/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
|
||||
github.com/mattermost/logr/v2 v2.0.9 h1:WZev0JYaWmRa/wmpXe8xGA+YDTj9iqbn25rM55Hgutw=
|
||||
github.com/mattermost/logr/v2 v2.0.9/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
|
||||
github.com/mattermost/logr/v2 v2.0.10 h1:i6rJbuX/EkBM9maM8M0eJ3rxB+fsBKNslPvzSlA2w/M=
|
||||
github.com/mattermost/logr/v2 v2.0.10/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg=
|
||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o=
|
||||
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
|
||||
@@ -5,13 +5,14 @@ package migrations
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/store/localcachelayer"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
type TestHelper struct {
|
||||
@@ -27,6 +28,8 @@ type TestHelper struct {
|
||||
SystemAdminUser *model.User
|
||||
|
||||
tempWorkspace string
|
||||
|
||||
TestLogger *mlog.Logger
|
||||
}
|
||||
|
||||
func setupTestHelper(enterprise bool) *TestHelper {
|
||||
@@ -44,6 +47,15 @@ func setupTestHelper(enterprise bool) *TestHelper {
|
||||
options = append(options, app.StoreOverride(mainHelper.Store))
|
||||
options = append(options, app.SkipPostInitializiation())
|
||||
|
||||
testLogger, _ := mlog.NewLogger()
|
||||
logCfg, _ := config.MloggerConfigFromLoggerConfig(&newConfig.LogSettings, nil, config.GetLogFileLocation)
|
||||
if errCfg := testLogger.ConfigureTargets(logCfg); errCfg != nil {
|
||||
panic("failed to configure test logger: " + errCfg.Error())
|
||||
}
|
||||
// lock logger config so server init cannot override it during testing.
|
||||
testLogger.LockConfiguration()
|
||||
options = append(options, app.SetLogger(testLogger))
|
||||
|
||||
s, err := app.NewServer(options...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -55,9 +67,10 @@ func setupTestHelper(enterprise bool) *TestHelper {
|
||||
}
|
||||
|
||||
th := &TestHelper{
|
||||
App: app.New(app.ServerConnector(s)),
|
||||
Context: &request.Context{},
|
||||
Server: s,
|
||||
App: app.New(app.ServerConnector(s)),
|
||||
Context: &request.Context{},
|
||||
Server: s,
|
||||
TestLogger: testLogger,
|
||||
}
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 })
|
||||
@@ -89,11 +102,11 @@ func setupTestHelper(enterprise bool) *TestHelper {
|
||||
return th
|
||||
}
|
||||
|
||||
func SetupEnterprise() *TestHelper {
|
||||
func SetupEnterprise(tb testing.TB) *TestHelper {
|
||||
return setupTestHelper(true)
|
||||
}
|
||||
|
||||
func Setup() *TestHelper {
|
||||
func Setup(tb testing.TB) *TestHelper {
|
||||
return setupTestHelper(false)
|
||||
}
|
||||
|
||||
@@ -126,12 +139,10 @@ func (th *TestHelper) CreateTeam() *model.Team {
|
||||
Type: model.TeamOpen,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if team, err = th.App.CreateTeam(th.Context, team); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return team
|
||||
}
|
||||
|
||||
@@ -146,12 +157,10 @@ func (th *TestHelper) CreateUser() *model.User {
|
||||
EmailVerified: true,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if user, err = th.App.CreateUser(th.Context, user); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -170,23 +179,19 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
|
||||
CreatorId: th.BasicUser.Id,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
var channel *model.Channel
|
||||
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return channel
|
||||
}
|
||||
|
||||
@@ -200,36 +205,25 @@ func (th *TestHelper) CreatePost(channel *model.Channel) *model.Post {
|
||||
CreateAt: model.GetMillis() - 10000,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
var err *model.AppError
|
||||
if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return post
|
||||
}
|
||||
|
||||
func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
_, err := th.App.JoinUserToTeam(th.Context, team, user, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
}
|
||||
|
||||
func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
|
||||
utils.DisableDebugLogForTest()
|
||||
|
||||
member, err := th.App.AddUserToChannel(user, channel, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
return member
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ package migrations
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
@@ -18,8 +17,6 @@ func TestMain(m *testing.M) {
|
||||
EnableResources: true,
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestGetMigrationState(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
th := Setup()
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
migrationKey := model.NewId()
|
||||
|
||||
@@ -1223,6 +1223,12 @@ type LogSettings struct {
|
||||
AdvancedLoggingConfig *string `access:"environment_logging,write_restrictable,cloud_restrictable"`
|
||||
}
|
||||
|
||||
func NewLogSettings() *LogSettings {
|
||||
settings := &LogSettings{}
|
||||
settings.SetDefaults()
|
||||
return settings
|
||||
}
|
||||
|
||||
func (s *LogSettings) SetDefaults() {
|
||||
if s.EnableConsole == nil {
|
||||
s.EnableConsole = NewBool(true)
|
||||
|
||||
@@ -115,7 +115,7 @@ func (h *hclogAdapter) ResetNamed(name string) hclog.Logger {
|
||||
}
|
||||
|
||||
func (h *hclogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
|
||||
return h.wrappedLogger.StdLog()
|
||||
return h.wrappedLogger.StdLogger(mlog.LvlInfo)
|
||||
}
|
||||
|
||||
func (h *hclogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
|
||||
|
||||
@@ -52,12 +52,8 @@ func testPluginHealthCheckSuccess(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
bundle := model.BundleInfoForPath(dir)
|
||||
log := mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
ConsoleLevel: "error",
|
||||
EnableFile: false,
|
||||
})
|
||||
log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
|
||||
defer log.Shutdown()
|
||||
|
||||
supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
|
||||
require.NoError(t, err)
|
||||
@@ -99,12 +95,8 @@ func testPluginHealthCheckPanic(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
bundle := model.BundleInfoForPath(dir)
|
||||
log := mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
ConsoleLevel: "error",
|
||||
EnableFile: false,
|
||||
})
|
||||
log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
|
||||
defer log.Shutdown()
|
||||
|
||||
supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -39,7 +39,7 @@ func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, driver Driver, par
|
||||
wrappedLogger := pluginInfo.WrapLogger(parentLogger)
|
||||
|
||||
hclogAdaptedLogger := &hclogAdapter{
|
||||
wrappedLogger: wrappedLogger.WithCallerSkip(1),
|
||||
wrappedLogger: wrappedLogger,
|
||||
extrasKey: "wrapped_extras",
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,8 @@ func testSupervisorInvalidExecutablePath(t *testing.T) {
|
||||
ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "server": {"executable": "/foo/../../backend.exe"}}`), 0600)
|
||||
|
||||
bundle := model.BundleInfoForPath(dir)
|
||||
log := mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
ConsoleLevel: "error",
|
||||
EnableFile: false,
|
||||
})
|
||||
log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
|
||||
defer log.Shutdown()
|
||||
supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
|
||||
assert.Nil(t, supervisor)
|
||||
assert.Error(t, err)
|
||||
@@ -54,12 +50,8 @@ func testSupervisorNonExistentExecutablePath(t *testing.T) {
|
||||
ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "server": {"executable": "thisfileshouldnotexist"}}`), 0600)
|
||||
|
||||
bundle := model.BundleInfoForPath(dir)
|
||||
log := mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
ConsoleLevel: "error",
|
||||
EnableFile: false,
|
||||
})
|
||||
log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
|
||||
defer log.Shutdown()
|
||||
supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, supervisor)
|
||||
@@ -84,12 +76,8 @@ func testSupervisorStartTimeout(t *testing.T) {
|
||||
ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "server": {"executable": "backend.exe"}}`), 0600)
|
||||
|
||||
bundle := model.BundleInfoForPath(dir)
|
||||
log := mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
ConsoleLevel: "error",
|
||||
EnableFile: false,
|
||||
})
|
||||
log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
|
||||
defer log.Shutdown()
|
||||
supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, supervisor)
|
||||
|
||||
@@ -46,12 +46,7 @@ func makeLocalBackend(proxy *ImageProxy) *LocalBackend {
|
||||
impl := imageproxy.NewProxy(proxy.HTTPService.MakeTransport(false), nil)
|
||||
|
||||
if proxy.Logger != nil {
|
||||
logger, err := proxy.Logger.StdLogAt(mlog.LevelDebug, mlog.String("image_proxy", "local"))
|
||||
if err != nil {
|
||||
mlog.Warn("Failed to initialize logger for image proxy", mlog.Err(err))
|
||||
}
|
||||
|
||||
impl.Logger = logger
|
||||
impl.Logger = proxy.Logger.With(mlog.String("image_proxy", "local")).StdLogger(mlog.LvlDebug)
|
||||
}
|
||||
|
||||
baseURL, err := url.Parse(*proxy.ConfigService.Config().ServiceSettings.SiteURL)
|
||||
|
||||
@@ -5,13 +5,8 @@ package remotecluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock"
|
||||
@@ -22,14 +17,16 @@ import (
|
||||
|
||||
type mockServer struct {
|
||||
remotes []*model.RemoteCluster
|
||||
logger *mockLogger
|
||||
logger *mlog.Logger
|
||||
user *model.User
|
||||
}
|
||||
|
||||
func newMockServer(t *testing.T, remotes []*model.RemoteCluster) *mockServer {
|
||||
testLogger := mlog.CreateTestLogger(t, nil, mlog.StdAll...)
|
||||
|
||||
return &mockServer{
|
||||
remotes: remotes,
|
||||
logger: &mockLogger{t: t},
|
||||
logger: testLogger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,88 +61,3 @@ func (ms *mockServer) GetStore() store.Store {
|
||||
return storeMock
|
||||
}
|
||||
func (ms *mockServer) Shutdown() { ms.logger.Shutdown() }
|
||||
|
||||
type mockLogger struct {
|
||||
t *testing.T
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func (ml *mockLogger) IsLevelEnabled(level mlog.LogLevel) bool {
|
||||
return true
|
||||
}
|
||||
func (ml *mockLogger) Debug(s string, flds ...mlog.Field) {
|
||||
ml.mux.Lock()
|
||||
defer ml.mux.Unlock()
|
||||
if ml.t != nil {
|
||||
ml.t.Log("debug", s, fieldsToStrings(flds))
|
||||
}
|
||||
}
|
||||
func (ml *mockLogger) Info(s string, flds ...mlog.Field) {
|
||||
ml.mux.Lock()
|
||||
defer ml.mux.Unlock()
|
||||
if ml.t != nil {
|
||||
ml.t.Log("info", s, fieldsToStrings(flds))
|
||||
}
|
||||
}
|
||||
func (ml *mockLogger) Warn(s string, flds ...mlog.Field) {
|
||||
ml.mux.Lock()
|
||||
defer ml.mux.Unlock()
|
||||
if ml.t != nil {
|
||||
ml.t.Log("warn", s, fieldsToStrings(flds))
|
||||
}
|
||||
}
|
||||
func (ml *mockLogger) Error(s string, flds ...mlog.Field) {
|
||||
ml.mux.Lock()
|
||||
defer ml.mux.Unlock()
|
||||
if ml.t != nil {
|
||||
ml.t.Log("error", s, fieldsToStrings(flds))
|
||||
}
|
||||
}
|
||||
func (ml *mockLogger) Critical(s string, flds ...mlog.Field) {
|
||||
ml.mux.Lock()
|
||||
defer ml.mux.Unlock()
|
||||
if ml.t != nil {
|
||||
ml.t.Log("crit", s, fieldsToStrings(flds))
|
||||
}
|
||||
}
|
||||
func (ml *mockLogger) Log(level mlog.LogLevel, s string, flds ...mlog.Field) {
|
||||
ml.mux.Lock()
|
||||
defer ml.mux.Unlock()
|
||||
if ml.t != nil {
|
||||
ml.t.Log(level.Name, s, fieldsToStrings(flds))
|
||||
}
|
||||
}
|
||||
func (ml *mockLogger) LogM(levels []mlog.LogLevel, s string, flds ...mlog.Field) {
|
||||
ml.mux.Lock()
|
||||
defer ml.mux.Unlock()
|
||||
if ml.t != nil {
|
||||
ml.t.Log(levelsToString(levels), s, fieldsToStrings(flds))
|
||||
}
|
||||
}
|
||||
func (ml *mockLogger) Shutdown() {
|
||||
ml.mux.Lock()
|
||||
defer ml.mux.Unlock()
|
||||
ml.t = nil
|
||||
}
|
||||
|
||||
func levelsToString(levels []mlog.LogLevel) string {
|
||||
sb := strings.Builder{}
|
||||
for _, l := range levels {
|
||||
sb.WriteString(l.Name)
|
||||
sb.WriteString(",")
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func fieldsToStrings(fields []mlog.Field) []string {
|
||||
encoder := zapcore.NewMapObjectEncoder()
|
||||
for _, zapField := range fields {
|
||||
zapField.AddTo(encoder)
|
||||
}
|
||||
|
||||
var result []string
|
||||
for k, v := range encoder.Fields {
|
||||
result = append(result, fmt.Sprintf("%s:%v", k, v))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type mockLogger struct {
|
||||
mlog.LoggerIFace
|
||||
}
|
||||
|
||||
func (ml *mockLogger) Log(level mlog.LogLevel, s string, flds ...mlog.Field) {}
|
||||
func (ml *mockLogger) Log(level mlog.Level, s string, flds ...mlog.Field) {}
|
||||
|
||||
func TestOnReceiveChannelInvite(t *testing.T) {
|
||||
t.Run("when msg payload is empty, it does nothing", func(t *testing.T) {
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -22,8 +20,6 @@ func TestMain(m *testing.M) {
|
||||
panic(fmt.Sprintf("Failed to set current working directory to %s: %s", "../..", err.Error()))
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
defer func() {
|
||||
err := os.Chdir(prevDir)
|
||||
if err != nil {
|
||||
|
||||
@@ -1228,7 +1228,7 @@ func (ts *TelemetryService) trackChannelModeration() {
|
||||
func (ts *TelemetryService) initRudder(endpoint string, rudderKey string) {
|
||||
if ts.rudderClient == nil {
|
||||
config := rudder.Config{}
|
||||
config.Logger = rudder.StdLogger(ts.log.StdLog(mlog.String("source", "rudder")))
|
||||
config.Logger = rudder.StdLogger(ts.log.With(mlog.String("source", "rudder")).StdLogger(mlog.LvlDebug))
|
||||
config.Endpoint = endpoint
|
||||
// For testing
|
||||
if endpoint != RudderDataplaneURL {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||
"github.com/mattermost/mattermost-server/v6/plugin/plugintest"
|
||||
@@ -40,6 +41,7 @@ func (fcs *FakeConfigService) AsymmetricSigningKey() *ecdsa.PrivateKey
|
||||
|
||||
func initializeMocks(cfg *model.Config) (*mocks.ServerIface, *storeMocks.Store, func(t *testing.T), func()) {
|
||||
serverIfaceMock := &mocks.ServerIface{}
|
||||
logger, _ := mlog.NewLogger()
|
||||
|
||||
configService := &FakeConfigService{cfg}
|
||||
serverIfaceMock.On("Config").Return(cfg)
|
||||
@@ -56,7 +58,7 @@ func initializeMocks(cfg *model.Config) (*mocks.ServerIface, *storeMocks.Store,
|
||||
func(m *model.Manifest) plugin.API { return pluginsAPIMock },
|
||||
nil,
|
||||
pluginDir, webappPluginDir,
|
||||
mlog.NewLogger(&mlog.LoggerConfiguration{}),
|
||||
logger,
|
||||
nil)
|
||||
serverIfaceMock.On("GetPluginsEnvironment").Return(pluginEnv, nil)
|
||||
|
||||
@@ -270,7 +272,14 @@ func TestRudderTelemetry(t *testing.T) {
|
||||
defer cleanUp()
|
||||
defer deferredAssertions(t)
|
||||
|
||||
telemetryService := New(serverIfaceMock, storeMock, searchengine.NewBroker(cfg, nil), mlog.NewLogger(&mlog.LoggerConfiguration{}))
|
||||
testLogger, _ := mlog.NewLogger()
|
||||
logCfg, _ := config.MloggerConfigFromLoggerConfig(&cfg.LogSettings, nil, config.GetLogFileLocation)
|
||||
if errCfg := testLogger.ConfigureTargets(logCfg); errCfg != nil {
|
||||
panic("failed to configure test logger: " + errCfg.Error())
|
||||
}
|
||||
defer testLogger.Shutdown()
|
||||
|
||||
telemetryService := New(serverIfaceMock, storeMock, searchengine.NewBroker(cfg, nil), testLogger)
|
||||
telemetryService.TelemetryID = telemetryID
|
||||
telemetryService.rudderClient = nil
|
||||
telemetryService.initRudder(server.URL, RudderKey)
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
@@ -26,8 +25,6 @@ func TestMain(m *testing.M) {
|
||||
WithReadReplica: replicaFlag,
|
||||
}
|
||||
|
||||
mlog.DisableZap()
|
||||
|
||||
mainHelper = testlib.NewMainHelperWithOptions(&options)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
@@ -33,12 +33,10 @@ type FileBackendTestSuite struct {
|
||||
func TestLocalFileBackendTestSuite(t *testing.T) {
|
||||
// Setup a global logger to catch tests logging outside of app context
|
||||
// The global logger will be stomped by apps initializing but that's fine for testing. Ideally this won't happen.
|
||||
mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
ConsoleLevel: "error",
|
||||
EnableFile: false,
|
||||
}))
|
||||
logger := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
|
||||
defer logger.Shutdown()
|
||||
|
||||
mlog.InitGlobalLogger(logger)
|
||||
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -4,26 +4,32 @@
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
)
|
||||
|
||||
// defaultLog manually encodes the log to STDERR, providing a basic, default logging implementation
|
||||
// before mlog is fully configured.
|
||||
func defaultLog(level, msg string, fields ...Field) {
|
||||
func defaultLog(level Level, msg string, fields ...Field) {
|
||||
mFields := make(map[string]string)
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for _, fld := range fields {
|
||||
buf.Reset()
|
||||
fld.ValueString(buf, shouldQuote)
|
||||
mFields[fld.Key] = buf.String()
|
||||
}
|
||||
|
||||
log := struct {
|
||||
Level string `json:"level"`
|
||||
Message string `json:"msg"`
|
||||
Fields []Field `json:"fields,omitempty"`
|
||||
Level string `json:"level"`
|
||||
Message string `json:"msg"`
|
||||
Fields map[string]string `json:"fields,omitempty"`
|
||||
}{
|
||||
level,
|
||||
level.Name,
|
||||
msg,
|
||||
fields,
|
||||
mFields,
|
||||
}
|
||||
|
||||
if b, err := json.Marshal(log); err != nil {
|
||||
@@ -33,67 +39,25 @@ func defaultLog(level, msg string, fields ...Field) {
|
||||
}
|
||||
}
|
||||
|
||||
func defaultIsLevelEnabled(level LogLevel) bool {
|
||||
func defaultIsLevelEnabled(level Level) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func defaultDebugLog(msg string, fields ...Field) {
|
||||
defaultLog("debug", msg, fields...)
|
||||
func defaultCustomMultiLog(lvl []Level, msg string, fields ...Field) {
|
||||
for _, level := range lvl {
|
||||
defaultLog(level, msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultInfoLog(msg string, fields ...Field) {
|
||||
defaultLog("info", msg, fields...)
|
||||
}
|
||||
|
||||
func defaultWarnLog(msg string, fields ...Field) {
|
||||
defaultLog("warn", msg, fields...)
|
||||
}
|
||||
|
||||
func defaultErrorLog(msg string, fields ...Field) {
|
||||
defaultLog("error", msg, fields...)
|
||||
}
|
||||
|
||||
func defaultCriticalLog(msg string, fields ...Field) {
|
||||
// We map critical to error in zap, so be consistent.
|
||||
defaultLog("error", msg, fields...)
|
||||
}
|
||||
|
||||
func defaultCustomLog(lvl LogLevel, msg string, fields ...Field) {
|
||||
// custom log levels are only output once log targets are configured.
|
||||
}
|
||||
|
||||
func defaultCustomMultiLog(lvl []LogLevel, msg string, fields ...Field) {
|
||||
// custom log levels are only output once log targets are configured.
|
||||
}
|
||||
|
||||
func defaultFlush(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultAdvancedConfig(cfg LogTargetCfg) error {
|
||||
// mlog.ConfigAdvancedConfig should not be called until default
|
||||
// logger is replaced with mlog.Logger instance.
|
||||
return errors.New("cannot config advanced logging on default logger")
|
||||
}
|
||||
|
||||
func defaultAdvancedShutdown(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultAddTarget(targets ...logr.Target) error {
|
||||
// mlog.AddTarget should not be called until default
|
||||
// logger is replaced with mlog.Logger instance.
|
||||
return errors.New("cannot AddTarget on default logger")
|
||||
}
|
||||
|
||||
func defaultRemoveTargets(ctx context.Context, f func(TargetInfo) bool) error {
|
||||
// mlog.RemoveTargets should not be called until default
|
||||
// logger is replaced with mlog.Logger instance.
|
||||
return errors.New("cannot RemoveTargets on default logger")
|
||||
}
|
||||
|
||||
func defaultEnableMetrics(collector logr.MetricsCollector) error {
|
||||
// mlog.EnableMetrics should not be called until default
|
||||
// logger is replaced with mlog.Logger instance.
|
||||
return errors.New("cannot EnableMetrics on default logger")
|
||||
// shouldQuote returns true if val contains any characters that require quotations.
|
||||
func shouldQuote(val string) bool {
|
||||
for _, c := range val {
|
||||
if !((c >= '0' && c <= '9') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
c == '-' || c == '.' || c == '_' || c == '/' || c == '@' || c == '^' || c == '+') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"github.com/mattermost/logr"
|
||||
)
|
||||
|
||||
// onLoggerError is called when the logging system encounters an error,
|
||||
// such as a target not able to write records. The targets will keep trying
|
||||
// however the error will be logged with a dedicated level that can be output
|
||||
// to a safe/always available target for monitoring or alerting.
|
||||
func onLoggerError(err error) {
|
||||
Log(LvlLogError, "advanced logging error", Err(err))
|
||||
}
|
||||
|
||||
// onQueueFull is called when the main logger queue is full, indicating the
|
||||
// volume and frequency of log record creation is too high for the queue size
|
||||
// and/or the target latencies.
|
||||
func onQueueFull(rec *logr.LogRec, maxQueueSize int) bool {
|
||||
Log(LvlLogError, "main queue full, dropping record", Any("rec", rec))
|
||||
return true // drop record
|
||||
}
|
||||
|
||||
// onTargetQueueFull is called when the main logger queue is full, indicating the
|
||||
// volume and frequency of log record creation is too high for the target's queue size
|
||||
// and/or the target latency.
|
||||
func onTargetQueueFull(target logr.Target, rec *logr.LogRec, maxQueueSize int) bool {
|
||||
Log(LvlLogError, "target queue full, dropping record", String("target", ""), Any("rec", rec))
|
||||
return true // drop record
|
||||
}
|
||||
@@ -4,95 +4,119 @@
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var globalLogger *Logger
|
||||
var (
|
||||
globalLogger *Logger
|
||||
muxGlobalLogger sync.RWMutex
|
||||
)
|
||||
|
||||
func InitGlobalLogger(logger *Logger) {
|
||||
// Clean up previous instance.
|
||||
if globalLogger != nil && globalLogger.logrLogger != nil {
|
||||
globalLogger.logrLogger.Logr().Shutdown()
|
||||
muxGlobalLogger.Lock()
|
||||
defer muxGlobalLogger.Unlock()
|
||||
|
||||
globalLogger = logger
|
||||
}
|
||||
|
||||
func getGlobalLogger() *Logger {
|
||||
muxGlobalLogger.RLock()
|
||||
defer muxGlobalLogger.RUnlock()
|
||||
|
||||
return globalLogger
|
||||
}
|
||||
|
||||
// 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 IsLevelEnabled(level Level) bool {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
return defaultIsLevelEnabled(level)
|
||||
}
|
||||
glob := *logger
|
||||
glob.zap = glob.zap.WithOptions(zap.AddCallerSkip(1))
|
||||
globalLogger = &glob
|
||||
IsLevelEnabled = globalLogger.IsLevelEnabled
|
||||
Debug = globalLogger.Debug
|
||||
Info = globalLogger.Info
|
||||
Warn = globalLogger.Warn
|
||||
Error = globalLogger.Error
|
||||
Critical = globalLogger.Critical
|
||||
Log = globalLogger.Log
|
||||
LogM = globalLogger.LogM
|
||||
Flush = globalLogger.Flush
|
||||
ConfigAdvancedLogging = globalLogger.ConfigAdvancedLogging
|
||||
ShutdownAdvancedLogging = globalLogger.ShutdownAdvancedLogging
|
||||
AddTarget = globalLogger.AddTarget
|
||||
RemoveTargets = globalLogger.RemoveTargets
|
||||
EnableMetrics = globalLogger.EnableMetrics
|
||||
return logger.IsLevelEnabled(level)
|
||||
}
|
||||
|
||||
// logWriterFunc provides access to mlog via io.Writer, so the standard logger
|
||||
// can be redirected to use mlog and whatever targets are defined.
|
||||
type logWriterFunc func([]byte) (int, error)
|
||||
|
||||
func (lw logWriterFunc) Write(p []byte) (int, error) {
|
||||
return lw(p)
|
||||
}
|
||||
|
||||
func RedirectStdLog(logger *Logger) {
|
||||
if atomic.LoadInt32(&disableZap) == 0 {
|
||||
zap.RedirectStdLogAt(logger.zap.With(zap.String("source", "stdlog")).WithOptions(zap.AddCallerSkip(-2)), zapcore.ErrorLevel)
|
||||
// Log emits the log record for any targets configured for the specified level.
|
||||
func Log(level Level, msg string, fields ...Field) {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
defaultLog(level, msg, fields...)
|
||||
return
|
||||
}
|
||||
logger.Log(level, msg, fields...)
|
||||
}
|
||||
|
||||
writer := func(p []byte) (int, error) {
|
||||
Log(LvlStdLog, string(p))
|
||||
return len(p), nil
|
||||
// LogM emits the log record for any targets configured for the specified levels.
|
||||
// Equivalent to calling `Log` once for each level.
|
||||
func LogM(levels []Level, msg string, fields ...Field) {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
defaultCustomMultiLog(levels, msg, fields...)
|
||||
return
|
||||
}
|
||||
log.SetOutput(logWriterFunc(writer))
|
||||
logger.LogM(levels, msg, fields...)
|
||||
}
|
||||
|
||||
type IsLevelEnabledFunc func(LogLevel) bool
|
||||
type LogFunc func(string, ...Field)
|
||||
type LogFuncCustom func(LogLevel, string, ...Field)
|
||||
type LogFuncCustomMulti func([]LogLevel, string, ...Field)
|
||||
type FlushFunc func(context.Context) error
|
||||
type ConfigFunc func(cfg LogTargetCfg) error
|
||||
type ShutdownFunc func(context.Context) error
|
||||
type AddTargetFunc func(...logr.Target) error
|
||||
type RemoveTargetsFunc func(context.Context, func(TargetInfo) bool) error
|
||||
type EnableMetricsFunc func(logr.MetricsCollector) error
|
||||
|
||||
// DON'T USE THIS Modify the level on the app logger
|
||||
func GloballyDisableDebugLogForTest() {
|
||||
globalLogger.consoleLevel.SetLevel(zapcore.ErrorLevel)
|
||||
// Convenience method equivalent to calling `Log` with the `Trace` level.
|
||||
func Trace(msg string, fields ...Field) {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
defaultLog(LvlTrace, msg, fields...)
|
||||
return
|
||||
}
|
||||
logger.Trace(msg, fields...)
|
||||
}
|
||||
|
||||
// DON'T USE THIS Modify the level on the app logger
|
||||
func GloballyEnableDebugLogForTest() {
|
||||
globalLogger.consoleLevel.SetLevel(zapcore.DebugLevel)
|
||||
// Convenience method equivalent to calling `Log` with the `Debug` level.
|
||||
func Debug(msg string, fields ...Field) {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
defaultLog(LvlDebug, msg, fields...)
|
||||
return
|
||||
}
|
||||
logger.Debug(msg, fields...)
|
||||
}
|
||||
|
||||
var IsLevelEnabled IsLevelEnabledFunc = defaultIsLevelEnabled
|
||||
var Debug LogFunc = defaultDebugLog
|
||||
var Info LogFunc = defaultInfoLog
|
||||
var Warn LogFunc = defaultWarnLog
|
||||
var Error LogFunc = defaultErrorLog
|
||||
var Critical LogFunc = defaultCriticalLog
|
||||
var Log LogFuncCustom = defaultCustomLog
|
||||
var LogM LogFuncCustomMulti = defaultCustomMultiLog
|
||||
var Flush FlushFunc = defaultFlush
|
||||
// Convenience method equivalent to calling `Log` with the `Info` level.
|
||||
func Info(msg string, fields ...Field) {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
defaultLog(LvlInfo, msg, fields...)
|
||||
return
|
||||
}
|
||||
logger.Info(msg, fields...)
|
||||
}
|
||||
|
||||
var ConfigAdvancedLogging ConfigFunc = defaultAdvancedConfig
|
||||
var ShutdownAdvancedLogging ShutdownFunc = defaultAdvancedShutdown
|
||||
var AddTarget AddTargetFunc = defaultAddTarget
|
||||
var RemoveTargets RemoveTargetsFunc = defaultRemoveTargets
|
||||
var EnableMetrics EnableMetricsFunc = defaultEnableMetrics
|
||||
// Convenience method equivalent to calling `Log` with the `Warn` level.
|
||||
func Warn(msg string, fields ...Field) {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
defaultLog(LvlWarn, msg, fields...)
|
||||
return
|
||||
}
|
||||
logger.Warn(msg, fields...)
|
||||
}
|
||||
|
||||
// Convenience method equivalent to calling `Log` with the `Error` level.
|
||||
func Error(msg string, fields ...Field) {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
defaultLog(LvlError, msg, fields...)
|
||||
return
|
||||
}
|
||||
logger.Error(msg, fields...)
|
||||
}
|
||||
|
||||
// Convenience method equivalent to calling `Log` with the `Critical` level.
|
||||
func Critical(msg string, fields ...Field) {
|
||||
logger := getGlobalLogger()
|
||||
if logger == nil {
|
||||
defaultLog(LvlCritical, msg, fields...)
|
||||
return
|
||||
}
|
||||
logger.Critical(msg, fields...)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package mlog_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -29,83 +31,86 @@ func TestLoggingBeforeInitialized(t *testing.T) {
|
||||
|
||||
func TestLoggingAfterInitialized(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Description string
|
||||
LoggerConfiguration *mlog.LoggerConfiguration
|
||||
ExpectedLogs []string
|
||||
description string
|
||||
cfg mlog.TargetCfg
|
||||
expectedLogs []string
|
||||
}{
|
||||
{
|
||||
"file logging, json, debug",
|
||||
&mlog.LoggerConfiguration{
|
||||
EnableConsole: false,
|
||||
EnableFile: true,
|
||||
FileJson: true,
|
||||
FileLevel: mlog.LevelDebug,
|
||||
mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
FormatOptions: json.RawMessage(`{"enable_caller":true}`),
|
||||
Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError, mlog.LvlWarn, mlog.LvlInfo, mlog.LvlDebug},
|
||||
},
|
||||
[]string{
|
||||
`{"level":"debug","ts":0,"caller":"mlog/global_test.go:0","msg":"real debug log"}`,
|
||||
`{"level":"info","ts":0,"caller":"mlog/global_test.go:0","msg":"real info log"}`,
|
||||
`{"level":"warn","ts":0,"caller":"mlog/global_test.go:0","msg":"real warning log"}`,
|
||||
`{"level":"error","ts":0,"caller":"mlog/global_test.go:0","msg":"real error log"}`,
|
||||
`{"level":"error","ts":0,"caller":"mlog/global_test.go:0","msg":"real critical log"}`,
|
||||
`{"timestamp":0,"level":"debug","msg":"real debug log","caller":"mlog/global_test.go:0"}`,
|
||||
`{"timestamp":0,"level":"info","msg":"real info log","caller":"mlog/global_test.go:0"}`,
|
||||
`{"timestamp":0,"level":"warn","msg":"real warning log","caller":"mlog/global_test.go:0"}`,
|
||||
`{"timestamp":0,"level":"error","msg":"real error log","caller":"mlog/global_test.go:0"}`,
|
||||
`{"timestamp":0,"level":"critical","msg":"real critical log","caller":"mlog/global_test.go:0"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
"file logging, json, error",
|
||||
&mlog.LoggerConfiguration{
|
||||
EnableConsole: false,
|
||||
EnableFile: true,
|
||||
FileJson: true,
|
||||
FileLevel: mlog.LevelError,
|
||||
mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
FormatOptions: json.RawMessage(`{"enable_caller":true}`),
|
||||
Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError},
|
||||
},
|
||||
[]string{
|
||||
`{"level":"error","ts":0,"caller":"mlog/global_test.go:0","msg":"real error log"}`,
|
||||
`{"level":"error","ts":0,"caller":"mlog/global_test.go:0","msg":"real critical log"}`,
|
||||
`{"timestamp":0,"level":"error","msg":"real error log","caller":"mlog/global_test.go:0"}`,
|
||||
`{"timestamp":0,"level":"critical","msg":"real critical log","caller":"mlog/global_test.go:0"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
"file logging, non-json, debug",
|
||||
&mlog.LoggerConfiguration{
|
||||
EnableConsole: false,
|
||||
EnableFile: true,
|
||||
FileJson: false,
|
||||
FileLevel: mlog.LevelDebug,
|
||||
mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "plain",
|
||||
FormatOptions: json.RawMessage(`{"delim":" | ", "enable_caller":true}`),
|
||||
Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError, mlog.LvlWarn, mlog.LvlInfo, mlog.LvlDebug},
|
||||
},
|
||||
[]string{
|
||||
`TIME debug mlog/global_test.go:0 real debug log`,
|
||||
`TIME info mlog/global_test.go:0 real info log`,
|
||||
`TIME warn mlog/global_test.go:0 real warning log`,
|
||||
`TIME error mlog/global_test.go:0 real error log`,
|
||||
`TIME error mlog/global_test.go:0 real critical log`,
|
||||
`debug | TIME | real debug log | caller="mlog/global_test.go:0"`,
|
||||
`info | TIME | real info log | caller="mlog/global_test.go:0"`,
|
||||
`warn | TIME | real warning log | caller="mlog/global_test.go:0"`,
|
||||
`error | TIME | real error log | caller="mlog/global_test.go:0"`,
|
||||
`critical | TIME | real critical log | caller="mlog/global_test.go:0"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
"file logging, non-json, error",
|
||||
&mlog.LoggerConfiguration{
|
||||
EnableConsole: false,
|
||||
EnableFile: true,
|
||||
FileJson: false,
|
||||
FileLevel: mlog.LevelError,
|
||||
mlog.TargetCfg{
|
||||
Type: "file",
|
||||
Format: "plain",
|
||||
FormatOptions: json.RawMessage(`{"delim":" | ", "enable_caller":true}`),
|
||||
Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError},
|
||||
},
|
||||
[]string{
|
||||
`TIME error mlog/global_test.go:0 real error log`,
|
||||
`TIME error mlog/global_test.go:0 real critical log`,
|
||||
`error | TIME | real error log | caller="mlog/global_test.go:0"`,
|
||||
`critical | TIME | real critical log | caller="mlog/global_test.go:0"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
var filePath string
|
||||
if testCase.LoggerConfiguration.EnableFile {
|
||||
if testCase.cfg.Type == "file" {
|
||||
tempDir, err := ioutil.TempDir(os.TempDir(), "TestLoggingAfterInitialized")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tempDir)
|
||||
|
||||
filePath = filepath.Join(tempDir, "file.log")
|
||||
testCase.LoggerConfiguration.FileLocation = filePath
|
||||
testCase.cfg.Options = json.RawMessage(fmt.Sprintf(`{"filename": "%s"}`, filePath))
|
||||
}
|
||||
|
||||
logger := mlog.NewLogger(testCase.LoggerConfiguration)
|
||||
logger, _ := mlog.NewLogger()
|
||||
err := logger.ConfigureTargets(map[string]mlog.TargetCfg{testCase.description: testCase.cfg})
|
||||
require.NoError(t, err)
|
||||
|
||||
mlog.InitGlobalLogger(logger)
|
||||
|
||||
mlog.Debug("real debug log")
|
||||
@@ -114,32 +119,26 @@ func TestLoggingAfterInitialized(t *testing.T) {
|
||||
mlog.Error("real error log")
|
||||
mlog.Critical("real critical log")
|
||||
|
||||
if testCase.LoggerConfiguration.EnableFile {
|
||||
logger.Shutdown()
|
||||
|
||||
if testCase.cfg.Type == "file" {
|
||||
logs, err := ioutil.ReadFile(filePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := strings.TrimSpace(string(logs))
|
||||
|
||||
if testCase.LoggerConfiguration.FileJson {
|
||||
reTs := regexp.MustCompile(`"ts":[0-9\.]+`)
|
||||
if testCase.cfg.Format == "json" {
|
||||
reTs := regexp.MustCompile(`"timestamp":"[0-9\.\-\:\sZ]+"`)
|
||||
reCaller := regexp.MustCompile(`"caller":"([^"]+):[0-9\.]+"`)
|
||||
actual = reTs.ReplaceAllString(actual, `"ts":0`)
|
||||
actual = reTs.ReplaceAllString(actual, `"timestamp":0`)
|
||||
actual = reCaller.ReplaceAllString(actual, `"caller":"$1:0"`)
|
||||
} else {
|
||||
actualRows := strings.Split(actual, "\n")
|
||||
for i, actualRow := range actualRows {
|
||||
actualFields := strings.Split(actualRow, "\t")
|
||||
if len(actualFields) > 3 {
|
||||
actualFields[0] = "TIME"
|
||||
reCaller := regexp.MustCompile(`([^"]+):[0-9\.]+`)
|
||||
actualFields[2] = reCaller.ReplaceAllString(actualFields[2], "$1:0")
|
||||
actualRows[i] = strings.Join(actualFields, "\t")
|
||||
}
|
||||
}
|
||||
|
||||
actual = strings.Join(actualRows, "\n")
|
||||
reTs := regexp.MustCompile(`\[\d\d\d\d-\d\d-\d\d\s[0-9\:\.\s\-Z]+\]`)
|
||||
reCaller := regexp.MustCompile(`caller="([^"]+):[0-9\.]+"`)
|
||||
actual = reTs.ReplaceAllString(actual, "TIME")
|
||||
actual = reCaller.ReplaceAllString(actual, `caller="$1:0"`)
|
||||
}
|
||||
require.ElementsMatch(t, testCase.ExpectedLogs, strings.Split(actual, "\n"))
|
||||
require.ElementsMatch(t, testCase.expectedLogs, strings.Split(actual, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package human
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
type LogEntry struct {
|
||||
Time time.Time
|
||||
Level string
|
||||
Message string
|
||||
Caller string
|
||||
Fields []mlog.Field
|
||||
}
|
||||
|
||||
// Provide default string representation. Used by SimpleWriter
|
||||
func (f LogEntry) String() string {
|
||||
var sb strings.Builder
|
||||
if !f.Time.IsZero() {
|
||||
sb.WriteString(f.Time.Format(time.RFC3339Nano))
|
||||
sb.WriteRune(' ')
|
||||
}
|
||||
if f.Level != "" {
|
||||
sb.WriteString(f.Level)
|
||||
sb.WriteRune(' ')
|
||||
}
|
||||
if f.Caller != "" {
|
||||
sb.WriteString(f.Caller)
|
||||
sb.WriteRune(' ')
|
||||
}
|
||||
for _, field := range f.Fields {
|
||||
sb.WriteString(field.Key)
|
||||
sb.WriteRune('=')
|
||||
sb.WriteString(fmt.Sprint(field.Interface))
|
||||
sb.WriteRune(' ')
|
||||
}
|
||||
if f.Message != "" {
|
||||
// If the message is multiple lines, start the whole message on a new line
|
||||
if strings.ContainsRune(f.Message, '\n') {
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
sb.WriteString(f.Message)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package human
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type LogrusWriter struct {
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (w *LogrusWriter) Write(e LogEntry) {
|
||||
if e.Level == "" {
|
||||
fmt.Fprintln(w.logger.Out, e.Message)
|
||||
return
|
||||
}
|
||||
|
||||
lvl, err := logrus.ParseLevel(e.Level)
|
||||
if err != nil {
|
||||
fmt.Fprintln(w.logger.Out, err)
|
||||
lvl = logrus.TraceLevel + 1 // will invoke Println
|
||||
}
|
||||
|
||||
logger := w.logger.WithTime(e.Time)
|
||||
|
||||
if e.Caller != "" {
|
||||
// logrus has a system of reporting the caller, but there's no easy way to override it
|
||||
logger = logger.WithField("caller", e.Caller)
|
||||
}
|
||||
|
||||
for _, field := range e.Fields {
|
||||
logger = logger.WithField(field.Key, field.Interface)
|
||||
}
|
||||
|
||||
switch lvl {
|
||||
case logrus.PanicLevel:
|
||||
// Prevent panic from causing us to exit
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
logger.Panic(e.Message)
|
||||
case logrus.FatalLevel:
|
||||
logger.Fatal(e.Message)
|
||||
case logrus.ErrorLevel:
|
||||
logger.Error(e.Message)
|
||||
case logrus.WarnLevel:
|
||||
logger.Warn(e.Message)
|
||||
case logrus.InfoLevel:
|
||||
logger.Info(e.Message)
|
||||
case logrus.DebugLevel:
|
||||
logger.Debug(e.Message)
|
||||
case logrus.TraceLevel:
|
||||
logger.Trace(e.Message)
|
||||
default:
|
||||
logger.Println(e.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogrusWriter(output io.Writer) *LogrusWriter {
|
||||
w := new(LogrusWriter)
|
||||
w.logger = logrus.New()
|
||||
w.logger.SetLevel(logrus.TraceLevel) // don't filter any logs
|
||||
w.logger.ExitFunc = func(int) {} // prevent Fatal from causing us to exit
|
||||
w.logger.SetReportCaller(false)
|
||||
w.logger.SetOutput(output)
|
||||
var tf logrus.TextFormatter
|
||||
tf.FullTimestamp = true
|
||||
tf.TimestampFormat = time.RFC3339Nano
|
||||
w.logger.SetFormatter(&tf)
|
||||
return w
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package human
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
func ParseLogMessage(msg string) LogEntry {
|
||||
result, err := parseLogMessage(msg)
|
||||
if err != nil {
|
||||
// If failed to parse, just output a LogEntry where all fields are blank, but Message is the original string
|
||||
var result2 LogEntry
|
||||
result2.Message = msg
|
||||
return result2
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func parseLogMessage(msg string) (result LogEntry, err error) {
|
||||
|
||||
// Note: This implementation uses a custom json decoding loop.
|
||||
// The primary advantage of this versus decoding directly into a map is to
|
||||
// preserve the order of the fields. This can be simplified if we end up
|
||||
// having the formatter sort fields alphabetically (logrus does by default)
|
||||
|
||||
dec := json.NewDecoder(strings.NewReader(msg))
|
||||
|
||||
// look for an initial "{"
|
||||
token, err := dec.Token()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
d, ok := token.(json.Delim)
|
||||
if !ok || d != '{' {
|
||||
return result, fmt.Errorf("input is not a JSON object, found: %v", token)
|
||||
}
|
||||
|
||||
// read all key-value pairs
|
||||
for dec.More() {
|
||||
key, err2 := dec.Token()
|
||||
if err2 != nil {
|
||||
return result, err2
|
||||
}
|
||||
skey, ok2 := key.(string)
|
||||
if !ok2 {
|
||||
return result, errors.New("key is not a value string")
|
||||
}
|
||||
if !dec.More() {
|
||||
return result, errors.New("missing value pair")
|
||||
}
|
||||
|
||||
switch skey {
|
||||
case "ts":
|
||||
var ts json.Number
|
||||
if err2 := dec.Decode(&ts); err2 != nil {
|
||||
return result, err2
|
||||
}
|
||||
timeVal, err2 := numberToTime(ts)
|
||||
if err2 != nil {
|
||||
return result, err2
|
||||
}
|
||||
result.Time = timeVal
|
||||
|
||||
case "level":
|
||||
s, err2 := decodeAsString(dec)
|
||||
if err2 != nil {
|
||||
return result, err2
|
||||
}
|
||||
result.Level = s
|
||||
|
||||
case "msg":
|
||||
s, err2 := decodeAsString(dec)
|
||||
if err2 != nil {
|
||||
return result, err2
|
||||
}
|
||||
result.Message = s
|
||||
|
||||
case "caller":
|
||||
s, err2 := decodeAsString(dec)
|
||||
if err2 != nil {
|
||||
return result, err2
|
||||
}
|
||||
result.Caller = s
|
||||
|
||||
default:
|
||||
var p interface{}
|
||||
if err2 := dec.Decode(&p); err2 != nil {
|
||||
return result, err2
|
||||
}
|
||||
var f mlog.Field
|
||||
f.Key = skey
|
||||
f.Interface = p
|
||||
result.Fields = append(result.Fields, f)
|
||||
}
|
||||
}
|
||||
|
||||
// read the "}"
|
||||
token, err = dec.Token()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
d, ok = token.(json.Delim)
|
||||
if !ok || d != '}' {
|
||||
return result, fmt.Errorf("failed to read '}', read: %v", token)
|
||||
}
|
||||
|
||||
// make sure nothing else trailing
|
||||
if token, err := dec.Token(); err != io.EOF {
|
||||
return result, err
|
||||
} else if token != nil {
|
||||
return result, errors.New("found trailing data")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Translate a number into a time
|
||||
func numberToTime(v json.Number) (time.Time, error) {
|
||||
// Using floating point math to extract the nanoseconds leads to a time that doesn't exactly match the input
|
||||
// Instead, parse out the components from the string representation
|
||||
|
||||
var t time.Time
|
||||
|
||||
// First make sure it is a number...
|
||||
flt, err := v.Float64()
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
s := v.String()
|
||||
|
||||
if strings.ContainsAny(s, "eE") {
|
||||
// input is in scientific notation. Convert to standard decimal notation
|
||||
s = strconv.FormatFloat(flt, 'f', -1, 64)
|
||||
}
|
||||
|
||||
// extract the seconds and nanoseconds separately
|
||||
var nanos, sec int64
|
||||
|
||||
parts := strings.SplitN(s, ".", 2)
|
||||
sec, err = strconv.ParseInt(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
if len(parts) == 2 {
|
||||
nanosText := parts[1] + "000000000"
|
||||
nanosText = nanosText[:9]
|
||||
nanos, err = strconv.ParseInt(nanosText, 10, 64)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
}
|
||||
|
||||
t = time.Unix(sec, nanos)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Decodes a value from JSON, coercing it to a string value as necessary
|
||||
func decodeAsString(dec *json.Decoder) (s string, err error) {
|
||||
var v interface{}
|
||||
if err = dec.Decode(&v); err != nil {
|
||||
return s, err
|
||||
}
|
||||
var ok bool
|
||||
if s, ok = v.(string); ok {
|
||||
return s, err
|
||||
}
|
||||
s = fmt.Sprint(v)
|
||||
return s, err
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package human
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
)
|
||||
|
||||
type LogWriter interface {
|
||||
Write(e LogEntry)
|
||||
}
|
||||
|
||||
// Read JSON logs from input and write formatted logs to the output
|
||||
func ProcessLogs(reader io.Reader, writer LogWriter) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
e := ParseLogMessage(s)
|
||||
writer.Write(e)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package human
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type SimpleWriter struct {
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func (w *SimpleWriter) Write(e LogEntry) {
|
||||
fmt.Fprintln(w.out, e)
|
||||
}
|
||||
|
||||
func NewSimpleWriter(out io.Writer) *SimpleWriter {
|
||||
w := new(SimpleWriter)
|
||||
w.out = out
|
||||
return w
|
||||
}
|
||||
@@ -3,49 +3,56 @@
|
||||
|
||||
package mlog
|
||||
|
||||
// Standard levels
|
||||
import "github.com/mattermost/logr/v2"
|
||||
|
||||
// Standard levels.
|
||||
var (
|
||||
LvlPanic = LogLevel{ID: 0, Name: "panic", Stacktrace: true}
|
||||
LvlFatal = LogLevel{ID: 1, Name: "fatal", Stacktrace: true}
|
||||
LvlError = LogLevel{ID: 2, Name: "error"}
|
||||
LvlWarn = LogLevel{ID: 3, Name: "warn"}
|
||||
LvlInfo = LogLevel{ID: 4, Name: "info"}
|
||||
LvlDebug = LogLevel{ID: 5, Name: "debug"}
|
||||
LvlTrace = LogLevel{ID: 6, Name: "trace"}
|
||||
LvlPanic = logr.Panic // ID = 0
|
||||
LvlFatal = logr.Fatal // ID = 1
|
||||
LvlError = logr.Error // ID = 2
|
||||
LvlWarn = logr.Warn // ID = 3
|
||||
LvlInfo = logr.Info // ID = 4
|
||||
LvlDebug = logr.Debug // ID = 5
|
||||
LvlTrace = logr.Trace // ID = 6
|
||||
StdAll = []Level{LvlPanic, LvlFatal, LvlError, LvlWarn, LvlInfo, LvlDebug, LvlTrace}
|
||||
// non-standard "critical" level
|
||||
LvlCritical = Level{ID: 7, Name: "critical"}
|
||||
// used by redirected standard logger
|
||||
LvlStdLog = LogLevel{ID: 10, Name: "stdlog"}
|
||||
LvlStdLog = Level{ID: 10, Name: "stdlog"}
|
||||
// used only by the logger
|
||||
LvlLogError = LogLevel{ID: 11, Name: "logerror", Stacktrace: true}
|
||||
LvlLogError = Level{ID: 11, Name: "logerror", Stacktrace: true}
|
||||
)
|
||||
|
||||
// Register custom (discrete) levels here.
|
||||
// !!!!! ID's must not exceed 32,768 !!!!!!
|
||||
// !!!!! Custom ID's must be between 20 and 32,768 !!!!!!
|
||||
var (
|
||||
// used by the audit system
|
||||
LvlAuditAPI = LogLevel{ID: 100, Name: "audit-api"}
|
||||
LvlAuditContent = LogLevel{ID: 101, Name: "audit-content"}
|
||||
LvlAuditPerms = LogLevel{ID: 102, Name: "audit-permissions"}
|
||||
LvlAuditCLI = LogLevel{ID: 103, Name: "audit-cli"}
|
||||
LvlAuditAPI = Level{ID: 100, Name: "audit-api"}
|
||||
LvlAuditContent = Level{ID: 101, Name: "audit-content"}
|
||||
LvlAuditPerms = Level{ID: 102, Name: "audit-permissions"}
|
||||
LvlAuditCLI = Level{ID: 103, Name: "audit-cli"}
|
||||
|
||||
// used by the TCP log target
|
||||
LvlTCPLogTarget = LogLevel{ID: 120, Name: "TcpLogTarget"}
|
||||
LvlTCPLogTarget = Level{ID: 120, Name: "TcpLogTarget"}
|
||||
|
||||
// used by Remote Cluster Service
|
||||
LvlRemoteClusterServiceDebug = LogLevel{ID: 130, Name: "RemoteClusterServiceDebug"}
|
||||
LvlRemoteClusterServiceError = LogLevel{ID: 131, Name: "RemoteClusterServiceError"}
|
||||
LvlRemoteClusterServiceWarn = LogLevel{ID: 132, Name: "RemoteClusterServiceWarn"}
|
||||
LvlRemoteClusterServiceDebug = Level{ID: 130, Name: "RemoteClusterServiceDebug"}
|
||||
LvlRemoteClusterServiceError = Level{ID: 131, Name: "RemoteClusterServiceError"}
|
||||
LvlRemoteClusterServiceWarn = Level{ID: 132, Name: "RemoteClusterServiceWarn"}
|
||||
|
||||
// used by Shared Channel Sync Service
|
||||
LvlSharedChannelServiceDebug = LogLevel{ID: 200, Name: "SharedChannelServiceDebug"}
|
||||
LvlSharedChannelServiceError = LogLevel{ID: 201, Name: "SharedChannelServiceError"}
|
||||
LvlSharedChannelServiceWarn = LogLevel{ID: 202, Name: "SharedChannelServiceWarn"}
|
||||
LvlSharedChannelServiceMessagesInbound = LogLevel{ID: 203, Name: "SharedChannelServiceMsgInbound"}
|
||||
LvlSharedChannelServiceMessagesOutbound = LogLevel{ID: 204, Name: "SharedChannelServiceMsgOutbound"}
|
||||
LvlSharedChannelServiceDebug = Level{ID: 200, Name: "SharedChannelServiceDebug"}
|
||||
LvlSharedChannelServiceError = Level{ID: 201, Name: "SharedChannelServiceError"}
|
||||
LvlSharedChannelServiceWarn = Level{ID: 202, Name: "SharedChannelServiceWarn"}
|
||||
LvlSharedChannelServiceMessagesInbound = Level{ID: 203, Name: "SharedChannelServiceMsgInbound"}
|
||||
LvlSharedChannelServiceMessagesOutbound = Level{ID: 204, Name: "SharedChannelServiceMsgOutbound"}
|
||||
|
||||
// add more here ...
|
||||
// Focalboard
|
||||
LvlFBTelemetry = Level{ID: 9000, Name: "telemetry"}
|
||||
LvlFBMetrics = Level{ID: 9001, Name: "metrics"}
|
||||
)
|
||||
|
||||
// Combinations for LogM (log multi)
|
||||
// Combinations for LogM (log multi).
|
||||
var (
|
||||
MLvlAuditAll = []LogLevel{LvlAuditAPI, LvlAuditContent, LvlAuditPerms, LvlAuditCLI}
|
||||
MLvlAuditAll = []Level{LvlAuditAPI, LvlAuditContent, LvlAuditPerms, LvlAuditCLI}
|
||||
)
|
||||
|
||||
@@ -1,361 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// Very verbose messages for debugging specific issues
|
||||
LevelDebug = "debug"
|
||||
// Default log level, informational
|
||||
LevelInfo = "info"
|
||||
// Warnings are messages about possible issues
|
||||
LevelWarn = "warn"
|
||||
// Errors are messages about things we know are problems
|
||||
LevelError = "error"
|
||||
|
||||
// DefaultFlushTimeout is the default amount of time mlog.Flush will wait
|
||||
// before timing out.
|
||||
DefaultFlushTimeout = time.Second * 5
|
||||
)
|
||||
|
||||
var (
|
||||
// disableZap is set when Zap should be disabled and Logr used instead.
|
||||
// This is needed for unit testing as Zap has no shutdown capabilities
|
||||
// and holds file handles until process exit. Currently unit test create
|
||||
// many server instances, and thus many Zap log files.
|
||||
// This flag will be removed when Zap is permanently replaced.
|
||||
disableZap int32
|
||||
)
|
||||
|
||||
// Type and function aliases from zap to limit the libraries scope into MM code
|
||||
type Field = zapcore.Field
|
||||
|
||||
var Int64 = zap.Int64
|
||||
var Int32 = zap.Int32
|
||||
var Int = zap.Int
|
||||
var Uint32 = zap.Uint32
|
||||
var String = zap.String
|
||||
var Any = zap.Any
|
||||
var Err = zap.Error
|
||||
var NamedErr = zap.NamedError
|
||||
var Bool = zap.Bool
|
||||
var Duration = zap.Duration
|
||||
|
||||
type LoggerIFace interface {
|
||||
IsLevelEnabled(LogLevel) bool
|
||||
Debug(string, ...Field)
|
||||
Info(string, ...Field)
|
||||
Warn(string, ...Field)
|
||||
Error(string, ...Field)
|
||||
Critical(string, ...Field)
|
||||
Log(LogLevel, string, ...Field)
|
||||
LogM([]LogLevel, string, ...Field)
|
||||
}
|
||||
|
||||
type TargetInfo logr.TargetInfo
|
||||
|
||||
type LoggerConfiguration struct {
|
||||
EnableConsole bool
|
||||
ConsoleJson bool
|
||||
EnableColor bool
|
||||
ConsoleLevel string
|
||||
EnableFile bool
|
||||
FileJson bool
|
||||
FileLevel string
|
||||
FileLocation string
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
zap *zap.Logger
|
||||
consoleLevel zap.AtomicLevel
|
||||
fileLevel zap.AtomicLevel
|
||||
logrLogger *logr.Logger
|
||||
mutex *sync.RWMutex
|
||||
}
|
||||
|
||||
func getZapLevel(level string) zapcore.Level {
|
||||
switch level {
|
||||
case LevelInfo:
|
||||
return zapcore.InfoLevel
|
||||
case LevelWarn:
|
||||
return zapcore.WarnLevel
|
||||
case LevelDebug:
|
||||
return zapcore.DebugLevel
|
||||
case LevelError:
|
||||
return zapcore.ErrorLevel
|
||||
default:
|
||||
return zapcore.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
func makeEncoder(json, color bool) zapcore.Encoder {
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
if json {
|
||||
return zapcore.NewJSONEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
if color {
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
}
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
return zapcore.NewConsoleEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
func NewLogger(config *LoggerConfiguration) *Logger {
|
||||
cores := []zapcore.Core{}
|
||||
logger := &Logger{
|
||||
consoleLevel: zap.NewAtomicLevelAt(getZapLevel(config.ConsoleLevel)),
|
||||
fileLevel: zap.NewAtomicLevelAt(getZapLevel(config.FileLevel)),
|
||||
logrLogger: newLogr(),
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
if config.EnableConsole {
|
||||
writer := zapcore.Lock(os.Stderr)
|
||||
core := zapcore.NewCore(makeEncoder(config.ConsoleJson, config.EnableColor), writer, logger.consoleLevel)
|
||||
cores = append(cores, core)
|
||||
}
|
||||
|
||||
if config.EnableFile {
|
||||
if atomic.LoadInt32(&disableZap) != 0 {
|
||||
t := &LogTarget{
|
||||
Type: "file",
|
||||
Format: "json",
|
||||
Levels: mlogLevelToLogrLevels(config.FileLevel),
|
||||
MaxQueueSize: DefaultMaxTargetQueue,
|
||||
Options: []byte(fmt.Sprintf(`{"Filename":"%s", "MaxSizeMB":%d, "Compress":%t}`,
|
||||
config.FileLocation, 100, true)),
|
||||
}
|
||||
if !config.FileJson {
|
||||
t.Format = "plain"
|
||||
}
|
||||
if tgt, err := NewLogrTarget("mlogFile", t); err == nil {
|
||||
logger.logrLogger.Logr().AddTarget(tgt)
|
||||
} else {
|
||||
Error("error creating mlogFile", Err(err))
|
||||
}
|
||||
} else {
|
||||
writer := zapcore.AddSync(&lumberjack.Logger{
|
||||
Filename: config.FileLocation,
|
||||
MaxSize: 100,
|
||||
Compress: true,
|
||||
})
|
||||
|
||||
core := zapcore.NewCore(makeEncoder(config.FileJson, false), writer, logger.fileLevel)
|
||||
cores = append(cores, core)
|
||||
}
|
||||
}
|
||||
|
||||
combinedCore := zapcore.NewTee(cores...)
|
||||
|
||||
logger.zap = zap.New(combinedCore,
|
||||
zap.AddCaller(),
|
||||
)
|
||||
return logger
|
||||
}
|
||||
|
||||
func (l *Logger) ChangeLevels(config *LoggerConfiguration) {
|
||||
l.consoleLevel.SetLevel(getZapLevel(config.ConsoleLevel))
|
||||
l.fileLevel.SetLevel(getZapLevel(config.FileLevel))
|
||||
}
|
||||
|
||||
func (l *Logger) SetConsoleLevel(level string) {
|
||||
l.consoleLevel.SetLevel(getZapLevel(level))
|
||||
}
|
||||
|
||||
func (l *Logger) With(fields ...Field) *Logger {
|
||||
newLogger := *l
|
||||
newLogger.zap = newLogger.zap.With(fields...)
|
||||
if newLogger.getLogger() != nil {
|
||||
ll := newLogger.getLogger().WithFields(zapToLogr(fields))
|
||||
newLogger.logrLogger = &ll
|
||||
}
|
||||
return &newLogger
|
||||
}
|
||||
|
||||
func (l *Logger) StdLog(fields ...Field) *log.Logger {
|
||||
return zap.NewStdLog(l.With(fields...).zap.WithOptions(getStdLogOption()))
|
||||
}
|
||||
|
||||
// StdLogAt returns *log.Logger which writes to supplied zap logger at required level.
|
||||
func (l *Logger) StdLogAt(level string, fields ...Field) (*log.Logger, error) {
|
||||
return zap.NewStdLogAt(l.With(fields...).zap.WithOptions(getStdLogOption()), getZapLevel(level))
|
||||
}
|
||||
|
||||
// StdLogWriter returns a writer that can be hooked up to the output of a golang standard logger
|
||||
// anything written will be interpreted as log entries accordingly
|
||||
func (l *Logger) StdLogWriter() io.Writer {
|
||||
newLogger := *l
|
||||
newLogger.zap = newLogger.zap.WithOptions(zap.AddCallerSkip(4), getStdLogOption())
|
||||
f := newLogger.Info
|
||||
return &loggerWriter{f}
|
||||
}
|
||||
|
||||
func (l *Logger) WithCallerSkip(skip int) *Logger {
|
||||
newLogger := *l
|
||||
newLogger.zap = newLogger.zap.WithOptions(zap.AddCallerSkip(skip))
|
||||
return &newLogger
|
||||
}
|
||||
|
||||
// Made for the plugin interface, wraps mlog in a simpler interface
|
||||
// at the cost of performance
|
||||
func (l *Logger) Sugar() *SugarLogger {
|
||||
return &SugarLogger{
|
||||
wrappedLogger: l,
|
||||
zapSugar: l.zap.Sugar(),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) IsLevelEnabled(level LogLevel) bool {
|
||||
return isLevelEnabled(l.getLogger(), logr.Level(level))
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(message string, fields ...Field) {
|
||||
l.zap.Debug(message, fields...)
|
||||
if isLevelEnabled(l.getLogger(), logr.Debug) {
|
||||
l.getLogger().WithFields(zapToLogr(fields)).Debug(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Info(message string, fields ...Field) {
|
||||
l.zap.Info(message, fields...)
|
||||
if isLevelEnabled(l.getLogger(), logr.Info) {
|
||||
l.getLogger().WithFields(zapToLogr(fields)).Info(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(message string, fields ...Field) {
|
||||
l.zap.Warn(message, fields...)
|
||||
if isLevelEnabled(l.getLogger(), logr.Warn) {
|
||||
l.getLogger().WithFields(zapToLogr(fields)).Warn(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Error(message string, fields ...Field) {
|
||||
l.zap.Error(message, fields...)
|
||||
if isLevelEnabled(l.getLogger(), logr.Error) {
|
||||
l.getLogger().WithFields(zapToLogr(fields)).Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Critical(message string, fields ...Field) {
|
||||
l.zap.Error(message, fields...)
|
||||
if isLevelEnabled(l.getLogger(), logr.Error) {
|
||||
l.getLogger().WithFields(zapToLogr(fields)).Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Log(level LogLevel, message string, fields ...Field) {
|
||||
l.getLogger().WithFields(zapToLogr(fields)).Log(logr.Level(level), message)
|
||||
}
|
||||
|
||||
func (l *Logger) LogM(levels []LogLevel, message string, fields ...Field) {
|
||||
var logger *logr.Logger
|
||||
for _, lvl := range levels {
|
||||
if isLevelEnabled(l.getLogger(), logr.Level(lvl)) {
|
||||
// don't create logger with fields unless at least one level is active.
|
||||
if logger == nil {
|
||||
l := l.getLogger().WithFields(zapToLogr(fields))
|
||||
logger = &l
|
||||
}
|
||||
logger.Log(logr.Level(lvl), message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Flush(cxt context.Context) error {
|
||||
return l.getLogger().Logr().FlushWithTimeout(cxt)
|
||||
}
|
||||
|
||||
// ShutdownAdvancedLogging stops the logger from accepting new log records and tries to
|
||||
// flush queues within the context timeout. Once complete all targets are shutdown
|
||||
// and any resources released.
|
||||
func (l *Logger) ShutdownAdvancedLogging(cxt context.Context) error {
|
||||
err := l.getLogger().Logr().ShutdownWithTimeout(cxt)
|
||||
l.setLogger(newLogr())
|
||||
return err
|
||||
}
|
||||
|
||||
// ConfigAdvancedLoggingConfig (re)configures advanced logging based on the
|
||||
// specified log targets. This is the easiest way to get the advanced logger
|
||||
// configured via a config source such as file.
|
||||
func (l *Logger) ConfigAdvancedLogging(targets LogTargetCfg) error {
|
||||
if err := l.ShutdownAdvancedLogging(context.Background()); err != nil {
|
||||
Error("error shutting down previous logger", Err(err))
|
||||
}
|
||||
|
||||
err := logrAddTargets(l.getLogger(), targets)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddTarget adds one or more logr.Target to the advanced logger. This is the preferred method
|
||||
// to add custom targets or provide configuration that cannot be expressed via a
|
||||
// config source.
|
||||
func (l *Logger) AddTarget(targets ...logr.Target) error {
|
||||
return l.getLogger().Logr().AddTarget(targets...)
|
||||
}
|
||||
|
||||
// RemoveTargets selectively removes targets that were previously added to this logger instance
|
||||
// using the passed in filter function. The filter function should return true to remove the target
|
||||
// and false to keep it.
|
||||
func (l *Logger) RemoveTargets(ctx context.Context, f func(ti TargetInfo) bool) error {
|
||||
// Use locally defined TargetInfo type so we don't spread Logr dependencies.
|
||||
fc := func(tic logr.TargetInfo) bool {
|
||||
return f(TargetInfo(tic))
|
||||
}
|
||||
return l.getLogger().Logr().RemoveTargets(ctx, fc)
|
||||
}
|
||||
|
||||
// EnableMetrics enables metrics collection by supplying a MetricsCollector.
|
||||
// The MetricsCollector provides counters and gauges that are updated by log targets.
|
||||
func (l *Logger) EnableMetrics(collector logr.MetricsCollector) error {
|
||||
return l.getLogger().Logr().SetMetricsCollector(collector)
|
||||
}
|
||||
|
||||
// getLogger is a concurrent safe getter of the logr logger
|
||||
func (l *Logger) getLogger() *logr.Logger {
|
||||
defer l.mutex.RUnlock()
|
||||
l.mutex.RLock()
|
||||
return l.logrLogger
|
||||
}
|
||||
|
||||
// setLogger is a concurrent safe setter of the logr logger
|
||||
func (l *Logger) setLogger(logger *logr.Logger) {
|
||||
defer l.mutex.Unlock()
|
||||
l.mutex.Lock()
|
||||
l.logrLogger = logger
|
||||
}
|
||||
|
||||
// DisableZap is called to disable Zap, and Logr will be used instead. Any Logger
|
||||
// instances created after this call will only use Logr.
|
||||
//
|
||||
// This is needed for unit testing as Zap has no shutdown capabilities
|
||||
// and holds file handles until process exit. Currently unit tests create
|
||||
// many server instances, and thus many Zap log file handles.
|
||||
//
|
||||
// This method will be removed when Zap is permanently replaced.
|
||||
func DisableZap() {
|
||||
atomic.StoreInt32(&disableZap, 1)
|
||||
}
|
||||
|
||||
// EnableZap re-enables Zap such that any Logger instances created after this
|
||||
// call will allow Zap targets.
|
||||
func EnableZap() {
|
||||
atomic.StoreInt32(&disableZap, 0)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
// Test race condition when shutting down advanced logging. This test must run with the -race flag in order to verify
|
||||
// that there is no race.
|
||||
func TestLogger_ShutdownAdvancedLoggingRace(t *testing.T) {
|
||||
logger := mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
EnableFile: false,
|
||||
FileLevel: mlog.LevelInfo,
|
||||
})
|
||||
started := make(chan bool)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
started <- true
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
logger.Debug("testing...")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
<-started
|
||||
|
||||
err := logger.ShutdownAdvancedLogging(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mattermost/logr"
|
||||
logrFmt "github.com/mattermost/logr/format"
|
||||
"github.com/mattermost/logr/target"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaxTargetQueue = 1000
|
||||
DefaultSysLogPort = 514
|
||||
)
|
||||
|
||||
type LogLevel struct {
|
||||
ID logr.LevelID
|
||||
Name string
|
||||
Stacktrace bool
|
||||
}
|
||||
|
||||
type LogTarget struct {
|
||||
Type string // one of "console", "file", "tcp", "syslog", "none".
|
||||
Format string // one of "json", "plain"
|
||||
Levels []LogLevel
|
||||
Options json.RawMessage
|
||||
MaxQueueSize int
|
||||
}
|
||||
|
||||
type LogTargetCfg map[string]*LogTarget
|
||||
type LogrCleanup func() error
|
||||
|
||||
func newLogr() *logr.Logger {
|
||||
lgr := &logr.Logr{}
|
||||
lgr.OnExit = func(int) {}
|
||||
lgr.OnPanic = func(interface{}) {}
|
||||
lgr.OnLoggerError = onLoggerError
|
||||
lgr.OnQueueFull = onQueueFull
|
||||
lgr.OnTargetQueueFull = onTargetQueueFull
|
||||
|
||||
logger := lgr.NewLogger()
|
||||
return &logger
|
||||
}
|
||||
|
||||
func logrAddTargets(logger *logr.Logger, targets LogTargetCfg) error {
|
||||
lgr := logger.Logr()
|
||||
var errs error
|
||||
for name, t := range targets {
|
||||
target, err := NewLogrTarget(name, t)
|
||||
if err != nil {
|
||||
errs = multierror.Append(err)
|
||||
continue
|
||||
}
|
||||
if target != nil {
|
||||
target.SetName(name)
|
||||
lgr.AddTarget(target)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// NewLogrTarget creates a `logr.Target` based on a target config.
|
||||
// Can be used when parsing custom config files, or when programmatically adding
|
||||
// built-in targets. Use `mlog.AddTarget` to add custom targets.
|
||||
func NewLogrTarget(name string, t *LogTarget) (logr.Target, error) {
|
||||
formatter, err := newFormatter(name, t.Format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter := newFilter(t.Levels)
|
||||
|
||||
if t.MaxQueueSize == 0 {
|
||||
t.MaxQueueSize = DefaultMaxTargetQueue
|
||||
}
|
||||
|
||||
switch t.Type {
|
||||
case "console":
|
||||
return newConsoleTarget(name, t, filter, formatter)
|
||||
case "file":
|
||||
return newFileTarget(name, t, filter, formatter)
|
||||
case "syslog":
|
||||
return newSyslogTarget(name, t, filter, formatter)
|
||||
case "tcp":
|
||||
return newTCPTarget(name, t, filter, formatter)
|
||||
case "none":
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid type '%s' for target %s", t.Type, name)
|
||||
}
|
||||
|
||||
func newFilter(levels []LogLevel) logr.Filter {
|
||||
filter := &logr.CustomFilter{}
|
||||
for _, lvl := range levels {
|
||||
filter.Add(logr.Level(lvl))
|
||||
}
|
||||
return filter
|
||||
}
|
||||
|
||||
func newFormatter(name string, format string) (logr.Formatter, error) {
|
||||
switch format {
|
||||
case "json", "":
|
||||
return &logrFmt.JSON{}, nil
|
||||
case "plain":
|
||||
return &logrFmt.Plain{Delim: " | "}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid format '%s' for target %s", format, name)
|
||||
}
|
||||
}
|
||||
|
||||
func newConsoleTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
|
||||
type consoleOptions struct {
|
||||
Out string `json:"Out"`
|
||||
}
|
||||
options := &consoleOptions{}
|
||||
if err := json.Unmarshal(t.Options, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var w io.Writer
|
||||
switch options.Out {
|
||||
case "stdout", "":
|
||||
w = os.Stdout
|
||||
case "stderr":
|
||||
w = os.Stderr
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid out '%s' for target %s", options.Out, name)
|
||||
}
|
||||
|
||||
newTarget := target.NewWriterTarget(filter, formatter, w, t.MaxQueueSize)
|
||||
return newTarget, nil
|
||||
}
|
||||
|
||||
func newFileTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
|
||||
type fileOptions struct {
|
||||
Filename string `json:"Filename"`
|
||||
MaxSize int `json:"MaxSizeMB"`
|
||||
MaxAge int `json:"MaxAgeDays"`
|
||||
MaxBackups int `json:"MaxBackups"`
|
||||
Compress bool `json:"Compress"`
|
||||
}
|
||||
options := &fileOptions{}
|
||||
if err := json.Unmarshal(t.Options, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newFileTargetWithOpts(name, t, target.FileOptions(*options), filter, formatter)
|
||||
}
|
||||
|
||||
func newFileTargetWithOpts(name string, t *LogTarget, opts target.FileOptions, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
|
||||
if opts.Filename == "" {
|
||||
return nil, fmt.Errorf("missing 'Filename' option for target %s", name)
|
||||
}
|
||||
if err := checkFileWritable(opts.Filename); err != nil {
|
||||
return nil, fmt.Errorf("error writing to 'Filename' for target %s: %w", name, err)
|
||||
}
|
||||
|
||||
newTarget := target.NewFileTarget(filter, formatter, opts, t.MaxQueueSize)
|
||||
return newTarget, nil
|
||||
}
|
||||
|
||||
func newSyslogTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
|
||||
options := &SyslogParams{}
|
||||
if err := json.Unmarshal(t.Options, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.IP == "" {
|
||||
return nil, fmt.Errorf("missing 'IP' option for target %s", name)
|
||||
}
|
||||
if options.Port == 0 {
|
||||
options.Port = DefaultSysLogPort
|
||||
}
|
||||
return NewSyslogTarget(filter, formatter, options, t.MaxQueueSize)
|
||||
}
|
||||
|
||||
func newTCPTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
|
||||
options := &TCPParams{}
|
||||
if err := json.Unmarshal(t.Options, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.IP == "" {
|
||||
return nil, fmt.Errorf("missing 'IP' option for target %s", name)
|
||||
}
|
||||
if options.Port == 0 {
|
||||
return nil, fmt.Errorf("missing 'Port' option for target %s", name)
|
||||
}
|
||||
return NewTCPTarget(filter, formatter, options, t.MaxQueueSize)
|
||||
}
|
||||
|
||||
func checkFileWritable(filename string) error {
|
||||
// try opening/creating the file for writing
|
||||
file, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLevelEnabled(logger *logr.Logger, level logr.Level) bool {
|
||||
if logger == nil || logger.Logr() == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
status := logger.Logr().IsLevelEnabled(level)
|
||||
return status.Enabled
|
||||
}
|
||||
|
||||
// zapToLogr converts Zap fields to Logr fields.
|
||||
// This will not be needed once Logr is used for all logging.
|
||||
func zapToLogr(zapFields []Field) logr.Fields {
|
||||
encoder := zapcore.NewMapObjectEncoder()
|
||||
for _, zapField := range zapFields {
|
||||
zapField.AddTo(encoder)
|
||||
}
|
||||
return logr.Fields(encoder.Fields)
|
||||
}
|
||||
|
||||
// mlogLevelToLogrLevel converts a mlog logger level to
|
||||
// an array of discrete Logr levels.
|
||||
func mlogLevelToLogrLevels(level string) []LogLevel {
|
||||
levels := make([]LogLevel, 0)
|
||||
levels = append(levels, LvlError, LvlPanic, LvlFatal, LvlStdLog)
|
||||
|
||||
switch level {
|
||||
case LevelDebug:
|
||||
levels = append(levels, LvlDebug)
|
||||
fallthrough
|
||||
case LevelInfo:
|
||||
levels = append(levels, LvlInfo)
|
||||
fallthrough
|
||||
case LevelWarn:
|
||||
levels = append(levels, LvlWarn)
|
||||
}
|
||||
return levels
|
||||
}
|
||||
407
shared/mlog/mlog.go
Обычный файл
407
shared/mlog/mlog.go
Обычный файл
@@ -0,0 +1,407 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Package mlog provides a simple wrapper around Logr.
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/logr/v2"
|
||||
logrcfg "github.com/mattermost/logr/v2/config"
|
||||
)
|
||||
|
||||
const (
|
||||
ShutdownTimeout = time.Second * 15
|
||||
FlushTimeout = time.Second * 15
|
||||
DefaultMaxQueueSize = 1000
|
||||
DefaultMetricsUpdateFreqMillis = 15000
|
||||
)
|
||||
|
||||
type LoggerIFace interface {
|
||||
IsLevelEnabled(Level) bool
|
||||
Debug(string, ...Field)
|
||||
Info(string, ...Field)
|
||||
Warn(string, ...Field)
|
||||
Error(string, ...Field)
|
||||
Critical(string, ...Field)
|
||||
Log(Level, string, ...Field)
|
||||
LogM([]Level, string, ...Field)
|
||||
}
|
||||
|
||||
// Type and function aliases from Logr to limit the spread of dependencies.
|
||||
type Field = logr.Field
|
||||
type Level = logr.Level
|
||||
type Option = logr.Option
|
||||
type Target = logr.Target
|
||||
type TargetInfo = logr.TargetInfo
|
||||
type LogRec = logr.LogRec
|
||||
type LogCloner = logr.LogCloner
|
||||
type MetricsCollector = logr.MetricsCollector
|
||||
type TargetCfg = logrcfg.TargetCfg
|
||||
type Sugar = logr.Sugar
|
||||
|
||||
// LoggerConfiguration is a map of LogTarget configurations.
|
||||
type LoggerConfiguration map[string]TargetCfg
|
||||
|
||||
func (lc LoggerConfiguration) Append(cfg LoggerConfiguration) {
|
||||
for k, v := range cfg {
|
||||
lc[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (lc LoggerConfiguration) toTargetCfg() map[string]logrcfg.TargetCfg {
|
||||
tcfg := make(map[string]logrcfg.TargetCfg)
|
||||
for k, v := range lc {
|
||||
tcfg[k] = v
|
||||
}
|
||||
return tcfg
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
lockConfig *int32
|
||||
}
|
||||
|
||||
// NewLogger creates a new Logger instance which can be configured via `(*Logger).Configure`.
|
||||
// Some options with invalid values can cause an error to be returned, however `NewLogger()`
|
||||
// using just defaults never errors.
|
||||
func NewLogger(options ...Option) (*Logger, error) {
|
||||
options = append(options, logr.StackFilter(logr.GetPackageName("NewLogger")))
|
||||
|
||||
lgr, err := logr.New(options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := lgr.NewLogger()
|
||||
var lockConfig int32
|
||||
|
||||
return &Logger{
|
||||
log: &log,
|
||||
lockConfig: &lockConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Configure provides a new configuration for this logger.
|
||||
// Zero or more sources of config can be provided:
|
||||
// cfgFile - path to file containing JSON
|
||||
// cfgEscaped - JSON string probably from ENV var
|
||||
//
|
||||
// For each case JSON containing log targets is provided. Target name collisions are resolved
|
||||
// using the following precedence:
|
||||
// cfgFile > cfgEscaped
|
||||
func (l *Logger) Configure(cfgFile string, cfgEscaped string) error {
|
||||
if atomic.LoadInt32(l.lockConfig) != 0 {
|
||||
return ErrConfigurationLock
|
||||
}
|
||||
|
||||
cfgMap := make(LoggerConfiguration)
|
||||
|
||||
// Add config from file
|
||||
if cfgFile != "" {
|
||||
b, err := ioutil.ReadFile(cfgFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading logger config file %s: %w", cfgFile, err)
|
||||
}
|
||||
|
||||
var mapCfgFile LoggerConfiguration
|
||||
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 != "" {
|
||||
var mapCfgEscaped LoggerConfiguration
|
||||
if err := json.Unmarshal([]byte(cfgEscaped), &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.toTargetCfg(), nil)
|
||||
}
|
||||
|
||||
// ConfigureTargets provides a new configuration for this logger via a `LoggerConfig` map.
|
||||
// Typically `mlog.Configure` is used instead which accepts JSON formatted configuration.
|
||||
func (l *Logger) ConfigureTargets(cfg LoggerConfiguration) error {
|
||||
if atomic.LoadInt32(l.lockConfig) != 0 {
|
||||
return ErrConfigurationLock
|
||||
}
|
||||
return logrcfg.ConfigureTargets(l.log.Logr(), cfg.toTargetCfg(), nil)
|
||||
}
|
||||
|
||||
// LockConfiguration disallows further configuration changes until `UnlockConfiguration`
|
||||
// is called. The previous locked stated is returned.
|
||||
func (l *Logger) LockConfiguration() bool {
|
||||
old := atomic.SwapInt32(l.lockConfig, 1)
|
||||
return old != 0
|
||||
}
|
||||
|
||||
// UnlockConfiguration allows configuration changes. The previous locked stated is returned.
|
||||
func (l *Logger) UnlockConfiguration() bool {
|
||||
old := atomic.SwapInt32(l.lockConfig, 0)
|
||||
return old != 0
|
||||
}
|
||||
|
||||
// IsConfigurationLocked returns the current state of the configuration lock.
|
||||
func (l *Logger) IsConfigurationLocked() bool {
|
||||
return atomic.LoadInt32(l.lockConfig) != 0
|
||||
}
|
||||
|
||||
// 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,
|
||||
lockConfig: l.lockConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.log.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 `Critical` level.
|
||||
func (l *Logger) Critical(msg string, fields ...Field) {
|
||||
l.log.Log(LvlCritical, 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)
|
||||
}
|
||||
|
||||
// StdLogWriter returns a writer that can be hooked up to the output of a golang standard logger
|
||||
// anything written will be interpreted as log entries and passed to this logger.
|
||||
func (l *Logger) StdLogWriter() io.Writer {
|
||||
return &logWriter{
|
||||
logger: l,
|
||||
}
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// RemoveTargets safely removes one or more targets based on the filtering method.
|
||||
// `f` should return true to delete the target, false to keep it.
|
||||
// When removing a target, best effort is made to write any queued log records before
|
||||
// closing, with cxt determining how much time can be spent in total.
|
||||
// Note, keep the timeout short since this method blocks certain logging operations.
|
||||
func (l *Logger) RemoveTargets(ctx context.Context, f func(ti TargetInfo) bool) error {
|
||||
return l.log.Logr().RemoveTargets(ctx, f)
|
||||
}
|
||||
|
||||
// SetMetricsCollector sets (or resets) the metrics collector to be used for gathering
|
||||
// metrics for all targets. Only targets added after this call will use the collector.
|
||||
//
|
||||
// To ensure all targets use a collector, use the `SetMetricsCollector` option when
|
||||
// creating the Logger instead, or configure/reconfigure the Logger after calling this method.
|
||||
func (l *Logger) SetMetricsCollector(collector MetricsCollector, updateFrequencyMillis int64) {
|
||||
l.log.Logr().SetMetricsCollector(collector, updateFrequencyMillis)
|
||||
}
|
||||
|
||||
// Sugar creates a new `Logger` with a less structured API. Any fields are preserved.
|
||||
func (l *Logger) Sugar(fields ...Field) Sugar {
|
||||
return l.log.Sugar(fields...)
|
||||
}
|
||||
|
||||
// Flush forces all targets to write out any queued log records with a default timeout.
|
||||
func (l *Logger) Flush() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), FlushTimeout)
|
||||
defer cancel()
|
||||
return l.log.Logr().FlushWithTimeout(ctx)
|
||||
}
|
||||
|
||||
// Flush forces all targets to write out any queued log records with the specfified timeout.
|
||||
func (l *Logger) FlushWithTimeout(ctx context.Context) error {
|
||||
return l.log.Logr().FlushWithTimeout(ctx)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Shutdown shuts down the logger after making best efforts to flush any
|
||||
// remaining records.
|
||||
func (l *Logger) ShutdownWithTimeout(ctx context.Context) error {
|
||||
return l.log.Logr().ShutdownWithTimeout(ctx)
|
||||
}
|
||||
|
||||
// GetPackageName reduces a fully qualified function name to the package name
|
||||
// By sirupsen: https://github.com/sirupsen/logrus/blob/master/entry.go
|
||||
func GetPackageName(f string) string {
|
||||
for {
|
||||
lastPeriod := strings.LastIndex(f, ".")
|
||||
lastSlash := strings.LastIndex(f, "/")
|
||||
if lastPeriod > lastSlash {
|
||||
f = f[:lastPeriod]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type logWriter struct {
|
||||
logger *Logger
|
||||
}
|
||||
|
||||
func (lw *logWriter) Write(p []byte) (int, error) {
|
||||
lw.logger.Info(string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// ErrConfigurationLock is returned when one of a logger's configuration APIs is called
|
||||
// while the configuration is locked.
|
||||
var ErrConfigurationLock = errors.New("configuration is locked")
|
||||
55
shared/mlog/options.go
Обычный файл
55
shared/mlog/options.go
Обычный файл
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import "github.com/mattermost/logr/v2"
|
||||
|
||||
// MaxQueueSize is the maximum number of log records that can be queued.
|
||||
// If exceeded, `OnQueueFull` is called which determines if the log
|
||||
// record will be dropped or block until add is successful.
|
||||
// Defaults to DefaultMaxQueueSize.
|
||||
func MaxQueueSize(size int) Option {
|
||||
return logr.MaxQueueSize(size)
|
||||
}
|
||||
|
||||
// OnLoggerError, when not nil, is called any time an internal
|
||||
// logging error occurs. For example, this can happen when a
|
||||
// target cannot connect to its data sink.
|
||||
func OnLoggerError(f func(error)) Option {
|
||||
return logr.OnLoggerError(f)
|
||||
}
|
||||
|
||||
// OnQueueFull, when not nil, is called on an attempt to add
|
||||
// a log record to a full Logr queue.
|
||||
// `MaxQueueSize` can be used to modify the maximum queue size.
|
||||
// This function should return quickly, with a bool indicating whether
|
||||
// the log record should be dropped (true) or block until the log record
|
||||
// is successfully added (false). If nil then blocking (false) is assumed.
|
||||
func OnQueueFull(f func(rec *LogRec, maxQueueSize int) bool) Option {
|
||||
return logr.OnQueueFull(f)
|
||||
}
|
||||
|
||||
// OnTargetQueueFull, when not nil, is called on an attempt to add
|
||||
// a log record to a full target queue provided the target supports reporting
|
||||
// this condition.
|
||||
// This function should return quickly, with a bool indicating whether
|
||||
// the log record should be dropped (true) or block until the log record
|
||||
// is successfully added (false). If nil then blocking (false) is assumed.
|
||||
func OnTargetQueueFull(f func(target Target, rec *LogRec, maxQueueSize int) bool) Option {
|
||||
return logr.OnTargetQueueFull(f)
|
||||
}
|
||||
|
||||
// SetMetricsCollector enables metrics collection by supplying a MetricsCollector.
|
||||
// The MetricsCollector provides counters and gauges that are updated by log targets.
|
||||
// `updateFreqMillis` determines how often polled metrics are updated. Defaults to 15000 (15 seconds)
|
||||
// and must be at least 250 so we don't peg the CPU.
|
||||
func SetMetricsCollector(collector MetricsCollector, updateFreqMillis int64) Option {
|
||||
return logr.SetMetricsCollector(collector, updateFreqMillis)
|
||||
}
|
||||
|
||||
// StackFilter provides a list of package names to exclude from the top of
|
||||
// stack traces. The Logr packages are automatically filtered.
|
||||
func StackFilter(pkg ...string) Option {
|
||||
return logr.StackFilter(pkg...)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Implementation of zapcore.Core to interpret log messages from a standard logger
|
||||
// and translate the levels to zapcore levels.
|
||||
type stdLogLevelInterpreterCore struct {
|
||||
wrappedCore zapcore.Core
|
||||
}
|
||||
|
||||
func stdLogInterpretZapEntry(entry zapcore.Entry) zapcore.Entry {
|
||||
message := entry.Message
|
||||
if strings.Index(message, "[DEBUG]") == 0 {
|
||||
entry.Level = zapcore.DebugLevel
|
||||
entry.Message = message[7:]
|
||||
} else if strings.Index(message, "[DEBG]") == 0 {
|
||||
entry.Level = zapcore.DebugLevel
|
||||
entry.Message = message[6:]
|
||||
} else if strings.Index(message, "[WARN]") == 0 {
|
||||
entry.Level = zapcore.WarnLevel
|
||||
entry.Message = message[6:]
|
||||
} else if strings.Index(message, "[ERROR]") == 0 {
|
||||
entry.Level = zapcore.ErrorLevel
|
||||
entry.Message = message[7:]
|
||||
} else if strings.Index(message, "[EROR]") == 0 {
|
||||
entry.Level = zapcore.ErrorLevel
|
||||
entry.Message = message[6:]
|
||||
} else if strings.Index(message, "[ERR]") == 0 {
|
||||
entry.Level = zapcore.ErrorLevel
|
||||
entry.Message = message[5:]
|
||||
} else if strings.Index(message, "[INFO]") == 0 {
|
||||
entry.Level = zapcore.InfoLevel
|
||||
entry.Message = message[6:]
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
func (s *stdLogLevelInterpreterCore) Enabled(lvl zapcore.Level) bool {
|
||||
return s.wrappedCore.Enabled(lvl)
|
||||
}
|
||||
|
||||
func (s *stdLogLevelInterpreterCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
return s.wrappedCore.With(fields)
|
||||
}
|
||||
|
||||
func (s *stdLogLevelInterpreterCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
entry = stdLogInterpretZapEntry(entry)
|
||||
return s.wrappedCore.Check(entry, checkedEntry)
|
||||
}
|
||||
|
||||
func (s *stdLogLevelInterpreterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
entry = stdLogInterpretZapEntry(entry)
|
||||
return s.wrappedCore.Write(entry, fields)
|
||||
}
|
||||
|
||||
func (s *stdLogLevelInterpreterCore) Sync() error {
|
||||
return s.wrappedCore.Sync()
|
||||
}
|
||||
|
||||
func getStdLogOption() zap.Option {
|
||||
return zap.WrapCore(
|
||||
func(core zapcore.Core) zapcore.Core {
|
||||
return &stdLogLevelInterpreterCore{core}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type loggerWriter struct {
|
||||
logFunc func(msg string, fields ...Field)
|
||||
}
|
||||
|
||||
func (l *loggerWriter) Write(p []byte) (int, error) {
|
||||
trimmed := string(bytes.TrimSpace(p))
|
||||
for _, line := range strings.Split(trimmed, "\n") {
|
||||
l.logFunc(line)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func TestStdLogInterpretZapEntry(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
testname string
|
||||
message string
|
||||
expectedMessage string
|
||||
expectedLevel zapcore.Level
|
||||
}{
|
||||
{"Debug Basic", "[DEBUG]My message", "My message", zapcore.DebugLevel},
|
||||
{"Debug Basic2", "[DEBG]My message", "My message", zapcore.DebugLevel},
|
||||
{"Warn Basic", "[WARN]My message", "My message", zapcore.WarnLevel},
|
||||
{"Error Basic", "[ERROR]My message", "My message", zapcore.ErrorLevel},
|
||||
{"Error Basic2", "[EROR]My message", "My message", zapcore.ErrorLevel},
|
||||
{"Error Basic3", "[ERR]My message", "My message", zapcore.ErrorLevel},
|
||||
{"Info Basic", "[INFO]My message", "My message", zapcore.InfoLevel},
|
||||
{"Unknown level", "[UNKNOWN]My message", "[UNKNOWN]My message", zapcore.PanicLevel},
|
||||
{"No level", "My message", "My message", zapcore.PanicLevel},
|
||||
{"Empty message", "", "", zapcore.PanicLevel},
|
||||
{"Malformed level", "INFO]My message", "INFO]My message", zapcore.PanicLevel},
|
||||
} {
|
||||
t.Run(tc.testname, func(t *testing.T) {
|
||||
inEntry := zapcore.Entry{
|
||||
Level: zapcore.PanicLevel,
|
||||
Message: tc.message,
|
||||
}
|
||||
resultEntry := stdLogInterpretZapEntry(inEntry)
|
||||
assert.Equal(t, tc.expectedMessage, resultEntry.Message)
|
||||
assert.Equal(t, tc.expectedLevel, resultEntry.Level)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Made for the plugin interface, use the regular logger for other uses
|
||||
type SugarLogger struct {
|
||||
wrappedLogger *Logger
|
||||
zapSugar *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (l *SugarLogger) Debug(msg string, keyValuePairs ...interface{}) {
|
||||
l.zapSugar.Debugw(msg, keyValuePairs...)
|
||||
}
|
||||
|
||||
func (l *SugarLogger) Info(msg string, keyValuePairs ...interface{}) {
|
||||
l.zapSugar.Infow(msg, keyValuePairs...)
|
||||
}
|
||||
|
||||
func (l *SugarLogger) Error(msg string, keyValuePairs ...interface{}) {
|
||||
l.zapSugar.Errorw(msg, keyValuePairs...)
|
||||
}
|
||||
|
||||
func (l *SugarLogger) Warn(msg string, keyValuePairs ...interface{}) {
|
||||
l.zapSugar.Warnw(msg, keyValuePairs...)
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
"github.com/wiggin77/merror"
|
||||
syslog "github.com/wiggin77/srslog"
|
||||
)
|
||||
|
||||
// Syslog outputs log records to local or remote syslog.
|
||||
type Syslog struct {
|
||||
logr.Basic
|
||||
w *syslog.Writer
|
||||
}
|
||||
|
||||
// SyslogParams provides parameters for dialing a syslog daemon.
|
||||
type SyslogParams struct {
|
||||
IP string `json:"IP"`
|
||||
Port int `json:"Port"`
|
||||
Tag string `json:"Tag"`
|
||||
TLS bool `json:"TLS"`
|
||||
Cert string `json:"Cert"`
|
||||
Insecure bool `json:"Insecure"`
|
||||
}
|
||||
|
||||
// NewSyslogTarget creates a target capable of outputting log records to remote or local syslog, with or without TLS.
|
||||
func NewSyslogTarget(filter logr.Filter, formatter logr.Formatter, params *SyslogParams, maxQueue int) (*Syslog, error) {
|
||||
network := "tcp"
|
||||
var config *tls.Config
|
||||
|
||||
if params.TLS {
|
||||
network = "tcp+tls"
|
||||
config = &tls.Config{InsecureSkipVerify: params.Insecure}
|
||||
if params.Cert != "" {
|
||||
pool, err := getCertPool(params.Cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.RootCAs = pool
|
||||
}
|
||||
}
|
||||
raddr := fmt.Sprintf("%s:%d", params.IP, params.Port)
|
||||
|
||||
writer, err := syslog.DialWithTLSConfig(network, raddr, syslog.LOG_INFO, params.Tag, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Syslog{w: writer}
|
||||
s.Basic.Start(s, s, filter, formatter, maxQueue)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Shutdown stops processing log records after making best effort to flush queue.
|
||||
func (s *Syslog) Shutdown(ctx context.Context) error {
|
||||
errs := merror.New()
|
||||
|
||||
err := s.Basic.Shutdown(ctx)
|
||||
errs.Append(err)
|
||||
|
||||
err = s.w.Close()
|
||||
errs.Append(err)
|
||||
|
||||
return errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
// getCertPool returns a x509.CertPool containing the cert(s)
|
||||
// from `cert`, which can be a path to a .pem or .crt file,
|
||||
// or a base64 encoded cert.
|
||||
func getCertPool(cert string) (*x509.CertPool, error) {
|
||||
if cert == "" {
|
||||
return nil, errors.New("no cert provided")
|
||||
}
|
||||
|
||||
// first treat as a file and try to read.
|
||||
serverCert, err := ioutil.ReadFile(cert)
|
||||
if err != nil {
|
||||
// maybe it's a base64 encoded cert
|
||||
serverCert, err = base64.StdEncoding.DecodeString(cert)
|
||||
if err != nil {
|
||||
return nil, errors.New("cert cannot be read")
|
||||
}
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
if ok := pool.AppendCertsFromPEM(serverCert); ok {
|
||||
return pool, nil
|
||||
}
|
||||
return nil, errors.New("cannot parse cert")
|
||||
}
|
||||
|
||||
// Write converts the log record to bytes, via the Formatter,
|
||||
// and outputs to syslog.
|
||||
func (s *Syslog) Write(rec *logr.LogRec) error {
|
||||
_, stacktrace := s.IsLevelEnabled(rec.Level())
|
||||
|
||||
buf := rec.Logger().Logr().BorrowBuffer()
|
||||
defer rec.Logger().Logr().ReleaseBuffer(buf)
|
||||
|
||||
buf, err := s.Formatter().Format(rec, stacktrace, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txt := buf.String()
|
||||
|
||||
switch rec.Level() {
|
||||
case logr.Panic, logr.Fatal:
|
||||
err = s.w.Crit(txt)
|
||||
case logr.Error:
|
||||
err = s.w.Err(txt)
|
||||
case logr.Warn:
|
||||
err = s.w.Warning(txt)
|
||||
case logr.Debug, logr.Trace:
|
||||
err = s.w.Debug(txt)
|
||||
default:
|
||||
// logr.Info plus all custom levels.
|
||||
err = s.w.Info(txt)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
reporter := rec.Logger().Logr().ReportError
|
||||
reporter(fmt.Errorf("syslog write fail: %w", err))
|
||||
// syslog writer will try to reconnect.
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns a string representation of this target.
|
||||
func (s *Syslog) String() string {
|
||||
return "SyslogTarget"
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_getCertPool(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cert string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "garbage in, garbage out", wantErr: true, cert: "THISISNOTACERT"},
|
||||
{name: "good cert base64", wantErr: false, cert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURqekNDQW5lZ0F3SUJBZ0lSQVBZZlJTd2R6S29wQkt4WXhLcXNsSlV3RFFZSktvWklodmNOQVFFTEJRQXcKSnpFbE1DTUdBMVVFQXd3Y1RXRjBkR1Z5Ylc5emRDd2dTVzVqTGlCSmJuUmxjbTVoYkNCRFFUQWVGdzB4T1RBegpNakl3TURFME1UVmFGdzB5TWpBek1EWXdNREUwTVRWYU1Ec3hPVEEzQmdOVkJBTVRNRTFoZEhSbGNtMXZjM1FzCklFbHVZeTRnU1c1MFpYSnVZV3dnU1c1MFpYSnRaV1JwWVhSbElFRjFkR2h2Y21sMGVUQ0NBU0l3RFFZSktvWkkKaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNamxpUmRtdm5OTDR1L0pyL00yZFB3UW1USlhFQlkvVnE5UQp2QVU1MlgzdFJNQ1B4Y2FGeit4NmZ0dXZkTzJOZG9oWEdBbXR4OVFVNUxaY3ZGZVREcG9WRUJvOUErNGp0THZECkRaWWFUTkxwSm1vU29KSGFEYmRXWCtPQU9xeURpV1M3NDFMdWlNS1dIaGV3OVFPaXNhdDJaSU5QeGptQWQ5d0UKeHRoVE1nenN2N01VcW5NZXI4VTVPR1EwUXk3d0FtTlJjKzJLM3FQd2t4ZTJSVXZjdGU1MERVRk5neEVnaW5zaAp2cmtPWFIzODN2VUNaZnU3MnF1OG9nZ2ppUXB5VGxsdTVqZTJBcDZKTGpZTGtFTWlNcXJZQUR1V29yL1pId2E2CldyRnFWRVR4V2ZBVjV1OUVoMHdaTS9LS1l3UlF1dzl5K05hbnM3N0ZtVWwxdFZXV05OOENBd0VBQWFPQm9UQ0IKbmpBTUJnTlZIUk1FQlRBREFRSC9NQjBHQTFVZERnUVdCQlFZNFVxc3d5cjJoTy9IZXRadDJSRHhKZFRJUGpCaQpCZ05WSFNNRVd6QlpnQlJGWlhWZzJaNXROSXNXZVdqQkxFeTJ5ektiTUtFcnBDa3dKekVsTUNNR0ExVUVBd3djClRXRjBkR1Z5Ylc5emRDd2dTVzVqTGlCSmJuUmxjbTVoYkNCRFFZSVVFaWZHVU9NK2JJRlpvMXRralpCNVlHQnIKMHhFd0N3WURWUjBQQkFRREFnRUdNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUFFZGV4TDMwUTB6QkhtUEFIOApMaGRLN2RielcxQ21JTGJ4UlpsS0F3Uk4raEtSWGlNVzNNSElraE51b1Y5QWV2NjAyUStqYTRsV3NSaS9rdE9MCm5pMUZXeDVnU1NjZ2RHOEpHajQ3ZE9tb1QzdlhLWDcrdW1pdjRyUUxQRGw5L0RLTXV2MjA0T1lKcTZWVCt1TlUKNkM2a0wxNTdqR0pFTzc2SDRmTVo4b1lzRDdTcTB6amlOS3R1Q1lpaTBuZ0gzajNnQjFqQUNMcVJndmVVN01kVApwcU9WMktmWTMxK2g4VkJ0a1V2bGpOenRROXhOWThGam10MFNNZjdFM0ZhVWNhYXIzWkNyNzBHNWFVM2RLYmU3CjQ3dkdPQmE1dENxdzRZSzBqZ0RLaWQzSUpRdWw5YTNKMW1Tc0g4V3kzdG85Y0FWNEtHWkJRTG56Q1gxNWEvK3YKM3lWaAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tIAotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJRGZqQ0NBbWFnQXdJQkFnSVVFaWZHVU9NK2JJRlpvMXRralpCNVlHQnIweEV3RFFZSktvWklodmNOQVFFTApCUUF3SnpFbE1DTUdBMVVFQXd3Y1RXRjBkR1Z5Ylc5emRDd2dTVzVqTGlCSmJuUmxjbTVoYkNCRFFUQWVGdzB4Ck9UQXpNakV5TVRJNE5ETmFGdzB5T1RBek1UZ3lNVEk0TkROYU1DY3hKVEFqQmdOVkJBTU1IRTFoZEhSbGNtMXYKYzNRc0lFbHVZeTRnU1c1MFpYSnVZV3dnUTBFd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFSwpBb0lCQVFESDBYcTVyTUJHcEtPVldUcGI1TW5hSklXRlAvdk90dkVrKzdoVnJmT2ZlMS81eDBLazNVZ0FIajg1Cm90YUVaRDFMaG4vSkxrRXFDaUUvVVhNSkZ3SkRsTmNPNENrZEtCU3BZWDRiS0FxeTVxL1gzUXdpb01TTnBKRzEKK1lZck5HQkgwc2dLY0tqeUNhTGhtcVlMRDB4WkRWT21XSVlCVTlqVVB5WHc1VTB0bnNWclRxR014VmttMXhDWQprckNXTjFab1VyTHZMME1DWmM1cXB4b1BUb3ByOVVPOWNxU0JTdXk2QlZXVnVFV0JaaHBxSHQrdWw4VnhoenpZCnExazRsN3IycXcrL3dtMWlKQmVkVGVCVmVXTmFnOEphVmZMZ3UrL1c3b0pWbFBPMzJQbzdwbnZIcDhpSjNiNEsKelh5VkhhVFg0UzZFbSs2TFY4ODU1VFlyU2h6bEFnTUJBQUdqZ2FFd2daNHdIUVlEVlIwT0JCWUVGRVZsZFdEWgpubTAwaXhaNWFNRXNUTGJMTXBzd01HSUdBMVVkSXdSYk1GbUFGRVZsZFdEWm5tMDBpeFo1YU1Fc1RMYkxNcHN3Cm9TdWtLVEFuTVNVd0l3WURWUVFEREJ4TllYUjBaWEp0YjNOMExDQkpibU11SUVsdWRHVnlibUZzSUVOQmdoUVMKSjhaUTR6NXNnVm1qVzJTTmtIbGdZR3ZURVRBTUJnTlZIUk1FQlRBREFRSC9NQXNHQTFVZER3UUVBd0lCQmpBTgpCZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFQaUNXRm1vcHlBa1kyVDNaeW80eWFSUGhYMStWT1RNS0p0WTZFVWhxCi9HSHo2a3pFeXZDVUJmME44OTJjaWJHeGVrckVvSXRZOU5xTzZSUVJmb3dnK0duNWtjMTN6NE55TDJXOC9lb1QKWHkwWnZmYVFiVSsrZlE2cFZ0V3RNYmxETVU5eGlZZDcvTUR2SnBPMzI4bDFWaGNkcDhrRWkrbEN2cHkwc0NSYwpQeHpQaGJnQ01BYlpFR3grNFRNUWQ0U1pLemxSeFcvMmZmbHBSZWg2djFEdjBWRFVTWVFXd3NVbmFMcGRLSGZoCmE1azB2dXlTWWNzekU0WUtsWTB6YWtlRmxKZnA3ZkJwMXhUd2NkVzhhVGZ3MTVFaWNQTXdUYzZ4eEE0SkpVSngKY2RkdTgxN24xbmF5SzV1NnI5UWgxb0lWa3IwbkM5WUVMTU15NGRwUGdKODhTQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"},
|
||||
{name: "good cert file", wantErr: false, cert: "test-tls-client-cert.pem"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pool, err := getCertPool(tt.cert)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, pool)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pool)
|
||||
|
||||
// Test PEM has 2 certs.
|
||||
subjects := pool.Subjects()
|
||||
assert.Len(t, subjects, 2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wiggin77/merror"
|
||||
)
|
||||
|
||||
const (
|
||||
testPort = 18066
|
||||
)
|
||||
|
||||
func TestNewTCPTarget(t *testing.T) {
|
||||
target := LogTarget{
|
||||
Type: "tcp",
|
||||
Format: "json",
|
||||
Levels: []LogLevel{LvlInfo},
|
||||
Options: []byte(`{"IP": "localhost", "Port": 18066}`),
|
||||
MaxQueueSize: 1000,
|
||||
}
|
||||
targets := map[string]*LogTarget{"tcp_test": &target}
|
||||
|
||||
t.Run("logging", func(t *testing.T) {
|
||||
buf := &buffer{}
|
||||
server, err := newSocketServer(testPort, buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
data := []string{"I drink your milkshake!", "We don't need no badges!", "You can't fight in here! This is the war room!"}
|
||||
|
||||
logger := newLogr()
|
||||
err = logrAddTargets(logger, targets)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, s := range data {
|
||||
logger.Info(s)
|
||||
}
|
||||
err = logger.Logr().Flush()
|
||||
require.NoError(t, err)
|
||||
err = logger.Logr().Shutdown()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = server.waitForAnyConnection()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = server.stopServer(true)
|
||||
require.NoError(t, err)
|
||||
|
||||
sdata := buf.String()
|
||||
for _, s := range data {
|
||||
require.Contains(t, sdata, s)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// socketServer is a simple socket server used for testing TCP log targets.
|
||||
// Note: There is more synchronization here than normally needed to avoid flaky tests.
|
||||
// For example, it's possible for a unit test to create a socketServer, attempt
|
||||
// writing to it, and stop the socket server before "go ss.listen()" gets scheduled.
|
||||
type socketServer struct {
|
||||
listener net.Listener
|
||||
anyConn chan struct{}
|
||||
buf *buffer
|
||||
conns map[string]*socketServerConn
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
type socketServerConn struct {
|
||||
raddy string
|
||||
conn net.Conn
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newSocketServer(port int, buf *buffer) (*socketServer, error) {
|
||||
ss := &socketServer{
|
||||
buf: buf,
|
||||
conns: make(map[string]*socketServerConn),
|
||||
anyConn: make(chan struct{}),
|
||||
}
|
||||
|
||||
addy := fmt.Sprintf(":%d", port)
|
||||
l, err := net.Listen("tcp4", addy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss.listener = l
|
||||
|
||||
go ss.listen()
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
func (ss *socketServer) listen() {
|
||||
for {
|
||||
conn, err := ss.listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sconn := &socketServerConn{raddy: conn.RemoteAddr().String(), conn: conn, done: make(chan struct{})}
|
||||
ss.registerConnection(sconn)
|
||||
go ss.handleConnection(sconn)
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *socketServer) waitForAnyConnection() error {
|
||||
var err error
|
||||
select {
|
||||
case <-ss.anyConn:
|
||||
case <-time.After(5 * time.Second):
|
||||
err = errors.New("wait for any connection timed out")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ss *socketServer) handleConnection(sconn *socketServerConn) {
|
||||
close(ss.anyConn)
|
||||
defer ss.unregisterConnection(sconn)
|
||||
buf := make([]byte, 1024)
|
||||
|
||||
for {
|
||||
n, err := sconn.conn.Read(buf)
|
||||
if n > 0 {
|
||||
ss.buf.Write(buf[:n])
|
||||
}
|
||||
if err == io.EOF {
|
||||
ss.signalDone(sconn)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *socketServer) registerConnection(sconn *socketServerConn) {
|
||||
ss.mux.Lock()
|
||||
defer ss.mux.Unlock()
|
||||
ss.conns[sconn.raddy] = sconn
|
||||
}
|
||||
|
||||
func (ss *socketServer) unregisterConnection(sconn *socketServerConn) {
|
||||
ss.mux.Lock()
|
||||
defer ss.mux.Unlock()
|
||||
delete(ss.conns, sconn.raddy)
|
||||
}
|
||||
|
||||
func (ss *socketServer) signalDone(sconn *socketServerConn) {
|
||||
ss.mux.Lock()
|
||||
defer ss.mux.Unlock()
|
||||
close(sconn.done)
|
||||
}
|
||||
|
||||
func (ss *socketServer) stopServer(wait bool) error {
|
||||
errs := merror.New()
|
||||
ss.listener.Close()
|
||||
|
||||
ss.mux.Lock()
|
||||
// defensive copy; no more connections can be accepted so copy will stay current.
|
||||
conns := make(map[string]*socketServerConn, len(ss.conns))
|
||||
for k, v := range ss.conns {
|
||||
conns[k] = v
|
||||
}
|
||||
ss.mux.Unlock()
|
||||
|
||||
for _, sconn := range conns {
|
||||
if wait {
|
||||
select {
|
||||
case <-sconn.done:
|
||||
case <-time.After(time.Second * 5):
|
||||
errs.Append(errors.New("timed out"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
type buffer struct {
|
||||
buf bytes.Buffer
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func (b *buffer) Write(p []byte) (n int, err error) {
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
return b.buf.Write(p)
|
||||
}
|
||||
|
||||
func (b *buffer) String() string {
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
return b.buf.String()
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// testingWriter is an io.Writer that writes through t.Log
|
||||
type testingWriter struct {
|
||||
tb testing.TB
|
||||
}
|
||||
|
||||
func (tw *testingWriter) Write(b []byte) (int, error) {
|
||||
tw.tb.Log(strings.TrimSpace(string(b)))
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// NewTestingLogger creates a Logger that proxies logs through a testing interface.
|
||||
// This allows tests that spin up App instances to avoid spewing logs unless the test fails or -verbose is specified.
|
||||
func NewTestingLogger(tb testing.TB, writer io.Writer) *Logger {
|
||||
logWriter := &testingWriter{tb}
|
||||
multiWriter := io.MultiWriter(logWriter, writer)
|
||||
logWriterSync := zapcore.AddSync(multiWriter)
|
||||
|
||||
testingLogger := &Logger{
|
||||
consoleLevel: zap.NewAtomicLevelAt(getZapLevel("debug")),
|
||||
fileLevel: zap.NewAtomicLevelAt(getZapLevel("info")),
|
||||
logrLogger: newLogr(),
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
logWriterCore := zapcore.NewCore(makeEncoder(true, false), zapcore.Lock(logWriterSync), testingLogger.consoleLevel)
|
||||
|
||||
testingLogger.zap = zap.New(logWriterCore,
|
||||
zap.AddCaller(),
|
||||
)
|
||||
return testingLogger
|
||||
}
|
||||
141
shared/mlog/tlog.go
Обычный файл
141
shared/mlog/tlog.go
Обычный файл
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package mlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/logr/v2"
|
||||
"github.com/mattermost/logr/v2/formatters"
|
||||
"github.com/mattermost/logr/v2/targets"
|
||||
)
|
||||
|
||||
// CreateTestLogger creates a logger for unit tests, using the `TB.Log`
|
||||
func CreateTestLogger(tb testing.TB, writer io.Writer, levels ...Level) *Logger {
|
||||
logger, _ := NewLogger()
|
||||
|
||||
filter := logr.NewCustomFilter(levels...)
|
||||
formatter := &formatters.Plain{}
|
||||
|
||||
if tb != nil {
|
||||
testtarget := newTestingTarget(tb)
|
||||
if err := logger.log.Logr().AddTarget(testtarget, "_testTB", filter, formatter, 1000); err != nil {
|
||||
tb.Fail()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if writer != nil {
|
||||
target := targets.NewWriterTarget(writer)
|
||||
if err := logger.log.Logr().AddTarget(target, "_testWriter", filter, formatter, 1000); err != nil {
|
||||
tb.Fail()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
func AddWriterTarget(logger *Logger, w io.Writer, useJSON bool, levels ...Level) error {
|
||||
filter := logr.NewCustomFilter(levels...)
|
||||
|
||||
var formatter logr.Formatter
|
||||
if useJSON {
|
||||
formatter = &formatters.JSON{EnableCaller: true}
|
||||
} else {
|
||||
formatter = &formatters.Plain{EnableCaller: true}
|
||||
}
|
||||
|
||||
target := targets.NewWriterTarget(w)
|
||||
return logger.log.Logr().AddTarget(target, "_testWriter", filter, formatter, 1000)
|
||||
}
|
||||
|
||||
// CreateConsoleTestLogger creates a logger for unit tests. Log records are output to `os.Stdout`.
|
||||
// Logs can also be mirrored to the optional `io.Writer`.
|
||||
func CreateConsoleTestLogger(useJSON bool, level Level) *Logger {
|
||||
logger, _ := NewLogger()
|
||||
|
||||
filter := logr.StdFilter{
|
||||
Lvl: level,
|
||||
Stacktrace: LvlPanic,
|
||||
}
|
||||
|
||||
var formatter logr.Formatter
|
||||
if useJSON {
|
||||
formatter = &formatters.JSON{EnableCaller: true}
|
||||
} else {
|
||||
formatter = &formatters.Plain{EnableCaller: true}
|
||||
}
|
||||
|
||||
target := targets.NewWriterTarget(os.Stdout)
|
||||
if err := logger.log.Logr().AddTarget(target, "_testcon", filter, formatter, 1000); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// testingTarget is a simple log target that writes to the testing log.
|
||||
type testingTarget struct {
|
||||
mux sync.Mutex
|
||||
tb testing.TB
|
||||
}
|
||||
|
||||
func newTestingTarget(tb testing.TB) *testingTarget {
|
||||
return &testingTarget{
|
||||
tb: tb,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.tb != nil {
|
||||
tt.tb.Helper()
|
||||
tt.tb.Log(strings.TrimSpace(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.tb = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Buffer provides a thread-safe buffer useful for logging to memory in unit tests.
|
||||
type Buffer struct {
|
||||
buf bytes.Buffer
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
return b.buf.Read(p)
|
||||
}
|
||||
func (b *Buffer) Write(p []byte) (n int, err error) {
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
return b.buf.Write(p)
|
||||
}
|
||||
func (b *Buffer) String() string {
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
return b.buf.String()
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/services/cache"
|
||||
cachemocks "github.com/mattermost/mattermost-server/v6/services/cache/mocks"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
"github.com/mattermost/mattermost-server/v6/store/sqlstore"
|
||||
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
|
||||
@@ -172,7 +171,6 @@ func getMockStore() *mocks.Store {
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
mlog.DisableZap()
|
||||
mainHelper = testlib.NewMainHelperWithOptions(nil)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ package sqlstore_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/store/sqlstore"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
var mainHelper *testlib.MainHelper
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
mlog.DisableZap()
|
||||
mainHelper = testlib.NewMainHelperWithOptions(nil)
|
||||
defer mainHelper.Close()
|
||||
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
package testlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// AssertLog asserts that a JSON-encoded buffer of logs contains one with the given level and message.
|
||||
func AssertLog(t *testing.T, logs *bytes.Buffer, level, message string) {
|
||||
func AssertLog(t *testing.T, logs io.Reader, level, message string) {
|
||||
dec := json.NewDecoder(logs)
|
||||
for {
|
||||
var log struct {
|
||||
@@ -33,7 +32,7 @@ func AssertLog(t *testing.T, logs *bytes.Buffer, level, message string) {
|
||||
}
|
||||
|
||||
// AssertNoLog asserts that a JSON-encoded buffer of logs does not contains one with the given level and message.
|
||||
func AssertNoLog(t *testing.T, logs *bytes.Buffer, level, message string) {
|
||||
func AssertNoLog(t *testing.T, logs io.Reader, level, message string) {
|
||||
dec := json.NewDecoder(logs)
|
||||
for {
|
||||
var log struct {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/services/searchengine"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
"github.com/mattermost/mattermost-server/v6/store/searchlayer"
|
||||
"github.com/mattermost/mattermost-server/v6/store/sqlstore"
|
||||
@@ -53,16 +52,6 @@ func NewMainHelperWithOptions(options *HelperOptions) *MainHelper {
|
||||
var mainHelper MainHelper
|
||||
flag.Parse()
|
||||
|
||||
// Setup a global logger to catch tests logging outside of app context
|
||||
// The global logger will be stomped by apps initializing but that's fine for testing.
|
||||
// Ideally this won't happen.
|
||||
mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{
|
||||
EnableConsole: true,
|
||||
ConsoleJson: true,
|
||||
ConsoleLevel: "error",
|
||||
EnableFile: false,
|
||||
}))
|
||||
|
||||
utils.TranslationsPreInit()
|
||||
|
||||
if options != nil {
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/utils/fileutils"
|
||||
)
|
||||
|
||||
const (
|
||||
LogRotateSize = 10000
|
||||
LogFilename = "mattermost.log"
|
||||
LogNotificationFilename = "notifications.log"
|
||||
)
|
||||
|
||||
type fileLocationFunc func(string) string
|
||||
|
||||
func MloggerConfigFromLoggerConfig(s *model.LogSettings, getFileFunc fileLocationFunc) *mlog.LoggerConfiguration {
|
||||
return &mlog.LoggerConfiguration{
|
||||
EnableConsole: *s.EnableConsole,
|
||||
ConsoleJson: *s.ConsoleJson,
|
||||
ConsoleLevel: strings.ToLower(*s.ConsoleLevel),
|
||||
EnableFile: *s.EnableFile,
|
||||
FileJson: *s.FileJson,
|
||||
FileLevel: strings.ToLower(*s.FileLevel),
|
||||
FileLocation: getFileFunc(*s.FileLocation),
|
||||
EnableColor: *s.EnableColor,
|
||||
}
|
||||
}
|
||||
|
||||
func GetLogFileLocation(fileLocation string) string {
|
||||
if fileLocation == "" {
|
||||
fileLocation, _ = fileutils.FindDir("logs")
|
||||
}
|
||||
|
||||
return filepath.Join(fileLocation, LogFilename)
|
||||
}
|
||||
|
||||
func GetNotificationsLogFileLocation(fileLocation string) string {
|
||||
if fileLocation == "" {
|
||||
fileLocation, _ = fileutils.FindDir("logs")
|
||||
}
|
||||
|
||||
return filepath.Join(fileLocation, LogNotificationFilename)
|
||||
}
|
||||
|
||||
func GetLogSettingsFromNotificationsLogSettings(notificationLogSettings *model.NotificationLogSettings) *model.LogSettings {
|
||||
return &model.LogSettings{
|
||||
ConsoleJson: notificationLogSettings.ConsoleJson,
|
||||
ConsoleLevel: notificationLogSettings.ConsoleLevel,
|
||||
EnableConsole: notificationLogSettings.EnableConsole,
|
||||
EnableFile: notificationLogSettings.EnableFile,
|
||||
FileJson: notificationLogSettings.FileJson,
|
||||
FileLevel: notificationLogSettings.FileLevel,
|
||||
FileLocation: notificationLogSettings.FileLocation,
|
||||
AdvancedLoggingConfig: notificationLogSettings.AdvancedLoggingConfig,
|
||||
EnableColor: notificationLogSettings.EnableColor,
|
||||
}
|
||||
}
|
||||
|
||||
// DON'T USE THIS Modify the level on the app logger
|
||||
func DisableDebugLogForTest() {
|
||||
mlog.GloballyDisableDebugLogForTest()
|
||||
}
|
||||
|
||||
// DON'T USE THIS Modify the level on the app logger
|
||||
func EnableDebugLogForTest() {
|
||||
mlog.GloballyEnableDebugLogForTest()
|
||||
}
|
||||
11
vendor/github.com/mattermost/logr/config.go
сгенерированный
поставляемый
11
vendor/github.com/mattermost/logr/config.go
сгенерированный
поставляемый
@@ -1,11 +0,0 @@
|
||||
package logr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wiggin77/cfg"
|
||||
)
|
||||
|
||||
func ConfigLogger(config *cfg.Config) error {
|
||||
return fmt.Errorf("Not implemented yet")
|
||||
}
|
||||
26
vendor/github.com/mattermost/logr/filter.go
сгенерированный
поставляемый
26
vendor/github.com/mattermost/logr/filter.go
сгенерированный
поставляемый
@@ -1,26 +0,0 @@
|
||||
package logr
|
||||
|
||||
// LevelID is the unique id of each level.
|
||||
type LevelID uint
|
||||
|
||||
// Level provides a mechanism to enable/disable specific log lines.
|
||||
type Level struct {
|
||||
ID LevelID
|
||||
Name string
|
||||
Stacktrace bool
|
||||
}
|
||||
|
||||
// String returns the name of this level.
|
||||
func (level Level) String() string {
|
||||
return level.Name
|
||||
}
|
||||
|
||||
// Filter allows targets to determine which Level(s) are active
|
||||
// for logging and which Level(s) require a stack trace to be output.
|
||||
// A default implementation using "panic, fatal..." is provided, and
|
||||
// a more flexible alternative implementation is also provided that
|
||||
// allows any number of custom levels.
|
||||
type Filter interface {
|
||||
IsEnabled(Level) bool
|
||||
IsStacktraceEnabled(Level) bool
|
||||
}
|
||||
273
vendor/github.com/mattermost/logr/format/json.go
сгенерированный
поставляемый
273
vendor/github.com/mattermost/logr/format/json.go
сгенерированный
поставляемый
@@ -1,273 +0,0 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/francoispqt/gojay"
|
||||
"github.com/mattermost/logr"
|
||||
)
|
||||
|
||||
// ContextField is a name/value pair within the context fields.
|
||||
type ContextField struct {
|
||||
Key string
|
||||
Val interface{}
|
||||
}
|
||||
|
||||
// JSON formats log records as JSON.
|
||||
type JSON struct {
|
||||
// DisableTimestamp disables output of timestamp field.
|
||||
DisableTimestamp bool
|
||||
// DisableLevel disables output of level field.
|
||||
DisableLevel bool
|
||||
// DisableMsg disables output of msg field.
|
||||
DisableMsg bool
|
||||
// DisableContext disables output of all context fields.
|
||||
DisableContext bool
|
||||
// DisableStacktrace disables output of stack trace.
|
||||
DisableStacktrace bool
|
||||
|
||||
// TimestampFormat is an optional format for timestamps. If empty
|
||||
// then DefTimestampFormat is used.
|
||||
TimestampFormat string
|
||||
|
||||
// Deprecated: this has no effect.
|
||||
Indent string
|
||||
|
||||
// EscapeHTML determines if certain characters (e.g. `<`, `>`, `&`)
|
||||
// are escaped.
|
||||
EscapeHTML bool
|
||||
|
||||
// KeyTimestamp overrides the timestamp field key name.
|
||||
KeyTimestamp string
|
||||
|
||||
// KeyLevel overrides the level field key name.
|
||||
KeyLevel string
|
||||
|
||||
// KeyMsg overrides the msg field key name.
|
||||
KeyMsg string
|
||||
|
||||
// KeyContextFields when not empty will group all context fields
|
||||
// under this key.
|
||||
KeyContextFields string
|
||||
|
||||
// KeyStacktrace overrides the stacktrace field key name.
|
||||
KeyStacktrace string
|
||||
|
||||
// ContextSorter allows custom sorting for the context fields.
|
||||
ContextSorter func(fields logr.Fields) []ContextField
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Format converts a log record to bytes in JSON format.
|
||||
func (j *JSON) Format(rec *logr.LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error) {
|
||||
j.once.Do(j.applyDefaultKeyNames)
|
||||
|
||||
if buf == nil {
|
||||
buf = &bytes.Buffer{}
|
||||
}
|
||||
enc := gojay.BorrowEncoder(buf)
|
||||
defer func() {
|
||||
enc.Release()
|
||||
}()
|
||||
|
||||
sorter := j.ContextSorter
|
||||
if sorter == nil {
|
||||
sorter = j.defaultContextSorter
|
||||
}
|
||||
|
||||
jlr := JSONLogRec{
|
||||
LogRec: rec,
|
||||
JSON: j,
|
||||
stacktrace: stacktrace,
|
||||
sorter: sorter,
|
||||
}
|
||||
|
||||
err := enc.EncodeObject(jlr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (j *JSON) applyDefaultKeyNames() {
|
||||
if j.KeyTimestamp == "" {
|
||||
j.KeyTimestamp = "timestamp"
|
||||
}
|
||||
if j.KeyLevel == "" {
|
||||
j.KeyLevel = "level"
|
||||
}
|
||||
if j.KeyMsg == "" {
|
||||
j.KeyMsg = "msg"
|
||||
}
|
||||
if j.KeyStacktrace == "" {
|
||||
j.KeyStacktrace = "stacktrace"
|
||||
}
|
||||
}
|
||||
|
||||
// defaultContextSorter sorts the context fields alphabetically by key.
|
||||
func (j *JSON) defaultContextSorter(fields logr.Fields) []ContextField {
|
||||
keys := make([]string, 0, len(fields))
|
||||
for k := range fields {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
cf := make([]ContextField, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
cf = append(cf, ContextField{Key: k, Val: fields[k]})
|
||||
}
|
||||
return cf
|
||||
}
|
||||
|
||||
// JSONLogRec decorates a LogRec adding JSON encoding.
|
||||
type JSONLogRec struct {
|
||||
*logr.LogRec
|
||||
*JSON
|
||||
stacktrace bool
|
||||
sorter func(fields logr.Fields) []ContextField
|
||||
}
|
||||
|
||||
// MarshalJSONObject encodes the LogRec as JSON.
|
||||
func (rec JSONLogRec) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
if !rec.DisableTimestamp {
|
||||
timestampFmt := rec.TimestampFormat
|
||||
if timestampFmt == "" {
|
||||
timestampFmt = logr.DefTimestampFormat
|
||||
}
|
||||
time := rec.Time()
|
||||
enc.AddTimeKey(rec.KeyTimestamp, &time, timestampFmt)
|
||||
}
|
||||
if !rec.DisableLevel {
|
||||
enc.AddStringKey(rec.KeyLevel, rec.Level().Name)
|
||||
}
|
||||
if !rec.DisableMsg {
|
||||
enc.AddStringKey(rec.KeyMsg, rec.Msg())
|
||||
}
|
||||
if !rec.DisableContext {
|
||||
ctxFields := rec.sorter(rec.Fields())
|
||||
if rec.KeyContextFields != "" {
|
||||
enc.AddObjectKey(rec.KeyContextFields, jsonFields(ctxFields))
|
||||
} else {
|
||||
if len(ctxFields) > 0 {
|
||||
for _, cf := range ctxFields {
|
||||
key := rec.prefixCollision(cf.Key)
|
||||
encodeField(enc, key, cf.Val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if rec.stacktrace && !rec.DisableStacktrace {
|
||||
frames := rec.StackFrames()
|
||||
if len(frames) > 0 {
|
||||
enc.AddArrayKey(rec.KeyStacktrace, stackFrames(frames))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// IsNil returns true if the LogRec pointer is nil.
|
||||
func (rec JSONLogRec) IsNil() bool {
|
||||
return rec.LogRec == nil
|
||||
}
|
||||
|
||||
func (rec JSONLogRec) prefixCollision(key string) string {
|
||||
switch key {
|
||||
case rec.KeyTimestamp, rec.KeyLevel, rec.KeyMsg, rec.KeyStacktrace:
|
||||
return rec.prefixCollision("_" + key)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
type stackFrames []runtime.Frame
|
||||
|
||||
// MarshalJSONArray encodes stackFrames slice as JSON.
|
||||
func (s stackFrames) MarshalJSONArray(enc *gojay.Encoder) {
|
||||
for _, frame := range s {
|
||||
enc.AddObject(stackFrame(frame))
|
||||
}
|
||||
}
|
||||
|
||||
// IsNil returns true if stackFrames is empty slice.
|
||||
func (s stackFrames) IsNil() bool {
|
||||
return len(s) == 0
|
||||
}
|
||||
|
||||
type stackFrame runtime.Frame
|
||||
|
||||
// MarshalJSONArray encodes stackFrame as JSON.
|
||||
func (f stackFrame) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
enc.AddStringKey("Function", f.Function)
|
||||
enc.AddStringKey("File", f.File)
|
||||
enc.AddIntKey("Line", f.Line)
|
||||
}
|
||||
|
||||
func (f stackFrame) IsNil() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type jsonFields []ContextField
|
||||
|
||||
// MarshalJSONObject encodes Fields map to JSON.
|
||||
func (f jsonFields) MarshalJSONObject(enc *gojay.Encoder) {
|
||||
for _, ctxField := range f {
|
||||
encodeField(enc, ctxField.Key, ctxField.Val)
|
||||
}
|
||||
}
|
||||
|
||||
// IsNil returns true if map is nil.
|
||||
func (f jsonFields) IsNil() bool {
|
||||
return f == nil
|
||||
}
|
||||
|
||||
func encodeField(enc *gojay.Encoder, key string, val interface{}) {
|
||||
switch vt := val.(type) {
|
||||
case gojay.MarshalerJSONObject:
|
||||
enc.AddObjectKey(key, vt)
|
||||
case gojay.MarshalerJSONArray:
|
||||
enc.AddArrayKey(key, vt)
|
||||
case string:
|
||||
enc.AddStringKey(key, vt)
|
||||
case error:
|
||||
enc.AddStringKey(key, vt.Error())
|
||||
case bool:
|
||||
enc.AddBoolKey(key, vt)
|
||||
case int:
|
||||
enc.AddIntKey(key, vt)
|
||||
case int64:
|
||||
enc.AddInt64Key(key, vt)
|
||||
case int32:
|
||||
enc.AddIntKey(key, int(vt))
|
||||
case int16:
|
||||
enc.AddIntKey(key, int(vt))
|
||||
case int8:
|
||||
enc.AddIntKey(key, int(vt))
|
||||
case uint64:
|
||||
enc.AddIntKey(key, int(vt))
|
||||
case uint32:
|
||||
enc.AddIntKey(key, int(vt))
|
||||
case uint16:
|
||||
enc.AddIntKey(key, int(vt))
|
||||
case uint8:
|
||||
enc.AddIntKey(key, int(vt))
|
||||
case float64:
|
||||
enc.AddFloatKey(key, vt)
|
||||
case float32:
|
||||
enc.AddFloat32Key(key, vt)
|
||||
case *gojay.EmbeddedJSON:
|
||||
enc.AddEmbeddedJSONKey(key, vt)
|
||||
case time.Time:
|
||||
enc.AddTimeKey(key, &vt, logr.DefTimestampFormat)
|
||||
case *time.Time:
|
||||
enc.AddTimeKey(key, vt, logr.DefTimestampFormat)
|
||||
default:
|
||||
s := fmt.Sprintf("%v", vt)
|
||||
enc.AddStringKey(key, s)
|
||||
}
|
||||
}
|
||||
75
vendor/github.com/mattermost/logr/format/plain.go
сгенерированный
поставляемый
75
vendor/github.com/mattermost/logr/format/plain.go
сгенерированный
поставляемый
@@ -1,75 +0,0 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
)
|
||||
|
||||
// Plain is the simplest formatter, outputting only text with
|
||||
// no colors.
|
||||
type Plain struct {
|
||||
// DisableTimestamp disables output of timestamp field.
|
||||
DisableTimestamp bool
|
||||
// DisableLevel disables output of level field.
|
||||
DisableLevel bool
|
||||
// DisableMsg disables output of msg field.
|
||||
DisableMsg bool
|
||||
// DisableContext disables output of all context fields.
|
||||
DisableContext bool
|
||||
// DisableStacktrace disables output of stack trace.
|
||||
DisableStacktrace bool
|
||||
|
||||
// Delim is an optional delimiter output between each log field.
|
||||
// Defaults to a single space.
|
||||
Delim string
|
||||
|
||||
// TimestampFormat is an optional format for timestamps. If empty
|
||||
// then DefTimestampFormat is used.
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
// Format converts a log record to bytes.
|
||||
func (p *Plain) Format(rec *logr.LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error) {
|
||||
delim := p.Delim
|
||||
if delim == "" {
|
||||
delim = " "
|
||||
}
|
||||
if buf == nil {
|
||||
buf = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
timestampFmt := p.TimestampFormat
|
||||
if timestampFmt == "" {
|
||||
timestampFmt = logr.DefTimestampFormat
|
||||
}
|
||||
|
||||
if !p.DisableTimestamp {
|
||||
var arr [128]byte
|
||||
tbuf := rec.Time().AppendFormat(arr[:0], timestampFmt)
|
||||
buf.Write(tbuf)
|
||||
buf.WriteString(delim)
|
||||
}
|
||||
if !p.DisableLevel {
|
||||
fmt.Fprintf(buf, "%v%s", rec.Level().Name, delim)
|
||||
}
|
||||
if !p.DisableMsg {
|
||||
fmt.Fprint(buf, rec.Msg(), delim)
|
||||
}
|
||||
if !p.DisableContext {
|
||||
ctx := rec.Fields()
|
||||
if len(ctx) > 0 {
|
||||
logr.WriteFields(buf, ctx, " ")
|
||||
}
|
||||
}
|
||||
if stacktrace && !p.DisableStacktrace {
|
||||
frames := rec.StackFrames()
|
||||
if len(frames) > 0 {
|
||||
buf.WriteString("\n")
|
||||
logr.WriteStacktrace(buf, rec.StackFrames())
|
||||
}
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
return buf, nil
|
||||
}
|
||||
119
vendor/github.com/mattermost/logr/formatter.go
сгенерированный
поставляемый
119
vendor/github.com/mattermost/logr/formatter.go
сгенерированный
поставляемый
@@ -1,119 +0,0 @@
|
||||
package logr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Formatter turns a LogRec into a formatted string.
|
||||
type Formatter interface {
|
||||
// Format converts a log record to bytes. If buf is not nil then it will be
|
||||
// be filled with the formatted results, otherwise a new buffer will be allocated.
|
||||
Format(rec *LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error)
|
||||
}
|
||||
|
||||
const (
|
||||
// DefTimestampFormat is the default time stamp format used by
|
||||
// Plain formatter and others.
|
||||
DefTimestampFormat = "2006-01-02 15:04:05.000 Z07:00"
|
||||
)
|
||||
|
||||
// DefaultFormatter is the default formatter, outputting only text with
|
||||
// no colors and a space delimiter. Use `format.Plain` instead.
|
||||
type DefaultFormatter struct {
|
||||
}
|
||||
|
||||
// Format converts a log record to bytes.
|
||||
func (p *DefaultFormatter) Format(rec *LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error) {
|
||||
if buf == nil {
|
||||
buf = &bytes.Buffer{}
|
||||
}
|
||||
delim := " "
|
||||
timestampFmt := DefTimestampFormat
|
||||
|
||||
fmt.Fprintf(buf, "%s%s", rec.Time().Format(timestampFmt), delim)
|
||||
fmt.Fprintf(buf, "%v%s", rec.Level(), delim)
|
||||
fmt.Fprint(buf, rec.Msg(), delim)
|
||||
|
||||
ctx := rec.Fields()
|
||||
if len(ctx) > 0 {
|
||||
WriteFields(buf, ctx, " ")
|
||||
}
|
||||
|
||||
if stacktrace {
|
||||
frames := rec.StackFrames()
|
||||
if len(frames) > 0 {
|
||||
buf.WriteString("\n")
|
||||
WriteStacktrace(buf, rec.StackFrames())
|
||||
}
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// WriteFields writes zero or more name value pairs to the io.Writer.
|
||||
// The pairs are sorted by key name and output in key=value format
|
||||
// with optional separator between fields.
|
||||
func WriteFields(w io.Writer, flds Fields, separator string) {
|
||||
keys := make([]string, 0, len(flds))
|
||||
for k := range flds {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
sep := ""
|
||||
for _, key := range keys {
|
||||
writeField(w, key, flds[key], sep)
|
||||
sep = separator
|
||||
}
|
||||
}
|
||||
|
||||
func writeField(w io.Writer, key string, val interface{}, sep string) {
|
||||
var template string
|
||||
switch v := val.(type) {
|
||||
case error:
|
||||
val := v.Error()
|
||||
if shouldQuote(val) {
|
||||
template = "%s%s=%q"
|
||||
} else {
|
||||
template = "%s%s=%s"
|
||||
}
|
||||
case string:
|
||||
if shouldQuote(v) {
|
||||
template = "%s%s=%q"
|
||||
} else {
|
||||
template = "%s%s=%s"
|
||||
}
|
||||
default:
|
||||
template = "%s%s=%v"
|
||||
}
|
||||
fmt.Fprintf(w, template, sep, key, val)
|
||||
}
|
||||
|
||||
// shouldQuote returns true if val contains any characters that might be unsafe
|
||||
// when injecting log output into an aggregator, viewer or report.
|
||||
func shouldQuote(val string) bool {
|
||||
for _, c := range val {
|
||||
if !((c >= '0' && c <= '9') ||
|
||||
(c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z')) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WriteStacktrace formats and outputs a stack trace to an io.Writer.
|
||||
func WriteStacktrace(w io.Writer, frames []runtime.Frame) {
|
||||
for _, frame := range frames {
|
||||
if frame.Function != "" {
|
||||
fmt.Fprintf(w, " %s\n", frame.Function)
|
||||
}
|
||||
if frame.File != "" {
|
||||
fmt.Fprintf(w, " %s:%d\n", frame.File, frame.Line)
|
||||
}
|
||||
}
|
||||
}
|
||||
45
vendor/github.com/mattermost/logr/levelcustom.go
сгенерированный
поставляемый
45
vendor/github.com/mattermost/logr/levelcustom.go
сгенерированный
поставляемый
@@ -1,45 +0,0 @@
|
||||
package logr
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// CustomFilter allows targets to enable logging via a list of levels.
|
||||
type CustomFilter struct {
|
||||
mux sync.RWMutex
|
||||
levels map[LevelID]Level
|
||||
}
|
||||
|
||||
// IsEnabled returns true if the specified Level exists in this list.
|
||||
func (st *CustomFilter) IsEnabled(level Level) bool {
|
||||
st.mux.RLock()
|
||||
defer st.mux.RUnlock()
|
||||
_, ok := st.levels[level.ID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsStacktraceEnabled returns true if the specified Level requires a stack trace.
|
||||
func (st *CustomFilter) IsStacktraceEnabled(level Level) bool {
|
||||
st.mux.RLock()
|
||||
defer st.mux.RUnlock()
|
||||
lvl, ok := st.levels[level.ID]
|
||||
if ok {
|
||||
return lvl.Stacktrace
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Add adds one or more levels to the list. Adding a level enables logging for
|
||||
// that level on any targets using this CustomFilter.
|
||||
func (st *CustomFilter) Add(levels ...Level) {
|
||||
st.mux.Lock()
|
||||
defer st.mux.Unlock()
|
||||
|
||||
if st.levels == nil {
|
||||
st.levels = make(map[LevelID]Level)
|
||||
}
|
||||
|
||||
for _, s := range levels {
|
||||
st.levels[s.ID] = s
|
||||
}
|
||||
}
|
||||
37
vendor/github.com/mattermost/logr/levelstd.go
сгенерированный
поставляемый
37
vendor/github.com/mattermost/logr/levelstd.go
сгенерированный
поставляемый
@@ -1,37 +0,0 @@
|
||||
package logr
|
||||
|
||||
// StdFilter allows targets to filter via classic log levels where any level
|
||||
// beyond a certain verbosity/severity is enabled.
|
||||
type StdFilter struct {
|
||||
Lvl Level
|
||||
Stacktrace Level
|
||||
}
|
||||
|
||||
// IsEnabled returns true if the specified Level is at or above this verbosity. Also
|
||||
// determines if a stack trace is required.
|
||||
func (lt StdFilter) IsEnabled(level Level) bool {
|
||||
return level.ID <= lt.Lvl.ID
|
||||
}
|
||||
|
||||
// IsStacktraceEnabled returns true if the specified Level requires a stack trace.
|
||||
func (lt StdFilter) IsStacktraceEnabled(level Level) bool {
|
||||
return level.ID <= lt.Stacktrace.ID
|
||||
}
|
||||
|
||||
var (
|
||||
// Panic is the highest level of severity. Logs the message and then panics.
|
||||
Panic = Level{ID: 0, Name: "panic"}
|
||||
// Fatal designates a catastrophic error. Logs the message and then calls
|
||||
// `logr.Exit(1)`.
|
||||
Fatal = Level{ID: 1, Name: "fatal"}
|
||||
// Error designates a serious but possibly recoverable error.
|
||||
Error = Level{ID: 2, Name: "error"}
|
||||
// Warn designates non-critical error.
|
||||
Warn = Level{ID: 3, Name: "warn"}
|
||||
// Info designates information regarding application events.
|
||||
Info = Level{ID: 4, Name: "info"}
|
||||
// Debug designates verbose information typically used for debugging.
|
||||
Debug = Level{ID: 5, Name: "debug"}
|
||||
// Trace designates the highest verbosity of log output.
|
||||
Trace = Level{ID: 6, Name: "trace"}
|
||||
)
|
||||
218
vendor/github.com/mattermost/logr/logger.go
сгенерированный
поставляемый
218
vendor/github.com/mattermost/logr/logger.go
сгенерированный
поставляемый
@@ -1,218 +0,0 @@
|
||||
package logr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to `WithFields`.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
// Logger provides context for logging via fields.
|
||||
type Logger struct {
|
||||
logr *Logr
|
||||
fields Fields
|
||||
}
|
||||
|
||||
// Logr returns the `Logr` instance that created this `Logger`.
|
||||
func (logger Logger) Logr() *Logr {
|
||||
return logger.logr
|
||||
}
|
||||
|
||||
// WithField creates a new `Logger` with any existing fields
|
||||
// plus the new one.
|
||||
func (logger Logger) WithField(key string, value interface{}) Logger {
|
||||
return logger.WithFields(Fields{key: value})
|
||||
}
|
||||
|
||||
// WithFields creates a new `Logger` with any existing fields
|
||||
// plus the new ones.
|
||||
func (logger Logger) WithFields(fields Fields) Logger {
|
||||
l := Logger{logr: logger.logr}
|
||||
// if parent has no fields then avoid creating a new map.
|
||||
oldLen := len(logger.fields)
|
||||
if oldLen == 0 {
|
||||
l.fields = fields
|
||||
return l
|
||||
}
|
||||
|
||||
l.fields = make(Fields, len(fields)+oldLen)
|
||||
for k, v := range logger.fields {
|
||||
l.fields[k] = v
|
||||
}
|
||||
for k, v := range fields {
|
||||
l.fields[k] = v
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Log checks that the level matches one or more targets, and
|
||||
// if so, generates a log record that is added to the Logr queue.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
func (logger Logger) Log(lvl Level, args ...interface{}) {
|
||||
status := logger.logr.IsLevelEnabled(lvl)
|
||||
if status.Enabled {
|
||||
rec := NewLogRec(lvl, logger, "", args, status.Stacktrace)
|
||||
logger.logr.enqueue(rec)
|
||||
}
|
||||
}
|
||||
|
||||
// Trace is a convenience method equivalent to `Log(TraceLevel, args...)`.
|
||||
func (logger Logger) Trace(args ...interface{}) {
|
||||
logger.Log(Trace, args...)
|
||||
}
|
||||
|
||||
// Debug is a convenience method equivalent to `Log(DebugLevel, args...)`.
|
||||
func (logger Logger) Debug(args ...interface{}) {
|
||||
logger.Log(Debug, args...)
|
||||
}
|
||||
|
||||
// Print ensures compatibility with std lib logger.
|
||||
func (logger Logger) Print(args ...interface{}) {
|
||||
logger.Info(args...)
|
||||
}
|
||||
|
||||
// Info is a convenience method equivalent to `Log(InfoLevel, args...)`.
|
||||
func (logger Logger) Info(args ...interface{}) {
|
||||
logger.Log(Info, args...)
|
||||
}
|
||||
|
||||
// Warn is a convenience method equivalent to `Log(WarnLevel, args...)`.
|
||||
func (logger Logger) Warn(args ...interface{}) {
|
||||
logger.Log(Warn, args...)
|
||||
}
|
||||
|
||||
// Error is a convenience method equivalent to `Log(ErrorLevel, args...)`.
|
||||
func (logger Logger) Error(args ...interface{}) {
|
||||
logger.Log(Error, args...)
|
||||
}
|
||||
|
||||
// Fatal is a convenience method equivalent to `Log(FatalLevel, args...)`
|
||||
// followed by a call to os.Exit(1).
|
||||
func (logger Logger) Fatal(args ...interface{}) {
|
||||
logger.Log(Fatal, args...)
|
||||
logger.logr.exit(1)
|
||||
}
|
||||
|
||||
// Panic is a convenience method equivalent to `Log(PanicLevel, args...)`
|
||||
// followed by a call to panic().
|
||||
func (logger Logger) Panic(args ...interface{}) {
|
||||
logger.Log(Panic, args...)
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
//
|
||||
// Printf style
|
||||
//
|
||||
|
||||
// Logf checks that the level matches one or more targets, and
|
||||
// if so, generates a log record that is added to the main
|
||||
// queue (channel). Arguments are handled in the manner of fmt.Printf.
|
||||
func (logger Logger) Logf(lvl Level, format string, args ...interface{}) {
|
||||
status := logger.logr.IsLevelEnabled(lvl)
|
||||
if status.Enabled {
|
||||
rec := NewLogRec(lvl, logger, format, args, status.Stacktrace)
|
||||
logger.logr.enqueue(rec)
|
||||
}
|
||||
}
|
||||
|
||||
// Tracef is a convenience method equivalent to `Logf(TraceLevel, args...)`.
|
||||
func (logger Logger) Tracef(format string, args ...interface{}) {
|
||||
logger.Logf(Trace, format, args...)
|
||||
}
|
||||
|
||||
// Debugf is a convenience method equivalent to `Logf(DebugLevel, args...)`.
|
||||
func (logger Logger) Debugf(format string, args ...interface{}) {
|
||||
logger.Logf(Debug, format, args...)
|
||||
}
|
||||
|
||||
// Infof is a convenience method equivalent to `Logf(InfoLevel, args...)`.
|
||||
func (logger Logger) Infof(format string, args ...interface{}) {
|
||||
logger.Logf(Info, format, args...)
|
||||
}
|
||||
|
||||
// Printf ensures compatibility with std lib logger.
|
||||
func (logger Logger) Printf(format string, args ...interface{}) {
|
||||
logger.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf is a convenience method equivalent to `Logf(WarnLevel, args...)`.
|
||||
func (logger Logger) Warnf(format string, args ...interface{}) {
|
||||
logger.Logf(Warn, format, args...)
|
||||
}
|
||||
|
||||
// Errorf is a convenience method equivalent to `Logf(ErrorLevel, args...)`.
|
||||
func (logger Logger) Errorf(format string, args ...interface{}) {
|
||||
logger.Logf(Error, format, args...)
|
||||
}
|
||||
|
||||
// Fatalf is a convenience method equivalent to `Logf(FatalLevel, args...)`
|
||||
// followed by a call to os.Exit(1).
|
||||
func (logger Logger) Fatalf(format string, args ...interface{}) {
|
||||
logger.Logf(Fatal, format, args...)
|
||||
logger.logr.exit(1)
|
||||
}
|
||||
|
||||
// Panicf is a convenience method equivalent to `Logf(PanicLevel, args...)`
|
||||
// followed by a call to panic().
|
||||
func (logger Logger) Panicf(format string, args ...interface{}) {
|
||||
logger.Logf(Panic, format, args...)
|
||||
}
|
||||
|
||||
//
|
||||
// Println style
|
||||
//
|
||||
|
||||
// Logln checks that the level matches one or more targets, and
|
||||
// if so, generates a log record that is added to the main
|
||||
// queue (channel). Arguments are handled in the manner of fmt.Println.
|
||||
func (logger Logger) Logln(lvl Level, args ...interface{}) {
|
||||
status := logger.logr.IsLevelEnabled(lvl)
|
||||
if status.Enabled {
|
||||
rec := NewLogRec(lvl, logger, "", args, status.Stacktrace)
|
||||
rec.newline = true
|
||||
logger.logr.enqueue(rec)
|
||||
}
|
||||
}
|
||||
|
||||
// Traceln is a convenience method equivalent to `Logln(TraceLevel, args...)`.
|
||||
func (logger Logger) Traceln(args ...interface{}) {
|
||||
logger.Logln(Trace, args...)
|
||||
}
|
||||
|
||||
// Debugln is a convenience method equivalent to `Logln(DebugLevel, args...)`.
|
||||
func (logger Logger) Debugln(args ...interface{}) {
|
||||
logger.Logln(Debug, args...)
|
||||
}
|
||||
|
||||
// Infoln is a convenience method equivalent to `Logln(InfoLevel, args...)`.
|
||||
func (logger Logger) Infoln(args ...interface{}) {
|
||||
logger.Logln(Info, args...)
|
||||
}
|
||||
|
||||
// Println ensures compatibility with std lib logger.
|
||||
func (logger Logger) Println(args ...interface{}) {
|
||||
logger.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln is a convenience method equivalent to `Logln(WarnLevel, args...)`.
|
||||
func (logger Logger) Warnln(args ...interface{}) {
|
||||
logger.Logln(Warn, args...)
|
||||
}
|
||||
|
||||
// Errorln is a convenience method equivalent to `Logln(ErrorLevel, args...)`.
|
||||
func (logger Logger) Errorln(args ...interface{}) {
|
||||
logger.Logln(Error, args...)
|
||||
}
|
||||
|
||||
// Fatalln is a convenience method equivalent to `Logln(FatalLevel, args...)`
|
||||
// followed by a call to os.Exit(1).
|
||||
func (logger Logger) Fatalln(args ...interface{}) {
|
||||
logger.Logln(Fatal, args...)
|
||||
logger.logr.exit(1)
|
||||
}
|
||||
|
||||
// Panicln is a convenience method equivalent to `Logln(PanicLevel, args...)`
|
||||
// followed by a call to panic().
|
||||
func (logger Logger) Panicln(args ...interface{}) {
|
||||
logger.Logln(Panic, args...)
|
||||
}
|
||||
664
vendor/github.com/mattermost/logr/logr.go
сгенерированный
поставляемый
664
vendor/github.com/mattermost/logr/logr.go
сгенерированный
поставляемый
@@ -1,664 +0,0 @@
|
||||
package logr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wiggin77/cfg"
|
||||
"github.com/wiggin77/merror"
|
||||
)
|
||||
|
||||
// Logr maintains a list of log targets and accepts incoming
|
||||
// log records.
|
||||
type Logr struct {
|
||||
tmux sync.RWMutex // target mutex
|
||||
targets []Target
|
||||
|
||||
mux sync.RWMutex
|
||||
maxQueueSizeActual int
|
||||
in chan *LogRec
|
||||
done chan struct{}
|
||||
once sync.Once
|
||||
shutdown bool
|
||||
lvlCache levelCache
|
||||
|
||||
metricsInitOnce sync.Once
|
||||
metricsCloseOnce sync.Once
|
||||
metricsDone chan struct{}
|
||||
metrics MetricsCollector
|
||||
queueSizeGauge Gauge
|
||||
loggedCounter Counter
|
||||
errorCounter Counter
|
||||
|
||||
bufferPool sync.Pool
|
||||
|
||||
// MaxQueueSize is the maximum number of log records that can be queued.
|
||||
// If exceeded, `OnQueueFull` is called which determines if the log
|
||||
// record will be dropped or block until add is successful.
|
||||
// If this is modified, it must be done before `Configure` or
|
||||
// `AddTarget`. Defaults to DefaultMaxQueueSize.
|
||||
MaxQueueSize int
|
||||
|
||||
// OnLoggerError, when not nil, is called any time an internal
|
||||
// logging error occurs. For example, this can happen when a
|
||||
// target cannot connect to its data sink.
|
||||
OnLoggerError func(error)
|
||||
|
||||
// OnQueueFull, when not nil, is called on an attempt to add
|
||||
// a log record to a full Logr queue.
|
||||
// `MaxQueueSize` can be used to modify the maximum queue size.
|
||||
// This function should return quickly, with a bool indicating whether
|
||||
// the log record should be dropped (true) or block until the log record
|
||||
// is successfully added (false). If nil then blocking (false) is assumed.
|
||||
OnQueueFull func(rec *LogRec, maxQueueSize int) bool
|
||||
|
||||
// OnTargetQueueFull, when not nil, is called on an attempt to add
|
||||
// a log record to a full target queue provided the target supports reporting
|
||||
// this condition.
|
||||
// This function should return quickly, with a bool indicating whether
|
||||
// the log record should be dropped (true) or block until the log record
|
||||
// is successfully added (false). If nil then blocking (false) is assumed.
|
||||
OnTargetQueueFull func(target Target, rec *LogRec, maxQueueSize int) bool
|
||||
|
||||
// OnExit, when not nil, is called when a FatalXXX style log API is called.
|
||||
// When nil, then the default behavior is to cleanly shut down this Logr and
|
||||
// call `os.Exit(code)`.
|
||||
OnExit func(code int)
|
||||
|
||||
// OnPanic, when not nil, is called when a PanicXXX style log API is called.
|
||||
// When nil, then the default behavior is to cleanly shut down this Logr and
|
||||
// call `panic(err)`.
|
||||
OnPanic func(err interface{})
|
||||
|
||||
// EnqueueTimeout is the amount of time a log record can take to be queued.
|
||||
// This only applies to blocking enqueue which happen after `logr.OnQueueFull`
|
||||
// is called and returns false.
|
||||
EnqueueTimeout time.Duration
|
||||
|
||||
// ShutdownTimeout is the amount of time `logr.Shutdown` can execute before
|
||||
// timing out.
|
||||
ShutdownTimeout time.Duration
|
||||
|
||||
// FlushTimeout is the amount of time `logr.Flush` can execute before
|
||||
// timing out.
|
||||
FlushTimeout time.Duration
|
||||
|
||||
// UseSyncMapLevelCache can be set to true before the first target is added
|
||||
// when high concurrency (e.g. >32 cores) is expected. This may improve
|
||||
// performance with large numbers of cores - benchmark for your use case.
|
||||
UseSyncMapLevelCache bool
|
||||
|
||||
// MaxPooledFormatBuffer determines the maximum size of a buffer that can be
|
||||
// pooled. To reduce allocations, the buffers needed during formatting (etc)
|
||||
// are pooled. A very large log item will grow a buffer that could stay in
|
||||
// memory indefinitely. This settings lets you control how big a pooled buffer
|
||||
// can be - anything larger will be garbage collected after use.
|
||||
// Defaults to 1MB.
|
||||
MaxPooledBuffer int
|
||||
|
||||
// DisableBufferPool when true disables the buffer pool. See MaxPooledBuffer.
|
||||
DisableBufferPool bool
|
||||
|
||||
// MetricsUpdateFreqMillis determines how often polled metrics are updated
|
||||
// when metrics are enabled.
|
||||
MetricsUpdateFreqMillis int64
|
||||
}
|
||||
|
||||
// Configure adds/removes targets via the supplied `Config`.
|
||||
func (logr *Logr) Configure(config *cfg.Config) error {
|
||||
// TODO
|
||||
return fmt.Errorf("not implemented yet")
|
||||
}
|
||||
|
||||
func (logr *Logr) ensureInit() {
|
||||
logr.once.Do(func() {
|
||||
defer func() {
|
||||
go logr.start()
|
||||
}()
|
||||
|
||||
logr.mux.Lock()
|
||||
defer logr.mux.Unlock()
|
||||
|
||||
logr.maxQueueSizeActual = logr.MaxQueueSize
|
||||
if logr.maxQueueSizeActual == 0 {
|
||||
logr.maxQueueSizeActual = DefaultMaxQueueSize
|
||||
}
|
||||
|
||||
if logr.maxQueueSizeActual < 0 {
|
||||
logr.maxQueueSizeActual = 0
|
||||
}
|
||||
|
||||
logr.in = make(chan *LogRec, logr.maxQueueSizeActual)
|
||||
logr.done = make(chan struct{})
|
||||
|
||||
if logr.UseSyncMapLevelCache {
|
||||
logr.lvlCache = &syncMapLevelCache{}
|
||||
} else {
|
||||
logr.lvlCache = &arrayLevelCache{}
|
||||
}
|
||||
|
||||
if logr.MaxPooledBuffer == 0 {
|
||||
logr.MaxPooledBuffer = DefaultMaxPooledBuffer
|
||||
}
|
||||
logr.bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
logr.lvlCache.setup()
|
||||
})
|
||||
}
|
||||
|
||||
// AddTarget adds one or more targets to the logger which will receive
|
||||
// log records for outputting.
|
||||
func (logr *Logr) AddTarget(targets ...Target) error {
|
||||
if logr.IsShutdown() {
|
||||
return fmt.Errorf("AddTarget called after Logr shut down")
|
||||
}
|
||||
|
||||
logr.ensureInit()
|
||||
metrics := logr.getMetricsCollector()
|
||||
defer logr.ResetLevelCache() // call this after tmux is released
|
||||
|
||||
logr.tmux.Lock()
|
||||
defer logr.tmux.Unlock()
|
||||
|
||||
errs := merror.New()
|
||||
for _, t := range targets {
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
logr.targets = append(logr.targets, t)
|
||||
if metrics != nil {
|
||||
if tm, ok := t.(TargetWithMetrics); ok {
|
||||
if err := tm.EnableMetrics(metrics, logr.MetricsUpdateFreqMillis); err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
// NewLogger creates a Logger using defaults. A `Logger` is light-weight
|
||||
// enough to create on-demand, but typically one or more Loggers are
|
||||
// created and re-used.
|
||||
func (logr *Logr) NewLogger() Logger {
|
||||
logger := Logger{logr: logr}
|
||||
return logger
|
||||
}
|
||||
|
||||
var levelStatusDisabled = LevelStatus{}
|
||||
|
||||
// IsLevelEnabled returns true if at least one target has the specified
|
||||
// level enabled. The result is cached so that subsequent checks are fast.
|
||||
func (logr *Logr) IsLevelEnabled(lvl Level) LevelStatus {
|
||||
status, ok := logr.isLevelEnabledFromCache(lvl)
|
||||
if ok {
|
||||
return status
|
||||
}
|
||||
|
||||
// Check each target.
|
||||
logr.tmux.RLock()
|
||||
for _, t := range logr.targets {
|
||||
e, s := t.IsLevelEnabled(lvl)
|
||||
if e {
|
||||
status.Enabled = true
|
||||
if s {
|
||||
status.Stacktrace = true
|
||||
break // if both enabled then no sense checking more targets
|
||||
}
|
||||
}
|
||||
}
|
||||
logr.tmux.RUnlock()
|
||||
|
||||
// Cache and return the result.
|
||||
if err := logr.updateLevelCache(lvl.ID, status); err != nil {
|
||||
logr.ReportError(err)
|
||||
return LevelStatus{}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func (logr *Logr) isLevelEnabledFromCache(lvl Level) (LevelStatus, bool) {
|
||||
logr.mux.RLock()
|
||||
defer logr.mux.RUnlock()
|
||||
|
||||
// Don't accept new log records after shutdown.
|
||||
if logr.shutdown {
|
||||
return levelStatusDisabled, true
|
||||
}
|
||||
|
||||
// Check cache. lvlCache may still be nil if no targets added.
|
||||
if logr.lvlCache == nil {
|
||||
return levelStatusDisabled, true
|
||||
}
|
||||
status, ok := logr.lvlCache.get(lvl.ID)
|
||||
if ok {
|
||||
return status, true
|
||||
}
|
||||
return LevelStatus{}, false
|
||||
}
|
||||
|
||||
func (logr *Logr) updateLevelCache(id LevelID, status LevelStatus) error {
|
||||
logr.mux.RLock()
|
||||
defer logr.mux.RUnlock()
|
||||
if logr.lvlCache != nil {
|
||||
return logr.lvlCache.put(id, status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasTargets returns true only if at least one target exists within the Logr.
|
||||
func (logr *Logr) HasTargets() bool {
|
||||
logr.tmux.RLock()
|
||||
defer logr.tmux.RUnlock()
|
||||
return len(logr.targets) > 0
|
||||
}
|
||||
|
||||
// TargetInfo provides name and type for a Target.
|
||||
type TargetInfo struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
// TargetInfos enumerates all the targets added to this Logr.
|
||||
// The resulting slice represents a snapshot at time of calling.
|
||||
func (logr *Logr) TargetInfos() []TargetInfo {
|
||||
logr.tmux.RLock()
|
||||
defer logr.tmux.RUnlock()
|
||||
|
||||
infos := make([]TargetInfo, 0)
|
||||
|
||||
for _, t := range logr.targets {
|
||||
inf := TargetInfo{
|
||||
Name: fmt.Sprintf("%v", t),
|
||||
Type: fmt.Sprintf("%T", t),
|
||||
}
|
||||
infos = append(infos, inf)
|
||||
}
|
||||
return infos
|
||||
}
|
||||
|
||||
// RemoveTargets safely removes one or more targets based on the filtering method.
|
||||
// f should return true to delete the target, false to keep it.
|
||||
// When removing a target, best effort is made to write any queued log records before
|
||||
// closing, with cxt determining how much time can be spent in total.
|
||||
// Note, keep the timeout short since this method blocks certain logging operations.
|
||||
func (logr *Logr) RemoveTargets(cxt context.Context, f func(ti TargetInfo) bool) error {
|
||||
var removed bool
|
||||
defer func() {
|
||||
if removed {
|
||||
// call this after tmux is released since
|
||||
// it will lock mux and we don't want to
|
||||
// introduce possible deadlock.
|
||||
logr.ResetLevelCache()
|
||||
}
|
||||
}()
|
||||
|
||||
errs := merror.New()
|
||||
|
||||
logr.tmux.Lock()
|
||||
defer logr.tmux.Unlock()
|
||||
|
||||
cp := make([]Target, 0)
|
||||
|
||||
for _, t := range logr.targets {
|
||||
inf := TargetInfo{
|
||||
Name: fmt.Sprintf("%v", t),
|
||||
Type: fmt.Sprintf("%T", t),
|
||||
}
|
||||
if f(inf) {
|
||||
if err := t.Shutdown(cxt); err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
removed = true
|
||||
} else {
|
||||
cp = append(cp, t)
|
||||
}
|
||||
}
|
||||
logr.targets = cp
|
||||
return errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
// ResetLevelCache resets the cached results of `IsLevelEnabled`. This is
|
||||
// called any time a Target is added or a target's level is changed.
|
||||
func (logr *Logr) ResetLevelCache() {
|
||||
// Write lock so that new cache entries cannot be stored while we
|
||||
// clear the cache.
|
||||
logr.mux.Lock()
|
||||
defer logr.mux.Unlock()
|
||||
logr.resetLevelCache()
|
||||
}
|
||||
|
||||
// resetLevelCache empties the level cache without locking.
|
||||
// mux.Lock must be held before calling this function.
|
||||
func (logr *Logr) resetLevelCache() {
|
||||
// lvlCache may still be nil if no targets added.
|
||||
if logr.lvlCache != nil {
|
||||
logr.lvlCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
// enqueue adds a log record to the logr queue. If the queue is full then
|
||||
// this function either blocks or the log record is dropped, depending on
|
||||
// the result of calling `OnQueueFull`.
|
||||
func (logr *Logr) enqueue(rec *LogRec) {
|
||||
if logr.in == nil {
|
||||
logr.ReportError(fmt.Errorf("AddTarget or Configure must be called before enqueue"))
|
||||
}
|
||||
|
||||
select {
|
||||
case logr.in <- rec:
|
||||
default:
|
||||
if logr.OnQueueFull != nil && logr.OnQueueFull(rec, logr.maxQueueSizeActual) {
|
||||
return // drop the record
|
||||
}
|
||||
select {
|
||||
case <-time.After(logr.enqueueTimeout()):
|
||||
logr.ReportError(fmt.Errorf("enqueue timed out for log rec [%v]", rec))
|
||||
case logr.in <- rec: // block until success or timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// exit is called by one of the FatalXXX style APIS. If `logr.OnExit` is not nil
|
||||
// then that method is called, otherwise the default behavior is to shut down this
|
||||
// Logr cleanly then call `os.Exit(code)`.
|
||||
func (logr *Logr) exit(code int) {
|
||||
if logr.OnExit != nil {
|
||||
logr.OnExit(code)
|
||||
return
|
||||
}
|
||||
|
||||
if err := logr.Shutdown(); err != nil {
|
||||
logr.ReportError(err)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// panic is called by one of the PanicXXX style APIS. If `logr.OnPanic` is not nil
|
||||
// then that method is called, otherwise the default behavior is to shut down this
|
||||
// Logr cleanly then call `panic(err)`.
|
||||
func (logr *Logr) panic(err interface{}) {
|
||||
if logr.OnPanic != nil {
|
||||
logr.OnPanic(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := logr.Shutdown(); err != nil {
|
||||
logr.ReportError(err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Flush blocks while flushing the logr queue and all target queues, by
|
||||
// writing existing log records to valid targets.
|
||||
// Any attempts to add new log records will block until flush is complete.
|
||||
// `logr.FlushTimeout` determines how long flush can execute before
|
||||
// timing out. Use `IsTimeoutError` to determine if the returned error is
|
||||
// due to a timeout.
|
||||
func (logr *Logr) Flush() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), logr.flushTimeout())
|
||||
defer cancel()
|
||||
return logr.FlushWithTimeout(ctx)
|
||||
}
|
||||
|
||||
// Flush blocks while flushing the logr queue and all target queues, by
|
||||
// writing existing log records to valid targets.
|
||||
// Any attempts to add new log records will block until flush is complete.
|
||||
// Use `IsTimeoutError` to determine if the returned error is
|
||||
// due to a timeout.
|
||||
func (logr *Logr) FlushWithTimeout(ctx context.Context) error {
|
||||
if !logr.HasTargets() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if logr.IsShutdown() {
|
||||
return errors.New("Flush called on shut down Logr")
|
||||
}
|
||||
|
||||
rec := newFlushLogRec(logr.NewLogger())
|
||||
logr.enqueue(rec)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return newTimeoutError("logr queue shutdown timeout")
|
||||
case <-rec.flush:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsShutdown returns true if this Logr instance has been shut down.
|
||||
// No further log records can be enqueued and no targets added after
|
||||
// shutdown.
|
||||
func (logr *Logr) IsShutdown() bool {
|
||||
logr.mux.Lock()
|
||||
defer logr.mux.Unlock()
|
||||
return logr.shutdown
|
||||
}
|
||||
|
||||
// Shutdown cleanly stops the logging engine after making best efforts
|
||||
// to flush all targets. Call this function right before application
|
||||
// exit - logr cannot be restarted once shut down.
|
||||
// `logr.ShutdownTimeout` determines how long shutdown can execute before
|
||||
// timing out. Use `IsTimeoutError` to determine if the returned error is
|
||||
// due to a timeout.
|
||||
func (logr *Logr) Shutdown() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), logr.shutdownTimeout())
|
||||
defer cancel()
|
||||
return logr.ShutdownWithTimeout(ctx)
|
||||
}
|
||||
|
||||
// Shutdown cleanly stops the logging engine after making best efforts
|
||||
// to flush all targets. Call this function right before application
|
||||
// exit - logr cannot be restarted once shut down.
|
||||
// Use `IsTimeoutError` to determine if the returned error is due to a
|
||||
// timeout.
|
||||
func (logr *Logr) ShutdownWithTimeout(ctx context.Context) error {
|
||||
logr.mux.Lock()
|
||||
if logr.shutdown {
|
||||
logr.mux.Unlock()
|
||||
return errors.New("Shutdown called again after shut down")
|
||||
}
|
||||
logr.shutdown = true
|
||||
logr.resetLevelCache()
|
||||
logr.mux.Unlock()
|
||||
|
||||
logr.metricsCloseOnce.Do(func() {
|
||||
if logr.metricsDone != nil {
|
||||
close(logr.metricsDone)
|
||||
}
|
||||
})
|
||||
|
||||
errs := merror.New()
|
||||
|
||||
// close the incoming channel and wait for read loop to exit.
|
||||
if logr.in != nil {
|
||||
close(logr.in)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
errs.Append(newTimeoutError("logr queue shutdown timeout"))
|
||||
case <-logr.done:
|
||||
}
|
||||
}
|
||||
|
||||
// logr.in channel should now be drained to targets and no more log records
|
||||
// can be added.
|
||||
logr.tmux.RLock()
|
||||
defer logr.tmux.RUnlock()
|
||||
for _, t := range logr.targets {
|
||||
err := t.Shutdown(ctx)
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
}
|
||||
return errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
// ReportError is used to notify the host application of any internal logging errors.
|
||||
// If `OnLoggerError` is not nil, it is called with the error, otherwise the error is
|
||||
// output to `os.Stderr`.
|
||||
func (logr *Logr) ReportError(err interface{}) {
|
||||
logr.incErrorCounter()
|
||||
|
||||
if logr.OnLoggerError == nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
logr.OnLoggerError(fmt.Errorf("%v", err))
|
||||
}
|
||||
|
||||
// BorrowBuffer borrows a buffer from the pool. Release the buffer to reduce garbage collection.
|
||||
func (logr *Logr) BorrowBuffer() *bytes.Buffer {
|
||||
if logr.DisableBufferPool {
|
||||
return &bytes.Buffer{}
|
||||
}
|
||||
return logr.bufferPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
// ReleaseBuffer returns a buffer to the pool to reduce garbage collection. The buffer is only
|
||||
// retained if less than MaxPooledBuffer.
|
||||
func (logr *Logr) ReleaseBuffer(buf *bytes.Buffer) {
|
||||
if !logr.DisableBufferPool && buf.Cap() < logr.MaxPooledBuffer {
|
||||
buf.Reset()
|
||||
logr.bufferPool.Put(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// enqueueTimeout returns amount of time a log record can take to be queued.
|
||||
// This only applies to blocking enqueue which happen after `logr.OnQueueFull` is called
|
||||
// and returns false.
|
||||
func (logr *Logr) enqueueTimeout() time.Duration {
|
||||
if logr.EnqueueTimeout == 0 {
|
||||
return DefaultEnqueueTimeout
|
||||
}
|
||||
return logr.EnqueueTimeout
|
||||
}
|
||||
|
||||
// shutdownTimeout returns the timeout duration for `logr.Shutdown`.
|
||||
func (logr *Logr) shutdownTimeout() time.Duration {
|
||||
if logr.ShutdownTimeout == 0 {
|
||||
return DefaultShutdownTimeout
|
||||
}
|
||||
return logr.ShutdownTimeout
|
||||
}
|
||||
|
||||
// flushTimeout returns the timeout duration for `logr.Flush`.
|
||||
func (logr *Logr) flushTimeout() time.Duration {
|
||||
if logr.FlushTimeout == 0 {
|
||||
return DefaultFlushTimeout
|
||||
}
|
||||
return logr.FlushTimeout
|
||||
}
|
||||
|
||||
// start selects on incoming log records until done channel signals.
|
||||
// Incoming log records are fanned out to all log targets.
|
||||
func (logr *Logr) start() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logr.ReportError(r)
|
||||
go logr.start()
|
||||
}
|
||||
}()
|
||||
|
||||
for rec := range logr.in {
|
||||
if rec.flush != nil {
|
||||
logr.flush(rec.flush)
|
||||
} else {
|
||||
rec.prep()
|
||||
logr.fanout(rec)
|
||||
}
|
||||
}
|
||||
close(logr.done)
|
||||
}
|
||||
|
||||
// startMetricsUpdater updates the metrics for any polled values every `MetricsUpdateFreqSecs` seconds until
|
||||
// logr is closed.
|
||||
func (logr *Logr) startMetricsUpdater() {
|
||||
for {
|
||||
updateFreq := logr.getMetricsUpdateFreqMillis()
|
||||
if updateFreq == 0 {
|
||||
updateFreq = DefMetricsUpdateFreqMillis
|
||||
}
|
||||
if updateFreq < 250 {
|
||||
updateFreq = 250 // don't peg the CPU
|
||||
}
|
||||
|
||||
select {
|
||||
case <-logr.metricsDone:
|
||||
return
|
||||
case <-time.After(time.Duration(updateFreq) * time.Millisecond):
|
||||
logr.setQueueSizeGauge(float64(len(logr.in)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (logr *Logr) getMetricsUpdateFreqMillis() int64 {
|
||||
logr.mux.RLock()
|
||||
defer logr.mux.RUnlock()
|
||||
return logr.MetricsUpdateFreqMillis
|
||||
}
|
||||
|
||||
// fanout pushes a LogRec to all targets.
|
||||
func (logr *Logr) fanout(rec *LogRec) {
|
||||
var target Target
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logr.ReportError(fmt.Errorf("fanout failed for target %s, %v", target, r))
|
||||
}
|
||||
}()
|
||||
|
||||
var logged bool
|
||||
defer func() {
|
||||
if logged {
|
||||
logr.incLoggedCounter() // call this after tmux is released
|
||||
}
|
||||
}()
|
||||
|
||||
logr.tmux.RLock()
|
||||
defer logr.tmux.RUnlock()
|
||||
for _, target = range logr.targets {
|
||||
if enabled, _ := target.IsLevelEnabled(rec.Level()); enabled {
|
||||
target.Log(rec)
|
||||
logged = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flush drains the queue and notifies when done.
|
||||
func (logr *Logr) flush(done chan<- struct{}) {
|
||||
// first drain the logr queue.
|
||||
loop:
|
||||
for {
|
||||
var rec *LogRec
|
||||
select {
|
||||
case rec = <-logr.in:
|
||||
if rec.flush == nil {
|
||||
rec.prep()
|
||||
logr.fanout(rec)
|
||||
}
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
logger := logr.NewLogger()
|
||||
|
||||
// drain all the targets; block until finished.
|
||||
logr.tmux.RLock()
|
||||
defer logr.tmux.RUnlock()
|
||||
for _, target := range logr.targets {
|
||||
rec := newFlushLogRec(logger)
|
||||
target.Log(rec)
|
||||
<-rec.flush
|
||||
}
|
||||
done <- struct{}{}
|
||||
}
|
||||
299
vendor/github.com/mattermost/logr/target.go
сгенерированный
поставляемый
299
vendor/github.com/mattermost/logr/target.go
сгенерированный
поставляемый
@@ -1,299 +0,0 @@
|
||||
package logr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Target represents a destination for log records such as file,
|
||||
// database, TCP socket, etc.
|
||||
type Target interface {
|
||||
// SetName provides an optional name for the target.
|
||||
SetName(name string)
|
||||
|
||||
// IsLevelEnabled returns true if this target should emit
|
||||
// logs for the specified level. Also determines if
|
||||
// a stack trace is required.
|
||||
IsLevelEnabled(Level) (enabled bool, stacktrace bool)
|
||||
|
||||
// Formatter returns the Formatter associated with this Target.
|
||||
Formatter() Formatter
|
||||
|
||||
// Log outputs the log record to this target's destination.
|
||||
Log(rec *LogRec)
|
||||
|
||||
// Shutdown makes best effort to flush target queue and
|
||||
// frees/closes all resources.
|
||||
Shutdown(ctx context.Context) error
|
||||
}
|
||||
|
||||
// RecordWriter can convert a LogRecord to bytes and output to some data sink.
|
||||
type RecordWriter interface {
|
||||
Write(rec *LogRec) error
|
||||
}
|
||||
|
||||
// Basic provides the basic functionality of a Target that can be used
|
||||
// to more easily compose your own Targets. To use, just embed Basic
|
||||
// in your target type, implement `RecordWriter`, and call `(*Basic).Start`.
|
||||
type Basic struct {
|
||||
target Target
|
||||
|
||||
filter Filter
|
||||
formatter Formatter
|
||||
|
||||
in chan *LogRec
|
||||
done chan struct{}
|
||||
w RecordWriter
|
||||
|
||||
mux sync.RWMutex
|
||||
name string
|
||||
|
||||
metrics bool
|
||||
queueSizeGauge Gauge
|
||||
loggedCounter Counter
|
||||
errorCounter Counter
|
||||
droppedCounter Counter
|
||||
blockedCounter Counter
|
||||
|
||||
metricsUpdateFreqMillis int64
|
||||
}
|
||||
|
||||
// Start initializes this target helper and starts accepting log records for processing.
|
||||
func (b *Basic) Start(target Target, rw RecordWriter, filter Filter, formatter Formatter, maxQueued int) {
|
||||
if filter == nil {
|
||||
filter = &StdFilter{Lvl: Fatal}
|
||||
}
|
||||
if formatter == nil {
|
||||
formatter = &DefaultFormatter{}
|
||||
}
|
||||
|
||||
b.target = target
|
||||
b.filter = filter
|
||||
b.formatter = formatter
|
||||
b.in = make(chan *LogRec, maxQueued)
|
||||
b.done = make(chan struct{}, 1)
|
||||
b.w = rw
|
||||
go b.start()
|
||||
|
||||
if b.hasMetrics() {
|
||||
go b.startMetricsUpdater()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Basic) SetName(name string) {
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
b.name = name
|
||||
}
|
||||
|
||||
// IsLevelEnabled returns true if this target should emit
|
||||
// logs for the specified level. Also determines if
|
||||
// a stack trace is required.
|
||||
func (b *Basic) IsLevelEnabled(lvl Level) (enabled bool, stacktrace bool) {
|
||||
return b.filter.IsEnabled(lvl), b.filter.IsStacktraceEnabled(lvl)
|
||||
}
|
||||
|
||||
// Formatter returns the Formatter associated with this Target.
|
||||
func (b *Basic) Formatter() Formatter {
|
||||
return b.formatter
|
||||
}
|
||||
|
||||
// Shutdown stops processing log records after making best
|
||||
// effort to flush queue.
|
||||
func (b *Basic) Shutdown(ctx context.Context) error {
|
||||
// close the incoming channel and wait for read loop to exit.
|
||||
close(b.in)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-b.done:
|
||||
}
|
||||
|
||||
// b.in channel should now be drained.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Log outputs the log record to this targets destination.
|
||||
func (b *Basic) Log(rec *LogRec) {
|
||||
lgr := rec.Logger().Logr()
|
||||
select {
|
||||
case b.in <- rec:
|
||||
default:
|
||||
handler := lgr.OnTargetQueueFull
|
||||
if handler != nil && handler(b.target, rec, cap(b.in)) {
|
||||
b.incDroppedCounter()
|
||||
return // drop the record
|
||||
}
|
||||
b.incBlockedCounter()
|
||||
|
||||
select {
|
||||
case <-time.After(lgr.enqueueTimeout()):
|
||||
lgr.ReportError(fmt.Errorf("target enqueue timeout for log rec [%v]", rec))
|
||||
case b.in <- rec: // block until success or timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics enables metrics collection using the provided MetricsCollector.
|
||||
func (b *Basic) EnableMetrics(collector MetricsCollector, updateFreqMillis int64) error {
|
||||
name := fmt.Sprintf("%v", b)
|
||||
|
||||
b.mux.Lock()
|
||||
defer b.mux.Unlock()
|
||||
|
||||
b.metrics = true
|
||||
b.metricsUpdateFreqMillis = updateFreqMillis
|
||||
|
||||
var err error
|
||||
|
||||
if b.queueSizeGauge, err = collector.QueueSizeGauge(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.loggedCounter, err = collector.LoggedCounter(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.errorCounter, err = collector.ErrorCounter(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.droppedCounter, err = collector.DroppedCounter(name); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.blockedCounter, err = collector.BlockedCounter(name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Basic) hasMetrics() bool {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
return b.metrics
|
||||
}
|
||||
|
||||
func (b *Basic) setQueueSizeGauge(val float64) {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
if b.queueSizeGauge != nil {
|
||||
b.queueSizeGauge.Set(val)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Basic) incLoggedCounter() {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
if b.loggedCounter != nil {
|
||||
b.loggedCounter.Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Basic) incErrorCounter() {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
if b.errorCounter != nil {
|
||||
b.errorCounter.Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Basic) incDroppedCounter() {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
if b.droppedCounter != nil {
|
||||
b.droppedCounter.Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Basic) incBlockedCounter() {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
if b.blockedCounter != nil {
|
||||
b.blockedCounter.Inc()
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a name for this target. Use `SetName` to specify a name.
|
||||
func (b *Basic) String() string {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
|
||||
if b.name != "" {
|
||||
return b.name
|
||||
}
|
||||
return fmt.Sprintf("%T", b.target)
|
||||
}
|
||||
|
||||
// Start accepts log records via In channel and writes to the
|
||||
// supplied writer, until Done channel signaled.
|
||||
func (b *Basic) start() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintln(os.Stderr, "Basic.start -- ", r)
|
||||
go b.start()
|
||||
}
|
||||
}()
|
||||
|
||||
for rec := range b.in {
|
||||
if rec.flush != nil {
|
||||
b.flush(rec.flush)
|
||||
} else {
|
||||
err := b.w.Write(rec)
|
||||
if err != nil {
|
||||
b.incErrorCounter()
|
||||
rec.Logger().Logr().ReportError(err)
|
||||
} else {
|
||||
b.incLoggedCounter()
|
||||
}
|
||||
}
|
||||
}
|
||||
close(b.done)
|
||||
}
|
||||
|
||||
// startMetricsUpdater updates the metrics for any polled values every `MetricsUpdateFreqSecs` seconds until
|
||||
// target is closed.
|
||||
func (b *Basic) startMetricsUpdater() {
|
||||
for {
|
||||
updateFreq := b.getMetricsUpdateFreqMillis()
|
||||
if updateFreq == 0 {
|
||||
updateFreq = DefMetricsUpdateFreqMillis
|
||||
}
|
||||
if updateFreq < 250 {
|
||||
updateFreq = 250 // don't peg the CPU
|
||||
}
|
||||
|
||||
select {
|
||||
case <-b.done:
|
||||
return
|
||||
case <-time.After(time.Duration(updateFreq) * time.Millisecond):
|
||||
b.setQueueSizeGauge(float64(len(b.in)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Basic) getMetricsUpdateFreqMillis() int64 {
|
||||
b.mux.RLock()
|
||||
defer b.mux.RUnlock()
|
||||
return b.metricsUpdateFreqMillis
|
||||
}
|
||||
|
||||
// flush drains the queue and notifies when done.
|
||||
func (b *Basic) flush(done chan<- struct{}) {
|
||||
for {
|
||||
var rec *LogRec
|
||||
var err error
|
||||
select {
|
||||
case rec = <-b.in:
|
||||
// ignore any redundant flush records.
|
||||
if rec.flush == nil {
|
||||
err = b.w.Write(rec)
|
||||
if err != nil {
|
||||
b.incErrorCounter()
|
||||
rec.Logger().Logr().ReportError(err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
done <- struct{}{}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
89
vendor/github.com/mattermost/logr/target/syslog.go
сгенерированный
поставляемый
89
vendor/github.com/mattermost/logr/target/syslog.go
сгенерированный
поставляемый
@@ -1,89 +0,0 @@
|
||||
// +build !windows,!nacl,!plan9
|
||||
|
||||
package target
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
"github.com/wiggin77/merror"
|
||||
)
|
||||
|
||||
// Syslog outputs log records to local or remote syslog.
|
||||
type Syslog struct {
|
||||
logr.Basic
|
||||
w *syslog.Writer
|
||||
}
|
||||
|
||||
// SyslogParams provides parameters for dialing a syslog daemon.
|
||||
type SyslogParams struct {
|
||||
Network string
|
||||
Raddr string
|
||||
Priority syslog.Priority
|
||||
Tag string
|
||||
}
|
||||
|
||||
// NewSyslogTarget creates a target capable of outputting log records to remote or local syslog.
|
||||
func NewSyslogTarget(filter logr.Filter, formatter logr.Formatter, params *SyslogParams, maxQueue int) (*Syslog, error) {
|
||||
writer, err := syslog.Dial(params.Network, params.Raddr, params.Priority, params.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Syslog{w: writer}
|
||||
s.Basic.Start(s, s, filter, formatter, maxQueue)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Shutdown stops processing log records after making best
|
||||
// effort to flush queue.
|
||||
func (s *Syslog) Shutdown(ctx context.Context) error {
|
||||
errs := merror.New()
|
||||
|
||||
err := s.Basic.Shutdown(ctx)
|
||||
errs.Append(err)
|
||||
|
||||
err = s.w.Close()
|
||||
errs.Append(err)
|
||||
|
||||
return errs.ErrorOrNil()
|
||||
}
|
||||
|
||||
// Write converts the log record to bytes, via the Formatter,
|
||||
// and outputs to syslog.
|
||||
func (s *Syslog) Write(rec *logr.LogRec) error {
|
||||
_, stacktrace := s.IsLevelEnabled(rec.Level())
|
||||
|
||||
buf := rec.Logger().Logr().BorrowBuffer()
|
||||
defer rec.Logger().Logr().ReleaseBuffer(buf)
|
||||
|
||||
buf, err := s.Formatter().Format(rec, stacktrace, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txt := buf.String()
|
||||
|
||||
switch rec.Level() {
|
||||
case logr.Panic, logr.Fatal:
|
||||
err = s.w.Crit(txt)
|
||||
case logr.Error:
|
||||
err = s.w.Err(txt)
|
||||
case logr.Warn:
|
||||
err = s.w.Warning(txt)
|
||||
case logr.Debug, logr.Trace:
|
||||
err = s.w.Debug(txt)
|
||||
default:
|
||||
// logr.Info plus all custom levels.
|
||||
err = s.w.Info(txt)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
reporter := rec.Logger().Logr().ReportError
|
||||
reporter(fmt.Errorf("syslog write fail: %w", err))
|
||||
// syslog writer will try to reconnect.
|
||||
}
|
||||
return err
|
||||
}
|
||||
40
vendor/github.com/mattermost/logr/target/writer.go
сгенерированный
поставляемый
40
vendor/github.com/mattermost/logr/target/writer.go
сгенерированный
поставляемый
@@ -1,40 +0,0 @@
|
||||
package target
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/mattermost/logr"
|
||||
)
|
||||
|
||||
// Writer outputs log records to any `io.Writer`.
|
||||
type Writer struct {
|
||||
logr.Basic
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
// NewWriterTarget creates a target capable of outputting log records to an io.Writer.
|
||||
func NewWriterTarget(filter logr.Filter, formatter logr.Formatter, out io.Writer, maxQueue int) *Writer {
|
||||
if out == nil {
|
||||
out = ioutil.Discard
|
||||
}
|
||||
w := &Writer{out: out}
|
||||
w.Basic.Start(w, w, filter, formatter, maxQueue)
|
||||
return w
|
||||
}
|
||||
|
||||
// Write converts the log record to bytes, via the Formatter,
|
||||
// and outputs to the io.Writer.
|
||||
func (w *Writer) Write(rec *logr.LogRec) error {
|
||||
_, stacktrace := w.IsLevelEnabled(rec.Level())
|
||||
|
||||
buf := rec.Logger().Logr().BorrowBuffer()
|
||||
defer rec.Logger().Logr().ReleaseBuffer(buf)
|
||||
|
||||
buf, err := w.Formatter().Format(rec, stacktrace, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.out.Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
1
vendor/github.com/mattermost/logr/.gitignore → vendor/github.com/mattermost/logr/v2/.gitignore
сгенерированный
поставляемый
1
vendor/github.com/mattermost/logr/.gitignore → vendor/github.com/mattermost/logr/v2/.gitignore
сгенерированный
поставляемый
@@ -34,3 +34,4 @@ logs
|
||||
# test apps
|
||||
test/cmd/testapp1/testapp1
|
||||
test/cmd/simple/simple
|
||||
test/cmd/gelf/gelf
|
||||
0
vendor/github.com/mattermost/logr/.travis.yml → vendor/github.com/mattermost/logr/v2/.travis.yml
сгенерированный
поставляемый
0
vendor/github.com/mattermost/logr/.travis.yml → vendor/github.com/mattermost/logr/v2/.travis.yml
сгенерированный
поставляемый
0
vendor/github.com/mattermost/logr/LICENSE → vendor/github.com/mattermost/logr/v2/LICENSE
сгенерированный
поставляемый
0
vendor/github.com/mattermost/logr/LICENSE → vendor/github.com/mattermost/logr/v2/LICENSE
сгенерированный
поставляемый
49
vendor/github.com/mattermost/logr/README.md → vendor/github.com/mattermost/logr/v2/README.md
сгенерированный
поставляемый
49
vendor/github.com/mattermost/logr/README.md → vendor/github.com/mattermost/logr/v2/README.md
сгенерированный
поставляемый
@@ -16,9 +16,9 @@ It is very much inspired by [Logrus](https://github.com/sirupsen/logrus) but add
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
| entity | description |
|
||||
| ------ | ----------- |
|
||||
| Logr | Engine instance typically instantiated once; used to configure logging.<br>```lgr := &Logr{}```|
|
||||
| Logr | Engine instance typically instantiated once; used to configure logging.<br>```lgr,_ := logr.New()```|
|
||||
| Logger | Provides contextual logging via fields; lightweight, can be created once and accessed globally or create on demand.<br>```logger := lgr.NewLogger()```<br>```logger2 := logger.WithField("user", "Sam")```|
|
||||
| Target | A destination for log items such as console, file, database or just about anything that can be written to. Each target has its own filter/level and formatter, and any number of targets can be added to a Logr. Targets for syslog and any io.Writer are built-in and it is easy to create your own. You can also use any [Logrus hooks](https://github.com/sirupsen/logrus/wiki/Hooks) via a simple [adapter](https://github.com/wiggin77/logrus4logr).|
|
||||
| Target | A destination for log items such as console, file, database or just about anything that can be written to. Each target has its own filter/level and formatter, and any number of targets can be added to a Logr. Targets for file, syslog and any io.Writer are built-in and it is easy to create your own. You can also use any [Logrus hooks](https://github.com/sirupsen/logrus/wiki/Hooks) via a simple [adapter](https://github.com/wiggin77/logrus4logr).|
|
||||
| Filter | Determines which logging calls get written versus filtered out. Also determines which logging calls generate a stack trace.<br>```filter := &logr.StdFilter{Lvl: logr.Warn, Stacktrace: logr.Fatal}```|
|
||||
| Formatter | Formats the output. Logr includes built-in formatters for JSON and plain text with delimiters. It is easy to create your own formatters or you can also use any [Logrus formatters](https://github.com/sirupsen/logrus#formatters) via a simple [adapter](https://github.com/wiggin77/logrus4logr).<br>```formatter := &format.Plain{Delim: " \| "}```|
|
||||
|
||||
@@ -26,15 +26,15 @@ It is very much inspired by [Logrus](https://github.com/sirupsen/logrus) but add
|
||||
|
||||
```go
|
||||
// Create Logr instance.
|
||||
lgr := &logr.Logr{}
|
||||
lgr,_ := logr.New()
|
||||
|
||||
// Create a filter and formatter. Both can be shared by multiple
|
||||
// targets.
|
||||
filter := &logr.StdFilter{Lvl: logr.Warn, Stacktrace: logr.Error}
|
||||
formatter := &format.Plain{Delim: " | "}
|
||||
formatter := &formatters.Plain{Delim: " | "}
|
||||
|
||||
// WriterTarget outputs to any io.Writer
|
||||
t := target.NewWriterTarget(filter, formatter, os.StdOut, 1000)
|
||||
t := targets.NewWriterTarget(filter, formatter, os.StdOut, 1000)
|
||||
lgr.AddTarget(t)
|
||||
|
||||
// One or more Loggers can be created, shared, used concurrently,
|
||||
@@ -56,7 +56,7 @@ Fields allow for contextual logging, meaning information can be added to log sta
|
||||
Fields are added via Loggers:
|
||||
|
||||
```go
|
||||
lgr := &Logr{}
|
||||
lgr,_ := logr.New()
|
||||
// ... add targets ...
|
||||
logger := lgr.NewLogger().WithFields(logr.Fields{
|
||||
"user": user,
|
||||
@@ -88,14 +88,14 @@ Logr also supports custom filters (logr.CustomFilter) which allow fine grained i
|
||||
LoginLevel := logr.Level{ID: 100, Name: "login ", Stacktrace: false}
|
||||
LogoutLevel := logr.Level{ID: 101, Name: "logout", Stacktrace: false}
|
||||
|
||||
lgr := &logr.Logr{}
|
||||
lgr,_ := logr.New()
|
||||
|
||||
// create a custom filter with custom levels.
|
||||
filter := &logr.CustomFilter{}
|
||||
filter.Add(LoginLevel, LogoutLevel)
|
||||
|
||||
formatter := &format.Plain{Delim: " | "}
|
||||
tgr := target.NewWriterTarget(filter, formatter, os.StdOut, 1000)
|
||||
formatter := &formatters.Plain{Delim: " | "}
|
||||
tgr := targets.NewWriterTarget(filter, formatter, os.StdOut, 1000)
|
||||
lgr.AddTarget(tgr)
|
||||
logger := lgr.NewLogger().WithFields(logr.Fields{"user": "Bob", "role": "admin"})
|
||||
|
||||
@@ -113,36 +113,31 @@ You can use any [Logrus hooks](https://github.com/sirupsen/logrus/wiki/Hooks) vi
|
||||
|
||||
You can create your own target by implementing the [Target](./target.go) interface.
|
||||
|
||||
An easier method is to use the [logr.Basic](./target.go) type target and build your functionality on that. Basic handles all the queuing and other plumbing so you only need to implement two methods. Example target that outputs to `io.Writer`:
|
||||
Example target that outputs to `io.Writer`:
|
||||
|
||||
```go
|
||||
type Writer struct {
|
||||
logr.Basic
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func NewWriterTarget(filter logr.Filter, formatter logr.Formatter, out io.Writer, maxQueue int) *Writer {
|
||||
func NewWriterTarget(out io.Writer) *Writer {
|
||||
w := &Writer{out: out}
|
||||
w.Basic.Start(w, w, filter, formatter, maxQueue)
|
||||
return w
|
||||
}
|
||||
|
||||
// Called once to initialize target.
|
||||
func (w *Writer) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write will always be called by a single goroutine, so no locking needed.
|
||||
// Just convert a log record to a []byte using the formatter and output the
|
||||
// bytes to your sink.
|
||||
func (w *Writer) Write(rec *logr.LogRec) error {
|
||||
_, stacktrace := w.IsLevelEnabled(rec.Level())
|
||||
func (w *Writer) Write(p []byte, rec *logr.LogRec) (int, error) {
|
||||
return w.out.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
// take a buffer from the pool to avoid allocations or just allocate a new one.
|
||||
buf := rec.Logger().Logr().BorrowBuffer()
|
||||
defer rec.Logger().Logr().ReleaseBuffer(buf)
|
||||
|
||||
buf, err := w.Formatter().Format(rec, stacktrace, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.out.Write(buf.Bytes())
|
||||
return err
|
||||
// Called once to cleanup/free resources for target.
|
||||
func (w *Writer) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
209
vendor/github.com/mattermost/logr/v2/config/config.go
сгенерированный
поставляемый
Обычный файл
209
vendor/github.com/mattermost/logr/v2/config/config.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,209 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/logr/v2"
|
||||
"github.com/mattermost/logr/v2/formatters"
|
||||
"github.com/mattermost/logr/v2/targets"
|
||||
)
|
||||
|
||||
type TargetCfg struct {
|
||||
Type string `json:"type"` // one of "console", "file", "tcp", "syslog", "none".
|
||||
Options json.RawMessage `json:"options,omitempty"`
|
||||
Format string `json:"format"` // one of "json", "plain", "gelf"
|
||||
FormatOptions json.RawMessage `json:"format_options,omitempty"`
|
||||
Levels []logr.Level `json:"levels"`
|
||||
MaxQueueSize int `json:"maxqueuesize,omitempty"`
|
||||
}
|
||||
|
||||
type ConsoleOptions struct {
|
||||
Out string `json:"out"` // one of "stdout", "stderr"
|
||||
}
|
||||
|
||||
type TargetFactory func(targetType string, options json.RawMessage) (logr.Target, error)
|
||||
type FormatterFactory func(format string, options json.RawMessage) (logr.Formatter, error)
|
||||
|
||||
type Factories struct {
|
||||
targetFactory TargetFactory // can be nil
|
||||
formatterFactory FormatterFactory // can be nil
|
||||
}
|
||||
|
||||
var removeAll = func(ti logr.TargetInfo) bool { return true }
|
||||
|
||||
// ConfigureTargets replaces the current list of log targets with a new one based on a map
|
||||
// of name->TargetCfg. The map of TargetCfg's would typically be serialized from a JSON
|
||||
// source or can be programmatically created.
|
||||
//
|
||||
// An optional set of factories can be provided which will be called to create any target
|
||||
// types or formatters not built-in.
|
||||
//
|
||||
// To append log targets to an existing config, use `(*Logr).AddTarget` or
|
||||
// `(*Logr).AddTargetFromConfig` instead.
|
||||
func ConfigureTargets(lgr *logr.Logr, config map[string]TargetCfg, factories *Factories) error {
|
||||
if err := lgr.RemoveTargets(context.Background(), removeAll); err != nil {
|
||||
return fmt.Errorf("error removing existing log targets: %w", err)
|
||||
}
|
||||
|
||||
if factories == nil {
|
||||
factories = &Factories{nil, nil}
|
||||
}
|
||||
|
||||
for name, tcfg := range config {
|
||||
target, err := newTarget(tcfg.Type, tcfg.Options, factories.targetFactory)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating log target %s: %w", name, err)
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
formatter, err := newFormatter(tcfg.Format, tcfg.FormatOptions, factories.formatterFactory)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating formatter for log target %s: %w", name, err)
|
||||
}
|
||||
|
||||
filter := newFilter(tcfg.Levels)
|
||||
qSize := tcfg.MaxQueueSize
|
||||
if qSize == 0 {
|
||||
qSize = logr.DefaultMaxQueueSize
|
||||
}
|
||||
|
||||
if err = lgr.AddTarget(target, name, filter, formatter, qSize); err != nil {
|
||||
return fmt.Errorf("error adding log target %s: %w", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newFilter(levels []logr.Level) logr.Filter {
|
||||
filter := &logr.CustomFilter{}
|
||||
for _, lvl := range levels {
|
||||
filter.Add(lvl)
|
||||
}
|
||||
return filter
|
||||
}
|
||||
|
||||
func newTarget(targetType string, options json.RawMessage, factory TargetFactory) (logr.Target, error) {
|
||||
switch strings.ToLower(targetType) {
|
||||
case "console":
|
||||
c := ConsoleOptions{}
|
||||
if len(options) != 0 {
|
||||
if err := json.Unmarshal(options, &c); err != nil {
|
||||
return nil, fmt.Errorf("error decoding console target options: %w", err)
|
||||
}
|
||||
}
|
||||
var w io.Writer
|
||||
switch c.Out {
|
||||
case "stderr":
|
||||
w = os.Stderr
|
||||
case "stdout", "":
|
||||
w = os.Stdout
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid console target option '%s'", c.Out)
|
||||
}
|
||||
return targets.NewWriterTarget(w), nil
|
||||
case "file":
|
||||
fo := targets.FileOptions{}
|
||||
if len(options) == 0 {
|
||||
return nil, errors.New("missing file target options")
|
||||
}
|
||||
if err := json.Unmarshal(options, &fo); err != nil {
|
||||
return nil, fmt.Errorf("error decoding file target options: %w", err)
|
||||
}
|
||||
if err := fo.CheckValid(); err != nil {
|
||||
return nil, fmt.Errorf("invalid file target options: %w", err)
|
||||
}
|
||||
return targets.NewFileTarget(fo), nil
|
||||
case "tcp":
|
||||
to := targets.TcpOptions{}
|
||||
if len(options) == 0 {
|
||||
return nil, errors.New("missing TCP target options")
|
||||
}
|
||||
if err := json.Unmarshal(options, &to); err != nil {
|
||||
return nil, fmt.Errorf("error decoding TCP target options: %w", err)
|
||||
}
|
||||
if err := to.CheckValid(); err != nil {
|
||||
return nil, fmt.Errorf("invalid TCP target options: %w", err)
|
||||
}
|
||||
return targets.NewTcpTarget(&to), nil
|
||||
case "syslog":
|
||||
so := targets.SyslogOptions{}
|
||||
if len(options) == 0 {
|
||||
return nil, errors.New("missing SysLog target options")
|
||||
}
|
||||
if err := json.Unmarshal(options, &so); err != nil {
|
||||
return nil, fmt.Errorf("error decoding Syslog target options: %w", err)
|
||||
}
|
||||
if err := so.CheckValid(); err != nil {
|
||||
return nil, fmt.Errorf("invalid SysLog target options: %w", err)
|
||||
}
|
||||
return targets.NewSyslogTarget(&so)
|
||||
case "none":
|
||||
return nil, nil
|
||||
default:
|
||||
if factory != nil {
|
||||
t, err := factory(targetType, options)
|
||||
if err != nil || t == nil {
|
||||
return nil, fmt.Errorf("error from target factory: %w", err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("target type '%s' is unrecogized", targetType)
|
||||
}
|
||||
|
||||
func newFormatter(format string, options json.RawMessage, factory FormatterFactory) (logr.Formatter, error) {
|
||||
switch strings.ToLower(format) {
|
||||
case "json":
|
||||
j := formatters.JSON{}
|
||||
if len(options) != 0 {
|
||||
if err := json.Unmarshal(options, &j); err != nil {
|
||||
return nil, fmt.Errorf("error decoding JSON formatter options: %w", err)
|
||||
}
|
||||
if err := j.CheckValid(); err != nil {
|
||||
return nil, fmt.Errorf("invalid JSON formatter options: %w", err)
|
||||
}
|
||||
}
|
||||
return &j, nil
|
||||
case "plain":
|
||||
p := formatters.Plain{}
|
||||
if len(options) != 0 {
|
||||
if err := json.Unmarshal(options, &p); err != nil {
|
||||
return nil, fmt.Errorf("error decoding Plain formatter options: %w", err)
|
||||
}
|
||||
if err := p.CheckValid(); err != nil {
|
||||
return nil, fmt.Errorf("invalid plain formatter options: %w", err)
|
||||
}
|
||||
}
|
||||
return &p, nil
|
||||
case "gelf":
|
||||
g := formatters.Gelf{}
|
||||
if len(options) != 0 {
|
||||
if err := json.Unmarshal(options, &g); err != nil {
|
||||
return nil, fmt.Errorf("error decoding Gelf formatter options: %w", err)
|
||||
}
|
||||
if err := g.CheckValid(); err != nil {
|
||||
return nil, fmt.Errorf("invalid GELF formatter options: %w", err)
|
||||
}
|
||||
}
|
||||
return &g, nil
|
||||
|
||||
default:
|
||||
if factory != nil {
|
||||
f, err := factory(format, options)
|
||||
if err != nil || f == nil {
|
||||
return nil, fmt.Errorf("error from formatter factory: %w", err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("format '%s' is unrecogized", format)
|
||||
}
|
||||
90
vendor/github.com/mattermost/logr/v2/config/sample-config.json
сгенерированный
поставляемый
Обычный файл
90
vendor/github.com/mattermost/logr/v2/config/sample-config.json
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"sample-console": {
|
||||
"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}
|
||||
],
|
||||
"maxqueuesize": 1000
|
||||
},
|
||||
"sample-file": {
|
||||
"type": "file",
|
||||
"options": {
|
||||
"filename": "test.log",
|
||||
"max_size": 1000000,
|
||||
"max_age": 1,
|
||||
"max_backups": 10,
|
||||
"compress": true
|
||||
},
|
||||
"format": "json",
|
||||
"format_options": {
|
||||
},
|
||||
"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}
|
||||
],
|
||||
"maxqueuesize": 1000
|
||||
},
|
||||
"sample-tcp": {
|
||||
"type": "tcp",
|
||||
"options": {
|
||||
"host": "localhost",
|
||||
"port": 18066,
|
||||
"tls": false,
|
||||
"cert": "",
|
||||
"insecure": false
|
||||
},
|
||||
"format": "gelf",
|
||||
"format_options": {
|
||||
"hostname": "server01"
|
||||
},
|
||||
"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}
|
||||
],
|
||||
"maxqueuesize": 1000
|
||||
},
|
||||
"sample-syslog": {
|
||||
"type": "syslog",
|
||||
"options": {
|
||||
"host": "localhost",
|
||||
"port": 18066,
|
||||
"tls": false,
|
||||
"cert": "",
|
||||
"insecure": false,
|
||||
"tag": "testapp"
|
||||
},
|
||||
"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}
|
||||
],
|
||||
"maxqueuesize": 1000
|
||||
}
|
||||
}
|
||||
2
vendor/github.com/mattermost/logr/const.go → vendor/github.com/mattermost/logr/v2/const.go
сгенерированный
поставляемый
2
vendor/github.com/mattermost/logr/const.go → vendor/github.com/mattermost/logr/v2/const.go
сгенерированный
поставляемый
@@ -13,7 +13,7 @@ const (
|
||||
|
||||
// MaxLevelID is the maximum value of a level ID. Some level cache implementations will
|
||||
// allocate a cache of this size. Cannot exceed uint.
|
||||
MaxLevelID = 256
|
||||
MaxLevelID = 65535
|
||||
|
||||
// DefaultEnqueueTimeout is the default amount of time a log record can take to be queued.
|
||||
// This only applies to blocking enqueue which happen after `logr.OnQueueFull` is called
|
||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user