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.
143 lines
3.4 KiB
Go
143 lines
3.4 KiB
Go
// 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"
|
|
}
|