@@ -11,9 +11,10 @@ This document is maintained by Jonathan Amsterdam `
[email protected] `.
11
11
1 . [ Loggers and their handlers] ( #loggers-and-their-handlers )
12
12
1 . [ Implementing ` Handler ` methods] ( #implementing-`handler`-methods )
13
13
1. [The `Enabled` method](#the-`enabled`-method)
14
+ 1. [The `Handle` method](#the-`handle`-method)
14
15
1. [The `WithAttrs` method](#the-`withattrs`-method)
15
16
1. [The `WithGroup` method](#the-`withgroup`-method)
16
- 1. [The `Handle` method ](#the-`handle`-method )
17
+ 1. [Testing ](#testing )
17
18
1 . [ General considerations] ( #general-considerations )
18
19
1. [Concurrency safety](#concurrency-safety)
19
20
1. [Robustness](#robustness)
@@ -149,7 +150,7 @@ Changes to `LevelVar`s are goroutine-safe.
149
150
The mutex will be used to ensure that writes to the ` io.Writer ` happen atomically.
150
151
Unusually, ` IndentHandler ` holds a pointer to a ` sync.Mutex ` rather than holding a
151
152
` sync.Mutex ` directly.
152
- But there is a good reason for that, which we'll explain later.
153
+ There is a good reason for that, which we'll explain later.
153
154
154
155
TODO(jba): add link to that later explanation.
155
156
@@ -175,6 +176,101 @@ of the work done by each request to be controlled independently.
175
176
176
177
TODO(jba): include Enabled example
177
178
179
+ ## The ` Handle ` method
180
+
181
+ The ` Handle ` method is passed a ` Record ` containing all the information to be
182
+ logged for a single call to a ` Logger ` output method.
183
+ The ` Handle ` method should deal with it in some way.
184
+ One way is to output the ` Record ` in some format, as ` TextHandler ` and ` JSONHandler ` do.
185
+ But other options are to modify the ` Record ` and pass it on to another handler,
186
+ enqueue the ` Record ` for later processing, or ignore it.
187
+
188
+ The signature of ` Handle ` is
189
+
190
+ Handle(context.Context, Record) error
191
+
192
+ The context is provided to support applications that provide logging information
193
+ along the call chain. In a break with usual Go practice, the ` Handle ` method
194
+ should not treat a canceled context as a signal to stop work.
195
+
196
+ If ` Handle ` processes its ` Record ` , it should follow the rules in the
197
+ [ documentation] ( https://pkg.go.dev/log/slog#Handler.Handle ) .
198
+ For example, a zero ` Time ` field should be ignored, as should zero ` Attr ` s.
199
+
200
+ A ` Handle ` method that is going to produce output should carry out the following steps:
201
+
202
+ 1 . Allocate a buffer, typically a ` []byte ` , to hold the output.
203
+ It's best to construct the output in memory first,
204
+ then write it with a single call to ` io.Writer.Write ` ,
205
+ to minimize interleaving with other goroutines using the same writer.
206
+
207
+ 2 . Format the special fields: time, level, message, and source location (PC).
208
+ As a general rule, these fields should appear first and are not nested in
209
+ groups established by ` WithGroup ` .
210
+
211
+ 3 . Format the result of ` WithGroup ` and ` WithAttrs ` calls.
212
+
213
+ 4 . Format the attributes in the ` Record ` .
214
+
215
+ 5 . Output the buffer.
216
+
217
+ That is how our ` IndentHandler ` 's ` Handle ` method is structured:
218
+
219
+ ```
220
+ func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
221
+ buf := make([]byte, 0, 1024)
222
+ if !r.Time.IsZero() {
223
+ buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0)
224
+ }
225
+ buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0)
226
+ if r.PC != 0 {
227
+ fs := runtime.CallersFrames([]uintptr{r.PC})
228
+ f, _ := fs.Next()
229
+ buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0)
230
+ }
231
+ buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0)
232
+ indentLevel := 0
233
+ // TODO: output the Attrs and groups from WithAttrs and WithGroup.
234
+ r.Attrs(func(a slog.Attr) bool {
235
+ buf = h.appendAttr(buf, a, indentLevel)
236
+ return true
237
+ })
238
+ buf = append(buf, "---\n"...)
239
+ h.mu.Lock()
240
+ defer h.mu.Unlock()
241
+ _, err := h.out.Write(buf)
242
+ return err
243
+ }
244
+ ```
245
+
246
+ The first line allocates a ` []byte ` that should be large enough for most log
247
+ output.
248
+ Allocating a buffer with some initial, fairly large capacity is a simple but
249
+ significant optimization: it avoids the repeated copying and allocation that
250
+ happen when the initial slice is empty or small.
251
+ We'll return to this line in the section on [ speed] ( #speed )
252
+ and show how we can do even better.
253
+
254
+ The next part of our ` Handle ` method formats the special attributes,
255
+ observing the rules to ignore a zero time and a zero PC.
256
+
257
+ Next, the method processes the result of ` WithAttrs ` and ` WithGroup ` calls.
258
+ We'll skip that for now.
259
+
260
+ Then it's time to process the attributes in the argument record.
261
+ We use the ` Record.Attrs ` method to iterate over the attributes
262
+ in the order the user passed them to the ` Logger ` output method.
263
+ Handlers are free to reorder or de-duplicate the attributes,
264
+ but ours does not.
265
+
266
+ Lastly, after adding the line "---" to the output to separate log records,
267
+ our handler makes a single call to ` h.out.Write ` with the buffer we've accumulated.
268
+ We hold the lock for this write to make it atomic with respect to other
269
+ goroutines that may be calling ` Handle ` at the same time.
270
+
271
+ TODO(jba): talk about appendAttr
272
+
273
+
178
274
## The ` WithAttrs ` method
179
275
180
276
One of ` slog ` 's performance optimizations is support for pre-formatting
@@ -232,76 +328,14 @@ one that doesn't.
232
328
233
329
TODO(jba): add IndentHandler examples
234
330
235
- ## The ` Handle ` method
331
+ ## Testing
236
332
237
- The ` Handle ` method is passed a ` Record ` containing all the information to be
238
- logged for a single call to a ` Logger ` output method.
239
- The ` Handle ` method should deal with it in some way.
240
- One way is to output the ` Record ` in some format, as ` TextHandler ` and ` JSONHandler ` do.
241
- But other options are to modify the ` Record ` and pass it on to another handler,
242
- enqueue the ` Record ` for later processing, or ignore it.
243
-
244
- The signature of ` Handle ` is
245
-
246
- Handle(context.Context, Record) error
247
-
248
- The context is provided to support applications that provide logging information
249
- along the call chain. In a break with usual Go practice, the ` Handle ` method
250
- should not treat a canceled context as a signal to stop work.
251
-
252
- If ` Handle ` processes its ` Record ` , it should follow the rules in the
253
- [ documentation] ( https://pkg.go.dev/log/slog#Handler.Handle ) .
254
- For example, a zero ` Time ` field should be ignored, as should zero ` Attr ` s.
255
333
To verify that your handler follows these rules and generally produces proper
256
334
output, use the [ testing/slogtest package] ( https://pkg.go.dev/log/slog ) .
257
335
258
- Before processing the attributes in the ` Record ` , remember to process the
259
- attributes from ` WithAttrs ` calls, qualified by the groups from ` WithGroup `
260
- calls. Then use ` Record.Attrs ` to process the attributes in the ` Record ` , also
261
- qualified by the groups from ` WithGroup ` calls.
262
-
263
- The attributes provided to ` Handler.WithAttrs ` appear in the order the user passed them
264
- to ` Logger.With ` , and ` Record.Attrs ` traverses attributes in the order the user
265
- passed them to the ` Logger ` output method. Handlers are free to reorder or
266
- de-duplicate the attributes, provided group qualification is preserved.
267
-
268
- Your handler can make a single copy of a ` Record ` with an ordinary Go
269
- assignment, channel send or function call if it doesn't retain the
270
- original.
271
- But if its actions result in more than one copy, it should call ` Record.Clone `
272
- to make the copies so that they don't share state.
273
- This ` Handle ` method passes the record to a single handler, so it doesn't require ` Clone ` :
274
-
275
- type Handler1 struct {
276
- h slog.Handler
277
- // ...
278
- }
279
-
280
- func (h *Handler1) Handle(ctx context.Context, r slog.Record) error {
281
- return h.h.Handle(ctx, r)
282
- }
283
-
284
- This ` Handle ` method might pass the record to more than one handler, so it
285
- should use ` Clone ` :
286
-
287
- type Handler2 struct {
288
- hs []slog.Handler
289
- // ...
290
- }
291
-
292
- func (h *Handler2) Handle(ctx context.Context, r slog.Record) error {
293
- for _, hh := range h.hs {
294
- if err := hh.Handle(ctx, r.Clone()); err != nil {
295
- return err
296
- }
297
- }
298
- return nil
299
- }
300
-
301
-
302
-
303
- TODO(jba): example use of slogtest
336
+ TODO(jba): show the test function.
304
337
338
+ TODO(jba): reintroduce the material on Record.Clone that used to be here.
305
339
306
340
# General considerations
307
341
@@ -392,3 +426,5 @@ error.
392
426
## Speed
393
427
394
428
TODO(jba): discuss
429
+
430
+ TODO(jba): show how to pool a [ ] byte.
0 commit comments