summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/logr/v2/config/config.go
blob: a93b7a25a210e518aa8c2d1cfb4f9ef797320175 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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 {
	targetFactory    TargetFactory    // can be nil
	formatterFactory FormatterFactory // can be nil
}

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 {
		target, err := newTarget(tcfg.Type, tcfg.Options, factories.targetFactory)
		if err != nil {
			return fmt.Errorf("error creating log target %s: %w", name, err)
		}

		if target == nil {
			continue
		}

		formatter, err := newFormatter(tcfg.Format, tcfg.FormatOptions, factories.formatterFactory)
		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
		}
	}
	return nil, fmt.Errorf("target type '%s' is unrecogized", targetType)
}

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
		}
	}
	return nil, fmt.Errorf("format '%s' is unrecogized", format)
}