Refactor mlog
- simplify mlog by removing redundant code
- remove Zap dependency
- update unit test helpers
- update logging config
- update auditing
Этот коммит содержится в:
Doug Lauder
2021-08-17 16:08:04 -04:00
коммит произвёл GitHub
родитель 04b27ce93c
Коммит a4507327a7
216 изменённых файлов: 4940 добавлений и 14674 удалений

Просмотреть файл

@@ -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)
})

Просмотреть файл

@@ -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.

Просмотреть файл

@@ -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) {

Просмотреть файл

@@ -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()

Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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
Просмотреть файл

@@ -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
Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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 сгенерированный поставляемый
Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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/LICENSE → vendor/github.com/mattermost/logr/v2/LICENSE сгенерированный поставляемый
Просмотреть файл

Просмотреть файл

@@ -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 сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -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 сгенерированный поставляемый Обычный файл
Просмотреть файл

@@ -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
}
}

Просмотреть файл

@@ -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

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше