-
Notifications
You must be signed in to change notification settings - Fork 232
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docd: add slog and JSON logging (#149)
- Loading branch information
1 parent
1937742
commit bad7570
Showing
14 changed files
with
264 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package cloudtrace | ||
|
||
import ( | ||
"context" | ||
) | ||
|
||
type traceKey struct{} | ||
type spanKey struct{} | ||
|
||
func contextWithTraceInfo(ctx context.Context, traceHeader string) context.Context { | ||
traceID, spanID := parseHeader(traceHeader) | ||
if traceID != "" { | ||
ctx = context.WithValue(ctx, traceKey{}, traceID) | ||
} | ||
if spanID != "" { | ||
ctx = context.WithValue(ctx, spanKey{}, spanID) | ||
} | ||
return ctx | ||
} | ||
|
||
func traceInfoFromContext(ctx context.Context) (traceID, spanID string) { | ||
traceID, _ = ctx.Value(traceKey{}).(string) | ||
spanID, _ = ctx.Value(spanKey{}).(string) | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package cloudtrace | ||
|
||
import "strings" | ||
|
||
// The header specification is: | ||
// "X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE" | ||
const CloudTraceContextHeader = "X-Cloud-Trace-Context" | ||
|
||
func parseHeader(value string) (traceID, spanID string) { | ||
var found bool | ||
traceID, after, found := strings.Cut(value, "/") | ||
if found { | ||
spanID, _, _ = strings.Cut(after, ";") | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package cloudtrace | ||
|
||
import "net/http" | ||
|
||
type HTTPHandler struct { | ||
// Handler to wrap. | ||
Handler http.Handler | ||
} | ||
|
||
func (h *HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
ctx := contextWithTraceInfo(r.Context(), r.Header.Get(CloudTraceContextHeader)) | ||
|
||
h.Handler.ServeHTTP(w, r.WithContext(ctx)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package cloudtrace | ||
|
||
// Inspired by https://github.com/remko/cloudrun-slog | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log/slog" | ||
"os" | ||
) | ||
|
||
// Extra log level supported by Cloud Logging | ||
const LevelCritical = slog.Level(12) | ||
|
||
// Handler that outputs JSON understood by the structured log agent. | ||
// See https://cloud.google.com/logging/docs/agent/logging/configuration#special-fields | ||
type CloudLoggingHandler struct { | ||
handler slog.Handler | ||
projectID string | ||
} | ||
|
||
var _ slog.Handler = (*CloudLoggingHandler)(nil) | ||
|
||
func NewCloudLoggingHandler(projectID string, level slog.Level) *CloudLoggingHandler { | ||
return &CloudLoggingHandler{ | ||
projectID: projectID, | ||
handler: slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ | ||
AddSource: true, | ||
Level: level, | ||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { | ||
if a.Key == slog.MessageKey { | ||
a.Key = "message" | ||
} else if a.Key == slog.SourceKey { | ||
a.Key = "logging.googleapis.com/sourceLocation" | ||
} else if a.Key == slog.LevelKey { | ||
a.Key = "severity" | ||
level := a.Value.Any().(slog.Level) | ||
if level == LevelCritical { | ||
a.Value = slog.StringValue("CRITICAL") | ||
} | ||
} | ||
return a | ||
}, | ||
}), | ||
} | ||
} | ||
|
||
func (h *CloudLoggingHandler) Enabled(ctx context.Context, level slog.Level) bool { | ||
return h.handler.Enabled(ctx, level) | ||
} | ||
|
||
func (h *CloudLoggingHandler) Handle(ctx context.Context, rec slog.Record) error { | ||
traceID, spanID := traceInfoFromContext(ctx) | ||
if traceID != "" { | ||
rec = rec.Clone() | ||
// https://cloud.google.com/logging/docs/agent/logging/configuration#special-fields | ||
trace := fmt.Sprintf("projects/%s/traces/%s", h.projectID, traceID) | ||
rec.Add("logging.googleapis.com/trace", slog.StringValue(trace)) | ||
if spanID != "" { | ||
rec.Add("logging.googleapis.com/spanId", slog.StringValue(spanID)) | ||
} | ||
} | ||
return h.handler.Handle(ctx, rec) | ||
} | ||
|
||
func (h *CloudLoggingHandler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||
return &CloudLoggingHandler{handler: h.handler.WithAttrs(attrs)} | ||
} | ||
|
||
func (h *CloudLoggingHandler) WithGroup(name string) slog.Handler { | ||
return &CloudLoggingHandler{handler: h.handler.WithGroup(name)} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package debug | ||
|
||
import ( | ||
"context" | ||
) | ||
|
||
type debugEnabledKey struct{} | ||
|
||
func debugEnabledInContext(ctx context.Context) bool { | ||
enabled, ok := ctx.Value(debugEnabledKey{}).(bool) | ||
if !ok { | ||
return false | ||
} | ||
return enabled | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package debug | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"strconv" | ||
) | ||
|
||
type HTTPHandler struct { | ||
// Handler to wrap. | ||
Handler http.Handler | ||
} | ||
|
||
func (h *HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
ctx := r.Context() | ||
|
||
if ok, _ := strconv.ParseBool(r.Header.Get(DebugHeader)); ok { | ||
ctx = context.WithValue(ctx, debugEnabledKey{}, true) | ||
} | ||
|
||
h.Handler.ServeHTTP(w, r.WithContext(ctx)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package debug | ||
|
||
import ( | ||
"context" | ||
"log/slog" | ||
) | ||
|
||
const DebugHeader = "X-Debug" | ||
|
||
type debugHandler struct { | ||
slog.Handler | ||
} | ||
|
||
func NewDebugHandler(h slog.Handler) *debugHandler { | ||
return &debugHandler{Handler: h} | ||
} | ||
|
||
var _ slog.Handler = (*debugHandler)(nil) | ||
|
||
func (h *debugHandler) Enabled(ctx context.Context, level slog.Level) bool { | ||
if debugEnabledInContext(ctx) { | ||
return true | ||
} | ||
return h.Handler.Enabled(ctx, level) | ||
} | ||
|
||
func (h *debugHandler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||
return &debugHandler{Handler: h.Handler.WithAttrs(attrs)} | ||
} | ||
|
||
func (h *debugHandler) WithGroup(name string) slog.Handler { | ||
return &debugHandler{Handler: h.Handler.WithGroup(name)} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.