Documentation ¶
Overview ¶
Package warning defines Warning objects as well as the warning Manager.
Warnings ¶
A "warning" facility is built into many of the tests to provide a way to:
- avoid scanning through a lot of `go test` error logs in detail over and over,
- get a compressed set of warnings about issues after testing is done, and
- provide a list of known issues in the test suite.
Manager ¶
The warning Manager provides all warning-related functionality and data for a test suite. Each test suite embeds a warning Manager for:
- Method calls to add warning records.
- Encapsulation and handling of warning data for a test suite.
- Display of warning data after completion of the test suite.
Usage ¶
Existing usage cases in bench and verify packages were implemented in more or less the following pattern:
- Define a `WarningManager`.
- Predefine various `Warning` objects that may be used in testing.
- When defining a test suite use `WarnOnly` to specify that during testing warning code should be run instead of assertions.
- In test code check the `WarningManager` for applicable warnings.
- In warning-specific code use `AddWarning` to note the condition exists or `UnusedWarning` to note that the warning is redundant.
- Run the tests with the `-useWarnings` flag set to true to invoke warning code. The `useWarnings` flag is turned on by default. Without this flag the `WarningManager` will never flag warning code and test assertions will raise conventional errors.
Index ¶
- Variables
- func ShowHandlersByWarning(showPrefix string)
- func WithWarnings(m *testing.M)
- type Instance
- type Instances
- type Level
- type Manager
- func (mgr *Manager) AddUnused(w *Warning, logRecordJSON string)
- func (mgr *Manager) AddWarning(w *Warning, text string, logRecordJSON string)
- func (mgr *Manager) AddWarningFn(w *Warning, fnName string, logRecordJSON string)
- func (mgr *Manager) AddWarningFnText(w *Warning, fnName string, text string, logRecordJSON string)
- func (mgr *Manager) GetWarnings() []*Instances
- func (mgr *Manager) HasWarning(w *Warning) bool
- func (mgr *Manager) HasWarnings(warnings ...*Warning) []*Warning
- func (mgr *Manager) Predefine(warnings ...*Warning)
- func (mgr *Manager) ShowWarnings(output io.Writer)
- func (mgr *Manager) SkipTest(because *Warning)
- func (mgr *Manager) SkipTestIf(warns ...*Warning) bool
- func (mgr *Manager) WarnOnly(w *Warning)
- type Warning
Constants ¶
This section is empty.
Variables ¶
var ( SkipDedup = NewWarning(LevelAdmin, "SkipDedup", "Test depends on dedup order", ` Some dedup tests return results that can't easily be tested.`) NoHandlerCreation = NewWarning(LevelAdmin, "NoHandlerCreation", "Test depends on unavailable handler creation", ` Some benchmark tests depend on access to a ^slog.Handler^ object. Some ^slog^ implementations create a ^slog.Logger^ but no ^slog.Handler^. In this case the relevant benchmark tests can't be run.`) SkippingTest = NewWarning(LevelAdmin, "SkippingTest", "Skipping test", ` A test has been skipped, likely due to the specification of some other warning.`) TestError = NewWarning(LevelAdmin, "TestError", "Test harness error", ` Some sort of error has occurred during testing. This will generally require a programming fix.`) Undefined = NewWarning(LevelAdmin, "Undefined", "Undefined Warnings(s)", ` An attempt to call ^WarnOnly^ with an undefined warning. Warnings must be predefined to the ^Manager^ prior to use.`) Unused = NewWarning(LevelAdmin, "Unused", "Unused Warnings(s)", ` If a warning is specified but the condition is not actually present one of these warnings will be issued with the specified warning. These are intended to help clean out unnecessary ^WarnOnly^ settings from a test suite as issues are fixed in the tested handler.`) )
var ( Mismatch = NewWarning(LevelRequired, "Mismatch", "Logged record does not match expected", ` During benchmark testing the test is run once to see if the logged record matches expectations.`) NotDisabled = NewWarning(LevelRequired, "NotDisabled", "Logging was not properly disabled", ` During benchmark testing a Debug log line is made with the current level set to Info. This warning is thrown if there is any logged output.`) )
var ( CanceledContext = NewWarning(LevelImplied, "CanceledContext", "Canceled context blocks logging", ` ["The context is provided to support applications that provide logging information along the call chain. In a break with usual Go practice, the Handle method should not treat a canceled context as a signal to stop work."](https://github.com/golang/example/tree/master/slog-handler-guide#the-handle-method)`) DefaultLevel = NewWarning(LevelImplied, "DefaultLevel", "Handler doesn't default to slog.LevelInfo", ` A new ^slog.Handler^ should default to ^slog.LevelInfo^. * ["First, we wanted the default level to be Info, Since Levels are ints, Info is the default value for int, zero."](https://pkg.go.dev/log/slog@master#Level)`) GroupAttrMsgTop = NewWarning(LevelImplied, "GroupAttrInfoTop", "Group-Attr-Info attributes end up at the top level.", ` Log records generated by: ^^^Go WithGroup('group1').Attr(attrs1...).Info(msg, attrs2...) ^^^ should have ^attrs2^ within the group ^group1^ (along with ^attrs1^). The current handler places ^attrs2^ at the top level. The preceding example applies to all of the "message" methods (e.g. ^Error()^, ^Debug()^). This sequence is verify specific, neither ^attrs1^ and ^attrs2^ can be empty or missing.`) LevelMath = NewWarning(LevelImplied, "LevelMath", "Log levels are not properly treated as integers", ` [Log levels are actually numbers](https://pkg.go.dev/log/slog@master#Level), with space between them for user-defined levels. Handlers should properly handle numeric levels and math applied to level values.`) MessageKey = NewWarning(LevelImplied, "MessageKey", "Wrong message key (should be 'msg')", ` The field name of the "message" key should be ^msg^. * [Constant values are defined for ^slog/log^](https://pkg.go.dev/log/slog@master#pkg-constants) * [Field values are defined for the ^JSONHandler.Handle()^ implementation](https://pkg.go.dev/log/slog@master#JSONHandler.Handle)`) NoReplAttr = NewWarning(LevelImplied, "NoReplAttr", "HandlerOptions.ReplaceAttr not available", ` If [^HandlerOptions.ReplaceAttr^](https://pkg.go.dev/log/slog@master#HandlerOptions) is provided it should be honored by the handler. However, documentation on implementing handler methods seems to suggest it is optional. * [Behavior defined for ^slog.HandlerOptions^](https://pkg.go.dev/log/slog@master#HandlerOptions) * ["You might also consider adding a ReplaceAttr option to your handler, like the one for the built-in handlers."](https://github.com/golang/example/tree/master/slog-handler-guide#implementing-handler-methods)`) NoReplAttrBasic = NewWarning(LevelImplied, "NoReplAttrBasic", "HandlerOptions.ReplaceAttr not available for basic fields", ` Some handlers (e.g. ^phsym/zeroslog^) support [^HandlerOptions.ReplaceAttr^](https://pkg.go.dev/log/slog@master#HandlerOptions) except for the four main fields ^time^, ^level^, ^msg^, and ^source^. When that is the case it is better to use this (^WarnNoReplAttrBasic^) warning.`) ReplAttrGroup = NewWarning(LevelImplied, "ReplAttrGroup", "ReplaceAttr groups argument usage results in an error", ` A ^ReplaceAttribute^ function has two arguments: an array of strings representing group names and an attribute. The ^TestReplaceAttrGroup^ test checks to see if group names are properly passed. This test may fail due to unrelated causes that show up as warnings for other tests.`) SourceCaller = NewWarning(LevelImplied, "SourceCaller", "Source data logged as 'caller' instead of 'source'", ` Some handlers return source data as a single string on the ^caller^ field in the format ^<file>:<line>^ where ^<file>^ and ^<line>^ correspond to the ^File^ and ^Line^ fields of the source data group and ^Function^ is not provided. * [Behavior defined for ^JSONHandler.Handle()^](https://pkg.go.dev/log/slog@master#JSONHandler.Handle) * [Definition of source data record](https://pkg.go.dev/log/slog@master#Source)`) SourceKey = NewWarning(LevelImplied, "SourceKey", "Source data not logged when AddSource flag set", ` Handlers should log source data when the ^slog.HandlerOptions.AddSource^ flag is set. * [Flag declaration as ^slog.HandlerOptions^ field](https://pkg.go.dev/log/slog@master#HandlerOptions) * [Behavior defined for ^JSONHandler.Handle()^](https://pkg.go.dev/log/slog@master#JSONHandler.Handle) * [Definition of source data record](https://pkg.go.dev/log/slog@master#Source)`) WithGroup = NewWarning(LevelImplied, "WithGroup", "WithGroup doesn't embed following attributes into group", ` Complex log statements involving ^WithGroup^ require attributes to be attached to groups. This warning represents situations where the attributes are attached to the wrong log group.`) )
var ( EmptyAttributes = NewWarning(LevelRequired, "EmptyAttributes", "Empty attribute(s) logged (\"\":null)", ` Handlers are supposed to avoid logging empty attributes. * ["- If an Attr's key and value are both the zero value, ignore the Attr."](https://pkg.go.dev/log/slog@master#Handler)`) GroupEmpty = NewWarning(LevelRequired, "GroupEmpty", "Empty (sub)group(s) logged", ` Handlers should not log groups (or subgroups) without fields, whether or not they have non-empty names. * ["- If a group has no Attrs (even if it has a non-empty key), ignore it."](https://pkg.go.dev/log/slog@master#Handler)`) GroupInline = NewWarning(LevelRequired, "GroupInline", "Group with empty key does not inline subfields", ` Handlers should expand groups named "" (the empty string) into the enclosing log record. * ["- If a group's key is empty, inline the group's Attrs."](https://pkg.go.dev/log/slog@master#Handler)`) LevelVar = NewWarning(LevelRequired, "LevelVar", "Unable to change level during execution via LevelVar", ` Slog handlers can use [^LevelVar^](https://pkg.go.dev/log/slog#LevelVar) to specify the handler logging level. Unlike specifying a [^Level^](https://pkg.go.dev/log/slog#Level) (which is actually an ^int^), a ^LevelVar^ is supposed to be changeable during program execution.`) Resolver = NewWarning(LevelRequired, "Resolver", "LogValuer objects are not resolved", ` Handlers should resolve all objects implementing the [^LogValuer^](https://pkg.go.dev/log/slog@master#LogValuer) or [^Stringer^](https://pkg.go.dev/fmt#Stringer) interfaces. This is a powerful feature which can customize logging of objects and [speed up logging by delaying argument resolution until logging time](https://pkg.go.dev/log/slog@master#hdr-Performance_considerations). * ["- Attr's values should be resolved."](https://pkg.go.dev/log/slog@master#Handler)`) SlogTest = NewWarning(LevelRequired, "SlogTest", "Failure of embedded slog/slogtest", ` Documentation on building a handler suggests testing using ^slog/slogtest^, part of the Go release since 1.21. While the verification suite includes tests patterned after those in ^slogtest^, an additional single test invokes the ^slogtest^ testing sequence (involving multiple tests). Since this is a separate package, all error messages are returned at once. This is the only warning that affects ^TestSlogTest^ and all of its error messages. * ["To verify that your handler follows these rules and generally produces proper output, use the testing/slogtest package."](https://github.com/golang/example/tree/master/slog-handler-guide#testing) * [^slog/slogtest^](https://pkg.go.dev/golang.org/x/exp@v0.0.0-20240222234643-814bf88cf225/slog/slogtest)`) WithGroupEmpty = NewWarning(LevelRequired, "WithGroupEmpty", "Empty WithGroup() logged", ` Handlers should not log ^WithGroup()^ groups with no fields, whether or not they have non-empty names. * ["- If a group has no Attrs (even if it has a non-empty key), ignore it."](https://pkg.go.dev/log/slog@master#Handler)`) ZeroPC = NewWarning(LevelRequired, "ZeroPC", "SourceKey logged for zero PC", ` The ^slog.Record.PC^ field can be loaded with a program counter (PC). This is normally done by the ^slog.Logger^ code. If the PC is non-zero and the ^slog.HandlerOptions.AddSource^ flag is set the ^source^ field will contain a [^slog.Source^](https://pkg.go.dev/log/slog@master#Source) record containing the function name, file name, and file line at which the log record was generated. If the PC is zero then this field and its associated group should not be logged. * ["- If r.PC is zero, ignore it."](https://pkg.go.dev/log/slog@master#Handler)`) ZeroTime = NewWarning(LevelRequired, "ZeroTime", "Zero time is logged", ` Handlers should not log the basic ^time^ field if it is zero. * ["- If r.Time is the zero time, ignore the time."](https://pkg.go.dev/log/slog@master#Handler)`) )
var ( Duplicates = NewWarning(LevelSuggested, "Duplicates", "Duplicate field(s) found", ` Some handlers (e.g. ^slog.JSONHandler^) will output multiple occurrences of the same field name if the logger is called with multiple instances of the same field, generally by using WithAttrs and then the same fields in the eventual log call (e.g. Info). This behavior is currently [under debate](https://github.com/golang/go/issues/59365) with no resolution at this time (2024-01-15) and a [release milestone of (currently unscheduled) Go 1.23](https://github.com/golang/go/milestone/212), (whereas [Go Release 1.22](https://tip.golang.org/doc/go1.22) is currently expected in February 2024).`) DurationMillis = NewWarning(LevelSuggested, "DurationMillis", "slog.Duration() logs milliseconds instead of nanoseconds", ` The ^slog.JSONHandler^ uses nanoseconds for ^time.Duration^ but some other handlers use seconds. * [Go issue 59345: Nanoseconds is a recent change with Go 1.21](https://github.com/golang/go/issues/59345)`) DurationSeconds = NewWarning(LevelSuggested, "DurationSeconds", "slog.Duration() logs seconds instead of nanoseconds", ` The ^slog.JSONHandler^ uses nanoseconds for ^time.Duration^ but some other handlers use seconds. * [Go issue 59345: Nanoseconds is a recent change with Go 1.21](https://github.com/golang/go/issues/59345)`) DurationString = NewWarning(LevelSuggested, "DurationString", "slog.Duration() logs a string representation instead of nanoseconds", ` The ^slog.JSONHandler^ uses nanoseconds for ^time.Duration^ but some other handlers use a string representation.`) GroupWithTop = NewWarning(LevelSuggested, "GroupWithTop", "^WithGroup().With()^ ends up at top level of log record instead of in the group", ` Almost all handlers treat ^logger.WithGroup(<name>).With(<attrs>)^ as writing ^<attrs>^ to the group ^<name>^. Some handlers write ^<attrs>^ to the top level of the log record.`) GroupDuration = NewWarning(LevelSuggested, "GroupDuration", "", ` Some handlers that change the way ^time.Duration^ objects are logged (see warnings ^DurationMillis^ and ^DurationSeconds^) only manage to make the change at the top level of the logged record, duration objects in groups are still in nanoseconds.`) LevelCase = NewWarning(LevelSuggested, "LevelCase", "Log level in lowercase", ` Each JSON log record contains the logging level of the log statement as a string. Different handlers provide that string in uppercase or lowercase. Documentation for [^slog.Level^](https://pkg.go.dev/log/slog@master#Level) says that its ^String()^ and ^MarshalJSON()^ methods will return uppercase but ^UnmarshalJSON()^ will parse in a case-insensitive manner.`) LevelWrong = NewWarning(LevelSuggested, "LevelWrong", "Log level is incorrect", ` The log level name is not what was expected (e.g. "WARNING" instead of "WARN"). This is different from the LevelCase warning which is from the right level name but the wrong character case.`) NoEmptyName = NewWarning(LevelSuggested, "NoEmptyName", "Attributes with empty names are not logged", ` Until documented otherwise, an attribute with an empty field name (^""^) and a non-nil value should be logged. * [Empty field names are logged by the ^JSONHandler.Handle()^ implementation](https://pkg.go.dev/log/slog@master#JSONHandler.Handle)`) NoNilValue = NewWarning(LevelSuggested, "NoNilValue", "Attributes with nil values are not logged", ` Until documented otherwise, an attribute with a non-empty name and a nil value should be logged. * [Fields with nil values are logged by the ^JSONHandler.Handle()^ implementation](https://pkg.go.dev/log/slog@master#JSONHandler.Handle)`) StringAny = NewWarning(LevelSuggested, "StringAny", "map[string]any converted to strings in log records", ` The ^slog.JSONHandler^ converts ^Any^ objects that are ^map[string]any^ into JSON maps. Some handlers convert these ^Any^ objects into strings instead of maps.`) TimeMillis = NewWarning(LevelSuggested, "TimeMillis", "slog.Time() logs milliseconds instead of nanoseconds", ` The ^slog.JSONHandler^ uses nanoseconds for ^time.Time^ but some other handlers use milliseconds. This does _not_ apply to the basic ^time^ field, only attribute fields. I can't find any supporting documentation or bug on this but [Go issue 59345](https://github.com/golang/go/issues/59345) (see previous warning) may have fixed this as well in Go 1.21.`) TimeSeconds = NewWarning(LevelSuggested, "TimeSeconds", "slog.Time() logs seconds instead of nanoseconds", ` The ^slog.JSONHandler^ uses nanoseconds for ^time.Time^ but some other handlers use seconds. This does _not_ apply to the basic ^time^ field, only attribute fields. I can't find any supporting documentation or bug on this but [Go issue 59345](https://github.com/golang/go/issues/59345) (see previous warning) may have fixed this as well in Go 1.21.`) )
var ( // LevelOrder returns an array of Level items ordered from most to least important. LevelOrder = []Level{ LevelRequired, LevelImplied, LevelSuggested, LevelAdmin, } )
Functions ¶
func ShowHandlersByWarning ¶
func ShowHandlersByWarning(showPrefix string)
ShowHandlersByWarning uses the global byWarning map to show the handlers that issue each warning.
func WithWarnings ¶
WithWarnings implements the guts of TestMain (see https://pkg.go.dev/testing#hdr-Main). This will cause the ShowWarnings method to be called on all test managers after all other output has been done, instead of buried in the middle. To use, add the following to a '_test' file:
func TestMain(m *testing.M) { test.WithWarnings(m) }
This step can be omitted if warning are being sent to an output file.
Types ¶
type Instances ¶
type Instances struct { // Level is the warning level. Level Level // Name of warning. Name string // Summary of warning. Summary string // Count of times warning is issued. Count uint // Data associated with the specific instances of the warning, if any. Data []Instance }
Instances gathers instances for a specific Warning.
type Level ¶
type Level uint
Level for warnings, used mainly to organize warning on output.
const ( // LevelAdmin contains administrative warnings. LevelAdmin Level // LevelSuggested contains "suggested" warnings. LevelSuggested // LevelImplied contains warnings implied by documentation. LevelImplied // LevelRequired contains warnings about conflicts with documentation. LevelRequired )
func ParseLevel ¶
ParseLevel attempts to parse a string as a Level name. If found, the Level is returned, otherwise an error.
type Manager ¶
type Manager struct { // Name of Handler for warning display. Name string // contains filtered or unexported fields }
Manager manages the warning set for a test run.
func NewWarningManager ¶
func (*Manager) AddUnused ¶
AddUnused adds an Unused warning to the results list. The warning added is Unused and the extra text is the name of the specified warning.
func (*Manager) AddWarning ¶
AddWarning to results list, specifying warning string and optional extra text. If the addLogRecord flag is true the current log record JSON is also stored. The current function name is acquired from the currentFunctionName() and stored.
func (*Manager) AddWarningFn ¶
AddWarningFn adds a warning to the results list, specifying warning string and function name. If the addLogRecord flag is true the current log record JSON is also stored. The current function name is acquired from the currentFunctionName() and stored.
func (*Manager) AddWarningFnText ¶
AddWarningFnText to results list, specifying warning string, function name, and optional extra text. If the addLogRecord flag is true the current log record JSON is also stored. The current function name is acquired from the currentFunctionName() and stored.
func (*Manager) GetWarnings ¶
GetWarnings returns an array of Instances records sorted by warning level and text. If there are no warning the result array will be nil. Use this method if manual processing of warning is required, otherwise use the WithWarnings method.
func (*Manager) HasWarning ¶
HasWarning returns true if the specified warning has been set in the test suite.
func (*Manager) HasWarnings ¶
HasWarnings checks all specified warning and returns an array of any that have been set in the test suite in the same order. If none are found an empty array is returned.
func (*Manager) ShowWarnings ¶
ShowWarnings prints any warning to Stdout in a preformatted manner. Use the Manager method if more control over output is required.
Note: Both Stdout and Stderr are captured by the the 'go test' command and shunted into Stdout (see https://pkg.go.dev/cmd/go#hdr-Test_packages). This output stream is only visible when the 'go test -v flag' is used.
func (*Manager) SkipTest ¶
SkipTest adds warning for a test that is being skipped. The first warning is for skipping a test with the text set to the 'because' warning argument. The second warning is for the 'because' warning with the text set to skipping the test.
func (*Manager) SkipTestIf ¶
SkipTestIf checks the warning provided to see if any have been set in the suite, adding SkipTest warning for the first one and returning true. False is returned if none of the warning are found.
type Warning ¶
type Warning struct { // Level is the warning level. Level Level // Name of the warning. Name string // Summary of the warning. Summary string // contains filtered or unexported fields }
Warning object declaration.
func Administrative ¶
func Administrative() []*Warning
Administrative returns an array of all LevelAdmin warnings.
func Benchmark ¶
func Benchmark() []*Warning
Benchmark returns an array of all benchmark-specific warnings.
func NewWarning ¶
NewWarning creates a new Warning object with the specified warning level and name. The optional summary is a single text sentence summarizing the warning. The optional description is a paragraph of Markdown with back quotes ("`") replaced by caret characters ("^"). The summary and description are provided for cmd/server web pages.
func Suggested ¶
func Suggested() []*Warning
Suggested returns an array of all LevelSuggested warnings.
func WarningsForLevel ¶
WarningsForLevel returns a list of warnings for the specified level.
func (*Warning) Description ¶
Description converts the Markdown description data into HTML and returns it.
func (*Warning) ErrorExtra ¶
ErrorExtra returns an error object for the warning along with an extra string.
func (*Warning) HasDescription ¶
HasDescription returns true if there is description data.