mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 11:12:26 -05:00 
			
		
		
		
	
		
			
	
	
		
			317 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			317 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | // Copyright 2022 The Go Authors. All rights reserved. | ||
|  | // Use of this source code is governed by a BSD-style | ||
|  | // license that can be found in the LICENSE file. | ||
|  | 
 | ||
|  | /* | ||
|  | Package slog provides structured logging, | ||
|  | in which log records include a message, | ||
|  | a severity level, and various other attributes | ||
|  | expressed as key-value pairs. | ||
|  | 
 | ||
|  | It defines a type, [Logger], | ||
|  | which provides several methods (such as [Logger.Info] and [Logger.Error]) | ||
|  | for reporting events of interest. | ||
|  | 
 | ||
|  | Each Logger is associated with a [Handler]. | ||
|  | A Logger output method creates a [Record] from the method arguments | ||
|  | and passes it to the Handler, which decides how to handle it. | ||
|  | There is a default Logger accessible through top-level functions | ||
|  | (such as [Info] and [Error]) that call the corresponding Logger methods. | ||
|  | 
 | ||
|  | A log record consists of a time, a level, a message, and a set of key-value | ||
|  | pairs, where the keys are strings and the values may be of any type. | ||
|  | As an example, | ||
|  | 
 | ||
|  | 	slog.Info("hello", "count", 3) | ||
|  | 
 | ||
|  | creates a record containing the time of the call, | ||
|  | a level of Info, the message "hello", and a single | ||
|  | pair with key "count" and value 3. | ||
|  | 
 | ||
|  | The [Info] top-level function calls the [Logger.Info] method on the default Logger. | ||
|  | In addition to [Logger.Info], there are methods for Debug, Warn and Error levels. | ||
|  | Besides these convenience methods for common levels, | ||
|  | there is also a [Logger.Log] method which takes the level as an argument. | ||
|  | Each of these methods has a corresponding top-level function that uses the | ||
|  | default logger. | ||
|  | 
 | ||
|  | The default handler formats the log record's message, time, level, and attributes | ||
|  | as a string and passes it to the [log] package. | ||
|  | 
 | ||
|  | 	2022/11/08 15:28:26 INFO hello count=3 | ||
|  | 
 | ||
|  | For more control over the output format, create a logger with a different handler. | ||
|  | This statement uses [New] to create a new logger with a TextHandler | ||
|  | that writes structured records in text form to standard error: | ||
|  | 
 | ||
|  | 	logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) | ||
|  | 
 | ||
|  | [TextHandler] output is a sequence of key=value pairs, easily and unambiguously | ||
|  | parsed by machine. This statement: | ||
|  | 
 | ||
|  | 	logger.Info("hello", "count", 3) | ||
|  | 
 | ||
|  | produces this output: | ||
|  | 
 | ||
|  | 	time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3 | ||
|  | 
 | ||
|  | The package also provides [JSONHandler], whose output is line-delimited JSON: | ||
|  | 
 | ||
|  | 	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) | ||
|  | 	logger.Info("hello", "count", 3) | ||
|  | 
 | ||
|  | produces this output: | ||
|  | 
 | ||
|  | 	{"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3} | ||
|  | 
 | ||
|  | Both [TextHandler] and [JSONHandler] can be configured with [HandlerOptions]. | ||
|  | There are options for setting the minimum level (see Levels, below), | ||
|  | displaying the source file and line of the log call, and | ||
|  | modifying attributes before they are logged. | ||
|  | 
 | ||
|  | Setting a logger as the default with | ||
|  | 
 | ||
|  | 	slog.SetDefault(logger) | ||
|  | 
 | ||
|  | will cause the top-level functions like [Info] to use it. | ||
|  | [SetDefault] also updates the default logger used by the [log] package, | ||
|  | so that existing applications that use [log.Printf] and related functions | ||
|  | will send log records to the logger's handler without needing to be rewritten. | ||
|  | 
 | ||
|  | Some attributes are common to many log calls. | ||
|  | For example, you may wish to include the URL or trace identifier of a server request | ||
|  | with all log events arising from the request. | ||
|  | Rather than repeat the attribute with every log call, you can use [Logger.With] | ||
|  | to construct a new Logger containing the attributes: | ||
|  | 
 | ||
|  | 	logger2 := logger.With("url", r.URL) | ||
|  | 
 | ||
|  | The arguments to With are the same key-value pairs used in [Logger.Info]. | ||
|  | The result is a new Logger with the same handler as the original, but additional | ||
|  | attributes that will appear in the output of every call. | ||
|  | 
 | ||
|  | # Levels | ||
|  | 
 | ||
|  | A [Level] is an integer representing the importance or severity of a log event. | ||
|  | The higher the level, the more severe the event. | ||
|  | This package defines constants for the most common levels, | ||
|  | but any int can be used as a level. | ||
|  | 
 | ||
|  | In an application, you may wish to log messages only at a certain level or greater. | ||
|  | One common configuration is to log messages at Info or higher levels, | ||
|  | suppressing debug logging until it is needed. | ||
|  | The built-in handlers can be configured with the minimum level to output by | ||
|  | setting [HandlerOptions.Level]. | ||
|  | The program's `main` function typically does this. | ||
|  | The default value is LevelInfo. | ||
|  | 
 | ||
|  | Setting the [HandlerOptions.Level] field to a [Level] value | ||
|  | fixes the handler's minimum level throughout its lifetime. | ||
|  | Setting it to a [LevelVar] allows the level to be varied dynamically. | ||
|  | A LevelVar holds a Level and is safe to read or write from multiple | ||
|  | goroutines. | ||
|  | To vary the level dynamically for an entire program, first initialize | ||
|  | a global LevelVar: | ||
|  | 
 | ||
|  | 	var programLevel = new(slog.LevelVar) // Info by default | ||
|  | 
 | ||
|  | Then use the LevelVar to construct a handler, and make it the default: | ||
|  | 
 | ||
|  | 	h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}) | ||
|  | 	slog.SetDefault(slog.New(h)) | ||
|  | 
 | ||
|  | Now the program can change its logging level with a single statement: | ||
|  | 
 | ||
|  | 	programLevel.Set(slog.LevelDebug) | ||
|  | 
 | ||
|  | # Groups | ||
|  | 
 | ||
|  | Attributes can be collected into groups. | ||
|  | A group has a name that is used to qualify the names of its attributes. | ||
|  | How this qualification is displayed depends on the handler. | ||
|  | [TextHandler] separates the group and attribute names with a dot. | ||
|  | [JSONHandler] treats each group as a separate JSON object, with the group name as the key. | ||
|  | 
 | ||
|  | Use [Group] to create a Group attribute from a name and a list of key-value pairs: | ||
|  | 
 | ||
|  | 	slog.Group("request", | ||
|  | 	    "method", r.Method, | ||
|  | 	    "url", r.URL) | ||
|  | 
 | ||
|  | TextHandler would display this group as | ||
|  | 
 | ||
|  | 	request.method=GET request.url=http://example.com | ||
|  | 
 | ||
|  | JSONHandler would display it as | ||
|  | 
 | ||
|  | 	"request":{"method":"GET","url":"http://example.com"} | ||
|  | 
 | ||
|  | Use [Logger.WithGroup] to qualify all of a Logger's output | ||
|  | with a group name. Calling WithGroup on a Logger results in a | ||
|  | new Logger with the same Handler as the original, but with all | ||
|  | its attributes qualified by the group name. | ||
|  | 
 | ||
|  | This can help prevent duplicate attribute keys in large systems, | ||
|  | where subsystems might use the same keys. | ||
|  | Pass each subsystem a different Logger with its own group name so that | ||
|  | potential duplicates are qualified: | ||
|  | 
 | ||
|  | 	logger := slog.Default().With("id", systemID) | ||
|  | 	parserLogger := logger.WithGroup("parser") | ||
|  | 	parseInput(input, parserLogger) | ||
|  | 
 | ||
|  | When parseInput logs with parserLogger, its keys will be qualified with "parser", | ||
|  | so even if it uses the common key "id", the log line will have distinct keys. | ||
|  | 
 | ||
|  | # Contexts | ||
|  | 
 | ||
|  | Some handlers may wish to include information from the [context.Context] that is | ||
|  | available at the call site. One example of such information | ||
|  | is the identifier for the current span when tracing is enabled. | ||
|  | 
 | ||
|  | The [Logger.Log] and [Logger.LogAttrs] methods take a context as a first | ||
|  | argument, as do their corresponding top-level functions. | ||
|  | 
 | ||
|  | Although the convenience methods on Logger (Info and so on) and the | ||
|  | corresponding top-level functions do not take a context, the alternatives ending | ||
|  | in "Context" do. For example, | ||
|  | 
 | ||
|  | 	slog.InfoContext(ctx, "message") | ||
|  | 
 | ||
|  | It is recommended to pass a context to an output method if one is available. | ||
|  | 
 | ||
|  | # Attrs and Values | ||
|  | 
 | ||
|  | An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as | ||
|  | alternating keys and values. The statement | ||
|  | 
 | ||
|  | 	slog.Info("hello", slog.Int("count", 3)) | ||
|  | 
 | ||
|  | behaves the same as | ||
|  | 
 | ||
|  | 	slog.Info("hello", "count", 3) | ||
|  | 
 | ||
|  | There are convenience constructors for [Attr] such as [Int], [String], and [Bool] | ||
|  | for common types, as well as the function [Any] for constructing Attrs of any | ||
|  | type. | ||
|  | 
 | ||
|  | The value part of an Attr is a type called [Value]. | ||
|  | Like an [any], a Value can hold any Go value, | ||
|  | but it can represent typical values, including all numbers and strings, | ||
|  | without an allocation. | ||
|  | 
 | ||
|  | For the most efficient log output, use [Logger.LogAttrs]. | ||
|  | It is similar to [Logger.Log] but accepts only Attrs, not alternating | ||
|  | keys and values; this allows it, too, to avoid allocation. | ||
|  | 
 | ||
|  | The call | ||
|  | 
 | ||
|  | 	logger.LogAttrs(nil, slog.LevelInfo, "hello", slog.Int("count", 3)) | ||
|  | 
 | ||
|  | is the most efficient way to achieve the same output as | ||
|  | 
 | ||
|  | 	slog.Info("hello", "count", 3) | ||
|  | 
 | ||
|  | # Customizing a type's logging behavior | ||
|  | 
 | ||
|  | If a type implements the [LogValuer] interface, the [Value] returned from its LogValue | ||
|  | method is used for logging. You can use this to control how values of the type | ||
|  | appear in logs. For example, you can redact secret information like passwords, | ||
|  | or gather a struct's fields in a Group. See the examples under [LogValuer] for | ||
|  | details. | ||
|  | 
 | ||
|  | A LogValue method may return a Value that itself implements [LogValuer]. The [Value.Resolve] | ||
|  | method handles these cases carefully, avoiding infinite loops and unbounded recursion. | ||
|  | Handler authors and others may wish to use Value.Resolve instead of calling LogValue directly. | ||
|  | 
 | ||
|  | # Wrapping output methods | ||
|  | 
 | ||
|  | The logger functions use reflection over the call stack to find the file name | ||
|  | and line number of the logging call within the application. This can produce | ||
|  | incorrect source information for functions that wrap slog. For instance, if you | ||
|  | define this function in file mylog.go: | ||
|  | 
 | ||
|  | 	func Infof(format string, args ...any) { | ||
|  | 	    slog.Default().Info(fmt.Sprintf(format, args...)) | ||
|  | 	} | ||
|  | 
 | ||
|  | and you call it like this in main.go: | ||
|  | 
 | ||
|  | 	Infof(slog.Default(), "hello, %s", "world") | ||
|  | 
 | ||
|  | then slog will report the source file as mylog.go, not main.go. | ||
|  | 
 | ||
|  | A correct implementation of Infof will obtain the source location | ||
|  | (pc) and pass it to NewRecord. | ||
|  | The Infof function in the package-level example called "wrapping" | ||
|  | demonstrates how to do this. | ||
|  | 
 | ||
|  | # Working with Records | ||
|  | 
 | ||
|  | Sometimes a Handler will need to modify a Record | ||
|  | before passing it on to another Handler or backend. | ||
|  | A Record contains a mixture of simple public fields (e.g. Time, Level, Message) | ||
|  | and hidden fields that refer to state (such as attributes) indirectly. This | ||
|  | means that modifying a simple copy of a Record (e.g. by calling | ||
|  | [Record.Add] or [Record.AddAttrs] to add attributes) | ||
|  | may have unexpected effects on the original. | ||
|  | Before modifying a Record, use [Clone] to | ||
|  | create a copy that shares no state with the original, | ||
|  | or create a new Record with [NewRecord] | ||
|  | and build up its Attrs by traversing the old ones with [Record.Attrs]. | ||
|  | 
 | ||
|  | # Performance considerations | ||
|  | 
 | ||
|  | If profiling your application demonstrates that logging is taking significant time, | ||
|  | the following suggestions may help. | ||
|  | 
 | ||
|  | If many log lines have a common attribute, use [Logger.With] to create a Logger with | ||
|  | that attribute. The built-in handlers will format that attribute only once, at the | ||
|  | call to [Logger.With]. The [Handler] interface is designed to allow that optimization, | ||
|  | and a well-written Handler should take advantage of it. | ||
|  | 
 | ||
|  | The arguments to a log call are always evaluated, even if the log event is discarded. | ||
|  | If possible, defer computation so that it happens only if the value is actually logged. | ||
|  | For example, consider the call | ||
|  | 
 | ||
|  | 	slog.Info("starting request", "url", r.URL.String())  // may compute String unnecessarily | ||
|  | 
 | ||
|  | The URL.String method will be called even if the logger discards Info-level events. | ||
|  | Instead, pass the URL directly: | ||
|  | 
 | ||
|  | 	slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed | ||
|  | 
 | ||
|  | The built-in [TextHandler] will call its String method, but only | ||
|  | if the log event is enabled. | ||
|  | Avoiding the call to String also preserves the structure of the underlying value. | ||
|  | For example [JSONHandler] emits the components of the parsed URL as a JSON object. | ||
|  | If you want to avoid eagerly paying the cost of the String call | ||
|  | without causing the handler to potentially inspect the structure of the value, | ||
|  | wrap the value in a fmt.Stringer implementation that hides its Marshal methods. | ||
|  | 
 | ||
|  | You can also use the [LogValuer] interface to avoid unnecessary work in disabled log | ||
|  | calls. Say you need to log some expensive value: | ||
|  | 
 | ||
|  | 	slog.Debug("frobbing", "value", computeExpensiveValue(arg)) | ||
|  | 
 | ||
|  | Even if this line is disabled, computeExpensiveValue will be called. | ||
|  | To avoid that, define a type implementing LogValuer: | ||
|  | 
 | ||
|  | 	type expensive struct { arg int } | ||
|  | 
 | ||
|  | 	func (e expensive) LogValue() slog.Value { | ||
|  | 	    return slog.AnyValue(computeExpensiveValue(e.arg)) | ||
|  | 	} | ||
|  | 
 | ||
|  | Then use a value of that type in log calls: | ||
|  | 
 | ||
|  | 	slog.Debug("frobbing", "value", expensive{arg}) | ||
|  | 
 | ||
|  | Now computeExpensiveValue will only be called when the line is enabled. | ||
|  | 
 | ||
|  | The built-in handlers acquire a lock before calling [io.Writer.Write] | ||
|  | to ensure that each record is written in one piece. User-defined | ||
|  | handlers are responsible for their own locking. | ||
|  | */ | ||
|  | package slog |