1 package logrusx
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "net/http"
8 "reflect"
9 "strings"
10
11 "github.com/gobuffalo/pop/v5/logging"
12
13 "github.com/sirupsen/logrus"
14
15 "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
16 "go.opentelemetry.io/otel/propagation"
17
18 "github.com/ory/x/errorsx"
19 )
20
21 type Logger struct {
22 *logrus.Entry
23 leakSensitive bool
24 opts []Option
25 name string
26 version string
27 }
28
29 var opts = otelhttptrace.WithPropagators(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
30
31 func (l *Logger) LeakSensitiveData() bool {
32 return l.leakSensitive
33 }
34
35 func (l *Logger) Logrus() *logrus.Logger {
36 return l.Entry.Logger
37 }
38
39 func (l *Logger) NewEntry() *Logger {
40 ll := *l
41 ll.Entry = logrus.NewEntry(l.Logger)
42 return &ll
43 }
44
45 func (l *Logger) WithContext(ctx context.Context) *Logger {
46 ll := *l
47 ll.Entry = l.Logger.WithContext(ctx)
48 return &ll
49 }
50
51 func (l *Logger) WithRequest(r *http.Request) *Logger {
52 headers := map[string]interface{}{}
53 if ua := r.UserAgent(); len(ua) > 0 {
54 headers["user-agent"] = ua
55 }
56
57 if cookie := l.maybeRedact(r.Header.Get("Cookie")); cookie != nil {
58 headers["cookie"] = cookie
59 }
60
61 if auth := l.maybeRedact(r.Header.Get("Authorization")); auth != nil {
62 headers["authorization"] = auth
63 }
64
65 for _, key := range []string{"Referer", "Origin", "Accept", "X-Request-ID", "If-None-Match",
66 "X-Forwarded-For", "X-Forwarded-Proto", "Cache-Control", "Accept-Encoding", "Accept-Language", "If-Modified-Since"} {
67 if value := r.Header.Get(key); len(value) > 0 {
68 headers[strings.ToLower(key)] = value
69 }
70 }
71
72 scheme := "https"
73 if r.TLS == nil {
74 scheme = "http"
75 }
76
77 ll := l.WithField("http_request", map[string]interface{}{
78 "remote": r.RemoteAddr,
79 "method": r.Method,
80 "path": r.URL.EscapedPath(),
81 "query": l.maybeRedact(r.URL.RawQuery),
82 "scheme": scheme,
83 "host": r.Host,
84 "headers": headers,
85 })
86
87 if _, _, spanCtx := otelhttptrace.Extract(r.Context(), r, opts); spanCtx.IsValid() {
88 traces := map[string]string{}
89 if spanCtx.HasTraceID() {
90 traces["trace_id"] = spanCtx.TraceID.String()
91 }
92 if spanCtx.HasSpanID() {
93 traces["span_id"] = spanCtx.SpanID.String()
94 }
95 ll = ll.WithField("otel", traces)
96 }
97
98 return ll
99 }
100
101 func (l *Logger) WithFields(f logrus.Fields) *Logger {
102 ll := *l
103 ll.Entry = l.Entry.WithFields(f)
104 return &ll
105 }
106
107 func (l *Logger) WithField(key string, value interface{}) *Logger {
108 ll := *l
109 ll.Entry = l.Entry.WithField(key, value)
110 return &ll
111 }
112
113 func (l *Logger) maybeRedact(value interface{}) interface{} {
114 if fmt.Sprintf("%v", value) == "" || value == nil {
115 return nil
116 }
117 if !l.leakSensitive {
118 return `Value is sensitive and has been redacted. To see the value set config key "log.leak_sensitive_values = true" or environment variable "LOG_LEAK_SENSITIVE_VALUES=true".`
119 }
120 return value
121 }
122
123 func (l *Logger) WithSensitiveField(key string, value interface{}) *Logger {
124 return l.WithField(key, l.maybeRedact(value))
125 }
126
127 func (l *Logger) WithError(err error) *Logger {
128 ctx := map[string]interface{}{"message": err.Error()}
129 if l.Entry.Logger.IsLevelEnabled(logrus.TraceLevel) {
130 if e, ok := err.(errorsx.StackTracer); ok {
131 ctx["trace"] = fmt.Sprintf("%+v", e.StackTrace())
132 } else {
133 ctx["trace"] = fmt.Sprintf("stack trace could not be recovered from error type %s", reflect.TypeOf(err))
134 }
135 }
136 if c := errorsx.ReasonCarrier(nil); errors.As(err, &c) {
137 ctx["reason"] = c.Reason()
138 }
139 if c := errorsx.RequestIDCarrier(nil); errors.As(err, &c) && c.RequestID() != "" {
140 ctx["request_id"] = c.RequestID()
141 }
142 if c := errorsx.DetailsCarrier(nil); errors.As(err, &c) && c.Details() != nil {
143 ctx["details"] = c.Details()
144 }
145 if c := errorsx.StatusCarrier(nil); errors.As(err, &c) && c.Status() != "" {
146 ctx["status"] = c.Status()
147 }
148 if c := errorsx.StatusCodeCarrier(nil); errors.As(err, &c) && c.StatusCode() != 0 {
149 ctx["status_code"] = c.StatusCode()
150 }
151 if c := errorsx.DebugCarrier(nil); errors.As(err, &c) {
152 ctx["debug"] = c.Debug()
153 }
154
155 return l.WithField("error", ctx)
156 }
157
158 var popLevelTranslations = map[logging.Level]logrus.Level{
159
160 logging.Debug: logrus.DebugLevel,
161 logging.Info: logrus.InfoLevel,
162 logging.Warn: logrus.WarnLevel,
163 logging.Error: logrus.ErrorLevel,
164 }
165
166 func (l *Logger) PopLogger(lvl logging.Level, s string, args ...interface{}) {
167 level, ok := popLevelTranslations[lvl]
168 if ok {
169 l.WithField("source", "pop").Logf(level, s, args...)
170 }
171 }
172
View as plain text