-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathslogstackdriver.go
114 lines (99 loc) · 3.2 KB
/
slogstackdriver.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
// Package slogstackdriver contains the slogger for google cloud's stackdriver.
package slogstackdriver // import "cdr.dev/slog/sloggers/slogstackdriver"
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"cloud.google.com/go/compute/metadata"
"cloud.google.com/go/logging/apiv2/loggingpb"
"go.opentelemetry.io/otel/trace"
logpbtype "google.golang.org/genproto/googleapis/logging/type"
"cdr.dev/slog"
"cdr.dev/slog/internal/syncwriter"
)
// Sink creates a slog.Sink configured to write JSON logs
// to stdout for stackdriver.
//
// See https://cloud.google.com/logging/docs/agent
func Sink(w io.Writer) slog.Sink {
// When not running in Google Cloud, the default metadata client will
// leak a goroutine.
//
// We use a very short timeout because the metadata server should be
// within the same datacenter as the cloud instance.
tp := http.DefaultTransport.(*http.Transport).Clone()
httpClient := &http.Client{
Timeout: time.Second * 3,
Transport: tp,
}
client := metadata.NewClient(httpClient)
projectID, _ := client.ProjectID()
httpClient.CloseIdleConnections()
return stackdriverSink{
projectID: projectID,
w: syncwriter.New(w),
}
}
type stackdriverSink struct {
projectID string
w *syncwriter.Writer
}
func (s stackdriverSink) LogEntry(ctx context.Context, ent slog.SinkEntry) {
// Note that these documents are inconsistent, so we only use the special
// keys described by both.
// https://cloud.google.com/logging/docs/agent/configuration#special-fields
// https://cloud.google.com/stackdriver/docs/solutions/agents/ops-agent/configuration#special-fields
e := slog.M(
slog.F("logging.googleapis.com/severity", sev(ent.Level)),
slog.F("severity", sev(ent.Level)),
slog.F("message", ent.Message),
// Unfortunately, both of these fields are required.
slog.F("timestampSeconds", ent.Time.Unix()),
slog.F("timestampNanos", ent.Time.UnixNano()%1e9),
slog.F("logging.googleapis.com/sourceLocation", &loggingpb.LogEntrySourceLocation{
File: ent.File,
Line: int64(ent.Line),
Function: ent.Func,
}),
)
if len(ent.LoggerNames) > 0 {
e = append(e, slog.F("logging.googleapis.com/operation", &loggingpb.LogEntryOperation{
Producer: strings.Join(ent.LoggerNames, "."),
}))
}
if ent.SpanContext.IsValid() {
e = append(e,
slog.F("logging.googleapis.com/trace", s.traceField(ent.SpanContext.TraceID())),
slog.F("logging.googleapis.com/spanId", ent.SpanContext.SpanID().String()),
slog.F("logging.googleapis.com/trace_sampled", ent.SpanContext.IsSampled()),
)
}
e = append(e, ent.Fields...)
buf, _ := json.Marshal(e)
buf = append(buf, '\n')
s.w.Write("slogstackdriver", buf)
}
func (s stackdriverSink) Sync() {
s.w.Sync("stackdriverSink")
}
func sev(level slog.Level) logpbtype.LogSeverity {
switch level {
case slog.LevelDebug:
return logpbtype.LogSeverity_DEBUG
case slog.LevelInfo:
return logpbtype.LogSeverity_INFO
case slog.LevelWarn:
return logpbtype.LogSeverity_WARNING
case slog.LevelError:
return logpbtype.LogSeverity_ERROR
default:
return logpbtype.LogSeverity_CRITICAL
}
}
func (s stackdriverSink) traceField(tID trace.TraceID) string {
return fmt.Sprintf("projects/%v/traces/%v", s.projectID, tID)
}