Files
mattermost/config/logging.go
Doug Lauder 90ff87a77f MM-25731 Advanced Logging (#14888)
Adds Advanced Logging to server. Advanced Logging is an optional logging capability that allows customers to send log records to any number of destinations.

Supported destinations:
- file
- syslog (with out without TLS)
- raw TCP socket (with out without TLS)

Allows developers to specify discrete log levels as well as the standard trace, debug, info, ... panic. Existing code and logging API usage is unchanged.

Log records are emitted asynchronously to reduce latency to the caller. Supports hot-reloading of logger config, including adding removing targets.

Advanced Logging is configured within config.json via "LogSettings.AdvancedLoggingConfig" which can contain a filespec to another config file, a database DSN, or JSON.
2020-07-15 14:40:36 -04:00

189 lines
3.9 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package config
import (
"os"
"strings"
"sync"
"github.com/mattermost/mattermost-server/v5/mlog"
)
type LogSrcListener func(old, new mlog.LogTargetCfg)
type FileGetter interface {
GetFile(name string) ([]byte, error)
}
// LogConfigSrc abstracts the Advanced Logging configuration so that implementations can
// fetch from file, database, etc.
type LogConfigSrc interface {
// Get fetches the current, cached configuration.
Get() mlog.LogTargetCfg
// Set updates the dsn specifying the source and reloads
Set(dsn string, fget FileGetter) (err error)
// AddListener adds a callback function to invoke when the configuration is modified.
AddListener(listener LogSrcListener) string
// RemoveListener removes a callback function using an id returned from AddListener.
RemoveListener(id string)
// Close cleans up resources.
Close() error
}
// NewLogConfigSrc creates an advanced logging configuration source, backed by a
// file, JSON string, or database.
func NewLogConfigSrc(dsn string, isJSON bool, fget FileGetter) (LogConfigSrc, error) {
dsn = strings.TrimSpace(dsn)
if isJSON {
return newJSONSrc(dsn)
}
return newFileSrc(dsn, fget)
}
// jsonSrc
type jsonSrc struct {
logSrcEmitter
mutex sync.RWMutex
cfg mlog.LogTargetCfg
}
func newJSONSrc(data string) (*jsonSrc, error) {
src := &jsonSrc{}
return src, src.Set(data, nil)
}
// Get fetches the current, cached configuration
func (src *jsonSrc) Get() mlog.LogTargetCfg {
src.mutex.RLock()
defer src.mutex.RUnlock()
return src.cfg
}
// Set updates the JSON specifying the source and reloads
func (src *jsonSrc) Set(data string, _ FileGetter) error {
cfg, err := JSONToLogTargetCfg([]byte(data))
if err != nil {
return err
}
src.set(cfg)
return nil
}
func (src *jsonSrc) set(cfg mlog.LogTargetCfg) {
src.mutex.Lock()
defer src.mutex.Unlock()
old := src.cfg
src.cfg = cfg
src.invokeConfigListeners(old, cfg)
}
// Close cleans up resources.
func (src *jsonSrc) Close() error {
return nil
}
// fileSrc
type fileSrc struct {
logSrcEmitter
mutex sync.RWMutex
cfg mlog.LogTargetCfg
path string
watcher *watcher
}
func newFileSrc(path string, fget FileGetter) (*fileSrc, error) {
src := &fileSrc{
path: path,
}
if err := src.Set(path, fget); err != nil {
return nil, err
}
return src, nil
}
// Get fetches the current, cached configuration
func (src *fileSrc) Get() mlog.LogTargetCfg {
src.mutex.RLock()
defer src.mutex.RUnlock()
return src.cfg
}
// Set updates the dsn specifying the file source and reloads.
// The file will be watched for changes and reloaded as needed,
// and all listeners notified.
func (src *fileSrc) Set(path string, fget FileGetter) error {
data, err := fget.GetFile(path)
if err != nil {
return err
}
cfg, err := JSONToLogTargetCfg(data)
if err != nil {
return err
}
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()
if src.watcher != nil {
if err = src.watcher.Close(); err != nil {
mlog.Error("Failed to close watcher", mlog.Err(err))
}
src.watcher = nil
}
watcher, err := newWatcher(path, func() {
if serr := src.Set(path, fget); serr != nil {
mlog.Error("Failed to reload file on change", mlog.String("path", path), mlog.Err(serr))
}
})
if err != nil {
return err
}
src.watcher = watcher
return nil
}
func (src *fileSrc) set(cfg mlog.LogTargetCfg) {
src.mutex.Lock()
defer src.mutex.Unlock()
old := src.cfg
src.cfg = cfg
src.invokeConfigListeners(old, cfg)
}
// Close cleans up resources.
func (src *fileSrc) Close() error {
var err error
src.mutex.Lock()
defer src.mutex.Unlock()
if src.watcher != nil {
err = src.watcher.Close()
src.watcher = nil
}
return err
}