-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlog.go
138 lines (120 loc) · 3.66 KB
/
log.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
133
134
135
136
137
138
package gen2
import (
"context"
"io"
"log/slog"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"cloud.google.com/go/logging"
"github.com/Songmu/flextime" // time compatible package with utilities for testing
"github.com/google/uuid"
"go.opentelemetry.io/otel/trace"
)
var rootDir = sync.OnceValue(func() string {
_, currentFile, _, _ := runtime.Caller(0)
return filepath.Dir(currentFile)
})
var (
logAttrReporting = slog.String(
"@type",
"type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent",
)
)
const (
logMessageKey = "message"
logSeverityKey = "severity"
logSourceLocationKey = "logging.googleapis.com/sourceLocation"
logTraceKey = "logging.googleapis.com/trace"
logSpanIDKey = "logging.googleapis.com/spanId"
logTraceSampledKey = "logging.googleapis.com/traceSampled"
logInsertIDKey = "insertId"
logTimestampKey = "timestamp"
)
// LogHandler は構造化ログ世界のバックエンドとしての slog.LogHandler をラップします
type LogHandler struct {
base slog.Handler
projectID string
}
func NewLogHandler(w io.Writer, minLevel slog.Level) *LogHandler {
replaceAttr := func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.SourceKey { // 絶対パスを相対パスに変換
if source, ok := a.Value.Any().(*slog.Source); ok {
return slog.Any(logSourceLocationKey, &slog.Source{
Function: source.Function,
File: "{root}" + strings.TrimPrefix(source.File, rootDir()),
Line: source.Line,
})
}
}
if a.Key == slog.MessageKey {
a.Key = logMessageKey
return a
}
if a.Key == slog.LevelKey {
return slog.String(logSeverityKey, logging.Severity(a.Value.Any().(slog.Level)).String())
}
return a
}
handler := slog.NewJSONHandler(w, &slog.HandlerOptions{
AddSource: true,
Level: minLevel,
ReplaceAttr: replaceAttr,
})
return &LogHandler{base: handler}
}
func (h *LogHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.base.Enabled(ctx, level)
}
func (h *LogHandler) Handle(ctx context.Context, record slog.Record) error {
// 一意なエントリを保証するための情報を生成
now := flextime.Now()
insertId := uuid.NewString()
// 属性を追加
attrs := []slog.Attr{
slog.String(logInsertIDKey, insertId),
slog.String(logTimestampKey, now.Format(time.RFC3339Nano)),
}
// Open Telemetry トレース情報を追加
if span := trace.SpanFromContext(ctx); span != nil {
sc := span.SpanContext()
if h.projectID != "" {
traceID := "projects/" + h.projectID + "/traces/" + sc.TraceID().String()
attrs = append(attrs, slog.String(logTraceKey, traceID))
}
attrs = append(attrs, slog.String(logSpanIDKey, sc.SpanID().String()))
attrs = append(attrs, slog.Bool(logTraceSampledKey, sc.IsSampled()))
}
if record.Level == slog.LevelError {
attrs = append(attrs, logAttrReporting)
}
record.AddAttrs(attrs...)
// timestamp, insertId によって重複対策をしているので、リトライを書いても良い?
return h.base.Handle(ctx, record)
}
const (
AttrKeyProjectID = "project_id"
)
func (h *LogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
var unknownAttrs []slog.Attr
projectID := h.projectID
for _, a := range attrs {
switch a.Key {
case AttrKeyProjectID:
projectID = a.Value.String()
default:
unknownAttrs = append(unknownAttrs, a)
}
}
base := h.base
if len(unknownAttrs) > 0 {
base = h.base.WithAttrs(unknownAttrs)
}
return &LogHandler{base: base, projectID: projectID}
}
func (h *LogHandler) WithGroup(name string) slog.Handler {
base := h.base.WithGroup(name)
return &LogHandler{base: base, projectID: h.projectID}
}