MM-27744 disable Zap for unit tests. (#15398)

MM-27744 disable Zap for unit tests.

Zap has no concept of shutdown or close. Zap is only shutdown when the app exits. Not a problem for console logging, but when creating a new Zap logger that outputs to files on every unit test, that leaves no easy way to clean up until process exit. Depending on what else is running this can exhaust all file handles and cause unit tests to fail.

Zap is now disabled unit tests and uses Logr instead, regardless of config settings. `make test-server` peak file handle usage dropped from ~5K to less than 100.
Этот коммит содержится в:
Doug Lauder
2020-09-09 15:25:55 -04:00
коммит произвёл GitHub
родитель 1fde5112b6
Коммит 1b67322fb9
25 изменённых файлов: 547 добавлений и 146 удалений

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

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

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

@@ -4,6 +4,7 @@
package app
import (
"context"
"fmt"
"io"
"io/ioutil"
@@ -59,6 +60,10 @@ func (s *Server) GetLogsSkipSend(page, perPage int) ([]string, *model.AppError)
var lines []string
if *s.Config().LogSettings.EnableFile {
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), mlog.DefaultFlushTimeout)
defer timeoutCancel()
mlog.Flush(timeoutCtx)
logFile := utils.GetLogFileLocation(*s.Config().LogSettings.FileLocation)
file, err := os.Open(logFile)
if err != nil {

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

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

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

@@ -115,20 +115,20 @@ type Server struct {
newStore func() store.Store
htmlTemplateWatcher *utils.HTMLTemplateWatcher
sessionCache cache.Cache
seenPendingPostIdsCache cache.Cache
statusCache cache.Cache
configListenerId string
licenseListenerId string
logListenerId string
clusterLeaderListenerId string
searchConfigListenerId string
searchLicenseListenerId string
loggerMetricsLicenseListenerId string
configStore config.Store
asymmetricSigningKey *ecdsa.PrivateKey
postActionCookieSecret []byte
htmlTemplateWatcher *utils.HTMLTemplateWatcher
sessionCache cache.Cache
seenPendingPostIdsCache cache.Cache
statusCache cache.Cache
configListenerId string
licenseListenerId string
logListenerId string
clusterLeaderListenerId string
searchConfigListenerId string
searchLicenseListenerId string
loggerLicenseListenerId string
configStore config.Store
asymmetricSigningKey *ecdsa.PrivateKey
postActionCookieSecret []byte
advancedLogListenerCleanup func()
@@ -480,19 +480,11 @@ func NewServer(options ...Option) (*Server, error) {
}
}
if !allowAdvancedLogging {
timeoutCtx, cancelCtx := context.WithTimeout(context.Background(), time.Second*5)
defer cancelCtx()
mlog.Info("Shutting down advanced logging")
mlog.ShutdownAdvancedLogging(timeoutCtx)
if s.advancedLogListenerCleanup != nil {
s.advancedLogListenerCleanup()
s.advancedLogListenerCleanup = nil
}
}
s.removeUnlicensedLogTargets(license)
s.enableLoggingMetrics()
s.loggerMetricsLicenseListenerId = s.AddLicenseListener(func(oldLicense, newLicense *model.License) {
s.loggerLicenseListenerId = s.AddLicenseListener(func(oldLicense, newLicense *model.License) {
s.removeUnlicensedLogTargets(newLicense)
s.enableLoggingMetrics()
})
@@ -561,6 +553,7 @@ func (s *Server) AppOptions() []AppOption {
}
}
// initLogging initializes and configures the logger. This may be called more than once.
func (s *Server) initLogging() error {
if s.Log == nil {
s.Log = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(&s.Config().LogSettings, utils.GetLogFileLocation))
@@ -578,6 +571,9 @@ func (s *Server) initLogging() error {
// Use this app logger as the global logger (eventually remove all instances of global logging)
mlog.InitGlobalLogger(s.Log)
if s.logListenerId != "" {
s.RemoveConfigListener(s.logListenerId)
}
s.logListenerId = s.AddConfigListener(func(_, after *model.Config) {
s.Log.ChangeLevels(utils.MloggerConfigFromLoggerConfig(&after.LogSettings, utils.GetLogFileLocation))
@@ -633,13 +629,27 @@ func (s *Server) initLogging() error {
return nil
}
func (s *Server) removeUnlicensedLogTargets(license *model.License) {
if license != nil && *license.Features.AdvancedLogging {
// advanced logging enabled via license; no need to remove any targets
return
}
timeoutCtx, cancelCtx := context.WithTimeout(context.Background(), time.Second*10)
defer cancelCtx()
mlog.RemoveTargets(timeoutCtx, func(ti mlog.TargetInfo) bool {
return ti.Type != "*target.Writer" && ti.Type != "*target.File"
})
}
func (s *Server) enableLoggingMetrics() {
if s.Metrics == nil {
return
}
if err := mlog.EnableMetrics(s.Metrics.GetLoggerMetricsCollector()); err != nil {
mlog.Debug("Failed to enable advanced logging metrics", mlog.Err(err))
mlog.Error("Failed to enable advanced logging metrics", mlog.Err(err))
} else {
mlog.Debug("Advanced logging metrics enabled")
}
@@ -677,7 +687,7 @@ func (s *Server) Shutdown() error {
s.HubStop()
s.ShutDownPlugins()
s.RemoveLicenseListener(s.licenseListenerId)
s.RemoveLicenseListener(s.loggerMetricsLicenseListenerId)
s.RemoveLicenseListener(s.loggerLicenseListenerId)
s.RemoveClusterLeaderChangedListener(s.clusterLeaderListenerId)
if s.tracer != nil {

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

@@ -304,6 +304,10 @@ func TestPanicLog(t *testing.T) {
require.NoError(t, os.Remove(tmpfile.Name()))
}()
// This test requires Zap file target for now.
mlog.EnableZap()
defer mlog.DisableZap()
// Creating logger to log to console and temp file
logger := mlog.NewLogger(&mlog.LoggerConfiguration{
EnableConsole: true,

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

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

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

@@ -9,6 +9,7 @@ import (
"testing"
"github.com/mattermost/mattermost-server/v5/api4"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/testlib"
)
@@ -27,6 +28,8 @@ func TestMain(m *testing.M) {
EnableResources: true,
}
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close()
api4.SetMainHelper(mainHelper)

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

@@ -9,6 +9,7 @@ import (
"github.com/go-sql-driver/mysql"
"github.com/lib/pq"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/testlib"
"github.com/stretchr/testify/require"
@@ -21,6 +22,8 @@ func TestMain(m *testing.M) {
EnableStore: true,
}
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close()

2
go.mod
Просмотреть файл

@@ -60,7 +60,7 @@ require (
github.com/mattermost/gorp v1.6.2-0.20200624165429-2595d5e54111
github.com/mattermost/gosaml2 v0.3.2
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d
github.com/mattermost/logr v1.0.9
github.com/mattermost/logr v1.0.13
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0
github.com/mattermost/viper v1.0.4
github.com/mattn/go-colorable v0.1.7 // indirect

8
go.sum
Просмотреть файл

@@ -484,6 +484,14 @@ github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d h1:2DV7VIlEv6J5R5o
github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
github.com/mattermost/logr v1.0.9 h1:jw6f6CjPC2YfPqzpGVM/vCGuqKLJdVS400ZRTFtEQwQ=
github.com/mattermost/logr v1.0.9/go.mod h1:Mt4DPu1NXMe6JxPdwCC0XBoxXmN9eXOIRPoZarU2PXs=
github.com/mattermost/logr v1.0.10 h1:J/M6OFJhzQCUPGLyL9s8hiE+8nyL7Y0DybbOxYOisi0=
github.com/mattermost/logr v1.0.10/go.mod h1:Mt4DPu1NXMe6JxPdwCC0XBoxXmN9eXOIRPoZarU2PXs=
github.com/mattermost/logr v1.0.11 h1:XlNLB3x9OhvoNxEus46dW38zejINe5D2dZBlwmIlX5Q=
github.com/mattermost/logr v1.0.11/go.mod h1:Mt4DPu1NXMe6JxPdwCC0XBoxXmN9eXOIRPoZarU2PXs=
github.com/mattermost/logr v1.0.12 h1:1Tt2dJppjW6XlpJgMpeN+SNG1QgbTr4ITYnxG3NLPbM=
github.com/mattermost/logr v1.0.12/go.mod h1:Mt4DPu1NXMe6JxPdwCC0XBoxXmN9eXOIRPoZarU2PXs=
github.com/mattermost/logr v1.0.13 h1:6F/fM3csvH6Oy5sUpJuW7YyZSzZZAhJm5VcgKMxA2P8=
github.com/mattermost/logr v1.0.13/go.mod h1:Mt4DPu1NXMe6JxPdwCC0XBoxXmN9eXOIRPoZarU2PXs=
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/viper v1.0.4 h1:cMYOz4PhguscGSPxrSokUtib5HrG4gCpiUh27wyA3d0=

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

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

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

@@ -76,12 +76,18 @@ func defaultAdvancedShutdown(ctx context.Context) error {
return nil
}
func defaultAddTarget(target logr.Target) error {
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.

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

@@ -5,6 +5,8 @@ package mlog
import (
"context"
"log"
"sync/atomic"
"github.com/mattermost/logr"
"go.uber.org/zap"
@@ -32,11 +34,29 @@ func InitGlobalLogger(logger *Logger) {
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
// can be redirected to use mlog and whatever targets are defined.
type logWriterFunc func([]byte) (int, error)
func (lw logWriterFunc) Write(p []byte) (int, error) {
return lw(p)
}
func RedirectStdLog(logger *Logger) {
zap.RedirectStdLogAt(logger.zap.With(zap.String("source", "stdlog")).WithOptions(zap.AddCallerSkip(-2)), zapcore.ErrorLevel)
if atomic.LoadInt32(&disableZap) == 0 {
zap.RedirectStdLogAt(logger.zap.With(zap.String("source", "stdlog")).WithOptions(zap.AddCallerSkip(-2)), zapcore.ErrorLevel)
return
}
writer := func(p []byte) (int, error) {
Log(LvlStdLog, string(p))
return len(p), nil
}
log.SetOutput(logWriterFunc(writer))
}
type LogFunc func(string, ...Field)
@@ -45,7 +65,8 @@ type LogFuncCustomMulti func([]LogLevel, string, ...Field)
type FlushFunc func(context.Context) error
type ConfigFunc func(cfg LogTargetCfg) error
type ShutdownFunc func(context.Context) error
type AddTargetFunc func(logr.Target) error
type AddTargetFunc func(...logr.Target) error
type RemoveTargetsFunc func(context.Context, func(TargetInfo) bool) error
type EnableMetricsFunc func(logr.MetricsCollector) error
// DON'T USE THIS Modify the level on the app logger
@@ -70,4 +91,5 @@ var Flush FlushFunc = defaultFlush
var ConfigAdvancedLogging ConfigFunc = defaultAdvancedConfig
var ShutdownAdvancedLogging ShutdownFunc = defaultAdvancedShutdown
var AddTarget AddTargetFunc = defaultAddTarget
var RemoveTargets RemoveTargetsFunc = defaultRemoveTargets
var EnableMetrics EnableMetricsFunc = defaultEnableMetrics

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

@@ -5,13 +5,15 @@ package mlog
// Standard levels
var (
LvlPanic = LogLevel{ID: 0, Name: "panic"}
LvlFatal = LogLevel{ID: 1, Name: "fatal"}
LvlPanic = LogLevel{ID: 0, Name: "panic", Stacktrace: true}
LvlFatal = LogLevel{ID: 1, Name: "fatal", Stacktrace: true}
LvlError = LogLevel{ID: 2, Name: "error"}
LvlWarn = LogLevel{ID: 3, Name: "warn"}
LvlInfo = LogLevel{ID: 4, Name: "info"}
LvlDebug = LogLevel{ID: 5, Name: "debug"}
LvlTrace = LogLevel{ID: 6, Name: "trace"}
// used by redirected standard logger
LvlStdLog = LogLevel{ID: 10, Name: "stdlog"}
// used only by the logger
LvlLogError = LogLevel{ID: 11, Name: "logerror", Stacktrace: true}
)

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

@@ -5,9 +5,12 @@ package mlog
import (
"context"
"fmt"
"io"
"log"
"os"
"sync/atomic"
"time"
"github.com/mattermost/logr"
"go.uber.org/zap"
@@ -24,6 +27,19 @@ const (
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
@@ -40,6 +56,8 @@ var NamedErr = zap.NamedError
var Bool = zap.Bool
var Duration = zap.Duration
type TargetInfo logr.TargetInfo
type LoggerConfiguration struct {
EnableConsole bool
ConsoleJson bool
@@ -97,14 +115,33 @@ func NewLogger(config *LoggerConfiguration) *Logger {
}
if config.EnableFile {
writer := zapcore.AddSync(&lumberjack.Logger{
Filename: config.FileLocation,
MaxSize: 100,
Compress: true,
})
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), writer, logger.fileLevel)
cores = append(cores, core)
core := zapcore.NewCore(makeEncoder(config.FileJson), writer, logger.fileLevel)
cores = append(cores, core)
}
}
combinedCore := zapcore.NewTee(cores...)
@@ -221,14 +258,14 @@ func (l *Logger) LogM(levels []LogLevel, message string, fields ...Field) {
}
func (l *Logger) Flush(cxt context.Context) error {
return l.logrLogger.Logr().Flush() // TODO: use context when Logr lib supports it.
return l.logrLogger.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.logrLogger.Logr().Shutdown() // TODO: use context when Logr lib supports it.
err := l.logrLogger.Logr().ShutdownWithTimeout(cxt)
l.logrLogger = newLogr()
return err
}
@@ -245,11 +282,22 @@ func (l *Logger) ConfigAdvancedLogging(targets LogTargetCfg) error {
return err
}
// AddTarget adds a logr.Target to the advanced logger. This is the preferred method
// 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(target logr.Target) error {
return l.logrLogger.Logr().AddTarget(target)
// config source.
func (l *Logger) AddTarget(targets ...logr.Target) error {
return l.logrLogger.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.logrLogger.Logr().RemoveTargets(ctx, fc)
}
// EnableMetrics enables metrics collection by supplying a MetricsCollector.
@@ -257,3 +305,21 @@ func (l *Logger) AddTarget(target logr.Target) error {
func (l *Logger) EnableMetrics(collector logr.MetricsCollector) error {
return l.logrLogger.Logr().SetMetricsCollector(collector)
}
// 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)
}

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

@@ -60,6 +60,7 @@ func logrAddTargets(logger *logr.Logger, targets LogTargetCfg) error {
continue
}
if target != nil {
target.SetName(name)
lgr.AddTarget(target)
}
}
@@ -152,15 +153,18 @@ func newFileTarget(name string, t *LogTarget, filter logr.Filter, formatter logr
if err := json.Unmarshal(t.Options, options); err != nil {
return nil, err
}
return newFileTargetWithOpts(name, t, target.FileOptions(*options), filter, formatter)
}
if options.Filename == "" {
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(options.Filename); err != nil {
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, target.FileOptions(*options), t.MaxQueueSize)
newTarget := target.NewFileTarget(filter, formatter, opts, t.MaxQueueSize)
return newTarget, nil
}
@@ -222,3 +226,22 @@ func zapToLogr(zapFields []Field) logr.Fields {
}
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
}

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

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

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

@@ -7,6 +7,7 @@ import (
"fmt"
"testing"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/services/cache"
"github.com/mattermost/mattermost-server/v5/model"
@@ -153,6 +154,7 @@ func getMockStore() *mocks.Store {
}
func TestMain(m *testing.M) {
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(nil)
defer mainHelper.Close()

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

@@ -4,15 +4,18 @@
package sqlstore_test
import (
"github.com/mattermost/mattermost-server/v5/store/sqlstore"
"testing"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/store/sqlstore"
"github.com/mattermost/mattermost-server/v5/testlib"
)
var mainHelper *testlib.MainHelper
func TestMain(m *testing.M) {
mlog.DisableZap()
mainHelper = testlib.NewMainHelperWithOptions(nil)
defer mainHelper.Close()

274
vendor/github.com/mattermost/logr/logr.go сгенерированный поставляемый
Просмотреть файл

@@ -27,12 +27,13 @@ type Logr struct {
shutdown bool
lvlCache levelCache
metricsOnce sync.Once
metricsDone chan struct{}
metrics MetricsCollector
queueSizeGauge Gauge
loggedCounter Counter
errorCounter Counter
metricsInitOnce sync.Once
metricsCloseOnce sync.Once
metricsDone chan struct{}
metrics MetricsCollector
queueSizeGauge Gauge
loggedCounter Counter
errorCounter Counter
bufferPool sync.Pool
@@ -114,42 +115,33 @@ func (logr *Logr) Configure(config *cfg.Config) error {
return fmt.Errorf("not implemented yet")
}
// AddTarget adds a target to the logger which will receive
// log records for outputting.
func (logr *Logr) AddTarget(target Target) error {
logr.mux.Lock()
defer logr.mux.Unlock()
if logr.shutdown {
return fmt.Errorf("logr shut down")
}
logr.tmux.Lock()
defer logr.tmux.Unlock()
logr.targets = append(logr.targets, target)
var err error
if logr.metrics != nil {
if tm, ok := target.(TargetWithMetrics); ok {
err = tm.EnableMetrics(logr.metrics, logr.MetricsUpdateFreqMillis)
}
}
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
}
@@ -158,11 +150,41 @@ func (logr *Logr) AddTarget(target Target) error {
return new(bytes.Buffer)
},
}
logr.lvlCache.setup()
go logr.start()
})
logr.resetLevelCache()
return err
}
// 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
@@ -178,28 +200,13 @@ 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 {
// Check cache. lvlCache may still be nil if no targets added.
if logr.lvlCache == nil {
return levelStatusDisabled
}
status, ok := logr.lvlCache.get(lvl.ID)
status, ok := logr.isLevelEnabledFromCache(lvl)
if ok {
return status
}
logr.mux.RLock()
defer logr.mux.RUnlock()
// Don't accept new log records after shutdown.
if logr.shutdown {
return levelStatusDisabled
}
status = LevelStatus{}
// Check each target.
logr.tmux.RLock()
defer logr.tmux.RUnlock()
for _, t := range logr.targets {
e, s := t.IsLevelEnabled(lvl)
if e {
@@ -210,15 +217,45 @@ func (logr *Logr) IsLevelEnabled(lvl Level) LevelStatus {
}
}
}
logr.tmux.RUnlock()
// Cache and return the result.
if err := logr.lvlCache.put(lvl.ID, status); err != nil {
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()
@@ -226,6 +263,71 @@ func (logr *Logr) HasTargets() bool {
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() {
@@ -304,15 +406,24 @@ func (logr *Logr) panic(err interface{}) {
// 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
}
logr.mux.Lock()
defer logr.mux.Unlock()
ctx, cancel := context.WithTimeout(context.Background(), logr.flushTimeout())
defer cancel()
if logr.IsShutdown() {
return errors.New("Flush called on shut down Logr")
}
rec := newFlushLogRec(logr.NewLogger())
logr.enqueue(rec)
@@ -325,6 +436,15 @@ func (logr *Logr) Flush() error {
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.
@@ -332,6 +452,17 @@ func (logr *Logr) Flush() error {
// 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()
@@ -339,16 +470,15 @@ func (logr *Logr) Shutdown() error {
}
logr.shutdown = true
logr.resetLevelCache()
if logr.metricsDone != nil {
close(logr.metricsDone)
logr.metricsDone = nil
}
logr.mux.Unlock()
errs := merror.New()
logr.metricsCloseOnce.Do(func() {
if logr.metricsDone != nil {
close(logr.metricsDone)
}
})
ctx, cancel := context.WithTimeout(context.Background(), logr.shutdownTimeout())
defer cancel()
errs := merror.New()
// close the incoming channel and wait for read loop to exit.
if logr.in != nil {
@@ -377,9 +507,8 @@ func (logr *Logr) Shutdown() error {
// 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{}) {
if logr.errorCounter != nil {
logr.errorCounter.Inc()
}
logr.incErrorCounter()
if logr.OnLoggerError == nil {
fmt.Fprintln(os.Stderr, err)
return
@@ -455,7 +584,7 @@ func (logr *Logr) start() {
// logr is closed.
func (logr *Logr) startMetricsUpdater() {
for {
updateFreq := logr.MetricsUpdateFreqMillis
updateFreq := logr.getMetricsUpdateFreqMillis()
if updateFreq == 0 {
updateFreq = DefMetricsUpdateFreqMillis
}
@@ -467,13 +596,17 @@ func (logr *Logr) startMetricsUpdater() {
case <-logr.metricsDone:
return
case <-time.After(time.Duration(updateFreq) * time.Millisecond):
if logr.queueSizeGauge != nil {
logr.queueSizeGauge.Set(float64(len(logr.in)))
}
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
@@ -484,6 +617,11 @@ func (logr *Logr) fanout(rec *LogRec) {
}()
var logged bool
defer func() {
if logged {
logr.incLoggedCounter() // call this after tmux is released
}
}()
logr.tmux.RLock()
defer logr.tmux.RUnlock()
@@ -493,10 +631,6 @@ func (logr *Logr) fanout(rec *LogRec) {
logged = true
}
}
if logged && logr.loggedCounter != nil {
logr.loggedCounter.Inc()
}
}
// flush drains the queue and notifies when done.

36
vendor/github.com/mattermost/logr/metrics.go сгенерированный поставляемый
Просмотреть файл

@@ -52,6 +52,12 @@ type TargetWithMetrics interface {
EnableMetrics(collector MetricsCollector, updateFreqMillis int64) error
}
func (logr *Logr) getMetricsCollector() MetricsCollector {
logr.mux.RLock()
defer logr.mux.RUnlock()
return logr.metrics
}
// SetMetricsCollector enables metrics collection by supplying a MetricsCollector.
// The MetricsCollector provides counters and gauges that are updated by log targets.
func (logr *Logr) SetMetricsCollector(collector MetricsCollector) error {
@@ -59,12 +65,14 @@ func (logr *Logr) SetMetricsCollector(collector MetricsCollector) error {
return errors.New("collector cannot be nil")
}
logr.mux.Lock()
logr.metrics = collector
logr.queueSizeGauge, _ = collector.QueueSizeGauge("_logr")
logr.loggedCounter, _ = collector.LoggedCounter("_logr")
logr.errorCounter, _ = collector.ErrorCounter("_logr")
logr.mux.Unlock()
logr.metricsOnce.Do(func() {
logr.metricsInitOnce.Do(func() {
logr.metricsDone = make(chan struct{})
go logr.startMetricsUpdater()
})
@@ -75,7 +83,7 @@ func (logr *Logr) SetMetricsCollector(collector MetricsCollector) error {
defer logr.tmux.RUnlock()
for _, target := range logr.targets {
if tm, ok := target.(TargetWithMetrics); ok {
if err := tm.EnableMetrics(logr.metrics, logr.MetricsUpdateFreqMillis); err != nil {
if err := tm.EnableMetrics(collector, logr.MetricsUpdateFreqMillis); err != nil {
merr.Append(err)
}
}
@@ -83,3 +91,27 @@ func (logr *Logr) SetMetricsCollector(collector MetricsCollector) error {
}
return merr.ErrorOrNil()
}
func (logr *Logr) setQueueSizeGauge(val float64) {
logr.mux.RLock()
defer logr.mux.RUnlock()
if logr.queueSizeGauge != nil {
logr.queueSizeGauge.Set(val)
}
}
func (logr *Logr) incLoggedCounter() {
logr.mux.RLock()
defer logr.mux.RUnlock()
if logr.loggedCounter != nil {
logr.loggedCounter.Inc()
}
}
func (logr *Logr) incErrorCounter() {
logr.mux.RLock()
defer logr.mux.RUnlock()
if logr.errorCounter != nil {
logr.errorCounter.Inc()
}
}

100
vendor/github.com/mattermost/logr/target.go сгенерированный поставляемый
Просмотреть файл

@@ -4,13 +4,14 @@ 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 option name for the target.
// SetName provides an optional name for the target.
SetName(name string)
// IsLevelEnabled returns true if this target should emit
@@ -39,7 +40,6 @@ type RecordWriter interface {
// in your target type, implement `RecordWriter`, and call `(*Basic).Start`.
type Basic struct {
target Target
name string
filter Filter
formatter Formatter
@@ -48,6 +48,10 @@ type Basic struct {
done chan struct{}
w RecordWriter
mux sync.RWMutex
name string
metrics bool
queueSizeGauge Gauge
loggedCounter Counter
errorCounter Counter
@@ -74,12 +78,14 @@ func (b *Basic) Start(target Target, rw RecordWriter, filter Filter, formatter F
b.w = rw
go b.start()
if b.queueSizeGauge != nil {
if b.hasMetrics() {
go b.startMetricsUpdater()
}
}
func (b *Basic) SetName(name string) {
b.mux.Lock()
defer b.mux.Unlock()
b.name = name
}
@@ -117,14 +123,10 @@ func (b *Basic) Log(rec *LogRec) {
default:
handler := lgr.OnTargetQueueFull
if handler != nil && handler(b.target, rec, cap(b.in)) {
if b.droppedCounter != nil {
b.droppedCounter.Inc()
}
b.incDroppedCounter()
return // drop the record
}
if b.blockedCounter != nil {
b.blockedCounter.Inc()
}
b.incBlockedCounter()
select {
case <-time.After(lgr.enqueueTimeout()):
@@ -136,9 +138,14 @@ func (b *Basic) Log(rec *LogRec) {
// 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
name := fmt.Sprintf("%v", b)
var err error
if b.queueSizeGauge, err = collector.QueueSizeGauge(name); err != nil {
@@ -159,8 +166,57 @@ func (b *Basic) EnableMetrics(collector MetricsCollector, updateFreqMillis int64
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
}
@@ -183,12 +239,10 @@ func (b *Basic) start() {
} else {
err := b.w.Write(rec)
if err != nil {
if b.errorCounter != nil {
b.errorCounter.Inc()
}
b.incErrorCounter()
rec.Logger().Logr().ReportError(err)
} else if b.loggedCounter != nil {
b.loggedCounter.Inc()
} else {
b.incLoggedCounter()
}
}
}
@@ -199,7 +253,7 @@ func (b *Basic) start() {
// target is closed.
func (b *Basic) startMetricsUpdater() {
for {
updateFreq := b.metricsUpdateFreqMillis
updateFreq := b.getMetricsUpdateFreqMillis()
if updateFreq == 0 {
updateFreq = DefMetricsUpdateFreqMillis
}
@@ -211,13 +265,17 @@ func (b *Basic) startMetricsUpdater() {
case <-b.done:
return
case <-time.After(time.Duration(updateFreq) * time.Millisecond):
if b.queueSizeGauge != nil {
b.queueSizeGauge.Set(float64(len(b.in)))
}
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 {
@@ -229,9 +287,7 @@ func (b *Basic) flush(done chan<- struct{}) {
if rec.flush == nil {
err = b.w.Write(rec)
if err != nil {
if b.errorCounter != nil {
b.errorCounter.Inc()
}
b.incErrorCounter()
rec.Logger().Logr().ReportError(err)
}
}

2
vendor/modules.txt поставляемый
Просмотреть файл

@@ -281,7 +281,7 @@ github.com/mattermost/gosaml2/uuid
# github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d
## explicit
github.com/mattermost/ldap
# github.com/mattermost/logr v1.0.9
# github.com/mattermost/logr v1.0.13
## explicit
github.com/mattermost/logr
github.com/mattermost/logr/format

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

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

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

@@ -15,6 +15,7 @@ import (
"github.com/mattermost/mattermost-server/v5/app"
"github.com/mattermost/mattermost-server/v5/config"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/store"
@@ -76,6 +77,8 @@ func setupTestHelper(t testing.TB, store store.Store, includeCacheLayer bool) *T
options = append(options, app.ConfigStore(memoryStore))
options = append(options, app.StoreOverride(mainHelper.Store))
mlog.DisableZap()
s, err := app.NewServer(options...)
if err != nil {
panic(err)