-
Notifications
You must be signed in to change notification settings - Fork 3
/
logger.go
160 lines (136 loc) · 4.94 KB
/
logger.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package logger
import (
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/getsentry/sentry-go"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const (
sentryEventIDHeader = "X-Sentry-Id"
)
// NewCore will create handy Core with sensible defaults:
// - messages with error level and higher will go to stderr, everything else to stdout
// - use json encoder for production and console for development.
func NewCore(debug bool) zapcore.Core {
var encoder zapcore.Encoder
if debug {
encoder = zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
} else {
encoder = zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
return zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(os.Stderr), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})),
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel
})),
)
}
// RequestLogger is a middleware for injecting sentry.Hub and zap.Logger into request context.
// If provided logger has sentryCoreWrapper as core injected logger will have core with same local core and
// sentry core based on an empty Hub for each request so breadcrumbs list will be empty each time.
// In other case logger.Core() will be used as a local core and sentry core will be created if sentry is initialized.
func RequestLogger(logger *zap.Logger) func(next http.Handler) http.Handler {
localCore := logger.Core()
client := sentry.CurrentHub().Client()
var options []SentryCoreOption
if wrappedCore, ok := localCore.(sentryCoreWrapper); ok {
localCore = wrappedCore.LocalCore()
sentryCore := wrappedCore.SentryCore()
client = sentryCore.hub.Client()
options = prepareOptions(sentryCore)
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ww := NewWrapResponseWriter(w, r.ProtoMajor)
var span *sentry.Span
var loggerOptions []zap.Option
core := localCore
if client != nil {
hub := sentry.NewHub(client, sentry.NewScope())
hub.Scope().SetRequest(r)
hub.Scope().SetUser(
sentry.User{
IPAddress: strings.Split(r.RemoteAddr, ":")[0],
},
)
ctx = WithHub(ctx, hub)
span = sentry.StartSpan(ctx, "http.handler",
sentry.WithTransactionName(fmt.Sprintf("%s %s", r.Method, r.URL.Path)),
sentry.ContinueFromRequest(r),
)
ctx = span.Context() //nolint:contextcheck
core = NewSentryCoreWrapper(localCore, hub, options...)
loggerOptions = append(loggerOptions, zap.Hooks(func(entry zapcore.Entry) error {
//nolint: forcetypeassert
if entry.Level >= core.(sentryCoreWrapper).SentryCore().EventLevel && hub.LastEventID() != "" {
ww.Header().Add(sentryEventIDHeader, string(hub.LastEventID()))
}
return nil
}))
}
requestLogger := zap.New(core, loggerOptions...)
ctx = WithLogger(ctx, requestLogger)
t1 := time.Now()
defer func() {
if span != nil {
span.Status = SpanStatus(ww.Status())
span.Finish()
}
// fetching logger from context because it can be changed by WithExtraFields middleware
Ctx(ctx).Debug("-",
zap.Duration("duration", time.Since(t1)),
zap.Int("status", ww.Status()),
zap.Int("size", ww.BytesWritten()),
zap.String("method", r.Method),
zap.String("url", r.URL.String()),
zap.String("ip", r.RemoteAddr),
)
}()
next.ServeHTTP(ww, r.WithContext(ctx))
})
}
}
// WithExtraFields is a middleware for injecting extra field to the logger injected by RequestLogger middleware.
func WithExtraFields(fieldsGenerator func(r *http.Request) []zap.Field) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fields := fieldsGenerator(r)
if loggerPointer, ok := r.Context().Value(zapLoggerCtxKey{}).(**zap.Logger); ok {
*loggerPointer = (*loggerPointer).With(fields...)
}
next.ServeHTTP(w, r)
})
}
}
// ForkedLogger will return a new logger with isolated sentry.Hub.
// No-op if logger is not using SentryCore.
func ForkedLogger(logger *zap.Logger) *zap.Logger {
wrappedCore, ok := logger.Core().(sentryCoreWrapper)
if !ok {
// This logger is not using Sentry core.
return logger
}
localCore := wrappedCore.LocalCore()
sentryCore := wrappedCore.SentryCore()
options := prepareOptions(sentryCore)
hub := sentry.NewHub(sentryCore.hub.Client(), sentry.NewScope())
core := NewSentryCoreWrapper(localCore, hub, options...)
return zap.New(core)
}
func prepareOptions(core *SentryCore) []SentryCoreOption {
var options []SentryCoreOption
if breadcrumbLevel := core.BreadcrumbLevel; breadcrumbLevel != defaultBreadcrumbLevel {
options = append(options, BreadcrumbLevel(breadcrumbLevel))
}
if eventLevel := core.EventLevel; eventLevel != defaultEventLevel {
options = append(options, EventLevel(eventLevel))
}
return options
}