blob: 2fe455688ed032e483694a69908e16bbb16b217e [file] [log] [blame]
Serge Bazanski04aa3df2024-09-12 14:07:25 +02001package logging
2
3import (
4 "fmt"
5 "io"
6 "runtime"
7 "strings"
8 "time"
9)
10
11// FunctionBackend is the simplest Backend (Leveled implementation). It
12// synchronously forwards logged entries to a function.
13type FunctionBackend struct {
14 depth int
15 fn func(severity Severity, msg string)
16 verbosity VerbosityLevel
17}
18
19// NewFunctionBackend returns a FunctionBackend (Leveled implementation) which
20// will call the given function on every log entry synchronously.
21func NewFunctionBackend(fn func(severity Severity, msg string)) *FunctionBackend {
22 return &FunctionBackend{
23 fn: fn,
24 }
25}
26
27func (w *FunctionBackend) Info(args ...any) {
28 w.fn(INFO, fmt.Sprint(args...))
29}
30func (w FunctionBackend) Infof(format string, args ...any) {
31 w.fn(INFO, fmt.Sprintf(format, args...))
32}
33func (w *FunctionBackend) Warning(args ...any) {
34 w.fn(WARNING, fmt.Sprint(args...))
35}
36func (w *FunctionBackend) Warningf(format string, args ...any) {
37 w.fn(WARNING, fmt.Sprintf(format, args...))
38}
39func (w *FunctionBackend) Error(args ...any) {
40 w.fn(ERROR, fmt.Sprint(args...))
41}
42func (w *FunctionBackend) Errorf(format string, args ...any) {
43 w.fn(ERROR, fmt.Sprintf(format, args...))
44}
45func (w *FunctionBackend) Fatal(args ...any) {
46 w.fn(FATAL, fmt.Sprint(args...))
47}
48func (w *FunctionBackend) Fatalf(format string, args ...any) {
49 w.fn(FATAL, fmt.Sprintf(format, args...))
50}
51
52type verboseFunctionBackend struct {
53 backend *FunctionBackend
54 enabled bool
55}
56
57func (w *FunctionBackend) V(level VerbosityLevel) VerboseLeveled {
58 return &verboseFunctionBackend{
59 backend: w,
60 enabled: level > w.verbosity,
61 }
62}
63
64func (w *FunctionBackend) WithAddedStackDepth(depth int) Leveled {
65 w2 := *w
66 w.depth += depth
67 return &w2
68}
69
70func (v *verboseFunctionBackend) Enabled() bool {
71 return v.enabled
72}
73
74func (v *verboseFunctionBackend) Info(args ...any) {
75 if !v.enabled {
76 return
77 }
78 v.backend.fn(INFO, fmt.Sprint(args...))
79}
80
81func (v *verboseFunctionBackend) Infof(format string, args ...any) {
82 if !v.enabled {
83 return
84 }
85 v.backend.fn(INFO, fmt.Sprintf(format, args...))
86}
87
88// WriterBackend is a Backend (Leveled implementation) which outputs log entries
89// to a writer using a given Formatter.
90type WriterBackend struct {
91 FunctionBackend
92 // Formatter is used to turn a log entry alongside metadata into a string. By
93 // default, it's set to LeveledFormatterGlog.
94 Formatter LeveledFormatter
95 out io.Writer
96 MinimumSeverity Severity
97}
98
99// LeveledFormatter is a function to turn a leveled log entry into a string which
100// can be output to a user.
101type LeveledFormatter func(file string, line int, time time.Time, severity Severity, msg string) string
102
103// LeveledFormatterGlog implements LeveledFormatter in a glog/klog-compatible
104// way.
105func LeveledFormatterGlog(file string, line int, ts time.Time, severity Severity, msg string) string {
106 // TODO(q3k): unify with //osbase/logtree.LeveledPayload.String.
107 _, month, day := ts.Date()
108 hour, minute, second := ts.Clock()
109 nsec := ts.Nanosecond() / 1000
110
111 res := fmt.Sprintf("%s%02d%02d %02d:%02d:%02d.%06d %s:%d] ", severity, month, day, hour, minute, second, nsec, file, line)
112 res += msg
113 return res
114}
115
116// NewWriterBackend constructs a WriterBackend (Leveled implementation) which
117// writes glog/klog-style entries to the given Writer.
118func NewWriterBackend(w io.Writer) *WriterBackend {
119 res := &WriterBackend{
120 Formatter: LeveledFormatterGlog,
121 out: w,
122 MinimumSeverity: INFO,
123 }
124 res.FunctionBackend.fn = res.log
125 return res
126}
127
128func (w *WriterBackend) log(severity Severity, msg string) {
129 if !severity.AtLeast(w.MinimumSeverity) {
130 return
131 }
132 _, file, line, ok := runtime.Caller(2 + w.depth)
133 if !ok {
134 file = "???"
135 line = 1
136 } else {
137 slash := strings.LastIndex(file, "/")
138 if slash >= 0 {
139 file = file[slash+1:]
140 }
141 }
142 res := w.Formatter(file, line, time.Now(), severity, msg)
143 w.out.Write([]byte(res + "\n"))
144}