1
- //
2
1
// Custom Structured Logger
3
2
// ========================
4
3
// This example demonstrates how to use middleware.RequestLogger,
5
4
// middleware.LogFormatter and middleware.LogEntry to build a structured
6
- // logger using the amazing sirupsen/logrus package as the logging
5
+ // logger using the preview version of the new log/slog package as the logging
7
6
// backend.
8
7
//
9
8
// Also: check out https://github.com/goware/httplog for an improved context
10
9
// logger with support for HTTP request logging, based on the example below.
11
- //
12
10
package main
13
11
14
12
import (
15
13
"fmt"
16
14
"net/http"
15
+ "os"
17
16
"time"
18
17
18
+ "golang.org/x/exp/slog"
19
+
19
20
"github.com/go-chi/chi/v5"
20
21
"github.com/go-chi/chi/v5/middleware"
21
- "github.com/sirupsen/logrus"
22
22
)
23
23
24
24
func main () {
25
-
26
- // Setup the logger backend using sirupsen/logrus and configure
27
- // it to use a custom JSONFormatter. See the logrus docs for how to
28
- // configure the backend at github.com/sirupsen/logrus
29
- logger := logrus .New ()
30
- logger .Formatter = & logrus.JSONFormatter {
31
- // disable, as we set our own
32
- DisableTimestamp : true ,
33
- }
25
+ // Setup a JSON handler for the new log/slog library
26
+ slogJSONHandler := slog.HandlerOptions {
27
+ // Remove default time slog.Attr, we create our own later
28
+ ReplaceAttr : func (groups []string , a slog.Attr ) slog.Attr {
29
+ if a .Key == slog .TimeKey {
30
+ return slog.Attr {}
31
+ }
32
+ return a
33
+ },
34
+ }.NewJSONHandler (os .Stdout )
34
35
35
36
// Routes
36
37
r := chi .NewRouter ()
37
38
r .Use (middleware .RequestID )
38
- r .Use (NewStructuredLogger (logger ))
39
+ r .Use (NewStructuredLogger (slogJSONHandler ))
39
40
r .Use (middleware .Recoverer )
40
41
41
42
r .Get ("/" , func (w http.ResponseWriter , r * http.Request ) {
@@ -49,70 +50,70 @@ func main() {
49
50
r .Get ("/panic" , func (w http.ResponseWriter , r * http.Request ) {
50
51
panic ("oops" )
51
52
})
53
+ r .Get ("/add_fields" , func (w http.ResponseWriter , r * http.Request ) {
54
+ LogEntrySetFields (r , map [string ]interface {}{"foo" : "bar" , "bar" : "foo" })
55
+ })
52
56
http .ListenAndServe (":3333" , r )
53
57
}
54
58
55
59
// StructuredLogger is a simple, but powerful implementation of a custom structured
56
- // logger backed on logrus . I encourage users to copy it, adapt it and make it their
60
+ // logger backed on log/slog . I encourage users to copy it, adapt it and make it their
57
61
// own. Also take a look at https://github.com/go-chi/httplog for a dedicated pkg based
58
62
// on this work, designed for context-based http routers.
59
63
60
- func NewStructuredLogger (logger * logrus. Logger ) func (next http.Handler ) http.Handler {
61
- return middleware .RequestLogger (& StructuredLogger {logger })
64
+ func NewStructuredLogger (handler slog. Handler ) func (next http.Handler ) http.Handler {
65
+ return middleware .RequestLogger (& StructuredLogger {Logger : handler })
62
66
}
63
67
64
68
type StructuredLogger struct {
65
- Logger * logrus. Logger
69
+ Logger slog. Handler
66
70
}
67
71
68
72
func (l * StructuredLogger ) NewLogEntry (r * http.Request ) middleware.LogEntry {
69
- entry := & StructuredLoggerEntry {Logger : logrus .NewEntry (l .Logger )}
70
- logFields := logrus.Fields {}
71
-
72
- logFields ["ts" ] = time .Now ().UTC ().Format (time .RFC1123 )
73
+ var logFields []slog.Attr
74
+ logFields = append (logFields , slog .String ("ts" , time .Now ().UTC ().Format (time .RFC1123 )))
73
75
74
76
if reqID := middleware .GetReqID (r .Context ()); reqID != "" {
75
- logFields [ "req_id" ] = reqID
77
+ logFields = append ( logFields , slog . String ( "req_id" , reqID ))
76
78
}
77
79
78
80
scheme := "http"
79
81
if r .TLS != nil {
80
82
scheme = "https"
81
83
}
82
- logFields ["http_scheme" ] = scheme
83
- logFields ["http_proto" ] = r .Proto
84
- logFields ["http_method" ] = r .Method
85
-
86
- logFields ["remote_addr" ] = r .RemoteAddr
87
- logFields ["user_agent" ] = r .UserAgent ()
88
84
89
- logFields ["uri" ] = fmt .Sprintf ("%s://%s%s" , scheme , r .Host , r .RequestURI )
85
+ handler := l .Logger .WithAttrs (append (logFields ,
86
+ slog .String ("http_scheme" , scheme ),
87
+ slog .String ("http_proto" , r .Proto ),
88
+ slog .String ("http_method" , r .Method ),
89
+ slog .String ("remote_addr" , r .RemoteAddr ),
90
+ slog .String ("user_agent" , r .UserAgent ()),
91
+ slog .String ("uri" , fmt .Sprintf ("%s://%s%s" , scheme , r .Host , r .RequestURI ))))
90
92
91
- entry . Logger = entry . Logger . WithFields ( logFields )
93
+ entry := StructuredLoggerEntry { Logger : slog . New ( handler )}
92
94
93
- entry .Logger .Infoln ( "request started" )
95
+ entry .Logger .LogAttrs ( slog . LevelInfo , "request started" , logFields ... )
94
96
95
- return entry
97
+ return & entry
96
98
}
97
99
98
100
type StructuredLoggerEntry struct {
99
- Logger logrus. FieldLogger
101
+ Logger * slog. Logger
100
102
}
101
103
102
104
func (l * StructuredLoggerEntry ) Write (status , bytes int , header http.Header , elapsed time.Duration , extra interface {}) {
103
- l .Logger = l .Logger .WithFields (logrus.Fields {
104
- "resp_status" : status , "resp_bytes_length" : bytes ,
105
- "resp_elapsed_ms" : float64 (elapsed .Nanoseconds ()) / 1000000.0 ,
106
- })
107
-
108
- l .Logger .Infoln ("request complete" )
105
+ l .Logger .LogAttrs (slog .LevelInfo , "request complete" ,
106
+ slog .Int ("resp_status" , status ),
107
+ slog .Int ("resp_byte_length" , bytes ),
108
+ slog .Float64 ("resp_elapsed_ms" , float64 (elapsed .Nanoseconds ())/ 1000000.0 ),
109
+ )
109
110
}
110
111
111
112
func (l * StructuredLoggerEntry ) Panic (v interface {}, stack []byte ) {
112
- l .Logger = l . Logger . WithFields (logrus. Fields {
113
- "stack" : string (stack ),
114
- "panic" : fmt .Sprintf ("%+v" , v ),
115
- } )
113
+ l .Logger . LogAttrs ( slog . LevelInfo , "" ,
114
+ slog . String ( "stack" , string (stack ) ),
115
+ slog . String ( "panic" , fmt .Sprintf ("%+v" , v ) ),
116
+ )
116
117
}
117
118
118
119
// Helper methods used by the application to get the request-scoped
@@ -122,19 +123,21 @@ func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
122
123
// passes through the handler chain, which at any point can be logged
123
124
// with a call to .Print(), .Info(), etc.
124
125
125
- func GetLogEntry (r * http.Request ) logrus. FieldLogger {
126
+ func GetLogEntry (r * http.Request ) * slog. Logger {
126
127
entry := middleware .GetLogEntry (r ).(* StructuredLoggerEntry )
127
128
return entry .Logger
128
129
}
129
130
130
131
func LogEntrySetField (r * http.Request , key string , value interface {}) {
131
132
if entry , ok := r .Context ().Value (middleware .LogEntryCtxKey ).(* StructuredLoggerEntry ); ok {
132
- entry .Logger = entry .Logger .WithField (key , value )
133
+ entry .Logger = entry .Logger .With (key , value )
133
134
}
134
135
}
135
136
136
137
func LogEntrySetFields (r * http.Request , fields map [string ]interface {}) {
137
138
if entry , ok := r .Context ().Value (middleware .LogEntryCtxKey ).(* StructuredLoggerEntry ); ok {
138
- entry .Logger = entry .Logger .WithFields (fields )
139
+ for k , v := range fields {
140
+ entry .Logger = entry .Logger .With (k , v )
141
+ }
139
142
}
140
143
}
0 commit comments