logfunk
Package logfunk provides a concise, pluggable API for structured logging.
logfunk uses custom type arguments to set additional logging options:
- optional spew output (struct decomposition) through %#+v
- optional json output (exported fields only) through %j
- adaptable output
- severity levels
- structured logging
- call-site logging
- stackable logging context
Basic use
package main
import "git.pixeltamer.net/gopub/logfunk"
func main() {
log := logfunk.MakeF(nil) // create a log func on os.Stdout/os.Stderr
log("Hello World!") // print a line
}
Output:
20210109.203008.622866 Info : Hello World!
Stacked Log Functions
Log functions can be created on top of existing log functions. Child log functions inherit the parameters of their parent. New parameters on the child function override those of the parent, except for logfunk.Module
, which gets appended for context.
package main
import "git.pixeltamer.net/gopub/logfunk"
func main() {
log:=logfunk.MakeF(nil,logfunk.Module("Example")) // create a log func on os.Stdout/os.Stderr
log("In main") // print a line
sub1(log)
}
func sub1(l logfunk.F) {
log:=logfunk.MakeF(l,logfunk.Module("Sub1"))
log(logfunk.Warn,"In sub1")
sub2(log)
}
func sub2(l logfunk.F) {
log:=logfunk.MakeF(l,logfunk.Module("Sub2"))
log(logfunk.Error,"In sub2")
}
Output:
20210109.203512.943886 Info Example: In main
20210109.203512.943908 Warn Example.Sub1: In sub1
20210109.203512.943915 Error Example.Sub1.Sub2: In sub2
Log Level
package main
import "git.pixeltamer.net/gopub/logfunk"
func main() {
log := logfunk.MakeF(nil, // create a log func on os.Stdout/os.Stderr,
logfunk.Module("Example"), // with module name "Example",
logfunk.MinLevel(logfunk.Warn), // minimum Level Warn
logfunk.ShortTime) // and short time stamps
log(logfunk.Info, "Info") // print a Info line, ignored due to MinLevel
log(logfunk.Warn, "Warn") // print a Warn line
log(logfunk.Error, "Error") // print a Error line
}
Output:
203908.059 Warn Example: Warn
203908.059 Error Example: Error
Attributes
package main
import "git.pixeltamer.net/gopub/logfunk"
func main() {
log := logfunk.MakeF(nil) // create a log func on os.Stdout/os.Stderr
log(logfunk.KV("SomeValue", 42), "Hello World!") // print a line
}
Output:
20210109.204145.943847 Info : Hello World! [SomeValue="42"]
package main
import "git.pixeltamer.net/gopub/logfunk"
func main() {
log := logfunk.MakeF(nil) // create a log func on os.Stdout/os.Stderr
v := struct {
A int
B []string
}{
A: 27,
B: []string{"1", "a", "Z"},
}
log(logfunk.KV("SomeStruct", v), "Line 1") // complex values are %v-printed
log("Line 2:\n%#+v", v) // optional spew deconstruction
log("Line 3:\n%j", v) // optional json deconstruction
}
Output:
20210124.111714.039069 Info : Line 1 [SomeStruct="{27 [1 a Z]}"]
20210124.111714.039076 Info : Line 2:
20210124.111714.039076 Info : (struct { A int; B []string }){A:(int)27 B:([]string)[1 a Z]}
20210124.111714.039089 Info : Line 3:
20210124.111714.039089 Info : {
20210124.111714.039089 Info : "A": 27,
20210124.111714.039089 Info : "B": [
20210124.111714.039089 Info : "1",
20210124.111714.039089 Info : "a",
20210124.111714.039089 Info : "Z"
20210124.111714.039089 Info : ]
20210124.111714.039089 Info : }
Note that line breaks are separated into individual log entries for consistent indentation.
Parameter Placement
logfunk uses specially typed parameters to provide extended functionality. Any such parameters are applied as arguments to the MakeF() function. Additionally, special parameters appearing before the format string argument in log func calls apply to a single call.
package main
import "git.pixeltamer.net/gopub/logfunk"
func main() {
log := logfunk.MakeF(nil) // create a log func on os.Stdout/os.Stderr
log(logfunk.Warn, "Line 1", logfunk.Error) // only leading parameters are effective
}
20210109.205426.699710 Warn : Line 1%!(EXTRA *spew.formatState=Error)
Call site logging
Passing the logfunk.ShowCaller
flag to MakeF or the log function causes an attribute "Caller" to be set with the name of the calling function and the line number in the source file.
log.Logger Shim
logfunk.NewLogger(logfunk.F)
creates a struct that implements all log.Logger methods to enable mixed use.
Custom LogSink
Any implementation of the LogSink interface can act as a target for logging.
Additionally, the WriterSink
and StdSink
functions are provided.
WriterSink
creates a LogSink that outputs to a given io.Writer.
StdSink
creates a LogSink that outputs to os.Stdout (Spam, Debug and Info levels) and os.Stderr (Warn, Error and Fatal levels).
Reference
Extended log func parameters
// log severity level
Level
Spam
Debug
Info
Warn
Error
Fatal
// minimum severity level
MinLevel
type KeyValue struct { // structured attribute, also see logfunk.KV()
Key string
Value string
}
// Flags should be set at the root log function and modify the verboseness.
type Flags uint32
ShortTime // use a shorter timestamp format
LongTime // use the default long timestamp format (default)
HideLevel // do not print the severity level
ShowLevel // print the severity level (default)
HideModule // do not print the module name
ShowModule // print the module name (default)
HideCaller // do not print the callers info (default)
ShowCaller // print the callers function name and line number
LineSplit // split output with \n into multiple lines (default)
NoLineSplit // do not split multiline output
// Module is a typed string capturing a module name for logging. Stacked Module names are concatenated with dots in the output.
type Module string
// Extractor values are special log arguments that cause context information to be returned from a log function.
type Extractor int
// ExtractSink has to be followed by a *LogSink argument that gets set to the parent LogSink instance for this log function.
ExtractSink
// ExtractDefaults has to be followed by a *[]interface{} argument that gets set to the default parameters for this log function.
ExtractDefaults
Types
// F is a logging function with some twists. All instances of F should be created through MakeF(). F implements the LogSink interface for stacking.
type F func(args ...interface{})
// The LogSink interface processes LogEntry values and provides default parameters. It is implemented by all logfunk.F returned from logfunk.MakeF()
type LogSink interface {
LogCommit(le LogEntry)
LogDefaults() []interface{}
}
// A LogEntry captures all information for a single log line.
type LogEntry struct {
Time timestamp.TS
Level Level
Module string
Text string
Flags Flags
Attr map[string]string
}
Functions
// KV creates a KeyValue object from a key name and an arbitrary value. val is taken verbatim if a string, String()ed if it implements fmt.Stringer and otherwises spew'ed
func KV(key string, val interface{}) KeyValue
// WriterSink creates a LogSink wrapper around a writer with optional defaults
func WriterSink(w io.Writer, defaults ...interface{}) LogSink
// StdSink creates a LogSink printing to os.StdOut or os.StdErr depending on Level
func StdSink(defaults ...interface{}) LogSink
// MakeF creates a new log function on a LogSink (which can be another LogFunc or nil) with optional default values
func MakeF(ls LogSink, defaults ...interface{}) F
// GetSink returns the LogSink that was used to create this LogFunc
func (lf F) GetSink() LogSink
Timestamp Type
logfunk uses git.pixeltamer.net/gopub/timestamp instead of the conventioal time.Time type. The motivation for this is:
- Always UTC, no timezone shenanigans
- Compact representation
- Encodable as javascript-compatible float64 value
- Stores easily as int64 in databases
Benchmarks
logfunk uses variadic parameters, so some heap allocation is currently unavoidable.
BenchmarkLogFunk/FixedString-16 6309463 245 ns/op 112 B/op 3 allocs/op
BenchmarkLogFunk/FixedLongString-16 5667570 240 ns/op 112 B/op 3 allocs/op
BenchmarkLogFunk/Format-16 1412878 772 ns/op 272 B/op 8 allocs/op
BenchmarkLogFunk/FormatIgnored-16 7940806 193 ns/op 128 B/op 3 allocs/op
BenchmarkLogFunk/FixedAttrib1-16 1000000 1194 ns/op 656 B/op 11 allocs/op
BenchmarkLogFunk/FixedAttrib5-16 1000000 1316 ns/op 848 B/op 15 allocs/op
BenchmarkLogFunk/FixedAttrib5Ignored-16 1288423 1073 ns/op 704 B/op 10 allocs/op
License
MIT License
(c) pixeltamer.net 2021