xlog

package module
v1.6.0 Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2024 License: MIT Imports: 15 Imported by: 2

README

Xlog

Build Status License Coverage Status Goreportcard Go Reference


Package xlog provides a structured leveled Logger implemented in two different strategies: synchronous and asynchronous.
The logs can be formatted in JSON, logfmt, custom text format.

Installation
$ go get github.com/actforgood/xlog
Supported level APIs:
  • Critical
  • Error
  • Warning
  • Info
  • Debug
  • Log // arbitrary log
Common options

A logger will need a CommonOpts through which you can configure some default keys and values used by the logger.

xOpts := xlog.NewCommonOpts() // instantiates new CommonOpts object with default values.
Configuring level options for a log.

Example of applicability:

  • you may want to log from Error above messages on production env, and all levels on dev env.
  • you may want to log messages by different level in different sources - see also ExampleMultiLogger_splitMessagesByLevel from doc reference.
xOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug) // by default is LevelWarning
xOpts.MaxLevel = xlog.FixedLevelProvider(xlog.LevelInfo) // by default is LevelCritical
xOpts.LevelKey = "level" // by default is "lvl"
xOpts.LevelLabels = map[xlog.Level]string{ // by default "CRITICAL", "ERROR", "WARN", "INFO", "DEBUG" are used
    xlog.LevelCritical: "CRT",
    xlog.LevelError: "ERR",
    xlog.LevelWarning: "WRN",
    xlog.LevelInfo: "INF",
    xlog.LevelDebug: "DBG", 
}

Check also the xlog.EnvLevelProvider - to get the level from OS's env.
You can make your own xlog.LevelProvider - to get the level from a remote API/other source, for example.

Configuring time options for a log.
xOpts.Time    = xlog.UTCTimeProvider(time.RFC3339) // by default is time.RFC3339Nano
xOpts.TimeKey = "t" // by default is "date"

Check also the xlog.LocalTimeProvider - to get time in local server timezone.
You can make your own xlog.Provider if needed for more custom logic.

Configuring source options for a log.
xOpts.Source = xlog.SourceProvider(4, 2) // by default logs full path with a stack level of 4.
xOpts.SourceKey = "source" // by default is "src"

By setting SourceKey to blank, you can disable source logging. By changing the first parameter in SourceProvider, you can manipulate the level in the stack trace. By changing the second parameter in SourceProvider, you can manipulate how many levels in the path to be logged. Example:

xlog.SourceProvider(4, 0) // => "src":"/Users/JohnDoe/work/go/xlog/example.go:65" (full path)
xlog.SourceProvider(4, 1) // => "src":"/example.go:65"
xlog.SourceProvider(4, 2) // => "src":"/xlog/example.go:65"
xlog.SourceProvider(4, 3) // => "src":"/go/xlog/example.go:65"
...
Configuring additional key-values to be logged with every log.
xOpts.AdditionalKeyValues = []any{
	"app", "demoXlog",
	"env", "prod",
	"release", "v1.10.0",
}
Configuring an I/O / formatting error handler for errors that may occur during logging.

By design, logger contract does not return error from its methods. A no operation ErrorHandler is set by default. You can change it to something else if suitable. For example, log with standard go logger the error.

xOpts.ErrHandler = func(err error, keyValues []any) {
	// import "log"
	log.Printf("An error occurred during logging. err = %v, logParams = %v", err, keyValues)
}
Loggers
SyncLogger

SyncLogger is a Logger which writes logs synchronously.
It just calls underlying writer with each log call.
Note: if used in a concurrent context, log writes are not concurrent safe, unless the writer is concurrent safe. See also NewSyncWriter on this matter.
Example of usage:

xLogger := xlog.NewSyncLogger(os.Stdout)
defer xLogger.Close()
xLogger.Error(
	xlog.MessageKey, "Could not read file",
	xlog.ErrorKey, io.ErrUnexpectedEOF,
	"file", "/some/file",
)

You can change the formatter (json is default), and common options like:

xOpts := xlog.NewCommonOpts()
xOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelInfo)
xLogger := xlog.NewSyncLogger(
	os.Stdout,
	xlog.SyncLoggerWithOptions(xOpts),
	xlog.SyncLoggerWithFormatter(xlog.LogfmtFormatter),
)
defer xLogger.Close()
AsyncLogger

AsyncLogger is a Logger which writes logs asynchronously.
Note: if used in a concurrent context, log writes are concurrent safe if only one worker is configured to process the logs. Otherwise, log writes are not concurrent safe, unless the writer is concurrent safe. See also NewSyncWriter and AsyncLoggerWithWorkersNo on this matter.
Example of usage:

xLogger := xlog.NewAsyncLogger(os.Stdout)
defer xLogger.Close()
xLogger.Error(
	xlog.MessageKey, "Could not read file",
	xlog.ErrorKey, io.ErrUnexpectedEOF,
	"file", "/some/file",
)

You can change some options on it like:

xOpts := xlog.NewCommonOpts()
xOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelInfo)
xLogger := xlog.NewAsyncLogger(
	os.Stdout,
	xlog.AsyncLoggerWithOptions(xOpts),
	xlog.AsyncLoggerWithFormatter(xlog.LogfmtFormatter),     // defaults to json
	xlog.AsyncLoggerWithWorkersNo(uint16(runtime.NumCPU())), // defaults to 1
	xlog.AsyncLoggerWithChannelSize(512),                    // defaults to 256
)
defer xLogger.Close()
Benchmark example between sync / async loggers
go test -run=^# -benchmem -benchtime=5s -bench ".*(sequential|parallel)"
goos: darwin
goarch: amd64
pkg: github.com/actforgood/xlog
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkAsyncLogger_json_withDiscardWriter_with256ChanSize_with1Worker_sequential-8     1855483              3241 ns/op            1704 B/op         32 allocs/op
BenchmarkAsyncLogger_json_withDiscardWriter_with256ChanSize_with1Worker_parallel-8       1713628              3565 ns/op            1704 B/op         32 allocs/op
BenchmarkSyncLogger_json_withDiscardWriter_sequential-8                                   984081              5269 ns/op            1696 B/op         32 allocs/op
BenchmarkSyncLogger_json_withDiscardWriter_parallel-8                                    3394920              1797 ns/op            1696 B/op         32 allocs/op

Note how in a high concurrency context (_parallel) the sync logger actually behaves more well than async one.

MultiLogger

MultiLogger is a composite Logger capable of logging to multiple loggers.
Example of usage:

xLogger := xlog.NewMultiLogger(loggerA, loggerB)
defer xLogger.Close()
xLogger.Error(
	xlog.MessageKey, "Could not read file",
	xlog.ErrorKey, io.ErrUnexpectedEOF,
	"file", "/some/file",
)
NopLogger

NopLogger is a no-operation Logger which does nothing. It simply ignores any log.
You can use it when benchmarking another component that uses logger, for example, in order for the logging process not to interfere with the main component's bench stats.

MockLogger

MockLogger is a mock for Logger contract, to be used in Unit Tests.

Formats
JSONFormatter

Logs get written in JSON format. Is the default format configured for sync / async loggers.
Example of log:

{"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"DEBUG","msg":"Hello World","src":"/logger_async_test.go:43","year":2022}
LogfmtFormatter

Logs get written in logfmt format.
Example of configuring:

xLogger := xlog.NewSyncLogger(
	os.Stdout,
	xlog.SyncLoggerWithOptions(xOpts),
	xlog.SyncLoggerWithFormatter(xlog.LogfmtFormatter),
)

Example of log:

date=2022-04-12T16:01:20Z lvl=INFO src=/formatter_logfmt_test.go:42 appName=demo env=dev msg="Hello World" year=2022
TextFormatter

Logs get written in custom, human friendly format: TIME SOURCE LEVEL MESSAGE KEY1=VALUE1 KEY2=VALUE2 ...
Note: this is not a structured logging format. It can be used for a "dev" logger, for example.
Example of configuring (see also ExampleSyncLogger_devLogger from doc reference):

xLogger := xlog.NewSyncLogger(
	os.Stdout,
	xlog.SyncLoggerWithOptions(xOpts),
	xlog.SyncLoggerWithFormatter(xlog.TextFormatter(xOpts)),
)

Example of log:

2022-03-14T16:01:20Z /formatter_text_test.go:40 DEBUG Hello World year=2022
SyslogFormatter

Logs get written to system syslog. Example of configuring (see also ExampleSyncLogger_withSyslog from doc reference):

xLogger := xlog.NewSyncLogger(
	syslogWriter,
	xlog.SyncLoggerWithFormatter(xlog.SyslogFormatter(
		xlog.JSONFormatter,
		xlog.NewDefaultSyslogLevelProvider(xOpts),
		"",
	)),
	xlog.SyncLoggerWithOptions(xOpts),
)
SentryFormatter

Logs get written to Sentry. Example of configuring (see also ExampleSyncLogger_withSentry from doc reference):

xLogger := xlog.NewSyncLogger(
	io.Discard, // no need for other writer, SentryFormatter will override it with a buffered one in order to get original Formatter output.
	xlog.SyncLoggerWithOptions(xOpts),
	xlog.SyncLoggerWithFormatter(xlog.SentryFormatter(
		xlog.JSONFormatter,
		sentry.CurrentHub().Clone(), // make a clone if you're not using sentry only in the logger.
		xOpts,
	)),
)
Writers
SyncWriter

SyncWriter decorates an io.Writer so that each call to Write is synchronized with a mutex, making is safe for concurrent use by multiple goroutines.
It should be used if writer's Write method is not thread safe.
For example an os.File is safe, so it doesn't need this wrapper, on the other hand, a bytes.Buffer is not.

BufferedWriter

BufferedWriter decorates an io.Writer so that written bytes are buffered.
It is concurrent safe to use.
It has the capability of auto-flushing the buffer, time interval based. This capability can also be disabled. If an error occurs in the write process, at next log write, this error is not persisted, opposite using directly a bufio.Writer (see this).
Example of benchmarks between directly writes to a file, and writing to a "buffered" file:

go test -run=^# -benchmem -benchtime=5s -bench ".*FileWriter"
goos: darwin
goarch: amd64
pkg: github.com/actforgood/xlog
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkAsyncLogger_json_withFileWriter_with256ChanSize_with1Worker-8                            
666721               9007 ns/op            1704 B/op           32 allocs/op
BenchmarkAsyncLogger_json_withBufferedFileWriter_with256ChanSize_with1Worker-8                   
1597966              3696 ns/op            1704 B/op           32 allocs/op

BenchmarkSyncLogger_json_withFileWriter-8                                                         
507146             10856 ns/op            1696 B/op           32 allocs/op
BenchmarkSyncLogger_json_withBufferedFileWriter-8                                                 
920844              5928 ns/op            1696 B/op           32 allocs/op
Misc

Feel free to use this logger if you like it and fits your needs.
Check also other popular, performant loggers like Uber Zap, Zerolog, Gokit...
Here stands some benchmarks made locally based on this repo.

go test -run=^# -benchmem -benchtime=5s -bench ".*JSON"
goos: darwin
goarch: amd64
pkg: github.com/imkira/go-loggers-bench
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkGokitJSONPositive-8          3904477       1466 ns/op      1544 B/op      24 allocs/op
BenchmarkLog15JSONPositive-8           973974       5464 ns/op      2009 B/op      30 allocs/op
BenchmarkLogrusJSONPositive-8         2950423       1986 ns/op      2212 B/op      34 allocs/op
BenchmarkXlogSyncJSONPositive-8       3880016       1520 ns/op      1662 B/op      28 allocs/op
BenchmarkZerologJSONPositive-8       28168381      202.8 ns/op         0 B/op       0 allocs/op

BenchmarkGokitJSONNegative-8        180957508      32.65 ns/op       128 B/op       1 allocs/op
BenchmarkLog15JSONNegative-8         12070347      466.3 ns/op       632 B/op       5 allocs/op
BenchmarkLogrusJSONNegative-8        24485853      211.4 ns/op       496 B/op       4 allocs/op
BenchmarkXlogSyncJSONNegative-8    1000000000      3.415 ns/op         0 B/op       0 allocs/op
BenchmarkZerologJSONNegative-8     1000000000      3.288 ns/op         0 B/op       0 allocs/op
License

This package is released under a MIT license. See LICENSE.
Other 3rd party packages directly used by this package are released under their own licenses.

Documentation

Overview

Package xlog provides a structured Logger implemented in two different strategies: synchronous and asynchronous. The logs can be formatted in JSON, logfmt, custom text format.

Index

Examples

Constants

View Source
const ErrorKey = "err"

ErrorKey represents the key under which an error resides. For a Logger.Error call usually you'll want to log an error. You are not obliged to use this key.

View Source
const MessageKey = "msg"

MessageKey represents the key under which the main message resides. You are not obliged to use this key.

View Source
const SyslogPrefixCee = "@cee:"

SyslogPrefixCee defines the @cee prefix for structured logging in syslog. See: http://cee.mitre.org/language/1.0-beta1/clt.html#appendix-1-cee-over-syslog-transport-mapping .

Variables

View Source
var ErrNotSyslogWriter = errors.New("the writer should be a *syslog.Writer")

ErrNotSyslogWriter is the error returned in case the writer is not syslog specific.

View Source
var SentryFormatter = func(formatter Formatter, hub *sentry.Hub, opts *CommonOpts) Formatter {
	var (
		mu             sync.Mutex
		sentryLevelMap = map[Level]sentry.Level{
			LevelDebug:    sentry.LevelDebug,
			LevelInfo:     sentry.LevelInfo,
			LevelWarning:  sentry.LevelWarning,
			LevelError:    sentry.LevelError,
			LevelCritical: sentry.LevelFatal,
			LevelNone:     sentry.Level(""),
		}
		labeledLevels = flipLevelLabels(opts.LevelLabels)
	)

	return func(_ io.Writer, keyValues []any) error {
		keyValues = AppendNoValue(keyValues)

		buf := bufPool.Get().(*bytes.Buffer)
		buf.Reset()
		defer bufPool.Put(buf)

		if err := formatter(buf, keyValues); err != nil {
			return err
		}

		sentryLevel := sentryLevelMap[extractLevel(labeledLevels, opts.LevelKey, keyValues)]

		mu.Lock()
		defer mu.Unlock()

		hub.Scope().SetLevel(sentryLevel)
		_ = hub.CaptureMessage(buf.String())

		return nil
	}
}

SentryFormatter is a decorator which sends another formatter 's output to Sentry. The writer from the Logger should be io.Discard, as it uses internally a bytes.Buffer.

View Source
var SyslogFormatter = func(
	formatter Formatter,
	syslogLevelProvider SyslogLevelProvider,
	prefix string,
) Formatter {
	return func(w io.Writer, keyValues []any) error {
		sw, ok := w.(syslogWriter)
		if !ok {
			return ErrNotSyslogWriter
		}
		keyValues = AppendNoValue(keyValues)

		buf := bufPool.Get().(*bytes.Buffer)
		buf.Reset()
		defer bufPool.Put(buf)

		if prefix != "" {
			_, _ = buf.WriteString(prefix)
		}

		if err := formatter(buf, keyValues); err != nil {
			return err
		}

		syslogLevel := syslogLevelProvider(keyValues)
		switch syslogLevel {
		case syslog.LOG_EMERG:
			return sw.Emerg(buf.String())
		case syslog.LOG_ALERT:
			return sw.Alert(buf.String())
		case syslog.LOG_CRIT:
			return sw.Crit(buf.String())
		case syslog.LOG_ERR:
			return sw.Err(buf.String())
		case syslog.LOG_WARNING:
			return sw.Warning(buf.String())
		case syslog.LOG_NOTICE:
			return sw.Notice(buf.String())
		case syslog.LOG_INFO:
			return sw.Info(buf.String())
		case syslog.LOG_DEBUG:
			return sw.Debug(buf.String())
		default:
			_, err := sw.Write(buf.Bytes())

			return err
		}
	}
}

SyslogFormatter is a decorator which writes another formatter 's output to system syslog. The second param is a function that knows to return a syslog level for the current log. You can use NewDefaultSyslogLevelProvider / NewExtractFromKeySyslogLevelProvider or custom provider (maybe you want to support other syslog levels - for example nothing stops you from doing this: logger.Log("lvl","NOTICE", ...) and map also "NOTICE" to syslog.LOG_NOTICE). The third param is a prefix to be written with each log. You'll pass here empty string or SyslogPrefixCee.

View Source
var TextFormatter = func(opts *CommonOpts) Formatter {
	return func(w io.Writer, keyValues []any) error {
		keyValues = AppendNoValue(keyValues)

		var (
			time, level, source, msg  string
			finalOutBuf, extraInfoBuf bytes.Buffer
			key, value                any
		)
		finalOutBuf.Grow(64)
		extraInfoBuf.Grow(64)

		for idx := 0; idx < len(keyValues); idx += 2 {
			key = keyValues[idx]
			value = keyValues[idx+1]
			switch key {
			case opts.LevelKey:
				level = stringify(value)
			case opts.TimeKey:
				time = stringify(value)
			case opts.SourceKey:
				source = stringify(value)
			case MessageKey:
				msg = stringify(value)
			default:
				_, _ = extraInfoBuf.WriteString(stringify(key))
				_ = extraInfoBuf.WriteByte('=')
				_, _ = extraInfoBuf.WriteString(stringify(value))
				_ = extraInfoBuf.WriteByte(' ')
			}
		}

		appendTextFinalOutput(&finalOutBuf, []byte(time))
		appendTextFinalOutput(&finalOutBuf, []byte(source))
		appendTextFinalOutput(&finalOutBuf, []byte(level))
		appendTextFinalOutput(&finalOutBuf, []byte(msg))
		finalOut := append(finalOutBuf.Bytes(), extraInfoBuf.Bytes()...)
		finalOut[len(finalOut)-1] = '\n'

		_, err := w.Write(finalOut)

		return err
	}
}

TextFormatter provides a more human friendly custom format. This formatter does not comply with any kind of well known standard. It can be used for example for local dev environment. Example of output: "TIME SOURCE LEVEL MESSAGE KEY1=VALUE1 KEY2=VALUE2 ...".

Functions

func AppendNoValue

func AppendNoValue(keyValues []any) []any

AppendNoValue is a safety function which adds a "*NoValue*" at the end of keyValues slice in case it is odd.

func NewSyncWriter

func NewSyncWriter(w io.Writer) io.Writer

NewSyncWriter instantiates a new Writer decorated with a mutex, making is safe for concurrent use by multiple goroutines.

Example
package main

import (
	"bytes"
	"fmt"
	"log"
	"strconv"
	"sync"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we wrap a bytes.Buffer (Writer)
	// with a SyncWriter in order for Write to be
	// concurrent safe.

	inMemoryWriter := new(bytes.Buffer)
	// writer := inMemoryWriter // you can enable this line instead of SyncWriter to check race conditions.
	writer := xlog.NewSyncWriter(inMemoryWriter)
	text := "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
	wg := sync.WaitGroup{}

	// perform 5 concurrent writes.
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func(threadNo int) {
			defer wg.Done()
			threadNoStr := strconv.FormatInt(int64(threadNo+1), 10)
			_, err := writer.Write([]byte(text + threadNoStr + "\n"))
			if err != nil {
				log.Printf("[%d] write error: %v\n", threadNo, err)
			}
		}(i)
	}
	wg.Wait()

	checkOutput := inMemoryWriter.Bytes()
	fmt.Print(string(checkOutput))

}
Output:

Lorem ipsum dolor sit amet, consectetur adipiscing elit.1
Lorem ipsum dolor sit amet, consectetur adipiscing elit.4
Lorem ipsum dolor sit amet, consectetur adipiscing elit.3
Lorem ipsum dolor sit amet, consectetur adipiscing elit.5
Lorem ipsum dolor sit amet, consectetur adipiscing elit.2

func StackErr

func StackErr(err error) string

StackErr return "%+v" fmt representation of an error. Can be useful to log stack of errors created with "github.com/pkg/errors" / "github.com/actforgood/xerr" packages.

Example of usage:

logger.Error(xlog.ErrorKey, xlog.StackErr(errWithStack), ...)

Types

type AsyncLogger

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

AsyncLogger is a Logger which writes logs asynchronously. Note: if used in a concurrent context, log writes are concurrent safe if only one worker is configured to process the logs. Otherwise, log writes are not concurrent safe, unless the writer is concurrent safe. See also NewSyncWriter and AsyncLoggerWithWorkersNo on this matter.

Example
package main

import (
	"io"
	"os"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we create a (async)logger that writes
	// logs to standard output.

	opts := xlog.NewCommonOpts()
	opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone)
	opts.AdditionalKeyValues = []any{
		"appName", "demo",
		"env", "dev",
	}
	opts.Time = func() any { // mock time for output check
		return "2022-03-16T16:01:20Z"
	}
	opts.Source = xlog.SourceProvider(4, 1) // keep only filename for output check

	logger := xlog.NewAsyncLogger(
		os.Stdout,
		xlog.AsyncLoggerWithOptions(opts),
		xlog.AsyncLoggerWithWorkersNo(2), // since workers no > 1, we expect output to be unordered.
	)
	defer logger.Close()

	logger.Log(xlog.MessageKey, "Hello World", "year", 2022)
	logger.Debug(xlog.MessageKey, "Hello World", "year", 2022)
	logger.Info(xlog.MessageKey, "Hello World", "year", 2022)
	logger.Warn(xlog.MessageKey, "Hello World", "year", 2022)
	logger.Error(xlog.MessageKey, "Could not read file", xlog.ErrorKey, io.ErrUnexpectedEOF, "file", "/some/file")
	logger.Critical(xlog.MessageKey, "DB connection is down")

}
Output:

{"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","msg":"Hello World","src":"/logger_async_test.go:43","year":2022}
{"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"DEBUG","msg":"Hello World","src":"/logger_async_test.go:44","year":2022}
{"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"INFO","msg":"Hello World","src":"/logger_async_test.go:45","year":2022}
{"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"WARN","msg":"Hello World","src":"/logger_async_test.go:46","year":2022}
{"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","err":"unexpected EOF","file":"/some/file","lvl":"ERROR","msg":"Could not read file","src":"/logger_async_test.go:47"}
{"appName":"demo","date":"2022-03-16T16:01:20Z","env":"dev","lvl":"CRITICAL","msg":"DB connection is down","src":"/logger_async_test.go:48"}

func NewAsyncLogger

func NewAsyncLogger(w io.Writer, opts ...AsyncLoggerOption) *AsyncLogger

NewAsyncLogger instantiates a new logger object that writes logs asynchronously. First param is a Writer where logs are written to. Example: os.Stdout, a custom opened os.File, an in memory strings.Buffer, etc. Second param is/are function option(s) through which you can customize the logger. Check for AsyncLoggerWith* options.

func (*AsyncLogger) Close

func (logger *AsyncLogger) Close() error

Close nicely closes logger. You should call it to make sure all logs have been processed (for example at your application shutdown). Once called, any further call to any of the logging methods will be ignored.

func (*AsyncLogger) Critical

func (logger *AsyncLogger) Critical(keyValues ...any)

Critical logs application component unavailable, fatal events.

func (*AsyncLogger) Debug

func (logger *AsyncLogger) Debug(keyValues ...any)

Debug logs detailed debug information.

func (*AsyncLogger) Error

func (logger *AsyncLogger) Error(keyValues ...any)

Error logs runtime errors that should typically be logged and monitored.

func (*AsyncLogger) Info

func (logger *AsyncLogger) Info(keyValues ...any)

Info logs interesting events. Example: User logs in, SQL logs.

func (*AsyncLogger) Log

func (logger *AsyncLogger) Log(keyValues ...any)

Log logs arbitrary data.

func (*AsyncLogger) Warn

func (logger *AsyncLogger) Warn(keyValues ...any)

Warn logs exceptional occurrences that are not errors. Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.

type AsyncLoggerOption

type AsyncLoggerOption func(*AsyncLogger)

AsyncLoggerOption defines optional function for configuring an async logger.

func AsyncLoggerWithChannelSize

func AsyncLoggerWithChannelSize(logsChanSize uint16) AsyncLoggerOption

AsyncLoggerWithChannelSize sets internal buffered channel's size, through which logs are processed. If not called, defaults to a 256 size. Note: logging blocks if internal logs channel is full (the rate of producing messages is much higher than consuming one). Using AsyncLoggerWithChannelSize to set a higher value to increase throughput in such case can be helpful.

func AsyncLoggerWithFormatter

func AsyncLoggerWithFormatter(formatter Formatter) AsyncLoggerOption

AsyncLoggerWithFormatter sets desired formatter for the logs. The JSON formatter is used by default.

func AsyncLoggerWithOptions

func AsyncLoggerWithOptions(opts *CommonOpts) AsyncLoggerOption

AsyncLoggerWithOptions sets the common options. A NewCommonOpts is used by default.

func AsyncLoggerWithWorkersNo

func AsyncLoggerWithWorkersNo(workersNo uint16) AsyncLoggerOption

AsyncLoggerWithWorkersNo sets the no. of workers to process internal logs channel. If not called, defaults to 1. Note: logging blocks if internal logs channel is full (the rate of producing messages is more high than consuming one). Using AsyncLoggerWithWorkersNo to increase number of workers might be helpful. Note: having a value greater than 1 implies that logs may not necessarily be written in their timestamp order. Example: goroutine #1 reads a log entry with timestamp sec :00.999 and goroutine #2 reads a log entry with timestamp sec :01.000 in the same time, but goroutine #2 is the first one to write its log entry. Note: having a value greater than 1 implies that the underlying writer is concurrent safe for Writes.

type BufferedWriter

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

BufferedWriter decorates an io.Writer so that written bytes are buffered. It is concurrent safe to use. It has the capability of auto-flushing the buffer, time interval based.

func NewBufferedWriter

func NewBufferedWriter(w io.Writer, opts ...BufferedWriterOption) *BufferedWriter

NewBufferedWriter instantiates a new buffered writer.

func (*BufferedWriter) Stop

func (bw *BufferedWriter) Stop()

Stop marks the writer as stopped. You should call it to make sure all data have been processed. Once called any further Write will be ignored.

func (*BufferedWriter) Write

func (bw *BufferedWriter) Write(p []byte) (int, error)

Write writes given bytes to the decorated writer (buffered). Returns no. of bytes written, or an error.

type BufferedWriterOption

type BufferedWriterOption func(*BufferedWriter)

BufferedWriterOption defines optional function for configuring a buffered writer.

func BufferedWriterWithFlushInterval

func BufferedWriterWithFlushInterval(flushInterval time.Duration) BufferedWriterOption

BufferedWriterWithFlushInterval sets desired auto-flush interval. 10s is used by default. Pass a value <=0 if you want to disable the interval based auto-flush.

func BufferedWriterWithSize

func BufferedWriterWithSize(bufSize int) BufferedWriterOption

BufferedWriterWithSize sets desired buffer size. 4Kb is used by default.

type CommonOpts

type CommonOpts struct {
	// MinLevel is a function that returns the minimum level
	// allowed to be logged.
	// By default, is set to return warning level.
	MinLevel LevelProvider

	// MaxLevel is a function that returns the maximum level
	// allowed to be logged.
	// By default, is set to return error level.
	MaxLevel LevelProvider

	// LevelLabels is a map which defines for each level
	// its string representation.
	// By default, "CRITICAL", "ERROR", "WARN", "INFO", "DEBUG" labels are used.
	LevelLabels map[Level]string

	// LevelKey is the key under which level is found.
	// By default, is set to "lvl".
	LevelKey string

	// TimeKey is the key under which the current log's time is found.
	// By default, is set to "date".
	TimeKey string

	// Time is a function that returns the time to be logged.
	// By default, is set to UTC time formatted as RFC3339Nano.
	Time Provider

	// SourceKey is the key under which caller filename and line are found.
	// It can be set to an empty string if you want to disable this information
	// from logs.
	// By default, is set to "src".
	SourceKey string

	// Source is a provider that returns the source where the log occurred
	// in the call stack.
	Source Provider

	// AdditionalKeyValues holds additional key-values that will be stored
	// with each log.
	// Example: you may want to log your application version or name or
	// environment (dev/stage/production/...), etc.
	// The value can be a Provider for dynamically retrieve a value at runtime.
	AdditionalKeyValues []any

	// ErrHandler callback to process errors that occurred during logging.
	// By design, the logger contract does not return errors from its methods
	// as you most probably use it for this purpose, to log an error, and
	// if an error rises in this process what can you do?
	// You may want to log the error with the standard logger if it
	// suits your needs for example.
	// Source of errors might come from IO errors / formatting errors.
	// By default, is set to a no-op ErrorHandler which disregards the error.
	ErrHandler ErrorHandler
}

CommonOpts is a struct holding common configurations for a logger.

func NewCommonOpts

func NewCommonOpts() *CommonOpts

NewCommonOpts instantiates a default configured CommonOpts object. You can start customization of fields from this object.

func (*CommonOpts) BetweenMinMax

func (opts *CommonOpts) BetweenMinMax(lvl Level) bool

BetweenMinMax returns true if passed level is found in [MinLevel, MaxLevel] interval, false otherwise.

func (*CommonOpts) WithDefaultKeyValues

func (opts *CommonOpts) WithDefaultKeyValues(lvl Level, keyValues ...any) []any

WithDefaultKeyValues returns keyValues enriched with default ones.

type ErrorHandler

type ErrorHandler func(err error, keyValues []any)

ErrorHandler is a callback to handle internal logging errors. It accepts as 1st param the internal logging error. It accepts as 2nd param the key-values log entry on which error occurred.

var NopErrorHandler ErrorHandler = func(_ error, _ []any) {}

NopErrorHandler is "no-op" error handler for any error occurred during log process. It simply ignores the error.

type Formatter

type Formatter func(w io.Writer, keyValues []any) error

Formatter writes the provided key-values in a given format. Returns error in case something goes wrong.

var JSONFormatter Formatter = func(w io.Writer, keyValues []any) error {
	keyValues = AppendNoValue(keyValues)

	keyValueMap := make(map[string]any, len(keyValues)/2)
	for idx := 0; idx < len(keyValues); idx += 2 {
		keyValueMap[stringify(keyValues[idx])] = valueForJSON(keyValues[idx+1])
	}

	encoder := json.NewEncoder(w)
	encoder.SetEscapeHTML(false)

	return encoder.Encode(keyValueMap)
}

JSONFormatter serializes key-values in JSON format and writes the resulted JSON to the writer. It returns error if a serialization/writing problem is encountered.

var LogfmtFormatter Formatter = func(w io.Writer, keyValues []any) error {
	keyValues = AppendNoValue(keyValues)

	enc := logfmtEncoderPool.Get().(*logfmtEncoder)
	enc.Reset()
	defer logfmtEncoderPool.Put(enc)

	if err := enc.Encode(keyValues...); err != nil {
		return err
	}

	if _, err := w.Write(enc.buf.Bytes()); err != nil {
		return err
	}

	return nil
}

LogfmtFormatter serializes key-values in logfmt format and writes the resulted bytes to the writer. It returns error if a serialization/writing problem is encountered. More about logfmt can be found here: https://brandur.org/logfmt .

type Level

type Level byte

Level of logging.

const (
	// LevelNone represents no level.
	// Is used internally for Log() method.
	LevelNone Level = 0

	// LevelDebug is the level for debug logs.
	LevelDebug Level = 10

	// LevelInfo is the level for info logs.
	LevelInfo Level = 20

	// LevelWarning is the level for warning logs.
	LevelWarning Level = 30

	// LevelError is the level for error logs.
	LevelError Level = 40

	// LevelCritical is the level for critical logs.
	LevelCritical Level = 50
)

type LevelProvider

type LevelProvider func() Level

LevelProvider is a function that provides at runtime the min/max level allowed to be logged.

func EnvLevelProvider

func EnvLevelProvider(envLvlKey string, defaultLvl Level, levelLabels map[Level]string) LevelProvider

EnvLevelProvider provides a level read from OS's ENV at each call. If the level environment key is not found, or value stored in it is invalid, default provided level is returned. As it is called on each log, you may change during application run the underlying env without restarting the app, and new configured value will be used in place, if suitable.

func FixedLevelProvider

func FixedLevelProvider(lvl Level) LevelProvider

FixedLevelProvider provides a fixed Level returned at each call.

type Logger

type Logger interface {
	io.Closer

	// Critical logs application component unavailable, fatal events.
	Critical(keyValues ...any)

	// Error logs runtime errors that
	// should typically be logged and monitored.
	Error(keyValues ...any)

	// Warn logs exceptional occurrences that are not errors.
	// Example: Use of deprecated APIs, poor use of an API,
	// undesirable things that are not necessarily wrong.
	Warn(keyValues ...any)

	// Info logs interesting events.
	// Example: User logs in, SQL logs.
	Info(keyValues ...any)

	// Debug logs detailed debug information.
	Debug(keyValues ...any)

	// Log logs arbitrary data.
	Log(keyValues ...any)
}

Logger provides prototype for logging with different levels. It is designed to accept variadic parameters useful for a structured logger.

type MockLogger

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

MockLogger is a mock for xlog.Logger contract, to be used in UT.

func NewMockLogger

func NewMockLogger() *MockLogger

NewMockLogger instantiates new mocked Logger.

func (*MockLogger) Close

func (mock *MockLogger) Close() error

Close mock logic.

func (*MockLogger) CloseCallsCount

func (mock *MockLogger) CloseCallsCount() int

CloseCallsCount returns the no. of times Close was called.

func (*MockLogger) Critical

func (mock *MockLogger) Critical(keyValues ...any)

Critical mock logic.

func (*MockLogger) Debug

func (mock *MockLogger) Debug(keyValues ...any)

Debug mock logic.

func (*MockLogger) Error

func (mock *MockLogger) Error(keyValues ...any)

Error mock logic.

func (*MockLogger) Info

func (mock *MockLogger) Info(keyValues ...any)

Info mock logic.

func (*MockLogger) Log

func (mock *MockLogger) Log(keyValues ...any)

Log mock logic.

func (*MockLogger) LogCallsCount

func (mock *MockLogger) LogCallsCount(lvl Level) int

LogCallsCount returns the no. of times Critical/Error/Warn/Info/Debug/Log was called. Differentiate methods calls count by passing appropriate level.

func (*MockLogger) SetCloseError

func (mock *MockLogger) SetCloseError(closeErr error)

SetCloseError sets the error to be returned by the Close method.

func (*MockLogger) SetLogCallback

func (mock *MockLogger) SetLogCallback(
	lvl Level,
	callback func(keyValues ...any),
)

SetLogCallback sets the callback to be executed inside Error/Warn/Info/Debug/Log. You can make assertions upon passed parameter(s) this way.

func (*MockLogger) Warn

func (mock *MockLogger) Warn(keyValues ...any)

Warn mock logic.

type MultiLogger

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

MultiLogger is a composite Logger capable of logging to multiple loggers.

Example (LogToStdOutAndCustomFile)
package main

import (
	"os"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we create a (multi)logger that writes
	// logs to standard output, and to a file.

	stdOutLgrOpts := xlog.NewCommonOpts()
	stdOutLgrOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone)
	stdOutLgrOpts.Time = func() any { // mock time for output check
		return "2022-03-15T16:01:20Z"
	}
	stdOutLgrOpts.Source = xlog.SourceProvider(5, 1) // keep only filename for output check
	stdOutLgr := xlog.NewSyncLogger(
		os.Stdout,
		xlog.SyncLoggerWithOptions(stdOutLgrOpts),
	)

	fileLgrOpts := xlog.NewCommonOpts()
	fileLgrOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone)
	fileLgrOpts.Time = func() any { // mock time for output check
		return "2022-03-15T16:01:20Z"
	}
	fileLgrOpts.Source = xlog.SourceProvider(5, 1) // keep only filename for output check
	f, err := os.CreateTemp("", "x.log-*")         // you will have your well known path defined
	if err != nil {
		panic(err)
	}
	fileLgr := xlog.NewSyncLogger(
		f,
		xlog.SyncLoggerWithOptions(fileLgrOpts),
	)

	logger := xlog.NewMultiLogger(stdOutLgr, fileLgr)
	defer func() {
		_ = logger.Close()
		_ = f.Close()
		_ = os.Remove(f.Name()) // you won't remove the file
	}()

	logger.Debug("msg", "I get written to standard output and to a file")

}
Output:

{"date":"2022-03-15T16:01:20Z","lvl":"DEBUG","msg":"I get written to standard output and to a file","src":"/logger_multi_test.go:91"}
Example (SplitMessagesByLevel)
package main

import (
	"os"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we create a (multi)logger that writes
	// debug and info logs to standard output and
	// warning, error and critical logs to error output.

	errLgrOpts := xlog.NewCommonOpts()
	errLgrOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelWarning)
	errLgrOpts.Time = func() any { // mock time for output check
		return "2022-03-20T16:01:20Z"
	}
	errLgrOpts.Source = xlog.SourceProvider(5, 1) // keep only filename for output check
	errLgr := xlog.NewSyncLogger(
		os.Stderr,
		xlog.SyncLoggerWithOptions(errLgrOpts),
	)

	dbgLgrOpts := xlog.NewCommonOpts()
	dbgLgrOpts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug)
	dbgLgrOpts.MaxLevel = xlog.FixedLevelProvider(xlog.LevelInfo)
	dbgLgrOpts.Time = func() any { // mock time for output check
		return "2022-03-20T16:01:20Z"
	}
	dbgLgrOpts.Source = xlog.SourceProvider(5, 1) // keep only filename for output check
	dbgLgr := xlog.NewSyncLogger(
		os.Stdout,
		xlog.SyncLoggerWithOptions(dbgLgrOpts),
	)

	logger := xlog.NewMultiLogger(errLgr, dbgLgr)
	defer logger.Close()

	logger.Debug("msg", "I get written to standard output")
	logger.Error("msg", "I get written to standard error")

}
Output:

{"date":"2022-03-20T16:01:20Z","lvl":"DEBUG","msg":"I get written to standard output","src":"/logger_multi_test.go:47"}

func NewMultiLogger

func NewMultiLogger(loggers ...Logger) *MultiLogger

NewMultiLogger instantiates a new multi logger object. Accepts the loggers multi-logger handles.

func (*MultiLogger) Close

func (logger *MultiLogger) Close() error

Close performs clean up actions, closes resources, avoids memory leaks, etc. Make sure to call it at your application shutdown for example.

func (*MultiLogger) Critical

func (logger *MultiLogger) Critical(keyValues ...any)

Critical logs application component unavailable, fatal events.

func (*MultiLogger) Debug

func (logger *MultiLogger) Debug(keyValues ...any)

Debug logs detailed debug information.

func (*MultiLogger) Error

func (logger *MultiLogger) Error(keyValues ...any)

Error logs runtime errors that should typically be logged and monitored.

func (*MultiLogger) Info

func (logger *MultiLogger) Info(keyValues ...any)

Info logs interesting events. Example: User logs in, SQL logs.

func (*MultiLogger) Log

func (logger *MultiLogger) Log(keyValues ...any)

Log logs arbitrarily data.

func (*MultiLogger) Warn

func (logger *MultiLogger) Warn(keyValues ...any)

Warn logs exceptional occurrences that are not errors. Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.

type NopLogger

type NopLogger struct{}

NopLogger is a no-operation Logger which does nothing. It simply ignores any log.

func (NopLogger) Close

func (NopLogger) Close() error

Close nicely closes logger.

func (NopLogger) Critical

func (NopLogger) Critical(...any)

Critical logs application component unavailable, fatal events.

func (NopLogger) Debug

func (NopLogger) Debug(...any)

Debug logs detailed debug information.

func (NopLogger) Error

func (NopLogger) Error(...any)

Error logs runtime errors that should typically be logged and monitored.

func (NopLogger) Info

func (NopLogger) Info(...any)

Info logs interesting events. Example: User logs in, SQL logs.

func (NopLogger) Log

func (NopLogger) Log(...any)

Log logs arbitrary data.

func (NopLogger) Warn

func (NopLogger) Warn(...any)

Warn logs exceptional occurrences that are not errors. Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.

type Provider

type Provider func() any

Provider is a function that returns at runtime a value.

func LocalTimeProvider

func LocalTimeProvider(format string) Provider

LocalTimeProvider is a formatted current local time provider.

func SourceProvider

func SourceProvider(skipFrames, skipPath int) Provider

SourceProvider is a file and line from call stack First param is the number of frames to skip in the call stack. Second param is number of directories to skip from file name backwards to root dir (0 means full path is returned).

func UTCTimeProvider

func UTCTimeProvider(format string) Provider

UTCTimeProvider is a formatted current UTC time provider.

type SyncLogger

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

SyncLogger is a Logger which writes logs synchronously. It just calls underlying writer with each log call. Note: if used in a concurrent context, log writes are not concurrent safe, unless the writer is concurrent safe. See also NewSyncWriter on this matter.

Example
package main

import (
	"io"
	"os"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we create a (sync)logger that writes
	// logs to standard output.

	opts := xlog.NewCommonOpts()
	opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone)
	opts.AdditionalKeyValues = []any{
		"appName", "demo",
		"env", "dev",
	}
	opts.Time = func() any { // mock time for output check
		return "2022-03-14T16:01:20Z"
	}
	opts.Source = xlog.SourceProvider(4, 1) // keep only filename for output check

	logger := xlog.NewSyncLogger(
		os.Stdout,
		xlog.SyncLoggerWithOptions(opts),
	)
	defer logger.Close()

	logger.Log(xlog.MessageKey, "Hello World", "year", 2022)
	logger.Debug(xlog.MessageKey, "Hello World", "year", 2022)
	logger.Info(xlog.MessageKey, "Hello World", "year", 2022)
	logger.Warn(xlog.MessageKey, "Hello World", "year", 2022)
	logger.Error(xlog.MessageKey, "Could not read file", xlog.ErrorKey, io.ErrUnexpectedEOF, "file", "/some/file")
	logger.Critical(xlog.MessageKey, "DB connection is down")

}
Output:

{"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","msg":"Hello World","src":"/logger_sync_test.go:42","year":2022}
{"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","lvl":"DEBUG","msg":"Hello World","src":"/logger_sync_test.go:43","year":2022}
{"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","lvl":"INFO","msg":"Hello World","src":"/logger_sync_test.go:44","year":2022}
{"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","lvl":"WARN","msg":"Hello World","src":"/logger_sync_test.go:45","year":2022}
{"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","err":"unexpected EOF","file":"/some/file","lvl":"ERROR","msg":"Could not read file","src":"/logger_sync_test.go:46"}
{"appName":"demo","date":"2022-03-14T16:01:20Z","env":"dev","lvl":"CRITICAL","msg":"DB connection is down","src":"/logger_sync_test.go:47"}
Example (DevLogger)
package main

import (
	"os"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we create a "dev", colorized levels, logger based on TextFormatter.

	opts := xlog.NewCommonOpts()
	opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone)
	opts.Time = func() any { // mock time for output check
		return "2022-03-14T16:01:20Z"
	}
	opts.Source = xlog.SourceProvider(4, 1)   // keep only filename for output check
	opts.LevelLabels = map[xlog.Level]string{ // we nicely colorize the levels
		xlog.LevelDebug:    "\033[0;34mDEBUG\033[0m",    // blue
		xlog.LevelInfo:     "\033[0;36mINFO\033[0m",     // cyan
		xlog.LevelWarning:  "\033[0;33mWARN\033[0m",     // yellow
		xlog.LevelError:    "\033[0;31mERROR\033[0m",    // red
		xlog.LevelCritical: "\033[0;31mCRITICAL\033[0m", // red
	}
	logger := xlog.NewSyncLogger(
		os.Stdout,
		xlog.SyncLoggerWithOptions(opts),
		xlog.SyncLoggerWithFormatter(xlog.TextFormatter(opts)),
	)
	defer logger.Close()

	logger.Debug(xlog.MessageKey, "Hello World", "year", 2022)

}
Output:

2022-03-14T16:01:20Z /formatter_text_test.go:41 �[0;34mDEBUG�[0m Hello World year=2022
Example (WithLogfmt)
package main

import (
	"os"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we create a SyncLogger that writes logs
	// in logfmt format.

	opts := xlog.NewCommonOpts()
	opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug)
	opts.AdditionalKeyValues = []any{
		"appName", "demo",
		"env", "dev",
	}
	opts.Time = func() any { // mock time for output check
		return "2022-04-12T16:01:20Z"
	}
	opts.Source = xlog.SourceProvider(4, 1) // keep only filename for output check
	logger := xlog.NewSyncLogger(
		os.Stdout,
		xlog.SyncLoggerWithOptions(opts),
		xlog.SyncLoggerWithFormatter(xlog.LogfmtFormatter),
	)
	defer logger.Close()

	logger.Info(xlog.MessageKey, "Hello World", "year", 2022)

}
Output:

date=2022-04-12T16:01:20Z lvl=INFO src=/formatter_logfmt_test.go:43 appName=demo env=dev msg="Hello World" year=2022
Example (WithSentry)
// In this example we create a SyncLogger that sends logs to Sentry.

err := sentry.Init(sentry.ClientOptions{
	Dsn:         "https://examplePublicKey@o0.ingest.sentry.io/0",
	Environment: "dev",
	Release:     "v1.4.0",
})
if err != nil {
	panic(err)
}
sentry.ConfigureScope(func(scope *sentry.Scope) {
	scope.SetTag("appName", "demo")
})

opts := xlog.NewCommonOpts()
opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug)
logger := xlog.NewSyncLogger(
	io.Discard,
	xlog.SyncLoggerWithOptions(opts),
	xlog.SyncLoggerWithFormatter(xlog.SentryFormatter(
		xlog.JSONFormatter,
		sentry.CurrentHub().Clone(),
		opts,
	)),
)
defer func() {
	_ = logger.Close()
	_ = sentry.Flush(2 * time.Second)
}()

logger.Info(xlog.MessageKey, "Hello World", "year", 2022)
Output:

Example (WithSyslog)
package main

import (
	"log/syslog"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we create a SyncLogger that logs to syslog.

	syslogWriter, err := syslog.Dial("", "", syslog.LOG_ERR, "demo")
	if err != nil {
		panic(err)
	}

	opts := xlog.NewCommonOpts()
	opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelDebug)
	logger := xlog.NewSyncLogger(
		syslogWriter,
		xlog.SyncLoggerWithFormatter(xlog.SyslogFormatter(
			xlog.JSONFormatter,
			xlog.NewDefaultSyslogLevelProvider(opts),
			"",
		)),
		xlog.SyncLoggerWithOptions(opts),
	)
	defer func() {
		_ = logger.Close()
		_ = syslogWriter.Close()
	}()

	logger.Info(xlog.MessageKey, "Hello World", "year", 2022)

	// sudo tail -f /var/log/syslog
	// Apr  1 03:03:16 bogdan-Aspire-5755G demo[7572]: {"date":"2022-04-01T00:03:16.209891806Z","lvl":"INFO","msg":"Hello World","src":"/home/bogdan/work/go/xlog/formatter_decorator_syslog_test.go:45","year":2022}
}
Output:

Example (WithSyslogSupportingAllSyslogLevels)
package main

import (
	"log/syslog"

	"github.com/actforgood/xlog"
)

func main() {
	// In this example we create a SyncLogger that logs to syslog,
	// supporting all syslog levels. We can log extra syslog
	// levels with Log() method. This is just an example, based on "lvl"
	// key and Log() method.

	syslogWriter, err := syslog.Dial("", "", syslog.LOG_ERR, "demo")
	if err != nil {
		panic(err)
	}

	opts := xlog.NewCommonOpts()
	opts.MinLevel = xlog.FixedLevelProvider(xlog.LevelNone) // need LevelNone to be able to use Log().
	allSyslogLevelsMap := map[any]syslog.Priority{          // we define all levels map.
		// default xlog levels/labels
		"CRITICAL": syslog.LOG_CRIT,
		"ERROR":    syslog.LOG_ERR,
		"WARN":     syslog.LOG_WARNING,
		"INFO":     syslog.LOG_INFO,
		"DEBUG":    syslog.LOG_DEBUG,
		// define extra syslog levels which will be logged with Log()
		"EMERG":  syslog.LOG_EMERG,
		"ALERT":  syslog.LOG_EMERG,
		"NOTICE": syslog.LOG_NOTICE,
	}
	logger := xlog.NewSyncLogger(
		syslogWriter,
		xlog.SyncLoggerWithFormatter(xlog.SyslogFormatter(
			xlog.JSONFormatter,
			xlog.NewExtractFromKeySyslogLevelProvider( // we set syslog level provider
				opts.LevelKey,
				allSyslogLevelsMap,
			),
			"",
		)),
		xlog.SyncLoggerWithOptions(opts),
	)
	defer func() {
		_ = logger.Close()
		_ = syslogWriter.Close()
	}()

	// log xlog levels through dedicated APIs as usual.
	logger.Info(xlog.MessageKey, "Hello World", "year", 2022)
	// log extra syslog levels through Log().
	logger.Log("lvl", "NOTICE", xlog.MessageKey, "Hello World", "year", 2022)
}
Output:

func NewSyncLogger

func NewSyncLogger(w io.Writer, opts ...SyncLoggerOption) *SyncLogger

NewSyncLogger instantiates a new logger object that writes logs synchronously. First param is a Writer where logs are written to. Example: os.Stdout, a custom opened os.File, an in memory strings.Buffer, etc. Second param is/are function option(s) through which you can customize the logger. Check for SyncLoggerWith* options.

func (*SyncLogger) Close

func (logger *SyncLogger) Close() error

Close performs clean up actions, closes resources, avoids memory leaks, etc. Make sure to call it at your application shutdown for example.

func (*SyncLogger) Critical

func (logger *SyncLogger) Critical(keyValues ...any)

Critical logs application component unavailable, fatal events.

func (*SyncLogger) Debug

func (logger *SyncLogger) Debug(keyValues ...any)

Debug logs detailed debug information.

func (*SyncLogger) Error

func (logger *SyncLogger) Error(keyValues ...any)

Error logs runtime errors that should typically be logged and monitored.

func (*SyncLogger) Info

func (logger *SyncLogger) Info(keyValues ...any)

Info logs interesting events. Example: User logs in, SQL logs.

func (*SyncLogger) Log

func (logger *SyncLogger) Log(keyValues ...any)

Log logs arbitrary data.

func (*SyncLogger) Warn

func (logger *SyncLogger) Warn(keyValues ...any)

Warn logs exceptional occurrences that are not errors. Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.

type SyncLoggerOption

type SyncLoggerOption func(*SyncLogger)

SyncLoggerOption defines optional function for configuring a sync logger.

func SyncLoggerWithFormatter

func SyncLoggerWithFormatter(formatter Formatter) SyncLoggerOption

SyncLoggerWithFormatter sets desired formatter. The JSON formatter is used by default.

func SyncLoggerWithOptions

func SyncLoggerWithOptions(opts *CommonOpts) SyncLoggerOption

SyncLoggerWithOptions sets the common options. A NewCommonOpts is used by default.

type SyslogLevelProvider

type SyslogLevelProvider func(keyValues []any) syslog.Priority

SyslogLevelProvider is a function that extracts the syslog level.

func NewDefaultSyslogLevelProvider

func NewDefaultSyslogLevelProvider(opts *CommonOpts) SyslogLevelProvider

NewDefaultSyslogLevelProvider returns a SyslogLevelProvider that maps xlog default Levels to their appropriate syslog Levels.

func NewExtractFromKeySyslogLevelProvider

func NewExtractFromKeySyslogLevelProvider(
	key string,
	syslogLevels map[any]syslog.Priority,
) SyslogLevelProvider

NewExtractFromKeySyslogLevelProvider extracts the value of given key as first param and returns the syslog level from provided map as second param.

Jump to

Keyboard shortcuts

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