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