cog

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: May 26, 2021 License: MIT Imports: 9 Imported by: 0

README

⚙️ cog GoDoc

Extremely fast structured and leveled logger for golang, now with most of the code test-covered ! (albeit pretty bad tests)

Installation

go get -u gitlab.com/k54/cog

Very Quick Start

log := cog.New(cog.Stderr, cog.Options{
    Level:     cog.DebugLevel,
})
defer log.Sync()

log.Debug("i'm here")
// {"message":"i'm here","level":"debug"}

log.Debug("failed to fetch URL", func(enc cog.Encoder) {
    enc.String("url", url)
    enc.Int("attempt", 3)
    enc.Stringer("backoff", time.Second)
})
// {"message":"failed to fetch URL","level":"debug","url":"https://google.com","attempt":3,"backoff":"1s"}

Quick Start

log := cog.New(cog.Stderr, cog.Options{
    Level:     cog.DebugLevel,
    CallerKey: "caller",
    TimeKey:   "time",
})
defer log.Sync()
const url = "https://example.com"

log.Debug("failed to fetch URL", func(enc cog.Encoder) {
    enc.String("url", url)
    enc.Int("attempt", 3)
    enc.Stringer("backoff", time.Second)
})

log.Debug("something wrong happened")

err := json.Unmarshal(..., &config)
log.Fatal(err) // doesnt do anything if err == nil, saves you from writing if err != nil {...}

Output (beautified):

{
    "message": "failed to fetch URL",
    "level": "debug",
    "time": 1599728064798211000,
    "caller": "cog/cog_test.go:24",
    "url": "https://google.com",
    "attempt": 3,
    "backoff": "1s"
}

Implementation

This package is implemented with a fast, 0 allocations JSON encoder, and two internal logging stacks: The debug, and the standard stacks.

The standard stack is just what you would expect in a logging library, and serves the Info and Error levels.

The debug stack is a little bit different, as it is optimized for debugging when the BetterDebug option is present.

Motivation

As pioneered by zap and zerolog, this package was created to support the need for high-performance, low-allocation and structured logging.

Some API niceties are also supported, such as log.Error(err) which logs only if err != nil.

Only JSON is supported, as this package is backed by an extremely fast and 0-allocation JSON encoder.

The API is pretty similar to onelog, but is a bit less heavy in my opinion (no Warn/WarnWith/WarnWithFields madness).

The decision to have your fields in a func makes them lazy-evaluated, so you won't compute fields nor encode them on a disabled logger, as you can see in the benchmarks below.

Although the syntax is pretty heavy, it's not that bad for this performance gain.

Cog supports a global Logger context that is cached at logger creation.

log := cog.New(cog.Stderr, cog.Options{
    Level:     cog.DebugLevel,
    CallerKey: "caller",
    TimeKey:   "time",
    Context: func(enc cog.Encoder) {
        enc.String("app", "api")
    },
})
defer log.Sync()

log.Debug("failed to fetch URL", func(enc cog.Encoder) {
    enc.String("url", url)
    enc.Int("attempt", 3)
    enc.Stringer("backoff", time.Second)
})
{
    "message": "failed to fetch URL",
    "level": "debug",
    "time": 1599728064798211000,
    "app": "api",
    "caller": "cog/cog_test.go:24",
    "url": "https://google.com",
    "attempt": 3,
    "backoff": "1s"
}

Benchmarks

Run on cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz (4c/8t)

Date: 2021-04-21, go1.16

Log a static message to a disabled logger level (no context)

Package Time Time% to cog
⚙️ cog 0.3823 ns/op +0%
⚡ zap 2.277 ns/op +495%
rs/zerolog 1.058 ns/op +176%
francoispqt/onelog 0.6566 ns/op +71%

Log a message to a disabled logger level with static context

Package Time Time% to cog
⚙️ cog 0.3642 ns/op +0%
⚡ zap 2.435 ns/op +568%
rs/zerolog 1.069 ns/op +193%
francoispqt/onelog 0.6525 ns/op +79%

Log a message to a disabled logger level with dynamic context

Package Time Time% to cog
⚙️ cog 0.8116 ns/op +0%
⚡ zap 177.8 ns/op +21807%
rs/zerolog 66.15 ns/op +8050%
francoispqt/onelog 1.349 ns/op +66%

Log a static message (no context)

Package Time Time% to cog
⚙️ cog 105.6 ns/op +0%
⚡ zap 236.7 ns/op +124%
rs/zerolog 163.7 ns/op +54%
francoispqt/onelog 91.55 ns/op -13%

Log a message with static context

Package Time Time% to cog
⚙️ cog 88.90 ns/op +0%
⚡ zap 236.0 ns/op +165%
rs/zerolog 176.7 ns/op +98%
francoispqt/onelog 1072 ns/op +1105%

Log a message with dynamic context

Package Time Time% to cog
⚙️ cog 841.0 ns/op +0%
⚡ zap 2234 ns/op +165%
rs/zerolog 7211 ns/op +757%
francoispqt/onelog 1020 ns/op +21%

As you can see, Cog is way faster than zap and zerolog in every benchmarked situation. If you can create a somewhat realistic benchmark where this isn't the case, please file an issue. The disabled logger performance is so good you can leave your debug calls in production mode.

Onelong has comparable performance (except for static context) and is more stable, so you should probably use it if you find its API to be better.

Considerations

While an enc.Value method is available, it's a lot slower than calling the correct encoder method.

For example, enc.Value("", duration) is ~3-4 times slower than enc.Stringer("", duration).

You also don't get type safety, so the encoder could resort to reflection, which is extremely slow. The enc.Value is still very useful for debugging, but should not be used in any kind of loop or resource-heavy task.

It's implemented as a best-effort algorithm to use internal methods, but falls back to encoding/json.

The log timestamp is a simple unixNano timesteamp, because marshalling a time.Time to a standard format, say RFC3339Nano, is 20x slower. Yes it is extremely slow (~400ns vs ~20ns). This is while including time.Now() in the benchmark loop.

Is is still extremely useful especially for debugging, so a BetterDebug option is provided so that a RFC3339 timestamp is output instead while debugging.

In case you want to slow down your logs, but have them more human-readable, a TimeFormat options is provided. Cog will still be faster than zap or zerolog though.

Development Status: Unstable

Breaking changes can and will happen anytime

I will try to make cog somewhat stable, but don't expect any stability from /pkg/

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Context

type Context func(Encoder)

Context represents the context in which the log was output.

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder is the type on which you encode all your context values.

func (Encoder) Array

func (e Encoder) Array(key string, fn func(Encoder))

Array encases fn in an array

Example:

e.Array(key, func(enc medea.Encoder) {
	for _, v := range mySlice {
		enc.String("", v.Name)
	}
})

func (Encoder) Bool

func (e Encoder) Bool(key string, value bool)

Bool encodes value as a boolean, i.e. JSON token `true` or `false`.

func (Encoder) BoolSlice

func (e Encoder) BoolSlice(key string, values []bool)

BoolSlice encodes values as an array of the contents.

func (Encoder) Bytes

func (e Encoder) Bytes(key string, values []byte)

Bytes writes values as base64 string.

func (Encoder) Err

func (e Encoder) Err(key string, value error)

Err is a short form of String(key, value.Error()).

func (Encoder) Float

func (e Encoder) Float(key string, value float64)

Float encodes value as a number, it is ~3x slower than Int64.

func (Encoder) FloatSlice

func (e Encoder) FloatSlice(key string, values []float64)

FloatSlice encodes values as an array of the contents.

func (Encoder) Int

func (e Encoder) Int(key string, value int)

Int encodes value as a number.

func (Encoder) Int64

func (e Encoder) Int64(key string, value int64)

Int64 encodes value as a number.

func (Encoder) Int64Slice

func (e Encoder) Int64Slice(key string, values []int64)

Int64Slice encodes values as an array of the contents.

func (Encoder) IntSlice

func (e Encoder) IntSlice(key string, values []int)

IntSlice encodes values as an array of the contents.

func (Encoder) Nil

func (e Encoder) Nil(key string)

Nil encodes absence of a value, i.e. JSON token `null`.

func (Encoder) Object

func (e Encoder) Object(key string, fn func(Encoder))

Object encases fn in an object

Example:

e.Object(key, func(enc medea.Encoder) {
	for k, v := range myMap {
		enc.String(k, v.Name)
	}
})

func (Encoder) RawString

func (e Encoder) RawString(key, value string)

RawString has no escaping at all, not even quotes for JSON. Use String in most cases (RawString is faster though).

func (Encoder) Release

func (e Encoder) Release()

func (Encoder) String

func (e Encoder) String(key, value string)

String escapes only what is mandated by RFC 8259.

func (Encoder) StringBytes

func (e Encoder) StringBytes(key string, value []byte)

StringBytes is similar to RawString(key, string(value)), but doesn't allocate.

func (Encoder) StringSlice

func (e Encoder) StringSlice(key string, values []string)

StringSlice encodes values as an array of the contents.

func (Encoder) Stringer

func (e Encoder) Stringer(key string, value fmt.Stringer)

Stringer is a short form of String(key, value.String()).

func (Encoder) Time

func (e Encoder) Time(key string, value time.Time, layout string)

TimeFormat encodes the time with layout as (time.Time).Format.

func (Encoder) Timestamp

func (e Encoder) Timestamp(key string, value time.Time)

Timestamp encodes the time as a UNIX timestamp (second).

func (Encoder) Uint64

func (e Encoder) Uint64(key string, value uint64)

Uint64 encodes value as a number.

func (Encoder) Uint64Slice

func (e Encoder) Uint64Slice(key string, values []uint64)

Uint64Slice encodes values as an array of the contents.

func (Encoder) Value

func (e Encoder) Value(key string, value interface{})

Value encodes value as a valid sub value It is quite slow as it doesnt know the type at compile time Useful for debugging.

type LogLevel

type LogLevel int8

LogLevel represents a logging level. If a *Logger is at ErrorLevel, a log.Warn WILL NOT be output. If a *Logger is at InfoLevel, a log.Info WILL be output.

Basic recommendations:

fatal: wake the sysadmin up in the middle of the night
panic: log them (deduplicated) to the devops slack/discord/teams channel
error: periodically log their count to the dev channel, make them easily accessible
info:  periodically log their count to the dev channel
debug: disabled in production
const (
	// Error: problem happened.
	ErrorLevel LogLevel = iota - 1
	// Informational: informational messages (lots of packets are dropping, system is slower than usual).
	InfoLevel
	// Debug: debug-level messages, should be disabled in production.
	DebugLevel

	// All levels enabled.
	All LogLevel = 127
	// Disables the logger.
	Disabled LogLevel = -128
)

func LevelOf

func LevelOf(in string) LogLevel

LevelOf returns the LogLevel corresponding to the input Returns InfoLevel on invalid input.

func (LogLevel) String

func (l LogLevel) String() string

String implements fmt.Stringer.

type Logger

type Logger struct {
	// contains filtered or unexported fields
}

Logger represents the logger An empty value is INVALID.

func Default

func Default() *Logger

Default returns a ready-to-use *Logger, writing on Stderr with Debug level.

func New

func New(name string, opts Options) *Logger

New creates a new *Logger for usage.

func (*Logger) Debug

func (l *Logger) Debug(message string, fields ...Context)

Debug logs message (and fields) at DebugLevel, and honors BetterDebug

Do NOT log sensitive info, even in debug level.

func (*Logger) Dump

func (l *Logger) Dump(v interface{})

Dump dumps the content of v with fmt.Printf("%+v", v) onto the debug stack (honoring BetterDebug).

func (*Logger) Error

func (l *Logger) Error(err error, fields ...Context)

Error logs err at ErrorLevel

If err is nil, doesn't log, so you can log.Error(file.Close()) and it won't log if there is no error closing the file.

func (*Logger) Info

func (l *Logger) Info(message string, fields ...Context)

Info logs message and fields at InfoLevel

func (*Logger) Panic

func (l *Logger) Panic(err error, fields ...Context)

Panic logs err at ErrorLevel, and then panics with err

If err is nil, doesn't log nor panics.

You can use for application initialization, for example in config loading:

conf, err := loadConfig()
log.Panic(err)
// now conf is valid

func (*Logger) Printf

func (l *Logger) Printf(format string, args ...interface{})

Printf is a helper, logs to InfoLevel with message as returned by fmt.Sprintf, meant to be used by external packages that only know of this basic interface

Is very slow and unhelpul compared to other methods.

func (*Logger) Sync

func (l *Logger) Sync() error

Sync calls the underlying Target's Sync method, flushing any buffered log entries. Applications should take care to call Sync before exiting.

type Options

type Options struct {
	Target     Target      // Target on which to output the logs, defaults to Stderr
	Level      LogLevel    // LogLevel of the Logger, defaults to ErrorLevel
	LevelKey   string      // Key to use for logging LogLevel, defaults to "level" if empty
	NameKey    string      // Key to use for logging Name, defaults to "name" if empty
	CallerKey  string      // Key to use to print caller (file, line, function name), big perforance hit
	TimeKey    string      // Key to use for logging timestamp
	TimeFormat string      // Format to use, uses a simple but fast UnixNano timestamp if empty
	Context    Context     // Static context to log alongside other fields
	OnError    func(error) // Called when internal write error happened
	// When true, TimeFormat defaults to as RFC3339, and caller is always on for the Debug stack
	BetterDebug bool
}

Options is the Logger options, passed to New() An empty *Key value means the corresponding context WILL NOT be output.

type Target

type Target interface {
	io.Writer
	sync.Locker
	Sync() error
}

Target is an io.Writer that can also flush any buffered data, and is safe for concurrent use.

func MustFile

func MustFile(path string) Target

MustFile opens a file in write-only|append|create mode, calls Writer on it, and panics if error happened.

func Stderr

func Stderr() Target

Stderr is the equivalent of Writer(os.Stderr).

func Stdout

func Stdout() Target

Stdout is the equivalent of Writer(os.Stdout).

func Writer

func Writer(w io.Writer) Target

Writer wraps an io.Writer with a mutex to make it safe for concurrent use. It also adds a no-op Sync method if none exist on the io.Writer. In particular, *os.Files get locked, but they have a Sync method already.

Directories

Path Synopsis
pkg
medea
Package medea implements fast and easy json serialization for all This library is optimized for 64-bit architectures and may not work on 32-bit
Package medea implements fast and easy json serialization for all This library is optimized for 64-bit architectures and may not work on 32-bit

Jump to

Keyboard shortcuts

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