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 @echo Running mattermost for development
mkdir -p $(BUILD_WEBAPP_DIR)/dist/files mkdir -p $(BUILD_WEBAPP_DIR)/dist/files
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) 2>&1 | \ $(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) $(RUN_IN_BACKGROUND)
$(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' $(PLATFORM_FILES) logs --logrus $(RUN_IN_BACKGROUND)
debug-server: start-docker ## Compile and start server using delve. debug-server: start-docker ## Compile and start server using delve.
mkdir -p $(BUILD_WEBAPP_DIR)/dist/files 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/localcachelayer"
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks" "github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
"github.com/mattermost/mattermost-server/v6/testlib" "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/web"
"github.com/mattermost/mattermost-server/v6/wsapi" "github.com/mattermost/mattermost-server/v6/wsapi"
) )
@@ -67,6 +66,8 @@ type TestHelper struct {
LocalClient *model.Client4 LocalClient *model.Client4
IncludeCacheLayer bool IncludeCacheLayer bool
TestLogger *mlog.Logger
} }
var mainHelper *testlib.MainHelper var mainHelper *testlib.MainHelper
@@ -120,6 +121,15 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
options = append(options, app.StoreOverride(dbStore)) 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...) s, err := app.NewServer(options...)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -131,6 +141,7 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
ConfigStore: configStore, ConfigStore: configStore,
IncludeCacheLayer: includeCache, IncludeCacheLayer: includeCache,
Context: &request.Context{}, Context: &request.Context{},
TestLogger: testLogger,
} }
if s.SearchEngine != nil && s.SearchEngine.BleveEngine != nil && searchEngine != nil { if s.SearchEngine != nil && s.SearchEngine.BleveEngine != nil && searchEngine != nil {
@@ -336,15 +347,11 @@ func (th *TestHelper) ShutdownApp() {
} }
func (th *TestHelper) TearDown() { func (th *TestHelper) TearDown() {
utils.DisableDebugLogForTest()
if th.IncludeCacheLayer { if th.IncludeCacheLayer {
// Clean all the caches // Clean all the caches
th.App.Srv().InvalidateAllCaches() th.App.Srv().InvalidateAllCaches()
} }
th.ShutdownApp() th.ShutdownApp()
utils.EnableDebugLogForTest()
} }
var initBasicOnce sync.Once var initBasicOnce sync.Once
@@ -501,12 +508,10 @@ func (th *TestHelper) CreateBotWithClient(client *model.Client4) *model.Bot {
Description: "bot", Description: "bot",
} }
utils.DisableDebugLogForTest()
rbot, _, err := client.CreateBot(bot) rbot, _, err := client.CreateBot(bot)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return rbot return rbot
} }
@@ -527,12 +532,10 @@ func (th *TestHelper) CreateTeamWithClient(client *model.Client4) *model.Team {
Type: model.TeamOpen, Type: model.TeamOpen,
} }
utils.DisableDebugLogForTest()
rteam, _, err := client.CreateTeam(team) rteam, _, err := client.CreateTeam(team)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return rteam return rteam
} }
@@ -548,7 +551,6 @@ func (th *TestHelper) CreateUserWithClient(client *model.Client4) *model.User {
Password: "Pa$$word11", Password: "Pa$$word11",
} }
utils.DisableDebugLogForTest()
ruser, _, err := client.CreateUser(user) ruser, _, err := client.CreateUser(user)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -559,7 +561,6 @@ func (th *TestHelper) CreateUserWithClient(client *model.Client4) *model.User {
if err != nil { if err != nil {
return nil return nil
} }
utils.EnableDebugLogForTest()
return ruser return ruser
} }
@@ -651,12 +652,10 @@ func (th *TestHelper) CreateChannelWithClientAndTeam(client *model.Client4, chan
TeamId: teamId, TeamId: teamId,
} }
utils.DisableDebugLogForTest()
rchannel, _, err := client.CreateChannel(channel) rchannel, _, err := client.CreateChannel(channel)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return rchannel return rchannel
} }
@@ -680,12 +679,10 @@ func (th *TestHelper) CreatePostWithClient(client *model.Client4, channel *model
Message: "message_" + id, Message: "message_" + id,
} }
utils.DisableDebugLogForTest()
rpost, _, err := client.CreatePost(post) rpost, _, err := client.CreatePost(post)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return rpost return rpost
} }
@@ -698,12 +695,10 @@ func (th *TestHelper) CreatePinnedPostWithClient(client *model.Client4, channel
IsPinned: true, IsPinned: true,
} }
utils.DisableDebugLogForTest()
rpost, _, err := client.CreatePost(post) rpost, _, err := client.CreatePost(post)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return rpost return rpost
} }
@@ -713,12 +708,10 @@ func (th *TestHelper) CreateMessagePostWithClient(client *model.Client4, channel
Message: message, Message: message,
} }
utils.DisableDebugLogForTest()
rpost, _, err := client.CreatePost(post) rpost, _, err := client.CreatePost(post)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return rpost return rpost
} }
@@ -738,13 +731,11 @@ func (th *TestHelper) CreateMessagePostNoClient(channel *model.Channel, message
} }
func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel { func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
var channel *model.Channel var channel *model.Channel
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil { if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
@@ -769,82 +760,59 @@ func (th *TestHelper) LoginSystemManager() {
} }
func (th *TestHelper) LoginBasicWithClient(client *model.Client4) { func (th *TestHelper) LoginBasicWithClient(client *model.Client4) {
utils.DisableDebugLogForTest()
_, _, err := client.Login(th.BasicUser.Email, th.BasicUser.Password) _, _, err := client.Login(th.BasicUser.Email, th.BasicUser.Password)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) LoginBasic2WithClient(client *model.Client4) { func (th *TestHelper) LoginBasic2WithClient(client *model.Client4) {
utils.DisableDebugLogForTest()
_, _, err := client.Login(th.BasicUser2.Email, th.BasicUser2.Password) _, _, err := client.Login(th.BasicUser2.Email, th.BasicUser2.Password)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) LoginTeamAdminWithClient(client *model.Client4) { func (th *TestHelper) LoginTeamAdminWithClient(client *model.Client4) {
utils.DisableDebugLogForTest()
_, _, err := client.Login(th.TeamAdminUser.Email, th.TeamAdminUser.Password) _, _, err := client.Login(th.TeamAdminUser.Email, th.TeamAdminUser.Password)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) LoginSystemManagerWithClient(client *model.Client4) { func (th *TestHelper) LoginSystemManagerWithClient(client *model.Client4) {
utils.DisableDebugLogForTest()
_, _, err := client.Login(th.SystemManagerUser.Email, th.SystemManagerUser.Password) _, _, err := client.Login(th.SystemManagerUser.Email, th.SystemManagerUser.Password)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) LoginSystemAdminWithClient(client *model.Client4) { func (th *TestHelper) LoginSystemAdminWithClient(client *model.Client4) {
utils.DisableDebugLogForTest()
_, _, err := client.Login(th.SystemAdminUser.Email, th.SystemAdminUser.Password) _, _, err := client.Login(th.SystemAdminUser.Email, th.SystemAdminUser.Password)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) UpdateActiveUser(user *model.User, active bool) { func (th *TestHelper) UpdateActiveUser(user *model.User, active bool) {
utils.DisableDebugLogForTest()
_, err := th.App.UpdateActive(th.Context, user, active) _, err := th.App.UpdateActive(th.Context, user, active)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) { func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
utils.DisableDebugLogForTest()
_, err := th.App.JoinUserToTeam(th.Context, team, user, "") _, err := th.App.JoinUserToTeam(th.Context, team, user, "")
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember { func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
utils.DisableDebugLogForTest()
member, err := th.App.AddUserToChannel(user, channel, false) member, err := th.App.AddUserToChannel(user, channel, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return member return member
} }
@@ -864,12 +832,10 @@ func (th *TestHelper) CreateGroup() *model.Group {
RemoteId: "ri_" + id, RemoteId: "ri_" + id,
} }
utils.DisableDebugLogForTest()
group, err := th.App.CreateGroup(group) group, err := th.App.CreateGroup(group)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return group return group
} }
@@ -1107,59 +1073,39 @@ func (th *TestHelper) cleanupTestFile(info *model.FileInfo) error {
} }
func (th *TestHelper) MakeUserChannelAdmin(user *model.User, channel *model.Channel) { 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 { if cm, err := th.App.Srv().Store.Channel().GetMember(context.Background(), channel.Id, user.Id); err == nil {
cm.SchemeAdmin = true cm.SchemeAdmin = true
if _, err = th.App.Srv().Store.Channel().UpdateMember(cm); err != nil { if _, err = th.App.Srv().Store.Channel().UpdateMember(cm); err != nil {
utils.EnableDebugLogForTest()
panic(err) panic(err)
} }
} else { } else {
utils.EnableDebugLogForTest()
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) UpdateUserToTeamAdmin(user *model.User, team *model.Team) { 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 { if tm, err := th.App.Srv().Store.Team().GetMember(context.Background(), team.Id, user.Id); err == nil {
tm.SchemeAdmin = true tm.SchemeAdmin = true
if _, err = th.App.Srv().Store.Team().UpdateMember(tm); err != nil { if _, err = th.App.Srv().Store.Team().UpdateMember(tm); err != nil {
utils.EnableDebugLogForTest()
panic(err) panic(err)
} }
} else { } else {
utils.EnableDebugLogForTest()
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) UpdateUserToNonTeamAdmin(user *model.User, team *model.Team) { 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 { if tm, err := th.App.Srv().Store.Team().GetMember(context.Background(), team.Id, user.Id); err == nil {
tm.SchemeAdmin = false tm.SchemeAdmin = false
if _, err = th.App.Srv().Store.Team().UpdateMember(tm); err != nil { if _, err = th.App.Srv().Store.Team().UpdateMember(tm); err != nil {
utils.EnableDebugLogForTest()
panic(err) panic(err)
} }
} else { } else {
utils.EnableDebugLogForTest()
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string { func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string {
utils.DisableDebugLogForTest()
results := make(map[string][]string) results := make(map[string][]string)
for _, roleName := range []string{ for _, roleName := range []string{
@@ -1172,24 +1118,18 @@ func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string {
} { } {
role, err1 := th.App.GetRoleByName(context.Background(), roleName) role, err1 := th.App.GetRoleByName(context.Background(), roleName)
if err1 != nil { if err1 != nil {
utils.EnableDebugLogForTest()
panic(err1) panic(err1)
} }
results[roleName] = role.Permissions results[roleName] = role.Permissions
} }
utils.EnableDebugLogForTest()
return results return results
} }
func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) { func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) {
utils.DisableDebugLogForTest()
for roleName, permissions := range data { for roleName, permissions := range data {
role, err1 := th.App.GetRoleByName(context.Background(), roleName) role, err1 := th.App.GetRoleByName(context.Background(), roleName)
if err1 != nil { if err1 != nil {
utils.EnableDebugLogForTest()
panic(err1) panic(err1)
} }
@@ -1201,20 +1141,14 @@ func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) {
_, err2 := th.App.UpdateRole(role) _, err2 := th.App.UpdateRole(role)
if err2 != nil { if err2 != nil {
utils.EnableDebugLogForTest()
panic(err2) panic(err2)
} }
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) { func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) {
utils.DisableDebugLogForTest()
role, err1 := th.App.GetRoleByName(context.Background(), roleName) role, err1 := th.App.GetRoleByName(context.Background(), roleName)
if err1 != nil { if err1 != nil {
utils.EnableDebugLogForTest()
panic(err1) panic(err1)
} }
@@ -1226,7 +1160,6 @@ func (th *TestHelper) RemovePermissionFromRole(permission string, roleName strin
} }
if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") { if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
utils.EnableDebugLogForTest()
return return
} }
@@ -1234,25 +1167,18 @@ func (th *TestHelper) RemovePermissionFromRole(permission string, roleName strin
_, err2 := th.App.UpdateRole(role) _, err2 := th.App.UpdateRole(role)
if err2 != nil { if err2 != nil {
utils.EnableDebugLogForTest()
panic(err2) panic(err2)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) AddPermissionToRole(permission string, roleName string) { func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
utils.DisableDebugLogForTest()
role, err1 := th.App.GetRoleByName(context.Background(), roleName) role, err1 := th.App.GetRoleByName(context.Background(), roleName)
if err1 != nil { if err1 != nil {
utils.EnableDebugLogForTest()
panic(err1) panic(err1)
} }
for _, existingPermission := range role.Permissions { for _, existingPermission := range role.Permissions {
if existingPermission == permission { if existingPermission == permission {
utils.EnableDebugLogForTest()
return return
} }
} }
@@ -1261,11 +1187,8 @@ func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
_, err2 := th.App.UpdateRole(role) _, err2 := th.App.UpdateRole(role)
if err2 != nil { if err2 != nil {
utils.EnableDebugLogForTest()
panic(err2) panic(err2)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) SetupTeamScheme() *model.Scheme { func (th *TestHelper) SetupTeamScheme() *model.Scheme {

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

@@ -21,7 +21,6 @@ import (
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock" "github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock"
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks" "github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
"github.com/mattermost/mattermost-server/v6/utils"
) )
func TestCreateChannel(t *testing.T) { func TestCreateChannel(t *testing.T) {
@@ -3238,7 +3237,6 @@ func TestAutocompleteChannels(t *testing.T) {
defer th.TearDown() defer th.TearDown()
// A private channel to make sure private channels are not used // A private channel to make sure private channels are not used
utils.DisableDebugLogForTest()
ptown, _, _ := th.Client.CreateChannel(&model.Channel{ ptown, _, _ := th.Client.CreateChannel(&model.Channel{
DisplayName: "Town", DisplayName: "Town",
Name: "town", Name: "town",
@@ -3251,7 +3249,6 @@ func TestAutocompleteChannels(t *testing.T) {
Type: model.ChannelTypeOpen, Type: model.ChannelTypeOpen,
TeamId: th.BasicTeam.Id, TeamId: th.BasicTeam.Id,
}) })
utils.EnableDebugLogForTest()
defer func() { defer func() {
th.Client.DeleteChannel(ptown.Id) th.Client.DeleteChannel(ptown.Id)
th.Client.DeleteChannel(tower.Id) th.Client.DeleteChannel(tower.Id)
@@ -3320,7 +3317,6 @@ func TestAutocompleteChannelsForSearch(t *testing.T) {
defer th.App.PermanentDeleteUser(th.Context, u4) defer th.App.PermanentDeleteUser(th.Context, u4)
// A private channel to make sure private channels are not used // A private channel to make sure private channels are not used
utils.DisableDebugLogForTest()
ptown, _, _ := th.SystemAdminClient.CreateChannel(&model.Channel{ ptown, _, _ := th.SystemAdminClient.CreateChannel(&model.Channel{
DisplayName: "Town", DisplayName: "Town",
Name: "town", Name: "town",
@@ -3339,7 +3335,6 @@ func TestAutocompleteChannelsForSearch(t *testing.T) {
defer func() { defer func() {
th.Client.DeleteChannel(mypriv.Id) th.Client.DeleteChannel(mypriv.Id)
}() }()
utils.EnableDebugLogForTest()
dc1, _, err := th.Client.CreateDirectChannel(th.BasicUser.Id, u1.Id) dc1, _, err := th.Client.CreateDirectChannel(th.BasicUser.Id, u1.Id)
require.NoError(t, err) require.NoError(t, err)
@@ -3450,7 +3445,6 @@ func TestAutocompleteChannelsForSearchGuestUsers(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// A private channel to make sure private channels are not used // A private channel to make sure private channels are not used
utils.DisableDebugLogForTest()
town, _, _ := th.SystemAdminClient.CreateChannel(&model.Channel{ town, _, _ := th.SystemAdminClient.CreateChannel(&model.Channel{
DisplayName: "Town", DisplayName: "Town",
Name: "town", Name: "town",
@@ -3475,8 +3469,6 @@ func TestAutocompleteChannelsForSearchGuestUsers(t *testing.T) {
_, _, err = th.SystemAdminClient.AddChannelMember(mypriv.Id, guest.Id) _, _, err = th.SystemAdminClient.AddChannelMember(mypriv.Id, guest.Id)
require.NoError(t, err) require.NoError(t, err)
utils.EnableDebugLogForTest()
dc1, _, err := th.SystemAdminClient.CreateDirectChannel(th.BasicUser.Id, guest.Id) dc1, _, err := th.SystemAdminClient.CreateDirectChannel(th.BasicUser.Id, guest.Id)
require.NoError(t, err) require.NoError(t, err)
defer func() { defer func() {

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

@@ -4,7 +4,6 @@
package api4 package api4
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@@ -468,7 +467,7 @@ func TestUpdateConfigDiffInAuditRecord(t *testing.T) {
require.Equal(t, timeoutVal+1, *cfg.ServiceSettings.ReadTimeout) require.Equal(t, timeoutVal+1, *cfg.ServiceSettings.ReadTimeout)
// Forcing a flush before attempting to read log's content. // 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, err)
require.NoError(t, logFile.Sync()) require.NoError(t, logFile.Sync())

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

@@ -7,7 +7,6 @@ import (
"flag" "flag"
"testing" "testing"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -25,8 +24,6 @@ func TestMain(m *testing.M) {
WithReadReplica: replicaFlag, WithReadReplica: replicaFlag,
} }
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options) mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -327,9 +327,12 @@ func TestGetLogs(t *testing.T) {
mlog.Info(strconv.Itoa(i)) 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) { th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
logs, _, err := c.GetLogs(0, 10) logs, _, err2 := c.GetLogs(0, 10)
require.NoError(t, err) require.NoError(t, err2)
require.Len(t, logs, 10) require.Len(t, logs, 10)
for i := 10; i < 20; i++ { 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.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
_, resp, err := th.Client.GetLogs(0, 10) _, resp, err2 := th.Client.GetLogs(0, 10)
require.Error(t, err) require.Error(t, err2)
CheckForbiddenStatus(t, resp) CheckForbiddenStatus(t, resp)
}) })

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

@@ -4,7 +4,6 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@@ -13,11 +12,11 @@ import (
"runtime/debug" "runtime/debug"
"time" "time"
"github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/i18n" "github.com/mattermost/mattermost-server/v6/shared/i18n"
"github.com/mattermost/mattermost-server/v6/shared/mail" "github.com/mattermost/mattermost-server/v6/shared/mail"
"github.com/mattermost/mattermost-server/v6/shared/mlog" "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) { 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 var lines []string
if *s.Config().LogSettings.EnableFile { if *s.Config().LogSettings.EnableFile {
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), mlog.DefaultFlushTimeout) s.Log.Flush()
defer timeoutCancel() logFile := config.GetLogFileLocation(*s.Config().LogSettings.FileLocation)
mlog.Flush(timeoutCtx)
logFile := utils.GetLogFileLocation(*s.Config().LogSettings.FileLocation)
file, err := os.Open(logFile) file, err := os.Open(logFile)
if err != nil { if err != nil {
return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError) 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 logs an audit record using default LvlAuditCLI.
LogAuditRec(rec *audit.Record, err error) LogAuditRec(rec *audit.Record, err error)
// LogAuditRecWithLevel logs an audit record using specified Level. // 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 creates a audit record pre-populated with defaults.
MakeAuditRecord(event string, initialStatus string) *audit.Record MakeAuditRecord(event string, initialStatus string) *audit.Record
// MarkChanelAsUnreadFromPost will take a post and set the channel as unread from that one. // MarkChanelAsUnreadFromPost will take a post and set the channel as unread from that one.

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

@@ -9,8 +9,6 @@ import (
"net/http" "net/http"
"os/user" "os/user"
"github.com/hashicorp/go-multierror"
"github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/audit"
"github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/model" "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. // 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 { if rec == nil {
return 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 { func (s *Server) configureAudit(adt *audit.Audit, bAllowAdvancedLogging bool) error {
var errs error
adt.OnQueueFull = s.onAuditTargetQueueFull adt.OnQueueFull = s.onAuditTargetQueueFull
adt.OnError = s.onAuditError adt.OnError = s.onAuditError
// Configure target for rotating file output (E0, E10) var logConfigSrc config.LogConfigSrc
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.
dsn := *s.Config().ExperimentalAuditSettings.AdvancedLoggingConfig dsn := *s.Config().ExperimentalAuditSettings.AdvancedLoggingConfig
if !bAllowAdvancedLogging || dsn == "" { if bAllowAdvancedLogging && dsn != "" {
return errs var err error
} logConfigSrc, err = config.NewLogConfigSrc(dsn, s.configStore)
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 err != nil { if err != nil {
errs = multierror.Append(err) return fmt.Errorf("invalid config source for audit, %w", err)
continue
}
if target != nil {
adt.AddTarget(target)
} }
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 { 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"
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks" "github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
"github.com/mattermost/mattermost-server/v6/utils"
) )
type TestHelper struct { type TestHelper struct {
@@ -172,12 +171,10 @@ func (th *TestHelper) CreateTeam() *model.Team {
Type: model.TeamOpen, Type: model.TeamOpen,
} }
utils.DisableDebugLogForTest()
var err error var err error
if team, err = th.store.Team().Save(team); err != nil { if team, err = th.store.Team().Save(team); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return team return team
} }
@@ -192,13 +189,11 @@ func (th *TestHelper) createChannel(team *model.Team, channelType string) *model
CreatorId: th.BasicUser.Id, CreatorId: th.BasicUser.Id,
} }
utils.DisableDebugLogForTest()
var err error var err error
if channel, err = th.store.Channel().Save(channel, *th.configStore.Get().TeamSettings.MaxChannelsPerTeam); err != nil { if channel, err = th.store.Channel().Save(channel, *th.configStore.Get().TeamSettings.MaxChannelsPerTeam); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }

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

@@ -7,7 +7,6 @@ import (
"flag" "flag"
"testing" "testing"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -26,8 +25,6 @@ func TestMain(m *testing.M) {
WithReadReplica: replicaFlag, WithReadReplica: replicaFlag,
} }
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options) mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -16,7 +16,6 @@ import (
"time" "time"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
) )
var randomJPEG []byte var randomJPEG []byte
@@ -59,8 +58,7 @@ func BenchmarkUploadFile(b *testing.B) {
prepareTestImages(b) prepareTestImages(b)
th := Setup(b).InitBasic() th := Setup(b).InitBasic()
defer th.TearDown() defer th.TearDown()
// disable logging in the benchmark, as best we can
th.App.Log().SetConsoleLevel(mlog.LevelError)
teamID := model.NewId() teamID := model.NewId()
channelID := model.NewId() channelID := model.NewId()
userID := model.NewId() userID := model.NewId()

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

@@ -4,7 +4,6 @@
package app package app
import ( import (
"bytes"
"context" "context"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -27,7 +26,6 @@ import (
"github.com/mattermost/mattermost-server/v6/store/sqlstore" "github.com/mattermost/mattermost-server/v6/store/sqlstore"
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks" "github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
"github.com/mattermost/mattermost-server/v6/utils"
) )
type TestHelper struct { type TestHelper struct {
@@ -41,7 +39,8 @@ type TestHelper struct {
BasicPost *model.Post BasicPost *model.Post
SystemAdminUser *model.User SystemAdminUser *model.User
LogBuffer *bytes.Buffer LogBuffer *mlog.Buffer
TestLogger *mlog.Logger
IncludeCacheLayer bool IncludeCacheLayer bool
tempWorkspace string tempWorkspace string
@@ -55,16 +54,16 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
configStore := config.NewTestMemoryStore() configStore := config.NewTestMemoryStore()
config := configStore.Get() memoryConfig := configStore.Get()
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins") *memoryConfig.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp") *memoryConfig.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
*config.PluginSettings.AutomaticPrepackagedPlugins = false *memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
*config.LogSettings.EnableSentry = false // disable error reporting during tests *memoryConfig.LogSettings.EnableSentry = false // disable error reporting during tests
*config.AnnouncementSettings.AdminNoticesEnabled = false *memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
*config.AnnouncementSettings.UserNoticesEnabled = false *memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
configStore.Set(config) configStore.Set(memoryConfig)
buffer := &bytes.Buffer{} buffer := &mlog.Buffer{}
var options []Option var options []Option
options = append(options, ConfigStore(configStore)) options = append(options, ConfigStore(configStore))
@@ -80,7 +79,18 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
} else { } else {
options = append(options, StoreOverride(dbStore)) 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...) s, err := NewServer(options...)
if err != nil { if err != nil {
@@ -92,6 +102,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
Context: &request.Context{}, Context: &request.Context{},
Server: s, Server: s,
LogBuffer: buffer, LogBuffer: buffer,
TestLogger: testLogger,
IncludeCacheLayer: includeCacheLayer, IncludeCacheLayer: includeCacheLayer,
} }
@@ -240,12 +251,10 @@ func (th *TestHelper) CreateTeam() *model.Team {
Type: model.TeamOpen, Type: model.TeamOpen,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if team, err = th.App.CreateTeam(th.Context, team); err != nil { if team, err = th.App.CreateTeam(th.Context, team); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return team return team
} }
@@ -268,7 +277,6 @@ func (th *TestHelper) CreateUserOrGuest(guest bool) *model.User {
EmailVerified: true, EmailVerified: true,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if guest { if guest {
if user, err = th.App.CreateGuest(th.Context, user); err != nil { 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) panic(err)
} }
} }
utils.EnableDebugLogForTest()
return user return user
} }
@@ -331,7 +338,6 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
option(channel) option(channel)
} }
utils.DisableDebugLogForTest()
var appErr *model.AppError var appErr *model.AppError
if channel, appErr = th.App.CreateChannel(th.Context, channel, true); appErr != nil { if channel, appErr = th.App.CreateChannel(th.Context, channel, true); appErr != nil {
panic(appErr) panic(appErr)
@@ -353,29 +359,24 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
panic(err) panic(err)
} }
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel { func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
var channel *model.Channel var channel *model.Channel
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil { if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
func (th *TestHelper) CreateGroupChannel(user1 *model.User, user2 *model.User) *model.Channel { func (th *TestHelper) CreateGroupChannel(user1 *model.User, user2 *model.User) *model.Channel {
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
var channel *model.Channel var channel *model.Channel
if channel, err = th.App.CreateGroupChannel([]string{th.BasicUser.Id, user1.Id, user2.Id}, th.BasicUser.Id); err != nil { if channel, err = th.App.CreateGroupChannel([]string{th.BasicUser.Id, user1.Id, user2.Id}, th.BasicUser.Id); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
@@ -389,12 +390,10 @@ func (th *TestHelper) CreatePost(channel *model.Channel) *model.Post {
CreateAt: model.GetMillis() - 10000, CreateAt: model.GetMillis() - 10000,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil { if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return post return post
} }
@@ -406,47 +405,32 @@ func (th *TestHelper) CreateMessagePost(channel *model.Channel, message string)
CreateAt: model.GetMillis() - 10000, CreateAt: model.GetMillis() - 10000,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil { if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return post return post
} }
func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) { func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
utils.DisableDebugLogForTest()
_, err := th.App.JoinUserToTeam(th.Context, team, user, "") _, err := th.App.JoinUserToTeam(th.Context, team, user, "")
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) RemoveUserFromTeam(user *model.User, team *model.Team) { func (th *TestHelper) RemoveUserFromTeam(user *model.User, team *model.Team) {
utils.DisableDebugLogForTest()
err := th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, "") err := th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, "")
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember { func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
utils.DisableDebugLogForTest()
member, err := th.App.AddUserToChannel(user, channel, false) member, err := th.App.AddUserToChannel(user, channel, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return member return member
} }
@@ -456,8 +440,6 @@ func (th *TestHelper) CreateRole(roleName string) *model.Role {
} }
func (th *TestHelper) CreateScheme() (*model.Scheme, []*model.Role) { func (th *TestHelper) CreateScheme() (*model.Scheme, []*model.Role) {
utils.DisableDebugLogForTest()
scheme, err := th.App.CreateScheme(&model.Scheme{ scheme, err := th.App.CreateScheme(&model.Scheme{
DisplayName: "Test Scheme Display Name", DisplayName: "Test Scheme Display Name",
Name: model.NewId(), Name: model.NewId(),
@@ -485,9 +467,6 @@ func (th *TestHelper) CreateScheme() (*model.Scheme, []*model.Role) {
} }
roles = append(roles, role) roles = append(roles, role)
} }
utils.EnableDebugLogForTest()
return scheme, roles return scheme, roles
} }
@@ -501,18 +480,14 @@ func (th *TestHelper) CreateGroup() *model.Group {
RemoteId: model.NewId(), RemoteId: model.NewId(),
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if group, err = th.App.CreateGroup(group); err != nil { if group, err = th.App.CreateGroup(group); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return group return group
} }
func (th *TestHelper) CreateEmoji() *model.Emoji { func (th *TestHelper) CreateEmoji() *model.Emoji {
utils.DisableDebugLogForTest()
emoji, err := th.App.Srv().Store.Emoji().Save(&model.Emoji{ emoji, err := th.App.Srv().Store.Emoji().Save(&model.Emoji{
CreatorId: th.BasicUser.Id, CreatorId: th.BasicUser.Id,
Name: model.NewRandomString(10), Name: model.NewRandomString(10),
@@ -520,15 +495,10 @@ func (th *TestHelper) CreateEmoji() *model.Emoji {
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return emoji return emoji
} }
func (th *TestHelper) AddReactionToPost(post *model.Post, user *model.User, emojiName string) *model.Reaction { 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{ reaction, err := th.App.SaveReactionForPost(th.Context, &model.Reaction{
UserId: user.Id, UserId: user.Id,
PostId: post.Id, PostId: post.Id,
@@ -537,9 +507,6 @@ func (th *TestHelper) AddReactionToPost(post *model.Post, user *model.User, emoj
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return reaction return reaction
} }
@@ -668,11 +635,8 @@ func (th *TestHelper) SetupPluginAPI() *PluginAPI {
} }
func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) { func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) {
utils.DisableDebugLogForTest()
role, err1 := th.App.GetRoleByName(context.Background(), roleName) role, err1 := th.App.GetRoleByName(context.Background(), roleName)
if err1 != nil { if err1 != nil {
utils.EnableDebugLogForTest()
panic(err1) panic(err1)
} }
@@ -684,7 +648,6 @@ func (th *TestHelper) RemovePermissionFromRole(permission string, roleName strin
} }
if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") { if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
utils.EnableDebugLogForTest()
return return
} }
@@ -692,25 +655,18 @@ func (th *TestHelper) RemovePermissionFromRole(permission string, roleName strin
_, err2 := th.App.UpdateRole(role) _, err2 := th.App.UpdateRole(role)
if err2 != nil { if err2 != nil {
utils.EnableDebugLogForTest()
panic(err2) panic(err2)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) AddPermissionToRole(permission string, roleName string) { func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
utils.DisableDebugLogForTest()
role, err1 := th.App.GetRoleByName(context.Background(), roleName) role, err1 := th.App.GetRoleByName(context.Background(), roleName)
if err1 != nil { if err1 != nil {
utils.EnableDebugLogForTest()
panic(err1) panic(err1)
} }
for _, existingPermission := range role.Permissions { for _, existingPermission := range role.Permissions {
if existingPermission == permission { if existingPermission == permission {
utils.EnableDebugLogForTest()
return return
} }
} }
@@ -719,11 +675,8 @@ func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
_, err2 := th.App.UpdateRole(role) _, err2 := th.App.UpdateRole(role)
if err2 != nil { if err2 != nil {
utils.EnableDebugLogForTest()
panic(err2) panic(err2)
} }
utils.EnableDebugLogForTest()
} }
// This function is copy of storetest/NewTestId // This function is copy of storetest/NewTestId

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

@@ -7,7 +7,6 @@ import (
"flag" "flag"
"testing" "testing"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -26,8 +25,6 @@ func TestMain(m *testing.M) {
WithReadReplica: replicaFlag, WithReadReplica: replicaFlag,
} }
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options) mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -11251,7 +11251,7 @@ func (a *OpenTracingAppLayer) LogAuditRec(rec *audit.Record, err error) {
a.app.LogAuditRec(rec, err) 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 origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.LogAuditRecWithLevel") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.LogAuditRecWithLevel")

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

@@ -25,7 +25,7 @@ type PluginAPI struct {
id string id string
app *App app *App
ctx *request.Context ctx *request.Context
logger *mlog.SugarLogger logger mlog.Sugar
manifest *model.Manifest manifest *model.Manifest
} }
@@ -35,7 +35,7 @@ func NewPluginAPI(a *App, c *request.Context, manifest *model.Manifest) *PluginA
manifest: manifest, manifest: manifest,
ctx: c, ctx: c,
app: a, 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) _, err := th.App.CreatePost(th.Context, post, th.BasicChannel, false, true)
assert.Nil(t, err) assert.Nil(t, err)
th.TestLogger.Flush()
// We shutdown plugins first so that the read on the log buffer is race-free. // We shutdown plugins first so that the read on the log buffer is race-free.
th.App.Srv().ShutDownPlugins() th.App.Srv().ShutDownPlugins()
tearDown() 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) _, appErr := th.App.CreatePostAsUser(th.Context, post, "", true)
require.Nil(t, appErr) 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) { 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) _, appErr = th.App.CreatePostAsUser(th.Context, post, "", true)
require.Nil(t, appErr) 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) { 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 statusCache cache.Cache
configListenerId string configListenerId string
licenseListenerId string licenseListenerId string
logListenerId string
clusterLeaderListenerId string clusterLeaderListenerId string
searchConfigListenerId string searchConfigListenerId string
searchLicenseListenerId string searchLicenseListenerId string
@@ -146,8 +145,6 @@ type Server struct {
configStore *config.Store configStore *config.Store
postActionCookieSecret []byte postActionCookieSecret []byte
advancedLogListenerCleanup func()
pluginCommands []*PluginCommand pluginCommands []*PluginCommand
pluginCommandsLock sync.RWMutex pluginCommandsLock sync.RWMutex
@@ -787,74 +784,79 @@ func (s *Server) DatabaseTypeAndMattermostVersion() (string, string) {
return *s.Config().SqlSettings.DriverName, mattermostVersion.Value 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 { func (s *Server) initLogging() error {
var err error
// create the app logger if needed
if s.Log == nil { if s.Log == nil {
s.Log = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(&s.Config().LogSettings, utils.GetLogFileLocation)) s.Log, err = mlog.NewLogger()
}
// 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)
if err != nil { if err != nil {
return fmt.Errorf("invalid advanced logging config, %w", err) return err
} }
}
if err := s.Log.ConfigAdvancedLogging(cfg.Get()); err != nil { // create notification logger if needed
return fmt.Errorf("error configuring advanced logging, %w", err) 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)) 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.
listenerId := cfg.AddListener(func(_, newCfg mlog.LogTargetCfg) { if !errors.Is(err, mlog.ErrConfigurationLock) {
if err := s.Log.ConfigAdvancedLogging(newCfg); err != nil { // revert to default logger if the config is invalid
mlog.Error("Error re-configuring advanced logging", mlog.Err(err)) mlog.InitGlobalLogger(nil)
} else { return err
mlog.Info("Re-configured advanced logging")
}
})
// In case initLogging is called more than once.
if s.advancedLogListenerCleanup != nil {
s.advancedLogListenerCleanup()
} }
}
s.advancedLogListenerCleanup = func() { // Redirect default Go logger to app logger.
cfg.RemoveListener(listenerId) 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 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) { func (s *Server) removeUnlicensedLogTargets(license *model.License) {
if license != nil && *license.Features.AdvancedLogging { if license != nil && *license.Features.AdvancedLogging {
// advanced logging enabled via license; no need to remove any targets // 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) timeoutCtx, cancelCtx := context.WithTimeout(context.Background(), time.Second*10)
defer cancelCtx() defer cancelCtx()
mlog.RemoveTargets(timeoutCtx, func(ti mlog.TargetInfo) bool { s.Log.RemoveTargets(timeoutCtx, func(ti mlog.TargetInfo) bool {
return ti.Type != "*target.Writer" && ti.Type != "*target.File" 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 return
} }
if err := mlog.EnableMetrics(s.Metrics.GetLoggerMetricsCollector()); err != nil { s.Log.SetMetricsCollector(s.Metrics.GetLoggerMetricsCollector(), mlog.DefaultMetricsUpdateFreqMillis)
mlog.Error("Failed to enable advanced logging metrics", mlog.Err(err))
} else { // logging config needs to be reloaded when metrics collector is added or changed.
mlog.Debug("Advanced logging metrics enabled") if err := s.initLogging(); err != nil {
mlog.Error("Error re-configuring logging for metrics")
return
} }
mlog.Debug("Logging metrics enabled")
} }
const TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second const TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second
@@ -1014,13 +1024,7 @@ func (s *Server) Shutdown() {
s.WaitForGoroutines() s.WaitForGoroutines()
if s.advancedLogListenerCleanup != nil {
s.advancedLogListenerCleanup()
s.advancedLogListenerCleanup = nil
}
s.RemoveConfigListener(s.configListenerId) s.RemoveConfigListener(s.configListenerId)
s.RemoveConfigListener(s.logListenerId)
s.stopSearchEngine() s.stopSearchEngine()
s.Audit.Shutdown() 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() s.dndTaskMut.Lock()
if s.dndTask != nil { if s.dndTask != nil {
s.dndTask.Cancel() s.dndTask.Cancel()
@@ -1072,10 +1070,15 @@ func (s *Server) Shutdown() {
mlog.Info("Server stopped") mlog.Info("Server stopped")
// this should just write the "server stopped" record, the rest are already flushed. // shutdown main and notification loggers which will flush any remaining log records.
timeoutCtx2, timeoutCancel2 := context.WithTimeout(context.Background(), time.Second*5) timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), time.Second*15)
defer timeoutCancel2() defer timeoutCancel()
_ = mlog.ShutdownAdvancedLogging(timeoutCtx2) 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 { 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 we have debugging of CORS turned on then forward messages to logs
if debug { 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) handler = corsWrapper.Handler(handler)
@@ -1222,10 +1225,7 @@ func (s *Server) Start() error {
s.Busy = NewBusy(s.Cluster) s.Busy = NewBusy(s.Cluster)
// Creating a logger for logging errors from http.Server at error level // Creating a logger for logging errors from http.Server at error level
errStdLog, err := s.Log.StdLogAt(mlog.LevelError, mlog.String("source", "httpserver")) errStdLog := s.Log.With(mlog.String("source", "httpserver")).StdLogger(mlog.LvlError)
if err != nil {
return err
}
s.Server = &http.Server{ s.Server = &http.Server{
Handler: handler, Handler: handler,
@@ -1270,7 +1270,7 @@ func (s *Server) Start() error {
server := &http.Server{ server := &http.Server{
Addr: httpListenAddress, Addr: httpListenAddress,
Handler: m.HTTPHandler(nil), 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() go server.ListenAndServe()
} else { } else {
@@ -1284,7 +1284,7 @@ func (s *Server) Start() error {
server := &http.Server{ server := &http.Server{
Handler: http.HandlerFunc(handleHTTPRedirect), 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) server.Serve(redirectListener)
}() }()
@@ -2026,7 +2026,7 @@ func (a *App) getNotificationsLog() (*model.FileData, string) {
// Getting notifications.log // Getting notifications.log
if *a.Srv().Config().NotificationLogSettings.EnableFile { if *a.Srv().Config().NotificationLogSettings.EnableFile {
// notifications.log // notifications.log
notificationsLog := utils.GetNotificationsLogFileLocation(*a.Srv().Config().LogSettings.FileLocation) notificationsLog := config.GetNotificationsLogFileLocation(*a.Srv().Config().LogSettings.FileLocation)
notificationsLogFileData, notificationsLogFileDataErr := ioutil.ReadFile(notificationsLog) notificationsLogFileData, notificationsLogFileDataErr := ioutil.ReadFile(notificationsLog)
@@ -2053,7 +2053,7 @@ func (a *App) getMattermostLog() (*model.FileData, string) {
// Getting mattermost.log // Getting mattermost.log
if *a.Srv().Config().LogSettings.EnableFile { if *a.Srv().Config().LogSettings.EnableFile {
// mattermost.log // mattermost.log
mattermostLog := utils.GetLogFileLocation(*a.Srv().Config().LogSettings.FileLocation) mattermostLog := config.GetLogFileLocation(*a.Srv().Config().LogSettings.FileLocation)
mattermostLogFileData, mattermostLogFileDataErr := ioutil.ReadFile(mattermostLog) 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) { func TestPanicLog(t *testing.T) {
// Creating a temp file to collect logs // Creating a temp dir for log
tmpfile, err := ioutil.TempFile("", "mlog") tmpDir, err := os.MkdirTemp("", "mlog-test")
if err != nil { require.NoError(t, err, "cannot create tmp dir for log file")
require.NoError(t, err)
}
defer func() { defer func() {
require.NoError(t, tmpfile.Close()) err2 := os.RemoveAll(tmpDir)
require.NoError(t, os.Remove(tmpfile.Name())) 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 // Creating logger to log to console and temp file
logger := mlog.NewLogger(&mlog.LoggerConfiguration{ logger, _ := mlog.NewLogger()
EnableConsole: true,
ConsoleJson: true, logSettings := model.NewLogSettings()
EnableFile: true, logSettings.EnableConsole = model.NewBool(true)
FileLocation: tmpfile.Name(), logSettings.ConsoleJson = model.NewBool(true)
FileLevel: mlog.LevelInfo, 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 // Creating a server with logger
s, err := NewServer(SetLogger(logger)) s, err := NewServer(SetLogger(logger))
@@ -567,16 +567,22 @@ func TestPanicLog(t *testing.T) {
client := &http.Client{Transport: tr} client := &http.Client{Transport: tr}
client.Get("https://localhost:" + strconv.Itoa(s.ListenAddr.Port) + "/panic") client.Get("https://localhost:" + strconv.Itoa(s.ListenAddr.Port) + "/panic")
err = logger.Flush()
assert.NoError(t, err, "flush should succeed")
s.Shutdown() s.Shutdown()
// Checking whether panic was logged // Checking whether panic was logged
var panicLogged = false var panicLogged = false
var infoLogged = 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) require.NoError(t, err)
scanner := bufio.NewScanner(tmpfile) scanner := bufio.NewScanner(logFile)
for scanner.Scan() { for scanner.Scan() {
if !infoLogged && strings.Contains(scanner.Text(), "inside panic handler") { if !infoLogged && strings.Contains(scanner.Text(), "inside panic handler") {
infoLogged = true infoLogged = true

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

@@ -21,7 +21,6 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/store" "github.com/mattermost/mattermost-server/v6/store"
"github.com/mattermost/mattermost-server/v6/store/localcachelayer" "github.com/mattermost/mattermost-server/v6/store/localcachelayer"
"github.com/mattermost/mattermost-server/v6/utils"
) )
type TestHelper struct { type TestHelper struct {
@@ -36,6 +35,7 @@ type TestHelper struct {
SystemAdminUser *model.User SystemAdminUser *model.User
LogBuffer *bytes.Buffer LogBuffer *bytes.Buffer
TestLogger *mlog.Logger
IncludeCacheLayer bool IncludeCacheLayer bool
tempWorkspace string tempWorkspace string
@@ -49,15 +49,15 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
memoryStore := config.NewTestMemoryStore() memoryStore := config.NewTestMemoryStore()
config := memoryStore.Get() memoryConfig := memoryStore.Get()
if configSet != nil { if configSet != nil {
configSet(config) configSet(memoryConfig)
} }
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins") *memoryConfig.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp") *memoryConfig.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
*config.PluginSettings.AutomaticPrepackagedPlugins = false *memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
*config.LogSettings.EnableSentry = false // disable error reporting during tests *memoryConfig.LogSettings.EnableSentry = false // disable error reporting during tests
memoryStore.Set(config) memoryStore.Set(memoryConfig)
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
@@ -74,7 +74,15 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
} else { } else {
options = append(options, app.StoreOverride(dbStore)) 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...) s, err := app.NewServer(options...)
if err != nil { if err != nil {
@@ -86,6 +94,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
Context: &request.Context{}, Context: &request.Context{},
Server: s, Server: s,
LogBuffer: buffer, LogBuffer: buffer,
TestLogger: testLogger,
IncludeCacheLayer: includeCacheLayer, IncludeCacheLayer: includeCacheLayer,
} }
@@ -188,12 +197,11 @@ func (th *TestHelper) createTeam() *model.Team {
Type: model.TeamOpen, Type: model.TeamOpen,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if team, err = th.App.CreateTeam(th.Context, team); err != nil { if team, err = th.App.CreateTeam(th.Context, team); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return team return team
} }
@@ -216,7 +224,6 @@ func (th *TestHelper) createUserOrGuest(guest bool) *model.User {
EmailVerified: true, EmailVerified: true,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if guest { if guest {
if user, err = th.App.CreateGuest(th.Context, user); err != nil { 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) panic(err)
} }
} }
utils.EnableDebugLogForTest()
return user return user
} }
@@ -262,7 +268,6 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
option(channel) option(channel)
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil { if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil {
panic(err) panic(err)
@@ -284,7 +289,6 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
panic(err) panic(err)
} }
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
@@ -299,34 +303,28 @@ func (th *TestHelper) createChannelWithAnotherUser(team *model.Team, channelType
CreatorId: userID, CreatorId: userID,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil { if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
func (th *TestHelper) createDmChannel(user *model.User) *model.Channel { func (th *TestHelper) createDmChannel(user *model.User) *model.Channel {
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
var channel *model.Channel var channel *model.Channel
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil { if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
func (th *TestHelper) createGroupChannel(user1 *model.User, user2 *model.User) *model.Channel { func (th *TestHelper) createGroupChannel(user1 *model.User, user2 *model.User) *model.Channel {
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
var channel *model.Channel var channel *model.Channel
if channel, err = th.App.CreateGroupChannel([]string{th.BasicUser.Id, user1.Id, user2.Id}, th.BasicUser.Id); err != nil { if channel, err = th.App.CreateGroupChannel([]string{th.BasicUser.Id, user1.Id, user2.Id}, th.BasicUser.Id); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
@@ -340,36 +338,25 @@ func (th *TestHelper) createPost(channel *model.Channel) *model.Post {
CreateAt: model.GetMillis() - 10000, CreateAt: model.GetMillis() - 10000,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil { if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return post return post
} }
func (th *TestHelper) linkUserToTeam(user *model.User, team *model.Team) { func (th *TestHelper) linkUserToTeam(user *model.User, team *model.Team) {
utils.DisableDebugLogForTest()
_, err := th.App.JoinUserToTeam(th.Context, team, user, "") _, err := th.App.JoinUserToTeam(th.Context, team, user, "")
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) addUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember { func (th *TestHelper) addUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
utils.DisableDebugLogForTest()
member, err := th.App.AddUserToChannel(user, channel, false) member, err := th.App.AddUserToChannel(user, channel, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return member return member
} }
@@ -401,11 +388,8 @@ func (th *TestHelper) tearDown() {
} }
func (th *TestHelper) removePermissionFromRole(permission string, roleName string) { func (th *TestHelper) removePermissionFromRole(permission string, roleName string) {
utils.DisableDebugLogForTest()
role, err1 := th.App.GetRoleByName(context.Background(), roleName) role, err1 := th.App.GetRoleByName(context.Background(), roleName)
if err1 != nil { if err1 != nil {
utils.EnableDebugLogForTest()
panic(err1) panic(err1)
} }
@@ -417,7 +401,6 @@ func (th *TestHelper) removePermissionFromRole(permission string, roleName strin
} }
if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") { if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
utils.EnableDebugLogForTest()
return return
} }
@@ -425,25 +408,18 @@ func (th *TestHelper) removePermissionFromRole(permission string, roleName strin
_, err2 := th.App.UpdateRole(role) _, err2 := th.App.UpdateRole(role)
if err2 != nil { if err2 != nil {
utils.EnableDebugLogForTest()
panic(err2) panic(err2)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) addPermissionToRole(permission string, roleName string) { func (th *TestHelper) addPermissionToRole(permission string, roleName string) {
utils.DisableDebugLogForTest()
role, err1 := th.App.GetRoleByName(context.Background(), roleName) role, err1 := th.App.GetRoleByName(context.Background(), roleName)
if err1 != nil { if err1 != nil {
utils.EnableDebugLogForTest()
panic(err1) panic(err1)
} }
for _, existingPermission := range role.Permissions { for _, existingPermission := range role.Permissions {
if existingPermission == permission { if existingPermission == permission {
utils.EnableDebugLogForTest()
return return
} }
} }
@@ -452,9 +428,6 @@ func (th *TestHelper) addPermissionToRole(permission string, roleName string) {
_, err2 := th.App.UpdateRole(role) _, err2 := th.App.UpdateRole(role)
if err2 != nil { if err2 != nil {
utils.EnableDebugLogForTest()
panic(err2) panic(err2)
} }
utils.EnableDebugLogForTest()
} }

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

@@ -6,7 +6,6 @@ package slashcommands
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -18,8 +17,6 @@ func TestMain(m *testing.M) {
EnableResources: true, EnableResources: true,
} }
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options) mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -5,17 +5,12 @@ package audit
import ( import (
"fmt" "fmt"
"sort"
"github.com/mattermost/logr"
"github.com/mattermost/logr/format"
"github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/shared/mlog"
) )
type Audit struct { type Audit struct {
lgr *logr.Logr logger *mlog.Logger
logger logr.Logger
// OnQueueFull is called on an attempt to add an audit record to a full queue. // 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. // 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) { func (a *Audit) Init(maxQueueSize int) {
a.lgr = &logr.Logr{MaxQueueSize: maxQueueSize} a.logger, _ = mlog.NewLogger(
a.logger = a.lgr.NewLogger() mlog.MaxQueueSize(maxQueueSize),
mlog.OnLoggerError(a.onLoggerError),
a.lgr.OnQueueFull = a.onQueueFull mlog.OnQueueFull(a.onQueueFull),
a.lgr.OnTargetQueueFull = a.onTargetQueueFull mlog.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
} }
// LogRecord emits an audit record with complete info. // LogRecord emits an audit record with complete info.
func (a *Audit) LogRecord(level mlog.LogLevel, rec Record) { func (a *Audit) LogRecord(level mlog.Level, rec Record) {
flds := logr.Fields{} flds := []mlog.Field{
flds[KeyAPIPath] = rec.APIPath mlog.String(KeyAPIPath, rec.APIPath),
flds[KeyEvent] = rec.Event mlog.String(KeyEvent, rec.Event),
flds[KeyStatus] = rec.Status mlog.String(KeyStatus, rec.Status),
flds[KeyUserID] = rec.UserID mlog.String(KeyUserID, rec.UserID),
flds[KeySessionID] = rec.SessionID mlog.String(KeySessionID, rec.SessionID),
flds[KeyClient] = rec.Client mlog.String(KeyClient, rec.Client),
flds[KeyIPAddress] = rec.IPAddress mlog.String(KeyIPAddress, rec.IPAddress),
for k, v := range rec.Meta {
flds[k] = v
} }
l := a.logger.WithFields(flds) for k, v := range rec.Meta {
l.Log(logr.Level(level)) flds = append(flds, mlog.Any(k, v))
}
a.logger.Log(level, "", flds...)
} }
// Log emits an audit record based on minimum required info. // 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{ a.LogRecord(level, Record{
APIPath: path, APIPath: path,
Event: evt, 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. // Configure sets zero or more target to output audit logs to.
func (a *Audit) AddTarget(target logr.Target) { func (a *Audit) Configure(cfg mlog.LoggerConfiguration) error {
a.lgr.AddTarget(target) return a.logger.ConfigureTargets(cfg)
} }
// Shutdown cleanly stops the audit engine after making best efforts to flush all targets. // Flush attempts to write all queued audit records to all targets.
func (a *Audit) Shutdown() { func (a *Audit) Flush() error {
err := a.lgr.Shutdown() err := a.logger.Flush()
if err != nil { if err != nil {
a.onLoggerError(err) 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 { if a.OnQueueFull != nil {
return a.OnQueueFull("main", maxQueueSize) return a.OnQueueFull("main", maxQueueSize)
} }
@@ -107,7 +90,7 @@ func (a *Audit) onQueueFull(rec *logr.LogRec, maxQueueSize int) bool {
return true 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 { if a.OnQueueFull != nil {
return a.OnQueueFull(fmt.Sprintf("%v", target), maxQueueSize) 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) { func (a *Audit) onLoggerError(err error) {
if a.OnError != nil { if a.OnError != nil {
a.OnError(err) a.OnError(err)
return
} }
} mlog.Error("Auditing error", mlog.Err(err))
// 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
} }

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

@@ -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" "testing"
"github.com/mattermost/mattermost-server/v6/api4" "github.com/mattermost/mattermost-server/v6/api4"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -28,8 +27,6 @@ func TestMain(m *testing.M) {
EnableResources: true, EnableResources: true,
} }
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options) mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close() defer mainHelper.Close()
api4.SetMainHelper(mainHelper) api4.SetMainHelper(mainHelper)

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

@@ -57,7 +57,7 @@ func (e *logSrcEmitter) RemoveListener(id string) {
} }
// invokeConfigListeners synchronously notifies all listeners about the configuration change. // 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 { e.listeners.Range(func(key, value interface{}) bool {
listener := value.(LogSrcListener) listener := value.(LogSrcListener)
listener(oldCfg, newCfg) listener(oldCfg, newCfg)

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

@@ -56,18 +56,18 @@ func TestEmitter(t *testing.T) {
func TestLogSrcEmitter(t *testing.T) { func TestLogSrcEmitter(t *testing.T) {
var e logSrcEmitter var e logSrcEmitter
expectedOldCfg := make(mlog.LogTargetCfg) expectedOldCfg := make(mlog.LoggerConfiguration)
expectedNewCfg := make(mlog.LogTargetCfg) expectedNewCfg := make(mlog.LoggerConfiguration)
listener1 := false 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, expectedOldCfg, oldCfg)
assert.Equal(t, expectedNewCfg, newCfg) assert.Equal(t, expectedNewCfg, newCfg)
listener1 = true listener1 = true
}) })
listener2 := false 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, expectedOldCfg, oldCfg)
assert.Equal(t, expectedNewCfg, newCfg) assert.Equal(t, expectedNewCfg, newCfg)
listener2 = true listener2 = true

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

@@ -6,7 +6,6 @@ package config
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
@@ -14,23 +13,17 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog" "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 // LogConfigSrc abstracts the Advanced Logging configuration so that implementations can
// fetch from file, database, etc. // fetch from file, database, etc.
type LogConfigSrc interface { type LogConfigSrc interface {
// Get fetches the current, cached configuration. // Get fetches the current, cached configuration.
Get() mlog.LogTargetCfg Get() mlog.LoggerConfiguration
// Set updates the dsn specifying the source and reloads // Set updates the dsn specifying the source and reloads
Set(dsn string, configStore *Store) (err error) 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 cleans up resources.
Close() error Close() error
} }
@@ -38,6 +31,10 @@ type LogConfigSrc interface {
// NewLogConfigSrc creates an advanced logging configuration source, backed by a // NewLogConfigSrc creates an advanced logging configuration source, backed by a
// file, JSON string, or database. // file, JSON string, or database.
func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) { func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) {
if dsn == "" {
return nil, errors.New("dsn should not be empty")
}
if configStore == nil { if configStore == nil {
return nil, errors.New("configStore should not be 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 { type jsonSrc struct {
logSrcEmitter logSrcEmitter
mutex sync.RWMutex mutex sync.RWMutex
cfg mlog.LogTargetCfg cfg mlog.LoggerConfiguration
} }
func newJSONSrc(data string) (*jsonSrc, error) { func newJSONSrc(data string) (*jsonSrc, error) {
@@ -72,7 +69,7 @@ func newJSONSrc(data string) (*jsonSrc, error) {
} }
// Get fetches the current, cached configuration // Get fetches the current, cached configuration
func (src *jsonSrc) Get() mlog.LogTargetCfg { func (src *jsonSrc) Get() mlog.LoggerConfiguration {
src.mutex.RLock() src.mutex.RLock()
defer src.mutex.RUnlock() defer src.mutex.RUnlock()
return src.cfg return src.cfg
@@ -89,7 +86,7 @@ func (src *jsonSrc) Set(data string, _ *Store) error {
return nil return nil
} }
func (src *jsonSrc) set(cfg mlog.LogTargetCfg) { func (src *jsonSrc) set(cfg mlog.LoggerConfiguration) {
src.mutex.Lock() src.mutex.Lock()
defer src.mutex.Unlock() defer src.mutex.Unlock()
@@ -106,11 +103,9 @@ func (src *jsonSrc) Close() error {
// fileSrc // fileSrc
type fileSrc struct { type fileSrc struct {
logSrcEmitter
mutex sync.RWMutex mutex sync.RWMutex
cfg mlog.LogTargetCfg cfg mlog.LoggerConfiguration
path string
path string
} }
func newFileSrc(path string, configStore *Store) (*fileSrc, error) { 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 // Get fetches the current, cached configuration
func (src *fileSrc) Get() mlog.LogTargetCfg { func (src *fileSrc) Get() mlog.LoggerConfiguration {
src.mutex.RLock() src.mutex.RLock()
defer src.mutex.RUnlock() defer src.mutex.RUnlock()
return src.cfg return src.cfg
@@ -145,26 +140,14 @@ func (src *fileSrc) Set(path string, configStore *Store) error {
} }
src.set(cfg) 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 return nil
} }
func (src *fileSrc) set(cfg mlog.LogTargetCfg) { func (src *fileSrc) set(cfg mlog.LoggerConfiguration) {
src.mutex.Lock() src.mutex.Lock()
defer src.mutex.Unlock() defer src.mutex.Unlock()
old := src.cfg
src.cfg = cfg src.cfg = cfg
src.invokeConfigListeners(old, cfg)
} }
// Close cleans up resources. // Close cleans up resources.
@@ -172,8 +155,8 @@ func (src *fileSrc) Close() error {
return nil return nil
} }
func logTargetCfgFromJSON(data []byte) (mlog.LogTargetCfg, error) { func logTargetCfgFromJSON(data []byte) (mlog.LoggerConfiguration, error) {
cfg := make(mlog.LogTargetCfg) cfg := make(mlog.LoggerConfiguration)
err := json.Unmarshal(data, &cfg) err := json.Unmarshal(data, &cfg)
if err != nil { if err != nil {
return nil, err 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/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -23,8 +22,6 @@ func TestMain(m *testing.M) {
EnableStore: true, EnableStore: true,
} }
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options) mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -4,8 +4,8 @@
package einterfaces package einterfaces
import ( import (
"github.com/mattermost/logr"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
) )
type MetricsInterface interface { type MetricsInterface interface {
@@ -66,7 +66,7 @@ type MetricsInterface interface {
ObservePluginAPIDuration(pluginID, apiName string, success bool, elapsed float64) ObservePluginAPIDuration(pluginID, apiName string, success bool, elapsed float64)
ObserveEnabledUsers(users int64) ObserveEnabledUsers(users int64)
GetLoggerMetricsCollector() logr.MetricsCollector GetLoggerMetricsCollector() mlog.MetricsCollector
IncrementRemoteClusterMsgSentCounter(remoteID string) IncrementRemoteClusterMsgSentCounter(remoteID string)
IncrementRemoteClusterMsgReceivedCounter(remoteID string) IncrementRemoteClusterMsgReceivedCounter(remoteID string)

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

@@ -5,7 +5,7 @@
package mocks package mocks
import ( import (
logr "github.com/mattermost/logr" logr "github.com/mattermost/logr/v2"
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
model "github.com/mattermost/mattermost-server/v6/model" 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-hclog v0.16.1
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
github.com/hashicorp/go-msgpack v1.1.5 // 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-plugin v1.4.2
github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // 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/gosaml2 v0.3.3
github.com/mattermost/gziphandler v0.0.1 github.com/mattermost/gziphandler v0.0.1
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d 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/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-runewidth 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/russellhaering/goxmldsig v1.1.0
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 // indirect 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/smartystreets/assertions v1.0.0 // indirect
github.com/spf13/cobra v1.1.3 github.com/spf13/cobra v1.1.3
github.com/splitio/go-client/v6 v6.1.0 github.com/splitio/go-client/v6 v6.1.0
@@ -118,7 +119,7 @@ require (
github.com/ulikunitz/xz v0.5.10 // indirect github.com/ulikunitz/xz v0.5.10 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.4 github.com/vmihailenco/msgpack/v5 v5.3.4
github.com/wiggin77/merror v1.0.3 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/willf/bitset v1.1.11 // indirect
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c
github.com/yuin/goldmark v1.3.8 github.com/yuin/goldmark v1.3.8
@@ -126,7 +127,7 @@ require (
go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect go.opentelemetry.io/otel/internal/metric v0.21.0 // indirect
go.uber.org/atomic v1.8.0 // indirect go.uber.org/atomic v1.8.0 // indirect
go.uber.org/multierr v1.7.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/crypto v0.0.0-20210616213533-5ff15b29337e
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c
golang.org/x/net v0.0.0-20210614182718-04defd469f4e 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/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/mail.v2 v2.3.1 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/olivere/elastic.v6 v6.2.35
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
willnorris.com/go/imageproxy v0.10.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/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 h1:6F/fM3csvH6Oy5sUpJuW7YyZSzZZAhJm5VcgKMxA2P8=
github.com/mattermost/logr v1.0.13/go.mod h1:Mt4DPu1NXMe6JxPdwCC0XBoxXmN9eXOIRPoZarU2PXs= 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 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o=
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs= 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= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=

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

@@ -5,13 +5,14 @@ package migrations
import ( import (
"os" "os"
"testing"
"github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/app"
"github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/app/request"
"github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/model" "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/store/localcachelayer"
"github.com/mattermost/mattermost-server/v6/utils"
) )
type TestHelper struct { type TestHelper struct {
@@ -27,6 +28,8 @@ type TestHelper struct {
SystemAdminUser *model.User SystemAdminUser *model.User
tempWorkspace string tempWorkspace string
TestLogger *mlog.Logger
} }
func setupTestHelper(enterprise bool) *TestHelper { 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.StoreOverride(mainHelper.Store))
options = append(options, app.SkipPostInitializiation()) 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...) s, err := app.NewServer(options...)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -55,9 +67,10 @@ func setupTestHelper(enterprise bool) *TestHelper {
} }
th := &TestHelper{ th := &TestHelper{
App: app.New(app.ServerConnector(s)), App: app.New(app.ServerConnector(s)),
Context: &request.Context{}, Context: &request.Context{},
Server: s, Server: s,
TestLogger: testLogger,
} }
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 })
@@ -89,11 +102,11 @@ func setupTestHelper(enterprise bool) *TestHelper {
return th return th
} }
func SetupEnterprise() *TestHelper { func SetupEnterprise(tb testing.TB) *TestHelper {
return setupTestHelper(true) return setupTestHelper(true)
} }
func Setup() *TestHelper { func Setup(tb testing.TB) *TestHelper {
return setupTestHelper(false) return setupTestHelper(false)
} }
@@ -126,12 +139,10 @@ func (th *TestHelper) CreateTeam() *model.Team {
Type: model.TeamOpen, Type: model.TeamOpen,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if team, err = th.App.CreateTeam(th.Context, team); err != nil { if team, err = th.App.CreateTeam(th.Context, team); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return team return team
} }
@@ -146,12 +157,10 @@ func (th *TestHelper) CreateUser() *model.User {
EmailVerified: true, EmailVerified: true,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if user, err = th.App.CreateUser(th.Context, user); err != nil { if user, err = th.App.CreateUser(th.Context, user); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return user return user
} }
@@ -170,23 +179,19 @@ func (th *TestHelper) createChannel(team *model.Team, channelType model.ChannelT
CreatorId: th.BasicUser.Id, CreatorId: th.BasicUser.Id,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil { if channel, err = th.App.CreateChannel(th.Context, channel, true); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel { func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
var channel *model.Channel var channel *model.Channel
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil { if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return channel return channel
} }
@@ -200,36 +205,25 @@ func (th *TestHelper) CreatePost(channel *model.Channel) *model.Post {
CreateAt: model.GetMillis() - 10000, CreateAt: model.GetMillis() - 10000,
} }
utils.DisableDebugLogForTest()
var err *model.AppError var err *model.AppError
if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil { if post, err = th.App.CreatePost(th.Context, post, channel, false, true); err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return post return post
} }
func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) { func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
utils.DisableDebugLogForTest()
_, err := th.App.JoinUserToTeam(th.Context, team, user, "") _, err := th.App.JoinUserToTeam(th.Context, team, user, "")
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
} }
func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember { func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
utils.DisableDebugLogForTest()
member, err := th.App.AddUserToChannel(user, channel, false) member, err := th.App.AddUserToChannel(user, channel, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
utils.EnableDebugLogForTest()
return member return member
} }

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

@@ -6,7 +6,6 @@ package migrations
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -18,8 +17,6 @@ func TestMain(m *testing.M) {
EnableResources: true, EnableResources: true,
} }
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options) mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -16,7 +16,7 @@ func TestGetMigrationState(t *testing.T) {
if testing.Short() { if testing.Short() {
t.SkipNow() t.SkipNow()
} }
th := Setup() th := Setup(t)
defer th.TearDown() defer th.TearDown()
migrationKey := model.NewId() migrationKey := model.NewId()

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

@@ -1223,6 +1223,12 @@ type LogSettings struct {
AdvancedLoggingConfig *string `access:"environment_logging,write_restrictable,cloud_restrictable"` AdvancedLoggingConfig *string `access:"environment_logging,write_restrictable,cloud_restrictable"`
} }
func NewLogSettings() *LogSettings {
settings := &LogSettings{}
settings.SetDefaults()
return settings
}
func (s *LogSettings) SetDefaults() { func (s *LogSettings) SetDefaults() {
if s.EnableConsole == nil { if s.EnableConsole == nil {
s.EnableConsole = NewBool(true) 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 { 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 { func (h *hclogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {

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

@@ -52,12 +52,8 @@ func testPluginHealthCheckSuccess(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
bundle := model.BundleInfoForPath(dir) bundle := model.BundleInfoForPath(dir)
log := mlog.NewLogger(&mlog.LoggerConfiguration{ log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
EnableConsole: true, defer log.Shutdown()
ConsoleJson: true,
ConsoleLevel: "error",
EnableFile: false,
})
supervisor, err := newSupervisor(bundle, nil, nil, log, nil) supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
require.NoError(t, err) require.NoError(t, err)
@@ -99,12 +95,8 @@ func testPluginHealthCheckPanic(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
bundle := model.BundleInfoForPath(dir) bundle := model.BundleInfoForPath(dir)
log := mlog.NewLogger(&mlog.LoggerConfiguration{ log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
EnableConsole: true, defer log.Shutdown()
ConsoleJson: true,
ConsoleLevel: "error",
EnableFile: false,
})
supervisor, err := newSupervisor(bundle, nil, nil, log, nil) supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
require.NoError(t, err) require.NoError(t, err)

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

@@ -39,7 +39,7 @@ func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, driver Driver, par
wrappedLogger := pluginInfo.WrapLogger(parentLogger) wrappedLogger := pluginInfo.WrapLogger(parentLogger)
hclogAdaptedLogger := &hclogAdapter{ hclogAdaptedLogger := &hclogAdapter{
wrappedLogger: wrappedLogger.WithCallerSkip(1), wrappedLogger: wrappedLogger,
extrasKey: "wrapped_extras", 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) ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "server": {"executable": "/foo/../../backend.exe"}}`), 0600)
bundle := model.BundleInfoForPath(dir) bundle := model.BundleInfoForPath(dir)
log := mlog.NewLogger(&mlog.LoggerConfiguration{ log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
EnableConsole: true, defer log.Shutdown()
ConsoleJson: true,
ConsoleLevel: "error",
EnableFile: false,
})
supervisor, err := newSupervisor(bundle, nil, nil, log, nil) supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
assert.Nil(t, supervisor) assert.Nil(t, supervisor)
assert.Error(t, err) 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) ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "server": {"executable": "thisfileshouldnotexist"}}`), 0600)
bundle := model.BundleInfoForPath(dir) bundle := model.BundleInfoForPath(dir)
log := mlog.NewLogger(&mlog.LoggerConfiguration{ log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
EnableConsole: true, defer log.Shutdown()
ConsoleJson: true,
ConsoleLevel: "error",
EnableFile: false,
})
supervisor, err := newSupervisor(bundle, nil, nil, log, nil) supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
require.Error(t, err) require.Error(t, err)
require.Nil(t, supervisor) 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) ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "server": {"executable": "backend.exe"}}`), 0600)
bundle := model.BundleInfoForPath(dir) bundle := model.BundleInfoForPath(dir)
log := mlog.NewLogger(&mlog.LoggerConfiguration{ log := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
EnableConsole: true, defer log.Shutdown()
ConsoleJson: true,
ConsoleLevel: "error",
EnableFile: false,
})
supervisor, err := newSupervisor(bundle, nil, nil, log, nil) supervisor, err := newSupervisor(bundle, nil, nil, log, nil)
require.Error(t, err) require.Error(t, err)
require.Nil(t, supervisor) require.Nil(t, supervisor)

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

@@ -46,12 +46,7 @@ func makeLocalBackend(proxy *ImageProxy) *LocalBackend {
impl := imageproxy.NewProxy(proxy.HTTPService.MakeTransport(false), nil) impl := imageproxy.NewProxy(proxy.HTTPService.MakeTransport(false), nil)
if proxy.Logger != nil { if proxy.Logger != nil {
logger, err := proxy.Logger.StdLogAt(mlog.LevelDebug, mlog.String("image_proxy", "local")) impl.Logger = proxy.Logger.With(mlog.String("image_proxy", "local")).StdLogger(mlog.LvlDebug)
if err != nil {
mlog.Warn("Failed to initialize logger for image proxy", mlog.Err(err))
}
impl.Logger = logger
} }
baseURL, err := url.Parse(*proxy.ConfigService.Config().ServiceSettings.SiteURL) baseURL, err := url.Parse(*proxy.ConfigService.Config().ServiceSettings.SiteURL)

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

@@ -5,13 +5,8 @@ package remotecluster
import ( import (
"context" "context"
"fmt"
"strings"
"sync"
"testing" "testing"
"go.uber.org/zap/zapcore"
"github.com/mattermost/mattermost-server/v6/einterfaces" "github.com/mattermost/mattermost-server/v6/einterfaces"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock" "github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock"
@@ -22,14 +17,16 @@ import (
type mockServer struct { type mockServer struct {
remotes []*model.RemoteCluster remotes []*model.RemoteCluster
logger *mockLogger logger *mlog.Logger
user *model.User user *model.User
} }
func newMockServer(t *testing.T, remotes []*model.RemoteCluster) *mockServer { func newMockServer(t *testing.T, remotes []*model.RemoteCluster) *mockServer {
testLogger := mlog.CreateTestLogger(t, nil, mlog.StdAll...)
return &mockServer{ return &mockServer{
remotes: remotes, remotes: remotes,
logger: &mockLogger{t: t}, logger: testLogger,
} }
} }
@@ -64,88 +61,3 @@ func (ms *mockServer) GetStore() store.Store {
return storeMock return storeMock
} }
func (ms *mockServer) Shutdown() { ms.logger.Shutdown() } 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 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) { func TestOnReceiveChannelInvite(t *testing.T) {
t.Run("when msg payload is empty, it does nothing", func(t *testing.T) { t.Run("when msg payload is empty, it does nothing", func(t *testing.T) {

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

@@ -7,8 +7,6 @@ import (
"fmt" "fmt"
"os" "os"
"testing" "testing"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
) )
func TestMain(m *testing.M) { 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())) panic(fmt.Sprintf("Failed to set current working directory to %s: %s", "../..", err.Error()))
} }
mlog.DisableZap()
defer func() { defer func() {
err := os.Chdir(prevDir) err := os.Chdir(prevDir)
if err != nil { if err != nil {

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

@@ -1228,7 +1228,7 @@ func (ts *TelemetryService) trackChannelModeration() {
func (ts *TelemetryService) initRudder(endpoint string, rudderKey string) { func (ts *TelemetryService) initRudder(endpoint string, rudderKey string) {
if ts.rudderClient == nil { if ts.rudderClient == nil {
config := rudder.Config{} 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 config.Endpoint = endpoint
// For testing // For testing
if endpoint != RudderDataplaneURL { if endpoint != RudderDataplaneURL {

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

@@ -19,6 +19,7 @@ import (
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "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/model"
"github.com/mattermost/mattermost-server/v6/plugin" "github.com/mattermost/mattermost-server/v6/plugin"
"github.com/mattermost/mattermost-server/v6/plugin/plugintest" "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()) { func initializeMocks(cfg *model.Config) (*mocks.ServerIface, *storeMocks.Store, func(t *testing.T), func()) {
serverIfaceMock := &mocks.ServerIface{} serverIfaceMock := &mocks.ServerIface{}
logger, _ := mlog.NewLogger()
configService := &FakeConfigService{cfg} configService := &FakeConfigService{cfg}
serverIfaceMock.On("Config").Return(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 }, func(m *model.Manifest) plugin.API { return pluginsAPIMock },
nil, nil,
pluginDir, webappPluginDir, pluginDir, webappPluginDir,
mlog.NewLogger(&mlog.LoggerConfiguration{}), logger,
nil) nil)
serverIfaceMock.On("GetPluginsEnvironment").Return(pluginEnv, nil) serverIfaceMock.On("GetPluginsEnvironment").Return(pluginEnv, nil)
@@ -270,7 +272,14 @@ func TestRudderTelemetry(t *testing.T) {
defer cleanUp() defer cleanUp()
defer deferredAssertions(t) 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.TelemetryID = telemetryID
telemetryService.rudderClient = nil telemetryService.rudderClient = nil
telemetryService.initRudder(server.URL, RudderKey) telemetryService.initRudder(server.URL, RudderKey)

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

@@ -7,7 +7,6 @@ import (
"flag" "flag"
"testing" "testing"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -26,8 +25,6 @@ func TestMain(m *testing.M) {
WithReadReplica: replicaFlag, WithReadReplica: replicaFlag,
} }
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options) mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -33,12 +33,10 @@ type FileBackendTestSuite struct {
func TestLocalFileBackendTestSuite(t *testing.T) { func TestLocalFileBackendTestSuite(t *testing.T) {
// Setup a global logger to catch tests logging outside of app context // 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. // 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{ logger := mlog.CreateConsoleTestLogger(true, mlog.LvlError)
EnableConsole: true, defer logger.Shutdown()
ConsoleJson: true,
ConsoleLevel: "error", mlog.InitGlobalLogger(logger)
EnableFile: false,
}))
dir, err := ioutil.TempDir("", "") dir, err := ioutil.TempDir("", "")
require.NoError(t, err) require.NoError(t, err)

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

@@ -4,26 +4,32 @@
package mlog package mlog
import ( import (
"context" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"github.com/mattermost/logr"
) )
// defaultLog manually encodes the log to STDERR, providing a basic, default logging implementation // defaultLog manually encodes the log to STDERR, providing a basic, default logging implementation
// before mlog is fully configured. // 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 { log := struct {
Level string `json:"level"` Level string `json:"level"`
Message string `json:"msg"` Message string `json:"msg"`
Fields []Field `json:"fields,omitempty"` Fields map[string]string `json:"fields,omitempty"`
}{ }{
level, level.Name,
msg, msg,
fields, mFields,
} }
if b, err := json.Marshal(log); err != nil { 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 return true
} }
func defaultDebugLog(msg string, fields ...Field) { func defaultCustomMultiLog(lvl []Level, msg string, fields ...Field) {
defaultLog("debug", msg, fields...) for _, level := range lvl {
defaultLog(level, msg, fields...)
}
} }
func defaultInfoLog(msg string, fields ...Field) { // shouldQuote returns true if val contains any characters that require quotations.
defaultLog("info", msg, fields...) func shouldQuote(val string) bool {
} for _, c := range val {
if !((c >= '0' && c <= '9') ||
func defaultWarnLog(msg string, fields ...Field) { (c >= 'a' && c <= 'z') ||
defaultLog("warn", msg, fields...) (c >= 'A' && c <= 'Z') ||
} c == '-' || c == '.' || c == '_' || c == '/' || c == '@' || c == '^' || c == '+') {
return true
func defaultErrorLog(msg string, fields ...Field) { }
defaultLog("error", msg, fields...) }
} return false
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")
} }

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

@@ -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 package mlog
import ( import (
"context" "sync"
"log"
"sync/atomic"
"github.com/mattermost/logr"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
var globalLogger *Logger var (
globalLogger *Logger
muxGlobalLogger sync.RWMutex
)
func InitGlobalLogger(logger *Logger) { func InitGlobalLogger(logger *Logger) {
// Clean up previous instance. muxGlobalLogger.Lock()
if globalLogger != nil && globalLogger.logrLogger != nil { defer muxGlobalLogger.Unlock()
globalLogger.logrLogger.Logr().Shutdown()
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 return logger.IsLevelEnabled(level)
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
} }
// logWriterFunc provides access to mlog via io.Writer, so the standard logger // Log emits the log record for any targets configured for the specified level.
// can be redirected to use mlog and whatever targets are defined. func Log(level Level, msg string, fields ...Field) {
type logWriterFunc func([]byte) (int, error) logger := getGlobalLogger()
if logger == nil {
func (lw logWriterFunc) Write(p []byte) (int, error) { defaultLog(level, msg, fields...)
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)
return return
} }
logger.Log(level, msg, fields...)
}
writer := func(p []byte) (int, error) { // LogM emits the log record for any targets configured for the specified levels.
Log(LvlStdLog, string(p)) // Equivalent to calling `Log` once for each level.
return len(p), nil 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 // Convenience method equivalent to calling `Log` with the `Trace` level.
type LogFunc func(string, ...Field) func Trace(msg string, fields ...Field) {
type LogFuncCustom func(LogLevel, string, ...Field) logger := getGlobalLogger()
type LogFuncCustomMulti func([]LogLevel, string, ...Field) if logger == nil {
type FlushFunc func(context.Context) error defaultLog(LvlTrace, msg, fields...)
type ConfigFunc func(cfg LogTargetCfg) error return
type ShutdownFunc func(context.Context) error }
type AddTargetFunc func(...logr.Target) error logger.Trace(msg, fields...)
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)
} }
// DON'T USE THIS Modify the level on the app logger // Convenience method equivalent to calling `Log` with the `Debug` level.
func GloballyEnableDebugLogForTest() { func Debug(msg string, fields ...Field) {
globalLogger.consoleLevel.SetLevel(zapcore.DebugLevel) logger := getGlobalLogger()
if logger == nil {
defaultLog(LvlDebug, msg, fields...)
return
}
logger.Debug(msg, fields...)
} }
var IsLevelEnabled IsLevelEnabledFunc = defaultIsLevelEnabled // Convenience method equivalent to calling `Log` with the `Info` level.
var Debug LogFunc = defaultDebugLog func Info(msg string, fields ...Field) {
var Info LogFunc = defaultInfoLog logger := getGlobalLogger()
var Warn LogFunc = defaultWarnLog if logger == nil {
var Error LogFunc = defaultErrorLog defaultLog(LvlInfo, msg, fields...)
var Critical LogFunc = defaultCriticalLog return
var Log LogFuncCustom = defaultCustomLog }
var LogM LogFuncCustomMulti = defaultCustomMultiLog logger.Info(msg, fields...)
var Flush FlushFunc = defaultFlush }
var ConfigAdvancedLogging ConfigFunc = defaultAdvancedConfig // Convenience method equivalent to calling `Log` with the `Warn` level.
var ShutdownAdvancedLogging ShutdownFunc = defaultAdvancedShutdown func Warn(msg string, fields ...Field) {
var AddTarget AddTargetFunc = defaultAddTarget logger := getGlobalLogger()
var RemoveTargets RemoveTargetsFunc = defaultRemoveTargets if logger == nil {
var EnableMetrics EnableMetricsFunc = defaultEnableMetrics 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 package mlog_test
import ( import (
"encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@@ -29,83 +31,86 @@ func TestLoggingBeforeInitialized(t *testing.T) {
func TestLoggingAfterInitialized(t *testing.T) { func TestLoggingAfterInitialized(t *testing.T) {
testCases := []struct { testCases := []struct {
Description string description string
LoggerConfiguration *mlog.LoggerConfiguration cfg mlog.TargetCfg
ExpectedLogs []string expectedLogs []string
}{ }{
{ {
"file logging, json, debug", "file logging, json, debug",
&mlog.LoggerConfiguration{ mlog.TargetCfg{
EnableConsole: false, Type: "file",
EnableFile: true, Format: "json",
FileJson: true, FormatOptions: json.RawMessage(`{"enable_caller":true}`),
FileLevel: mlog.LevelDebug, Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError, mlog.LvlWarn, mlog.LvlInfo, mlog.LvlDebug},
}, },
[]string{ []string{
`{"level":"debug","ts":0,"caller":"mlog/global_test.go:0","msg":"real debug log"}`, `{"timestamp":0,"level":"debug","msg":"real debug log","caller":"mlog/global_test.go:0"}`,
`{"level":"info","ts":0,"caller":"mlog/global_test.go:0","msg":"real info log"}`, `{"timestamp":0,"level":"info","msg":"real info log","caller":"mlog/global_test.go:0"}`,
`{"level":"warn","ts":0,"caller":"mlog/global_test.go:0","msg":"real warning log"}`, `{"timestamp":0,"level":"warn","msg":"real warning log","caller":"mlog/global_test.go:0"}`,
`{"level":"error","ts":0,"caller":"mlog/global_test.go:0","msg":"real error log"}`, `{"timestamp":0,"level":"error","msg":"real error log","caller":"mlog/global_test.go:0"}`,
`{"level":"error","ts":0,"caller":"mlog/global_test.go:0","msg":"real critical log"}`, `{"timestamp":0,"level":"critical","msg":"real critical log","caller":"mlog/global_test.go:0"}`,
}, },
}, },
{ {
"file logging, json, error", "file logging, json, error",
&mlog.LoggerConfiguration{ mlog.TargetCfg{
EnableConsole: false, Type: "file",
EnableFile: true, Format: "json",
FileJson: true, FormatOptions: json.RawMessage(`{"enable_caller":true}`),
FileLevel: mlog.LevelError, Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError},
}, },
[]string{ []string{
`{"level":"error","ts":0,"caller":"mlog/global_test.go:0","msg":"real error log"}`, `{"timestamp":0,"level":"error","msg":"real error log","caller":"mlog/global_test.go:0"}`,
`{"level":"error","ts":0,"caller":"mlog/global_test.go:0","msg":"real critical log"}`, `{"timestamp":0,"level":"critical","msg":"real critical log","caller":"mlog/global_test.go:0"}`,
}, },
}, },
{ {
"file logging, non-json, debug", "file logging, non-json, debug",
&mlog.LoggerConfiguration{ mlog.TargetCfg{
EnableConsole: false, Type: "file",
EnableFile: true, Format: "plain",
FileJson: false, FormatOptions: json.RawMessage(`{"delim":" | ", "enable_caller":true}`),
FileLevel: mlog.LevelDebug, Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError, mlog.LvlWarn, mlog.LvlInfo, mlog.LvlDebug},
}, },
[]string{ []string{
`TIME debug mlog/global_test.go:0 real debug log`, `debug | TIME | real debug log | caller="mlog/global_test.go:0"`,
`TIME info mlog/global_test.go:0 real info log`, `info | TIME | real info log | caller="mlog/global_test.go:0"`,
`TIME warn mlog/global_test.go:0 real warning log`, `warn | TIME | real warning log | caller="mlog/global_test.go:0"`,
`TIME error mlog/global_test.go:0 real error log`, `error | TIME | real error log | caller="mlog/global_test.go:0"`,
`TIME error mlog/global_test.go:0 real critical log`, `critical | TIME | real critical log | caller="mlog/global_test.go:0"`,
}, },
}, },
{ {
"file logging, non-json, error", "file logging, non-json, error",
&mlog.LoggerConfiguration{ mlog.TargetCfg{
EnableConsole: false, Type: "file",
EnableFile: true, Format: "plain",
FileJson: false, FormatOptions: json.RawMessage(`{"delim":" | ", "enable_caller":true}`),
FileLevel: mlog.LevelError, Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError},
}, },
[]string{ []string{
`TIME error mlog/global_test.go:0 real error log`, `error | TIME | real error log | caller="mlog/global_test.go:0"`,
`TIME error mlog/global_test.go:0 real critical log`, `critical | TIME | real critical log | caller="mlog/global_test.go:0"`,
}, },
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) { t.Run(testCase.description, func(t *testing.T) {
var filePath string var filePath string
if testCase.LoggerConfiguration.EnableFile { if testCase.cfg.Type == "file" {
tempDir, err := ioutil.TempDir(os.TempDir(), "TestLoggingAfterInitialized") tempDir, err := ioutil.TempDir(os.TempDir(), "TestLoggingAfterInitialized")
require.NoError(t, err) require.NoError(t, err)
defer os.Remove(tempDir) defer os.Remove(tempDir)
filePath = filepath.Join(tempDir, "file.log") 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.InitGlobalLogger(logger)
mlog.Debug("real debug log") mlog.Debug("real debug log")
@@ -114,32 +119,26 @@ func TestLoggingAfterInitialized(t *testing.T) {
mlog.Error("real error log") mlog.Error("real error log")
mlog.Critical("real critical log") mlog.Critical("real critical log")
if testCase.LoggerConfiguration.EnableFile { logger.Shutdown()
if testCase.cfg.Type == "file" {
logs, err := ioutil.ReadFile(filePath) logs, err := ioutil.ReadFile(filePath)
require.NoError(t, err) require.NoError(t, err)
actual := strings.TrimSpace(string(logs)) actual := strings.TrimSpace(string(logs))
if testCase.LoggerConfiguration.FileJson { if testCase.cfg.Format == "json" {
reTs := regexp.MustCompile(`"ts":[0-9\.]+`) reTs := regexp.MustCompile(`"timestamp":"[0-9\.\-\:\sZ]+"`)
reCaller := regexp.MustCompile(`"caller":"([^"]+):[0-9\.]+"`) 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"`) actual = reCaller.ReplaceAllString(actual, `"caller":"$1:0"`)
} else { } else {
actualRows := strings.Split(actual, "\n") reTs := regexp.MustCompile(`\[\d\d\d\d-\d\d-\d\d\s[0-9\:\.\s\-Z]+\]`)
for i, actualRow := range actualRows { reCaller := regexp.MustCompile(`caller="([^"]+):[0-9\.]+"`)
actualFields := strings.Split(actualRow, "\t") actual = reTs.ReplaceAllString(actual, "TIME")
if len(actualFields) > 3 { actual = reCaller.ReplaceAllString(actual, `caller="$1:0"`)
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")
} }
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 package mlog
// Standard levels import "github.com/mattermost/logr/v2"
// Standard levels.
var ( var (
LvlPanic = LogLevel{ID: 0, Name: "panic", Stacktrace: true} LvlPanic = logr.Panic // ID = 0
LvlFatal = LogLevel{ID: 1, Name: "fatal", Stacktrace: true} LvlFatal = logr.Fatal // ID = 1
LvlError = LogLevel{ID: 2, Name: "error"} LvlError = logr.Error // ID = 2
LvlWarn = LogLevel{ID: 3, Name: "warn"} LvlWarn = logr.Warn // ID = 3
LvlInfo = LogLevel{ID: 4, Name: "info"} LvlInfo = logr.Info // ID = 4
LvlDebug = LogLevel{ID: 5, Name: "debug"} LvlDebug = logr.Debug // ID = 5
LvlTrace = LogLevel{ID: 6, Name: "trace"} 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 // used by redirected standard logger
LvlStdLog = LogLevel{ID: 10, Name: "stdlog"} LvlStdLog = Level{ID: 10, Name: "stdlog"}
// used only by the logger // 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. // Register custom (discrete) levels here.
// !!!!! ID's must not exceed 32,768 !!!!!! // !!!!! Custom ID's must be between 20 and 32,768 !!!!!!
var ( var (
// used by the audit system // used by the audit system
LvlAuditAPI = LogLevel{ID: 100, Name: "audit-api"} LvlAuditAPI = Level{ID: 100, Name: "audit-api"}
LvlAuditContent = LogLevel{ID: 101, Name: "audit-content"} LvlAuditContent = Level{ID: 101, Name: "audit-content"}
LvlAuditPerms = LogLevel{ID: 102, Name: "audit-permissions"} LvlAuditPerms = Level{ID: 102, Name: "audit-permissions"}
LvlAuditCLI = LogLevel{ID: 103, Name: "audit-cli"} LvlAuditCLI = Level{ID: 103, Name: "audit-cli"}
// used by the TCP log target // used by the TCP log target
LvlTCPLogTarget = LogLevel{ID: 120, Name: "TcpLogTarget"} LvlTCPLogTarget = Level{ID: 120, Name: "TcpLogTarget"}
// used by Remote Cluster Service // used by Remote Cluster Service
LvlRemoteClusterServiceDebug = LogLevel{ID: 130, Name: "RemoteClusterServiceDebug"} LvlRemoteClusterServiceDebug = Level{ID: 130, Name: "RemoteClusterServiceDebug"}
LvlRemoteClusterServiceError = LogLevel{ID: 131, Name: "RemoteClusterServiceError"} LvlRemoteClusterServiceError = Level{ID: 131, Name: "RemoteClusterServiceError"}
LvlRemoteClusterServiceWarn = LogLevel{ID: 132, Name: "RemoteClusterServiceWarn"} LvlRemoteClusterServiceWarn = Level{ID: 132, Name: "RemoteClusterServiceWarn"}
// used by Shared Channel Sync Service // used by Shared Channel Sync Service
LvlSharedChannelServiceDebug = LogLevel{ID: 200, Name: "SharedChannelServiceDebug"} LvlSharedChannelServiceDebug = Level{ID: 200, Name: "SharedChannelServiceDebug"}
LvlSharedChannelServiceError = LogLevel{ID: 201, Name: "SharedChannelServiceError"} LvlSharedChannelServiceError = Level{ID: 201, Name: "SharedChannelServiceError"}
LvlSharedChannelServiceWarn = LogLevel{ID: 202, Name: "SharedChannelServiceWarn"} LvlSharedChannelServiceWarn = Level{ID: 202, Name: "SharedChannelServiceWarn"}
LvlSharedChannelServiceMessagesInbound = LogLevel{ID: 203, Name: "SharedChannelServiceMsgInbound"} LvlSharedChannelServiceMessagesInbound = Level{ID: 203, Name: "SharedChannelServiceMsgInbound"}
LvlSharedChannelServiceMessagesOutbound = LogLevel{ID: 204, Name: "SharedChannelServiceMsgOutbound"} 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 ( 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/model"
"github.com/mattermost/mattermost-server/v6/services/cache" "github.com/mattermost/mattermost-server/v6/services/cache"
cachemocks "github.com/mattermost/mattermost-server/v6/services/cache/mocks" 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"
"github.com/mattermost/mattermost-server/v6/store/sqlstore" "github.com/mattermost/mattermost-server/v6/store/sqlstore"
"github.com/mattermost/mattermost-server/v6/store/storetest/mocks" "github.com/mattermost/mattermost-server/v6/store/storetest/mocks"
@@ -172,7 +171,6 @@ func getMockStore() *mocks.Store {
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(nil) mainHelper = testlib.NewMainHelperWithOptions(nil)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -6,7 +6,6 @@ package sqlstore_test
import ( import (
"testing" "testing"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/store/sqlstore" "github.com/mattermost/mattermost-server/v6/store/sqlstore"
"github.com/mattermost/mattermost-server/v6/testlib" "github.com/mattermost/mattermost-server/v6/testlib"
) )
@@ -14,7 +13,6 @@ import (
var mainHelper *testlib.MainHelper var mainHelper *testlib.MainHelper
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(nil) mainHelper = testlib.NewMainHelperWithOptions(nil)
defer mainHelper.Close() defer mainHelper.Close()

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

@@ -4,14 +4,13 @@
package testlib package testlib
import ( import (
"bytes"
"encoding/json" "encoding/json"
"io" "io"
"testing" "testing"
) )
// AssertLog asserts that a JSON-encoded buffer of logs contains one with the given level and message. // 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) dec := json.NewDecoder(logs)
for { for {
var log struct { 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. // 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) dec := json.NewDecoder(logs)
for { for {
var log struct { var log struct {

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

@@ -16,7 +16,6 @@ import (
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/services/searchengine" "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"
"github.com/mattermost/mattermost-server/v6/store/searchlayer" "github.com/mattermost/mattermost-server/v6/store/searchlayer"
"github.com/mattermost/mattermost-server/v6/store/sqlstore" "github.com/mattermost/mattermost-server/v6/store/sqlstore"
@@ -53,16 +52,6 @@ func NewMainHelperWithOptions(options *HelperOptions) *MainHelper {
var mainHelper MainHelper var mainHelper MainHelper
flag.Parse() 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() utils.TranslationsPreInit()
if options != nil { 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 apps
test/cmd/testapp1/testapp1 test/cmd/testapp1/testapp1
test/cmd/simple/simple 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 --> <!-- markdownlint-disable MD033 -->
| entity | description | | 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")```| | 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}```| | 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: " \| "}```| | 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 ```go
// Create Logr instance. // Create Logr instance.
lgr := &logr.Logr{} lgr,_ := logr.New()
// Create a filter and formatter. Both can be shared by multiple // Create a filter and formatter. Both can be shared by multiple
// targets. // targets.
filter := &logr.StdFilter{Lvl: logr.Warn, Stacktrace: logr.Error} filter := &logr.StdFilter{Lvl: logr.Warn, Stacktrace: logr.Error}
formatter := &format.Plain{Delim: " | "} formatter := &formatters.Plain{Delim: " | "}
// WriterTarget outputs to any io.Writer // 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) lgr.AddTarget(t)
// One or more Loggers can be created, shared, used concurrently, // 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: Fields are added via Loggers:
```go ```go
lgr := &Logr{} lgr,_ := logr.New()
// ... add targets ... // ... add targets ...
logger := lgr.NewLogger().WithFields(logr.Fields{ logger := lgr.NewLogger().WithFields(logr.Fields{
"user": user, "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} LoginLevel := logr.Level{ID: 100, Name: "login ", Stacktrace: false}
LogoutLevel := logr.Level{ID: 101, Name: "logout", Stacktrace: false} LogoutLevel := logr.Level{ID: 101, Name: "logout", Stacktrace: false}
lgr := &logr.Logr{} lgr,_ := logr.New()
// create a custom filter with custom levels. // create a custom filter with custom levels.
filter := &logr.CustomFilter{} filter := &logr.CustomFilter{}
filter.Add(LoginLevel, LogoutLevel) filter.Add(LoginLevel, LogoutLevel)
formatter := &format.Plain{Delim: " | "} formatter := &formatters.Plain{Delim: " | "}
tgr := target.NewWriterTarget(filter, formatter, os.StdOut, 1000) tgr := targets.NewWriterTarget(filter, formatter, os.StdOut, 1000)
lgr.AddTarget(tgr) lgr.AddTarget(tgr)
logger := lgr.NewLogger().WithFields(logr.Fields{"user": "Bob", "role": "admin"}) 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. 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 ```go
type Writer struct { type Writer struct {
logr.Basic
out io.Writer 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 := &Writer{out: out}
w.Basic.Start(w, w, filter, formatter, maxQueue)
return w 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. // 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 func (w *Writer) Write(p []byte, rec *logr.LogRec) (int, error) {
// bytes to your sink. return w.out.Write(buf.Bytes())
func (w *Writer) Write(rec *logr.LogRec) error { }
_, stacktrace := w.IsLevelEnabled(rec.Level())
// take a buffer from the pool to avoid allocations or just allocate a new one. // Called once to cleanup/free resources for target.
buf := rec.Logger().Logr().BorrowBuffer() func (w *Writer) Shutdown() error {
defer rec.Logger().Logr().ReleaseBuffer(buf) return nil
buf, err := w.Formatter().Format(rec, stacktrace, buf)
if err != nil {
return err
}
_, err = w.out.Write(buf.Bytes())
return err
} }
``` ```

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 // MaxLevelID is the maximum value of a level ID. Some level cache implementations will
// allocate a cache of this size. Cannot exceed uint. // 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. // 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 // This only applies to blocking enqueue which happen after `logr.OnQueueFull` is called

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