Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 1 | // Copyright 2022 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 5 | /* |
| 6 | Package slog provides structured logging, |
| 7 | in which log records include a message, |
| 8 | a severity level, and various other attributes |
| 9 | expressed as key-value pairs. |
| 10 | |
| 11 | It defines a type, [Logger], |
| 12 | which provides several methods (such as [Logger.Info] and [Logger.Error]) |
| 13 | for reporting events of interest. |
| 14 | |
| 15 | Each Logger is associated with a [Handler]. |
| 16 | A Logger output method creates a [Record] from the method arguments |
| 17 | and passes it to the Handler, which decides how to handle it. |
| 18 | There is a default Logger accessible through top-level functions |
| 19 | (such as [Info] and [Error]) that call the corresponding Logger methods. |
| 20 | |
| 21 | A log record consists of a time, a level, a message, and a set of key-value |
| 22 | pairs, where the keys are strings and the values may be of any type. |
| 23 | As an example, |
| 24 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 25 | slog.Info("hello", "count", 3) |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 26 | |
| 27 | creates a record containing the time of the call, |
| 28 | a level of Info, the message "hello", and a single |
| 29 | pair with key "count" and value 3. |
| 30 | |
| 31 | The [Info] top-level function calls the [Logger.Info] method on the default Logger. |
| 32 | In addition to [Logger.Info], there are methods for Debug, Warn and Error levels. |
| 33 | Besides these convenience methods for common levels, |
| 34 | there is also a [Logger.Log] method which takes the level as an argument. |
| 35 | Each of these methods has a corresponding top-level function that uses the |
| 36 | default logger. |
| 37 | |
| 38 | The default handler formats the log record's message, time, level, and attributes |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 39 | as a string and passes it to the [log] package. |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 40 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 41 | 2022/11/08 15:28:26 INFO hello count=3 |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 42 | |
| 43 | For more control over the output format, create a logger with a different handler. |
| 44 | This statement uses [New] to create a new logger with a TextHandler |
| 45 | that writes structured records in text form to standard error: |
| 46 | |
Jonathan Amsterdam | dd950f8 | 2023-05-10 16:24:09 -0400 | [diff] [blame] | 47 | logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 48 | |
| 49 | [TextHandler] output is a sequence of key=value pairs, easily and unambiguously |
| 50 | parsed by machine. This statement: |
| 51 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 52 | logger.Info("hello", "count", 3) |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 53 | |
| 54 | produces this output: |
| 55 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 56 | time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3 |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 57 | |
| 58 | The package also provides [JSONHandler], whose output is line-delimited JSON: |
| 59 | |
Jonathan Amsterdam | dd950f8 | 2023-05-10 16:24:09 -0400 | [diff] [blame] | 60 | logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 61 | logger.Info("hello", "count", 3) |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 62 | |
| 63 | produces this output: |
| 64 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 65 | {"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3} |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 66 | |
Jonathan Amsterdam | dd950f8 | 2023-05-10 16:24:09 -0400 | [diff] [blame] | 67 | Both [TextHandler] and [JSONHandler] can be configured with [HandlerOptions]. |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 68 | There are options for setting the minimum level (see Levels, below), |
| 69 | displaying the source file and line of the log call, and |
| 70 | modifying attributes before they are logged. |
| 71 | |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 72 | Setting a logger as the default with |
| 73 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 74 | slog.SetDefault(logger) |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 75 | |
| 76 | will cause the top-level functions like [Info] to use it. |
| 77 | [SetDefault] also updates the default logger used by the [log] package, |
| 78 | so that existing applications that use [log.Printf] and related functions |
| 79 | will send log records to the logger's handler without needing to be rewritten. |
| 80 | |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 81 | Some attributes are common to many log calls. |
| 82 | For example, you may wish to include the URL or trace identifier of a server request |
| 83 | with all log events arising from the request. |
| 84 | Rather than repeat the attribute with every log call, you can use [Logger.With] |
| 85 | to construct a new Logger containing the attributes: |
| 86 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 87 | logger2 := logger.With("url", r.URL) |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 88 | |
| 89 | The arguments to With are the same key-value pairs used in [Logger.Info]. |
| 90 | The result is a new Logger with the same handler as the original, but additional |
| 91 | attributes that will appear in the output of every call. |
| 92 | |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 93 | # Levels |
| 94 | |
Jonathan Amsterdam | 6dcec33 | 2022-11-13 08:36:00 -0500 | [diff] [blame] | 95 | A [Level] is an integer representing the importance or severity of a log event. |
| 96 | The higher the level, the more severe the event. |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 97 | This package defines constants for the most common levels, |
Jonathan Amsterdam | 6dcec33 | 2022-11-13 08:36:00 -0500 | [diff] [blame] | 98 | but any int can be used as a level. |
| 99 | |
| 100 | In an application, you may wish to log messages only at a certain level or greater. |
| 101 | One common configuration is to log messages at Info or higher levels, |
| 102 | suppressing debug logging until it is needed. |
| 103 | The built-in handlers can be configured with the minimum level to output by |
| 104 | setting [HandlerOptions.Level]. |
| 105 | The program's `main` function typically does this. |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 106 | The default value is LevelInfo. |
Jonathan Amsterdam | 6dcec33 | 2022-11-13 08:36:00 -0500 | [diff] [blame] | 107 | |
| 108 | Setting the [HandlerOptions.Level] field to a [Level] value |
| 109 | fixes the handler's minimum level throughout its lifetime. |
| 110 | Setting it to a [LevelVar] allows the level to be varied dynamically. |
| 111 | A LevelVar holds a Level and is safe to read or write from multiple |
| 112 | goroutines. |
| 113 | To vary the level dynamically for an entire program, first initialize |
| 114 | a global LevelVar: |
| 115 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 116 | var programLevel = new(slog.LevelVar) // Info by default |
Jonathan Amsterdam | 6dcec33 | 2022-11-13 08:36:00 -0500 | [diff] [blame] | 117 | |
| 118 | Then use the LevelVar to construct a handler, and make it the default: |
| 119 | |
Jonathan Amsterdam | dd950f8 | 2023-05-10 16:24:09 -0400 | [diff] [blame] | 120 | h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}) |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 121 | slog.SetDefault(slog.New(h)) |
Jonathan Amsterdam | 6dcec33 | 2022-11-13 08:36:00 -0500 | [diff] [blame] | 122 | |
| 123 | Now the program can change its logging level with a single statement: |
| 124 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 125 | programLevel.Set(slog.LevelDebug) |
Jonathan Amsterdam | 6dcec33 | 2022-11-13 08:36:00 -0500 | [diff] [blame] | 126 | |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 127 | # Groups |
| 128 | |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 129 | Attributes can be collected into groups. |
| 130 | A group has a name that is used to qualify the names of its attributes. |
| 131 | How this qualification is displayed depends on the handler. |
| 132 | [TextHandler] separates the group and attribute names with a dot. |
| 133 | [JSONHandler] treats each group as a separate JSON object, with the group name as the key. |
| 134 | |
Jonathan Amsterdam | dd950f8 | 2023-05-10 16:24:09 -0400 | [diff] [blame] | 135 | Use [Group] to create a Group attribute from a name and a list of key-value pairs: |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 136 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 137 | slog.Group("request", |
Jonathan Amsterdam | dd950f8 | 2023-05-10 16:24:09 -0400 | [diff] [blame] | 138 | "method", r.Method, |
| 139 | "url", r.URL) |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 140 | |
| 141 | TextHandler would display this group as |
| 142 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 143 | request.method=GET request.url=http://example.com |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 144 | |
| 145 | JSONHandler would display it as |
| 146 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 147 | "request":{"method":"GET","url":"http://example.com"} |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 148 | |
| 149 | Use [Logger.WithGroup] to qualify all of a Logger's output |
| 150 | with a group name. Calling WithGroup on a Logger results in a |
| 151 | new Logger with the same Handler as the original, but with all |
| 152 | its attributes qualified by the group name. |
| 153 | |
| 154 | This can help prevent duplicate attribute keys in large systems, |
| 155 | where subsystems might use the same keys. |
| 156 | Pass each subsystem a different Logger with its own group name so that |
| 157 | potential duplicates are qualified: |
| 158 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 159 | logger := slog.Default().With("id", systemID) |
| 160 | parserLogger := logger.WithGroup("parser") |
| 161 | parseInput(input, parserLogger) |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 162 | |
| 163 | When parseInput logs with parserLogger, its keys will be qualified with "parser", |
| 164 | so even if it uses the common key "id", the log line will have distinct keys. |
| 165 | |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 166 | # Contexts |
| 167 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 168 | Some handlers may wish to include information from the [context.Context] that is |
| 169 | available at the call site. One example of such information |
Jonathan Amsterdam | dd950f8 | 2023-05-10 16:24:09 -0400 | [diff] [blame] | 170 | is the identifier for the current span when tracing is enabled. |
Jonathan Amsterdam | 971e412 | 2022-12-12 07:21:03 -0500 | [diff] [blame] | 171 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 172 | The [Logger.Log] and [Logger.LogAttrs] methods take a context as a first |
| 173 | argument, as do their corresponding top-level functions. |
Jonathan Amsterdam | 971e412 | 2022-12-12 07:21:03 -0500 | [diff] [blame] | 174 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 175 | Although the convenience methods on Logger (Info and so on) and the |
| 176 | corresponding top-level functions do not take a context, the alternatives ending |
Jonathan Amsterdam | 06a737e | 2023-07-06 13:10:23 -0400 | [diff] [blame] | 177 | in "Context" do. For example, |
Jonathan Amsterdam | 971e412 | 2022-12-12 07:21:03 -0500 | [diff] [blame] | 178 | |
Jonathan Amsterdam | 06a737e | 2023-07-06 13:10:23 -0400 | [diff] [blame] | 179 | slog.InfoContext(ctx, "message") |
Jonathan Amsterdam | 971e412 | 2022-12-12 07:21:03 -0500 | [diff] [blame] | 180 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 181 | It is recommended to pass a context to an output method if one is available. |
Jonathan Amsterdam | 971e412 | 2022-12-12 07:21:03 -0500 | [diff] [blame] | 182 | |
Jonathan Amsterdam | dd950f8 | 2023-05-10 16:24:09 -0400 | [diff] [blame] | 183 | # Attrs and Values |
| 184 | |
| 185 | An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as |
| 186 | alternating keys and values. The statement |
| 187 | |
| 188 | slog.Info("hello", slog.Int("count", 3)) |
| 189 | |
| 190 | behaves the same as |
| 191 | |
| 192 | slog.Info("hello", "count", 3) |
| 193 | |
| 194 | There are convenience constructors for [Attr] such as [Int], [String], and [Bool] |
| 195 | for common types, as well as the function [Any] for constructing Attrs of any |
| 196 | type. |
| 197 | |
| 198 | The value part of an Attr is a type called [Value]. |
| 199 | Like an [any], a Value can hold any Go value, |
| 200 | but it can represent typical values, including all numbers and strings, |
| 201 | without an allocation. |
| 202 | |
| 203 | For the most efficient log output, use [Logger.LogAttrs]. |
| 204 | It is similar to [Logger.Log] but accepts only Attrs, not alternating |
| 205 | keys and values; this allows it, too, to avoid allocation. |
| 206 | |
| 207 | The call |
| 208 | |
| 209 | logger.LogAttrs(nil, slog.LevelInfo, "hello", slog.Int("count", 3)) |
| 210 | |
| 211 | is the most efficient way to achieve the same output as |
| 212 | |
| 213 | slog.Info("hello", "count", 3) |
| 214 | |
Jonathan Amsterdam | db07412 | 2023-03-14 13:59:12 -0400 | [diff] [blame] | 215 | # Customizing a type's logging behavior |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 216 | |
Jonathan Amsterdam | 02c3fc3 | 2022-12-17 11:13:41 -0500 | [diff] [blame] | 217 | If a type implements the [LogValuer] interface, the [Value] returned from its LogValue |
| 218 | method is used for logging. You can use this to control how values of the type |
| 219 | appear in logs. For example, you can redact secret information like passwords, |
| 220 | or gather a struct's fields in a Group. See the examples under [LogValuer] for |
| 221 | details. |
| 222 | |
| 223 | A LogValue method may return a Value that itself implements [LogValuer]. The [Value.Resolve] |
| 224 | method handles these cases carefully, avoiding infinite loops and unbounded recursion. |
| 225 | Handler authors and others may wish to use Value.Resolve instead of calling LogValue directly. |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 226 | |
Jonathan Amsterdam | db07412 | 2023-03-14 13:59:12 -0400 | [diff] [blame] | 227 | # Wrapping output methods |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 228 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 229 | The logger functions use reflection over the call stack to find the file name |
| 230 | and line number of the logging call within the application. This can produce |
| 231 | incorrect source information for functions that wrap slog. For instance, if you |
| 232 | define this function in file mylog.go: |
Jonathan Amsterdam | 3160c0a | 2022-12-17 11:33:13 -0500 | [diff] [blame] | 233 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 234 | func Infof(format string, args ...any) { |
| 235 | slog.Default().Info(fmt.Sprintf(format, args...)) |
| 236 | } |
Jonathan Amsterdam | 3160c0a | 2022-12-17 11:33:13 -0500 | [diff] [blame] | 237 | |
| 238 | and you call it like this in main.go: |
| 239 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 240 | Infof(slog.Default(), "hello, %s", "world") |
Jonathan Amsterdam | 3160c0a | 2022-12-17 11:33:13 -0500 | [diff] [blame] | 241 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 242 | then slog will report the source file as mylog.go, not main.go. |
Jonathan Amsterdam | 3160c0a | 2022-12-17 11:33:13 -0500 | [diff] [blame] | 243 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 244 | A correct implementation of Infof will obtain the source location |
| 245 | (pc) and pass it to NewRecord. |
| 246 | The Infof function in the package-level example called "wrapping" |
| 247 | demonstrates how to do this. |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 248 | |
Jonathan Amsterdam | db07412 | 2023-03-14 13:59:12 -0400 | [diff] [blame] | 249 | # Working with Records |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 250 | |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 251 | Sometimes a Handler will need to modify a Record |
| 252 | before passing it on to another Handler or backend. |
| 253 | A Record contains a mixture of simple public fields (e.g. Time, Level, Message) |
| 254 | and hidden fields that refer to state (such as attributes) indirectly. This |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 255 | means that modifying a simple copy of a Record (e.g. by calling |
| 256 | [Record.Add] or [Record.AddAttrs] to add attributes) |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 257 | may have unexpected effects on the original. |
| 258 | Before modifying a Record, use [Clone] to |
| 259 | create a copy that shares no state with the original, |
| 260 | or create a new Record with [NewRecord] |
| 261 | and build up its Attrs by traversing the old ones with [Record.Attrs]. |
| 262 | |
Jonathan Amsterdam | db07412 | 2023-03-14 13:59:12 -0400 | [diff] [blame] | 263 | # Performance considerations |
Jonathan Amsterdam | 3c43f8b | 2022-12-11 20:42:58 -0500 | [diff] [blame] | 264 | |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 265 | If profiling your application demonstrates that logging is taking significant time, |
| 266 | the following suggestions may help. |
| 267 | |
| 268 | If many log lines have a common attribute, use [Logger.With] to create a Logger with |
| 269 | that attribute. The built-in handlers will format that attribute only once, at the |
| 270 | call to [Logger.With]. The [Handler] interface is designed to allow that optimization, |
| 271 | and a well-written Handler should take advantage of it. |
| 272 | |
| 273 | The arguments to a log call are always evaluated, even if the log event is discarded. |
| 274 | If possible, defer computation so that it happens only if the value is actually logged. |
| 275 | For example, consider the call |
| 276 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 277 | slog.Info("starting request", "url", r.URL.String()) // may compute String unnecessarily |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 278 | |
| 279 | The URL.String method will be called even if the logger discards Info-level events. |
| 280 | Instead, pass the URL directly: |
| 281 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 282 | slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 283 | |
| 284 | The built-in [TextHandler] will call its String method, but only |
| 285 | if the log event is enabled. |
| 286 | Avoiding the call to String also preserves the structure of the underlying value. |
| 287 | For example [JSONHandler] emits the components of the parsed URL as a JSON object. |
| 288 | If you want to avoid eagerly paying the cost of the String call |
| 289 | without causing the handler to potentially inspect the structure of the value, |
| 290 | wrap the value in a fmt.Stringer implementation that hides its Marshal methods. |
| 291 | |
| 292 | You can also use the [LogValuer] interface to avoid unnecessary work in disabled log |
| 293 | calls. Say you need to log some expensive value: |
| 294 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 295 | slog.Debug("frobbing", "value", computeExpensiveValue(arg)) |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 296 | |
| 297 | Even if this line is disabled, computeExpensiveValue will be called. |
| 298 | To avoid that, define a type implementing LogValuer: |
| 299 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 300 | type expensive struct { arg int } |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 301 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 302 | func (e expensive) LogValue() slog.Value { |
| 303 | return slog.AnyValue(computeExpensiveValue(e.arg)) |
| 304 | } |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 305 | |
| 306 | Then use a value of that type in log calls: |
| 307 | |
Jonathan Amsterdam | c95f2b4 | 2023-02-21 13:20:15 -0500 | [diff] [blame] | 308 | slog.Debug("frobbing", "value", expensive{arg}) |
Jonathan Amsterdam | 738e83a | 2022-12-17 12:47:27 -0500 | [diff] [blame] | 309 | |
| 310 | Now computeExpensiveValue will only be called when the line is enabled. |
Jonathan Amsterdam | d38c7dc | 2023-01-24 09:40:34 -0500 | [diff] [blame] | 311 | |
| 312 | The built-in handlers acquire a lock before calling [io.Writer.Write] |
| 313 | to ensure that each record is written in one piece. User-defined |
| 314 | handlers are responsible for their own locking. |
Jonathan Amsterdam | 8509921 | 2022-11-08 15:13:51 -0500 | [diff] [blame] | 315 | */ |
Piers | 5e25df0 | 2023-01-14 13:17:09 +0000 | [diff] [blame] | 316 | package slog |