warning

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2024 License: MIT Imports: 16 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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.`)
)
View Source
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.`)
)
View Source
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.`)
)
View Source
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)`)
)
View Source
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.`)
)
View Source
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

func WithWarnings(m *testing.M)

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 Instance

type Instance struct {
	Function string
	Record   string
	Text     string
}

Instance encapsulates data for a specific warning instance.

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

func ParseLevel(text string) (Level, error)

ParseLevel attempts to parse a string as a Level name. If found, the Level is returned, otherwise an error.

func (Level) String

func (l Level) String() string

String implements the string interface for Level objects.

func (Level) Summary

func (l Level) Summary() template.HTML

Summary returns template.HTML derived from the Level summary Markdown strings.

func (Level) Warnings

func (l Level) Warnings() []*Warning

Warnings returns an array of warnings for the current Level.

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 NewWarningManager(name string, fnPrefix string, showPrefix string) *Manager

func (*Manager) AddUnused

func (mgr *Manager) AddUnused(w *Warning, logRecordJSON string)

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

func (mgr *Manager) AddWarning(w *Warning, text string, logRecordJSON string)

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

func (mgr *Manager) AddWarningFn(w *Warning, fnName string, logRecordJSON string)

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

func (mgr *Manager) AddWarningFnText(w *Warning, fnName string, text string, logRecordJSON string)

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

func (mgr *Manager) GetWarnings() []*Instances

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

func (mgr *Manager) HasWarning(w *Warning) bool

HasWarning returns true if the specified warning has been set in the test suite.

func (*Manager) HasWarnings

func (mgr *Manager) HasWarnings(warnings ...*Warning) []*Warning

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) Predefine

func (mgr *Manager) Predefine(warnings ...*Warning)

Predefine warning that can be referenced during testing.

func (*Manager) ShowWarnings

func (mgr *Manager) ShowWarnings(output io.Writer)

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

func (mgr *Manager) SkipTest(because *Warning)

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

func (mgr *Manager) SkipTestIf(warns ...*Warning) bool

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.

func (*Manager) WarnOnly

func (mgr *Manager) WarnOnly(w *Warning)

WarnOnly sets a flag to collect warning instead of failing tests. The warning argument is one of the global constants beginning with 'Warn' and it must be predefined to the manager.

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 ByName

func ByName(name string) *Warning

ByName returns the warning with the specified name, if any, else nil.

func Implied

func Implied() []*Warning

Implied returns an array of all LevelImplied warnings.

func NewWarning

func NewWarning(level Level, name, summary, description string) *Warning

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 Required

func Required() []*Warning

Required returns an array of all LevelRequired warnings.

func Suggested

func Suggested() []*Warning

Suggested returns an array of all LevelSuggested warnings.

func WarningsForLevel

func WarningsForLevel(level Level) []*Warning

WarningsForLevel returns a list of warnings for the specified level.

func (*Warning) Description

func (w *Warning) Description() template.HTML

Description converts the Markdown description data into HTML and returns it.

func (*Warning) Error

func (w *Warning) Error() string

Error implements the error interface so all Warning objects are error type.

func (*Warning) ErrorExtra

func (w *Warning) ErrorExtra(extra string) error

ErrorExtra returns an error object for the warning along with an extra string.

func (*Warning) HasDescription

func (w *Warning) HasDescription() bool

HasDescription returns true if there is description data.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL