mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
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.
189 lines
3.9 KiB
Go
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
|
|
}
|