Skip to content

Commit 5237f18

Browse files
committed
cmd/log: move stackdriver logging to its own package
This change breaks out the stackdriver logging to its own package. It also only does logging of trace ids and labels to the stackdriver logger, to keep the logging interface simple. For golang/go#61399 Change-Id: I9a25d0c6391d1667fe476e5fdc30fc057f07c40f Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/515375 TryBot-Result: Gopher Robot <[email protected]> kokoro-CI: kokoro <[email protected]> Run-TryBot: Michael Matloob <[email protected]> Reviewed-by: Jamal Carvalho <[email protected]>
1 parent 52eb228 commit 5237f18

File tree

6 files changed

+230
-140
lines changed

6 files changed

+230
-140
lines changed

cmd/internal/cmdconfig/cmdconfig.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"golang.org/x/pkgsite/internal/database"
2323
"golang.org/x/pkgsite/internal/derrors"
2424
"golang.org/x/pkgsite/internal/log"
25+
"golang.org/x/pkgsite/internal/log/stackdriverlogger"
2526
"golang.org/x/pkgsite/internal/middleware"
2627
"golang.org/x/pkgsite/internal/postgres"
2728
)
@@ -36,11 +37,12 @@ func Logger(ctx context.Context, cfg *config.Config, logName string) middleware.
3637
"k8s-pod/app": cfg.Application(),
3738
}))
3839
}
39-
logger, err := log.UseStackdriver(ctx, logName, cfg.ProjectID, opts)
40+
logger, parent, err := stackdriverlogger.New(ctx, logName, cfg.ProjectID, opts)
41+
log.Use(logger)
4042
if err != nil {
4143
log.Fatal(ctx, err)
4244
}
43-
return logger
45+
return parent
4446
}
4547
return middleware.LocalLogger{}
4648
}

internal/log/log.go

Lines changed: 65 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,57 @@ package log
77

88
import (
99
"context"
10-
"errors"
1110
"fmt"
1211
"log"
1312
"os"
1413
"strings"
1514
"sync"
1615

17-
"cloud.google.com/go/logging"
18-
"golang.org/x/pkgsite/internal/derrors"
1916
"golang.org/x/pkgsite/internal/experiment"
2017
)
2118

19+
type Severity int
20+
21+
const (
22+
SeverityDefault = Severity(iota)
23+
SeverityDebug
24+
SeverityInfo
25+
SeverityWarning
26+
SeverityError
27+
SeverityCritical
28+
)
29+
30+
func (s Severity) String() string {
31+
switch s {
32+
case SeverityDefault:
33+
return "Default"
34+
case SeverityDebug:
35+
return "Debug"
36+
case SeverityInfo:
37+
return "Info"
38+
case SeverityWarning:
39+
return "Warning"
40+
case SeverityError:
41+
return "Error"
42+
case SeverityCritical:
43+
return "Critical"
44+
default:
45+
return fmt.Sprint(int(s))
46+
}
47+
}
48+
49+
type Logger interface {
50+
Log(ctx context.Context, s Severity, payload any)
51+
Flush()
52+
}
53+
2254
var (
2355
mu sync.Mutex
24-
logger interface {
25-
log(context.Context, logging.Severity, any)
26-
} = stdlibLogger{}
56+
logger Logger = stdlibLogger{}
2757

2858
// currentLevel holds current log level.
2959
// No logs will be printed below currentLevel.
30-
currentLevel = logging.Default
31-
)
32-
33-
type (
34-
// traceIDKey is the type of the context key for trace IDs.
35-
traceIDKey struct{}
36-
37-
// labelsKey is the type of the context key for labels.
38-
labelsKey struct{}
60+
currentLevel = SeverityDefault
3961
)
4062

4163
// Set the log level
@@ -45,79 +67,17 @@ func SetLevel(v string) {
4567
currentLevel = toLevel(v)
4668
}
4769

48-
func getLevel() logging.Severity {
70+
func getLevel() Severity {
4971
mu.Lock()
5072
defer mu.Unlock()
5173
return currentLevel
5274
}
5375

54-
// NewContextWithTraceID creates a new context from ctx that adds the trace ID.
55-
func NewContextWithTraceID(ctx context.Context, traceID string) context.Context {
56-
return context.WithValue(ctx, traceIDKey{}, traceID)
57-
}
58-
59-
// NewContextWithLabel creates anew context from ctx that adds a label that will
60-
// appear in the log entry.
61-
func NewContextWithLabel(ctx context.Context, key, value string) context.Context {
62-
oldLabels, _ := ctx.Value(labelsKey{}).(map[string]string)
63-
// Copy the labels, to preserve immutability of contexts.
64-
newLabels := map[string]string{}
65-
for k, v := range oldLabels {
66-
newLabels[k] = v
67-
}
68-
newLabels[key] = value
69-
return context.WithValue(ctx, labelsKey{}, newLabels)
70-
}
71-
72-
// stackdriverLogger logs to GCP Stackdriver.
73-
type stackdriverLogger struct {
74-
sdlogger *logging.Logger
75-
}
76-
77-
func (l *stackdriverLogger) log(ctx context.Context, s logging.Severity, payload any) {
78-
// Convert errors to strings, or they may serialize as the empty JSON object.
79-
if err, ok := payload.(error); ok {
80-
payload = err.Error()
81-
}
82-
traceID, _ := ctx.Value(traceIDKey{}).(string) // if not present, traceID is "", which is fine
83-
labels, _ := ctx.Value(labelsKey{}).(map[string]string)
84-
es := experimentString(ctx)
85-
if len(es) > 0 {
86-
nl := map[string]string{}
87-
for k, v := range labels {
88-
nl[k] = v
89-
}
90-
nl["experiments"] = es
91-
labels = nl
92-
}
93-
l.sdlogger.Log(logging.Entry{
94-
Severity: s,
95-
Labels: labels,
96-
Payload: payload,
97-
Trace: traceID,
98-
})
99-
}
100-
10176
// stdlibLogger uses the Go standard library logger.
10277
type stdlibLogger struct{}
10378

104-
func init() {
105-
// Log to stdout on GKE so the log messages are severity Info, rather than Error.
106-
if os.Getenv("GO_DISCOVERY_ON_GKE") != "" {
107-
log.SetOutput(os.Stdout)
108-
109-
}
110-
}
111-
112-
func (stdlibLogger) log(ctx context.Context, s logging.Severity, payload any) {
79+
func (stdlibLogger) Log(ctx context.Context, s Severity, payload any) {
11380
var extras []string
114-
traceID, _ := ctx.Value(traceIDKey{}).(string) // if not present, traceID is ""
115-
if traceID != "" {
116-
extras = append(extras, fmt.Sprintf("traceID %s", traceID))
117-
}
118-
if labels, ok := ctx.Value(labelsKey{}).(map[string]string); ok {
119-
extras = append(extras, fmt.Sprint(labels))
120-
}
12181
es := experimentString(ctx)
12282
if len(es) > 0 {
12383
extras = append(extras, fmt.Sprintf("experiments %s", es))
@@ -130,126 +90,106 @@ func (stdlibLogger) log(ctx context.Context, s logging.Severity, payload any) {
13090

13191
}
13292

93+
func (stdlibLogger) Flush() {}
94+
13395
func experimentString(ctx context.Context) string {
13496
return strings.Join(experiment.FromContext(ctx).Active(), ", ")
13597
}
13698

137-
// UseStackdriver switches from the default stdlib logger to a Stackdriver
138-
// logger. It assumes config.Init has been called. UseStackdriver returns a
139-
// "parent" *logging.Logger that should be used to log the start and end of a
140-
// request. It also creates and remembers internally a "child" logger that will
141-
// be used to log within a request. The two loggers are necessary to get request-scoped
142-
// logs in Stackdriver.
143-
// See https://cloud.google.com/appengine/docs/standard/go/writing-application-logs.
144-
//
145-
// UseStackdriver can only be called once. If it is called a second time, it returns an error.
146-
func UseStackdriver(ctx context.Context, logName, projectID string, opts []logging.LoggerOption) (_ *logging.Logger, err error) {
147-
defer derrors.Wrap(&err, "UseStackdriver(ctx, %q)", logName)
148-
client, err := logging.NewClient(ctx, projectID)
149-
if err != nil {
150-
return nil, err
151-
}
152-
parent := client.Logger(logName, opts...)
153-
child := client.Logger(logName+"-child", opts...)
99+
func Use(l Logger) {
154100
mu.Lock()
155101
defer mu.Unlock()
156-
if _, ok := logger.(*stackdriverLogger); ok {
157-
return nil, errors.New("already called once")
158-
}
159-
logger = &stackdriverLogger{child}
160-
return parent, nil
102+
logger = l
161103
}
162104

163105
// Infof logs a formatted string at the Info level.
164106
func Infof(ctx context.Context, format string, args ...any) {
165-
logf(ctx, logging.Info, format, args)
107+
logf(ctx, SeverityInfo, format, args)
166108
}
167109

168110
// Warningf logs a formatted string at the Warning level.
169111
func Warningf(ctx context.Context, format string, args ...any) {
170-
logf(ctx, logging.Warning, format, args)
112+
logf(ctx, SeverityWarning, format, args)
171113
}
172114

173115
// Errorf logs a formatted string at the Error level.
174116
func Errorf(ctx context.Context, format string, args ...any) {
175-
logf(ctx, logging.Error, format, args)
117+
logf(ctx, SeverityError, format, args)
176118
}
177119

178120
// Debugf logs a formatted string at the Debug level.
179121
func Debugf(ctx context.Context, format string, args ...any) {
180-
logf(ctx, logging.Debug, format, args)
122+
logf(ctx, SeverityDebug, format, args)
181123
}
182124

183125
// Fatalf logs formatted string at the Critical level followed by exiting the program.
184126
func Fatalf(ctx context.Context, format string, args ...any) {
185-
logf(ctx, logging.Critical, format, args)
127+
logf(ctx, SeverityCritical, format, args)
186128
die()
187129
}
188130

189-
func logf(ctx context.Context, s logging.Severity, format string, args []any) {
131+
func logf(ctx context.Context, s Severity, format string, args []any) {
190132
doLog(ctx, s, fmt.Sprintf(format, args...))
191133
}
192134

193135
// Info logs arg, which can be a string or a struct, at the Info level.
194-
func Info(ctx context.Context, arg any) { doLog(ctx, logging.Info, arg) }
136+
func Info(ctx context.Context, arg any) { doLog(ctx, SeverityInfo, arg) }
195137

196138
// Warning logs arg, which can be a string or a struct, at the Warning level.
197-
func Warning(ctx context.Context, arg any) { doLog(ctx, logging.Warning, arg) }
139+
func Warning(ctx context.Context, arg any) { doLog(ctx, SeverityWarning, arg) }
198140

199141
// Error logs arg, which can be a string or a struct, at the Error level.
200-
func Error(ctx context.Context, arg any) { doLog(ctx, logging.Error, arg) }
142+
func Error(ctx context.Context, arg any) { doLog(ctx, SeverityError, arg) }
201143

202144
// Debug logs arg, which can be a string or a struct, at the Debug level.
203-
func Debug(ctx context.Context, arg any) { doLog(ctx, logging.Debug, arg) }
145+
func Debug(ctx context.Context, arg any) { doLog(ctx, SeverityDebug, arg) }
204146

205147
// Fatal logs arg, which can be a string or a struct, at the Critical level followed by exiting the program.
206148
func Fatal(ctx context.Context, arg any) {
207-
doLog(ctx, logging.Critical, arg)
149+
doLog(ctx, SeverityCritical, arg)
208150
die()
209151
}
210152

211-
func doLog(ctx context.Context, s logging.Severity, payload any) {
153+
func doLog(ctx context.Context, s Severity, payload any) {
212154
if getLevel() > s {
213155
return
214156
}
215157
mu.Lock()
216158
l := logger
217159
mu.Unlock()
218-
l.log(ctx, s, payload)
160+
l.Log(ctx, s, payload)
219161
}
220162

221163
func die() {
222164
mu.Lock()
223-
if sl, ok := logger.(*stackdriverLogger); ok {
224-
sl.sdlogger.Flush()
225-
}
165+
logger.Flush()
226166
mu.Unlock()
227167
os.Exit(1)
228168
}
229169

230170
// toLevel returns the logging.Severity for a given string.
231171
// Possible input values are "", "debug", "info", "warning", "error", "fatal".
232172
// In case of invalid string input, it maps to DefaultLevel.
233-
func toLevel(v string) logging.Severity {
173+
func toLevel(v string) Severity {
234174
v = strings.ToLower(v)
235175

236176
switch v {
237177
case "":
238178
// default log level will print everything.
239-
return logging.Default
179+
return SeverityDefault
240180
case "debug":
241-
return logging.Debug
181+
return SeverityDebug
242182
case "info":
243-
return logging.Info
183+
return SeverityInfo
244184
case "warning":
245-
return logging.Warning
185+
return SeverityWarning
246186
case "error":
247-
return logging.Error
187+
return SeverityError
248188
case "fatal":
249-
return logging.Critical
189+
return SeverityCritical
250190
}
251191

252192
// Default log level in case of invalid input.
253193
log.Printf("Error: %s is invalid LogLevel. Possible values are [debug, info, warning, error, fatal]", v)
254-
return logging.Default
194+
return SeverityDefault
255195
}

internal/log/log_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
"fmt"
1010
"strings"
1111
"testing"
12-
13-
"cloud.google.com/go/logging"
1412
)
1513

1614
const (
@@ -27,15 +25,15 @@ func TestSetLogLevel(t *testing.T) {
2725
tests := []struct {
2826
name string
2927
newLevel string
30-
wantLevel logging.Severity
28+
wantLevel Severity
3129
}{
32-
{name: "default level", newLevel: "", wantLevel: logging.Default},
33-
{name: "invalid level", newLevel: "xyz", wantLevel: logging.Default},
34-
{name: "debug level", newLevel: "debug", wantLevel: logging.Debug},
35-
{name: "info level", newLevel: "info", wantLevel: logging.Info},
36-
{name: "warning level", newLevel: "warning", wantLevel: logging.Warning},
37-
{name: "error level", newLevel: "error", wantLevel: logging.Error},
38-
{name: "fatal level", newLevel: "fatal", wantLevel: logging.Critical},
30+
{name: "default level", newLevel: "", wantLevel: SeverityDefault},
31+
{name: "invalid level", newLevel: "xyz", wantLevel: SeverityDefault},
32+
{name: "debug level", newLevel: "debug", wantLevel: SeverityDebug},
33+
{name: "info level", newLevel: "info", wantLevel: SeverityInfo},
34+
{name: "warning level", newLevel: "warning", wantLevel: SeverityWarning},
35+
{name: "error level", newLevel: "error", wantLevel: SeverityError},
36+
{name: "fatal level", newLevel: "fatal", wantLevel: SeverityCritical},
3937
}
4038
for _, test := range tests {
4139
t.Run(test.name, func(t *testing.T) {
@@ -118,6 +116,8 @@ type mockLogger struct {
118116
logs string
119117
}
120118

121-
func (l *mockLogger) log(ctx context.Context, s logging.Severity, payload any) {
119+
func (l *mockLogger) Log(ctx context.Context, s Severity, payload any) {
122120
l.logs += fmt.Sprintf("%s: %+v", s, payload)
123121
}
122+
123+
func (l *mockLogger) Flush() {}

0 commit comments

Comments
 (0)