Skip to content

Commit d2d7628

Browse files
authored
fix(enterprise/cli): add CODER_PROVISIONER_DAEMON_LOG_* options (coder#11279)
- Extracts cli.BuildLogger to clilog package - Updates existing usage of cli.BuildLogger and removes it - Use clilog to initialize provisionerd logger
1 parent 7c4fbe5 commit d2d7628

File tree

8 files changed

+587
-128
lines changed

8 files changed

+587
-128
lines changed

cli/clilog/clilog.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package clilog
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"os"
8+
"regexp"
9+
"strings"
10+
11+
"golang.org/x/xerrors"
12+
13+
"cdr.dev/slog"
14+
"cdr.dev/slog/sloggers/sloghuman"
15+
"cdr.dev/slog/sloggers/slogjson"
16+
"cdr.dev/slog/sloggers/slogstackdriver"
17+
"github.com/coder/coder/v2/cli/clibase"
18+
"github.com/coder/coder/v2/coderd/tracing"
19+
"github.com/coder/coder/v2/codersdk"
20+
)
21+
22+
type (
23+
Option func(*Builder)
24+
Builder struct {
25+
Filter []string
26+
Human string
27+
JSON string
28+
Stackdriver string
29+
Trace bool
30+
Verbose bool
31+
}
32+
)
33+
34+
func New(opts ...Option) *Builder {
35+
b := &Builder{}
36+
for _, opt := range opts {
37+
opt(b)
38+
}
39+
return b
40+
}
41+
42+
func WithFilter(filters ...string) Option {
43+
return func(b *Builder) {
44+
b.Filter = filters
45+
}
46+
}
47+
48+
func WithHuman(loc string) Option {
49+
return func(b *Builder) {
50+
b.Human = loc
51+
}
52+
}
53+
54+
func WithJSON(loc string) Option {
55+
return func(b *Builder) {
56+
b.JSON = loc
57+
}
58+
}
59+
60+
func WithStackdriver(loc string) Option {
61+
return func(b *Builder) {
62+
b.Stackdriver = loc
63+
}
64+
}
65+
66+
func WithTrace() Option {
67+
return func(b *Builder) {
68+
b.Trace = true
69+
}
70+
}
71+
72+
func WithVerbose() Option {
73+
return func(b *Builder) {
74+
b.Verbose = true
75+
}
76+
}
77+
78+
func FromDeploymentValues(vals *codersdk.DeploymentValues) Option {
79+
return func(b *Builder) {
80+
b.Filter = vals.Logging.Filter.Value()
81+
b.Human = vals.Logging.Human.Value()
82+
b.JSON = vals.Logging.JSON.Value()
83+
b.Stackdriver = vals.Logging.Stackdriver.Value()
84+
b.Trace = vals.Trace.Enable.Value()
85+
b.Verbose = vals.Verbose.Value()
86+
}
87+
}
88+
89+
func (b *Builder) Build(inv *clibase.Invocation) (log slog.Logger, closeLog func(), err error) {
90+
var (
91+
sinks = []slog.Sink{}
92+
closers = []func() error{}
93+
)
94+
defer func() {
95+
if err != nil {
96+
for _, closer := range closers {
97+
_ = closer()
98+
}
99+
}
100+
}()
101+
102+
noopClose := func() {}
103+
104+
addSinkIfProvided := func(sinkFn func(io.Writer) slog.Sink, loc string) error {
105+
switch loc {
106+
case "":
107+
108+
case "/dev/stdout":
109+
sinks = append(sinks, sinkFn(inv.Stdout))
110+
111+
case "/dev/stderr":
112+
sinks = append(sinks, sinkFn(inv.Stderr))
113+
114+
default:
115+
fi, err := os.OpenFile(loc, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644)
116+
if err != nil {
117+
return xerrors.Errorf("open log file %q: %w", loc, err)
118+
}
119+
closers = append(closers, fi.Close)
120+
sinks = append(sinks, sinkFn(fi))
121+
}
122+
return nil
123+
}
124+
125+
err = addSinkIfProvided(sloghuman.Sink, b.Human)
126+
if err != nil {
127+
return slog.Logger{}, noopClose, xerrors.Errorf("add human sink: %w", err)
128+
}
129+
err = addSinkIfProvided(slogjson.Sink, b.JSON)
130+
if err != nil {
131+
return slog.Logger{}, noopClose, xerrors.Errorf("add json sink: %w", err)
132+
}
133+
err = addSinkIfProvided(slogstackdriver.Sink, b.Stackdriver)
134+
if err != nil {
135+
return slog.Logger{}, noopClose, xerrors.Errorf("add stackdriver sink: %w", err)
136+
}
137+
138+
if b.Trace {
139+
sinks = append(sinks, tracing.SlogSink{})
140+
}
141+
142+
// User should log to null device if they don't want logs.
143+
if len(sinks) == 0 {
144+
return slog.Logger{}, noopClose, xerrors.New("no loggers provided, use /dev/null to disable logging")
145+
}
146+
147+
filter := &debugFilterSink{next: sinks}
148+
149+
err = filter.compile(b.Filter)
150+
if err != nil {
151+
return slog.Logger{}, noopClose, xerrors.Errorf("compile filters: %w", err)
152+
}
153+
154+
level := slog.LevelInfo
155+
// Debug logging is always enabled if a filter is present.
156+
if b.Verbose || filter.re != nil {
157+
level = slog.LevelDebug
158+
}
159+
160+
return inv.Logger.AppendSinks(filter).Leveled(level), func() {
161+
for _, closer := range closers {
162+
_ = closer()
163+
}
164+
}, nil
165+
}
166+
167+
var _ slog.Sink = &debugFilterSink{}
168+
169+
type debugFilterSink struct {
170+
next []slog.Sink
171+
re *regexp.Regexp
172+
}
173+
174+
func (f *debugFilterSink) compile(res []string) error {
175+
if len(res) == 0 {
176+
return nil
177+
}
178+
179+
var reb strings.Builder
180+
for i, re := range res {
181+
_, _ = fmt.Fprintf(&reb, "(%s)", re)
182+
if i != len(res)-1 {
183+
_, _ = reb.WriteRune('|')
184+
}
185+
}
186+
187+
re, err := regexp.Compile(reb.String())
188+
if err != nil {
189+
return xerrors.Errorf("compile regex: %w", err)
190+
}
191+
f.re = re
192+
return nil
193+
}
194+
195+
func (f *debugFilterSink) LogEntry(ctx context.Context, ent slog.SinkEntry) {
196+
if ent.Level == slog.LevelDebug {
197+
logName := strings.Join(ent.LoggerNames, ".")
198+
if f.re != nil && !f.re.MatchString(logName) && !f.re.MatchString(ent.Message) {
199+
return
200+
}
201+
}
202+
for _, sink := range f.next {
203+
sink.LogEntry(ctx, ent)
204+
}
205+
}
206+
207+
func (f *debugFilterSink) Sync() {
208+
for _, sink := range f.next {
209+
sink.Sync()
210+
}
211+
}

0 commit comments

Comments
 (0)