2021-10-16 14:11:32 -07:00
|
|
|
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 {
|
2021-12-11 15:05:15 -08:00
|
|
|
TargetFactory TargetFactory // can be nil
|
|
|
|
FormatterFactory FormatterFactory // can be nil
|
2021-10-16 14:11:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2021-12-11 15:05:15 -08:00
|
|
|
target, err := newTarget(tcfg.Type, tcfg.Options, factories.TargetFactory)
|
2021-10-16 14:11:32 -07:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating log target %s: %w", name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if target == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-12-11 15:05:15 -08:00
|
|
|
formatter, err := newFormatter(tcfg.Format, tcfg.FormatOptions, factories.FormatterFactory)
|
2021-10-16 14:11:32 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2024-05-24 14:08:09 -07:00
|
|
|
return nil, fmt.Errorf("target type '%s' is unrecognized", targetType)
|
2021-10-16 14:11:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2024-05-24 14:08:09 -07:00
|
|
|
return nil, fmt.Errorf("format '%s' is unrecognized", format)
|
2021-10-16 14:11:32 -07:00
|
|
|
}
|