-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
132 lines (115 loc) · 3.74 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package main
import (
"context"
"fmt"
"log"
"log/slog"
"net/http"
"os"
"strings"
"cloud.google.com/go/compute/metadata"
)
// Extra log level supported by Cloud Logging
const (
LevelCritical = slog.Level(12)
)
// Middleware that adds the Cloud Trace ID to the context
// This is used to correlate the structured logs with the Cloud Run
// request log.
func WithCloudTraceContext(h http.Handler) http.Handler {
// Get the project ID from the environment if specified
projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
if projectID == "" {
var err error
// Get from metadata server
// You can avoid this dependency by using environment variables, or by connecting
// to the metadata endpoint directly using an `http.Client`
// See https://cloud.google.com/compute/docs/metadata/overview
projectID, err = metadata.ProjectID()
if err != nil {
panic(err)
}
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var trace string
traceHeader := r.Header.Get("X-Cloud-Trace-Context")
traceParts := strings.Split(traceHeader, "/")
if len(traceParts) > 0 && len(traceParts[0]) > 0 {
trace = fmt.Sprintf("projects/%s/traces/%s", projectID, traceParts[0])
}
h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "trace", trace)))
})
}
func traceFromContext(ctx context.Context) string {
trace := ctx.Value("trace")
if trace == nil {
return ""
}
return trace.(string)
}
////////////////////////////////////////////////////////////////////////////////
// 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 }
func NewCloudLoggingHandler() *CloudLoggingHandler {
return &CloudLoggingHandler{handler: slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
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 {
trace := traceFromContext(ctx)
if trace != "" {
rec = rec.Clone()
// Add trace ID to the record so it is correlated with the Cloud Run request log
// See https://cloud.google.com/trace/docs/trace-log-integration
rec.Add("logging.googleapis.com/trace", slog.StringValue(trace))
}
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)}
}
////////////////////////////////////////////////////////////////////////////////
func main() {
// Set up structured logging
slog.SetDefault(slog.New(NewCloudLoggingHandler()))
// Example handler with logging
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
slog.InfoContext(ctx, "my message",
"mycount", 42,
"mystring", "myvalue",
)
}))
// Start server
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("listening on port %s", port)
if err := http.ListenAndServe(":"+port, WithCloudTraceContext(mux)); err != nil {
log.Fatal(err)
}
}