rlog

package module
v2.2.0 Latest Latest
Warning

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

Go to latest
Published: Feb 3, 2020 License: Apache-2.0 Imports: 15 Imported by: 9

README

rlog - A simple Golang logger with lots of features and no external dependencies

Rlog is a simple logging package, rich in features. It is configurable 'from the outside' via environment variables and/or config file and has no dependencies other than the standard Golang library.

It is called "rlog", because it was originally written for the Romana project.

This fork extends the original implementation isolating the logic of a Logger to a struct. This way, now, this fork enables you to create as many loggers as you need to (yeah, this is not so common).

Additionally, based on the sirupsen/logrus, the methods WithField and WithFields were added to provide contextualized "sub-loggers" that uses a "real logger". This strategy is very useful to append customer information in a multi-tenant logging environment, for example.

Features

  • Logging configuration of a running process can be modified, without needing to restart it. This allows for on-demand finer level logging, if a process starts to experience issues, for example.
  • Is configured through environment variables or config file: No need to call a special init function of some kind to initialize and configure the logger.
  • A new config file can be specified and applied programmatically at any time.
  • Offers familiar and easy to use log functions for the usual levels: Debug, Info, Warn, Error and Critical.
  • Offers an additional multi level logging facility with arbitrary depth, called Trace.
  • Log and trace levels can be configured separately for the individual files that make up your executable.
  • Every log function comes in a 'plain' version (to be used like Println) and in a formatted version (to be used like Printf). For example, there is Debug() and Debugf(), which takes a format string as first parameter.
  • Can be configured to print caller info (process ID, module filename and line, function name). In addition, can also print the goroutine ID in the caller info.
  • Has NO external dependencies, except things contained in the standard Go library.
  • Fully configurable date/time format.
  • Logging of date and time can be disabled (useful in case of systemd, which adds its own time stamps in its log database).
  • By default logs to stderr or stdout. A logfile can be configured via environment variable. Output may happen exclusively to the logfile or in addition to the output on stderr/stdout. Also, a different output stream or file can be specified from within your programs at any time.

Defaults

Rlog comes with reasonable defaults, so you can just start using it without any configuration at all. By default:

  • Log level set to INFO.
  • Trace messages are not logged.
  • Time stamps are logged with each message.
  • No caller information.
  • Output is sent to stderr.

All those defaults can easily be changed through environment variables or the config file.

Controlling rlog through environment or config file variables

Rlog is configured via the following settings, which may either be defined as environment variables or via a config file.

  • RLOG_LOG_LEVEL: Set to "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" or "NONE". Any message of a level >= than what's configured will be printed. If this is not defined it will default to "INFO". If it is set to "NONE" then all logging is disabled, except Trace logs, which are controlled via a separate variable. In addition, log levels can be set for individual files (see below for more information). Default: INFO - meaning that INFO and higher is logged.
  • RLOG_TRACE_LEVEL: "Trace" log messages take an additional numeric level as first parameter. The user can specify an arbitrary number of levels. Set RLOG_TRACE_LEVEL to a number. All Trace messages with a level <= RLOG_TRACE_LEVEL will be printed. If this variable is undefined, or set to -1 then no Trace messages are printed. The idea is that the higher the RLOG_TRACE_LEVEL value, the more 'chatty' and verbose the Trace message output becomes. In addition, trace levels can be set for individual files (see below for more information). Default: Not set - meaning that no trace messages are logged.
  • RLOG_CALLER_INFO: If this variable is set to "1", "yes" or something else that evaluates to 'true' then the message also contains the caller information, consisting of the process ID, file and line number as well as function name from which the log message was called. Default: No - meaning that no caller info is logged.
  • RLOG_GOROUTINE_ID: If this variable is set to "1", "yes" or something else that evaluates to 'true' AND the printing of caller info is requested, then the caller info contains the goroutine ID, separated from the process ID by a ':'. Note that calculation of the goroutine ID has a performance impact, so please only enable this option if needed.
  • RLOG_TIME_FORMAT: Use this variable to customize the date/time format. The format is specified either by the well known formats listed in https://golang.org/src/time/format.go, for example "UnixDate" or "RFC3339". Or as an example date/time output, which is described here: https://golang.org/pkg/time/#Time.Format Default: Not set - formatted according to RFC3339.
  • RLOG_LOG_NOTIME: If this variable is set to "1", "yes" or something else that evaluates to 'true' then no date/time stamp is logged with each log message. This is useful in environments that use systemd where access to the logs via their logging tools already gives you time stamps. Default: No - meaning that time/date is logged.
  • RLOG_LOG_FILE: Provide a filename here to determine if the logfile should be written to a file, in addition to the output stream specified in RLOG_LOG_STREAM. Default: Not set - meaning that output is not written to a file.
  • RLOG_LOG_STREAM: Use this to direct the log output to a different output stream, instead of stderr. This accepts three values: "stderr", "stdout" or "none". If either stderr or stdout is defined here AND a logfile is specified via RLOG_LOG_FILE then the output is sent to both. Default: Not set - meaning the output goes to stderr.

There are two more settings, related to the configuration file, which can only be set via environment variables.

  • RLOG_CONF_FILE: If this variable is set then rlog looks for the config file at the specified location, which needs to be the path of the file. If this variable is not defined, then rlog will look for the config file in /etc/rlog/<your-executable-name>.conf. Therefore, by default every executable has its own config file. By setting this variable, you could force multiple processes to share the same config file. Note that with the SetConfFile() function you can specify a new config file programmatically at any time, even with a relative path.
  • RLOG_CONF_CHECK_INTERVAL: Number of seconds between checking whether the config file has changed. By default, this is set to 15 seconds. This means that within 15 seconds a changed logging configuration in the config file will take effect. Note that this check is only performed when a log message is actually written. If the program does nothing or doesn't log messages, the config file won't be read. If there is no config file or it has been removed then the configuration from the environment variables is used. Set this value to 0 in order to switch off the regular config file checking: The config file will then only be read once at the start.

Please note! If these environment variables have incorrect or misspelled values then they will be silently ignored and a default value will be used.

Using the config file

A config file for rlog is entirely optional, since rlog works just fine even without it. However, it does provide you with a very neat feature: You can change the logging configuration of a running program from the outside and without having to restart it!

When rlog is imported it starts out with the defaults described above. It then takes an initial configuration from environment variables, which may override the default values. Next, it looks for the rlog config file. If it cannot find the config file it will quietly continue without error. If the config file is found then the configuration from environment variables is combined with the configuration from the config file. More about how this combination works, and what takes precedence, in a moment.

Config file location

The path for the config file can be set via the RLOG_CONF_FILE environment variable. Absent that, rlog looks for a config file in /etc/rlog/<your-executable-name>.conf. This means that you can easily provide different logging configurations for each of your processes.

A new config file location can also be specified at any time via the SetConfFile() function. An absolute or relative path may be specfied with that function.

Config file format

The format of the config file is simple. Each setting is referred to by the same name as the environment variable. So, your config file may look like this:

# Comment lines start with a '#'
RLOG_LOG_LEVEL   = WARN
RLOG_LOG_STREAM  = stdout
RLOG_TIME_FORMAT = UnixDate
RLOG_LOG_FILE    = /var/log/myapp.log

A few notes about config file formatting:

  • Empty lines, or lines starting with '#' are ignored.
  • Leading and trailing spaces in lines are removed.
  • Everything after the first '=' will be taken as the value of the setting.
  • Leading and trailing spaces in values are removed.
  • Spaces or further '=' characters within values are taken as they are.
Combining configuration from environment variables and config file

Generally, environment variables take precedence. Assume you have set a log level of INFO via the RLOG_LOG_LEVEL variable. This value will be used, even if you specified DEBUG in the config file, since an explicitly set environment variable takes precedence.

There are only two cases when a config file value takes precedence:

  1. If you do not have an explicit value set in the environment variable. For example, if you do not have the RLOG_LOG_LEVEL environment variable defined at all, or if it is set to the empty string.
  2. If you apply a '!' as prefix in the config file. That marks this value as higher priority than the environment variable. Consider the following config file as example. Here RLOG_LOG_LEVEL and RLOG_TIME_FORMAT will take precedence over whatever was defined in the environment variables.

An example of using '!' in the config file:

!RLOG_LOG_LEVEL=WARN
RLOG_LOG_STREAM=stdout
!RLOG_TIME_FORMAT=UnixDate
RLOG_LOG_FILE=/var/log/myapp.log

Updating the logging config of a running program

From the outside: By modifying the config file

Every time you log a message and at least RLOG_CONF_CHECK_INTERVAL seconds have elapsed since the last reading of the config file, rlog will automatically re-read the content of the conf file and re-apply the configuration it finds there over the initial configuration, which was based on the environment variables.

You can always just delete the config file to go back to the configuration based solely on environment variables.

From the inside: By modifying your own environment variables

A running program may also change its rlog configuration on its own: The process can use the os.Setenv() function to modify its own environment variables and then call rlog.UpdatEnv() to reapply the settings from the environment variables. The examples/example.go file shows how this is done. But in short:

// Programmatically change an rlog setting from within the program
os.Setenv("RLOG_LOG_LEVEL", "DEBUG")
rlog.UpdateEnv()

Note that this will not change rlog behaviour if the value for this config setting was specified with a '!' in the config file.

Per file level log and trace levels

In most cases you might want to set just a single log or trace level, which is then applied to all log messages in your program. With environment variables, you would set it like this:

export RLOG_LOG_LEVEL=INFO
export RLOG_TRACE_LEVEL=3

However, with rlog the log and trace levels can not only be configured 'globally' with a single value, but can also independently be set for the individual module files that were compiled into your executable. This is useful if enabling high trace levels or DEBUG logging for the entire executable would fill up logs or consume too many resources.

For example, if your executable is compiled out of several files and one of those files is called 'example.go' then you could set log levels like this:

export RLOG_LOG_LEVEL=INFO,example.go=DEBUG

This sets the global log level to INFO, but for the messages originating from the module file 'example.go' it is DEBUG.

Similarly, you can set trace levels for individual module files:

export RLOG_TRACE_LEVEL=example.go=5,2

This sets a trace level of 5 for example.go and 2 for everyone else.

More examples:

# DEBUG level for all files whose name starts with 'ex', WARNING level for
# everyone else.
export RLOG_LOG_LEVEL=WARN,ex*=DEBUG

# DEBUG level for example.go, INFO for everyone else, since INFO is the
# default level if nothing is specified.
export RLOG_LOG_LEVEL=example.go=DEBUG

# DEBUG level for example.go, no logging for anyone else.
export RLOG_LOG_LEVEL=NONE,example.go=DEBUG

# Multiple files' levels can be specified at once.
export RLOG_LOG_LEVEL=NONE,example.go=DEBUG,foo.go=INFO

# The default log level can appear anywhere in the list.
export RLOG_LOG_LEVEL=example.go=DEBUG,INFO,foo.go=WARN

Note that as before, if in RLOG_LOG_LEVEL no global log level is specified then INFO is assumed to be the global log level. If in RLOG_TRACE_LEVEL no global trace level is specified then -1 (no trace output) is assumed as the global trace level.

Usage example

import "github.com/lab259/rlog/v2"

func main() {

rlog.Debug("A debug message: For the developer") rlog.Info("An info message: Normal operation messages") rlog.Warn("A warning message: Intermittent issues, high load, etc.") rlog.Error("An error message: An error occurred, I will recover.") rlog.Critical("A critical message: That's it! I give up!") rlog.Trace(2, "A trace message") rlog.Trace(3, "An even deeper trace message") }

For a more interesting example, please check out 'examples/example.go'.

Sample output

With time stamp, trace to level 2, log level WARNING, no caller info:

$ export RLOG_LOG_LEVEL=WARN
$ export RLOG_TRACE_LEVEL=2
$ go run examples/example.go

2017-11-16T08:06:56+13:00 WARN     : Warning level log message
2017-11-16T08:06:56+13:00 ERROR    : Error level log message
2017-11-16T08:06:56+13:00 CRITICAL : Critical level log message
2017-11-16T08:06:56+13:00 DEBUG    : You can see this message, because we changed level to DEBUG.
2017-11-16T08:06:56+13:00 TRACE(1) : Trace messages have their own numeric levels
2017-11-16T08:06:56+13:00 TRACE(1) : To see them set RLOG_TRACE_LEVEL to the cut-off number
2017-11-16T08:06:56+13:00 TRACE(1) : We're 1 levels down now...
2017-11-16T08:06:56+13:00 TRACE(2) : We're 2 levels down now...
2017-11-16T08:06:56+13:00 INFO     : Reached end of recursion at level 10
2017-11-16T08:06:56+13:00 INFO     : About to change log output. Check /tmp/rlog-output.log...
2017-11-16T08:06:56+13:00 INFO     : Back to stderr

With time stamp, log level WARN, no trace logging (switched off by unsetting the variable), but with caller info ('23730' in the example below is the process ID):

$ export RLOG_CALLER_INFO=yes
$ export RLOG_LOG_LEVEL=WARN
$ export RLOG_TRACE_LEVEL=
$ go run examples/example.go

2017-11-16T08:07:57+13:00 WARN     : [21233 examples/example.go:31 (main.main)] Warning level log message
2017-11-16T08:07:57+13:00 ERROR    : [21233 examples/example.go:32 (main.main)] Error level log message
2017-11-16T08:07:57+13:00 CRITICAL : [21233 examples/example.go:33 (main.main)] Critical level log message
2017-11-16T08:07:57+13:00 DEBUG    : [21233 examples/example.go:42 (main.main)] You can see this message, because we changed level to DEBUG.
2017-11-16T08:07:57+13:00 INFO     : [21233 examples/example.go:17 (main.someRecursiveFunction)] Reached end of recursion at level 10
2017-11-16T08:07:57+13:00 INFO     : [21233 examples/example.go:52 (main.main)] About to change log output. Check /tmp/rlog-output.log...
2017-11-16T08:07:57+13:00 INFO     : [21233 examples/example.go:61 (main.main)] Back to stderr

Without time stamp, no trace logging, no caller info:

$ export RLOG_LOG_NOTIME=yes
$ export RLOG_CALLER_INFO=no
$ go run examples/example.go

WARN     : Warning level log message
ERROR    : Error level log message
CRITICAL : Critical level log message
DEBUG    : You can see this message, because we changed level to DEBUG.
INFO     : Reached end of recursion at level 10
INFO     : About to change log output. Check /tmp/rlog-output.log...
INFO     : Back to stderr

With time stamp in RFC822 format.

$ export RLOG_LOG_NOTIME=no
$ export RLOG_TIME_FORMAT=RFC822
$ go run examples/example.go

2017-11-16T08:08:49+13:00 WARN     : Warning level log message
2017-11-16T08:08:49+13:00 ERROR    : Error level log message
2017-11-16T08:08:49+13:00 CRITICAL : Critical level log message
2017-11-16T08:08:49+13:00 DEBUG    : You can see this message, because we changed level to DEBUG.
2017-11-16T08:08:49+13:00 INFO     : Reached end of recursion at level 10
2017-11-16T08:08:49+13:00 INFO     : About to change log output. Check /tmp/rlog-output.log...
2017-11-16T08:08:49+13:00 INFO     : Back to stderr

With custom time stamp:

$ export RLOG_TIME_FORMAT="2006/01/06 15:04:05"
$ go run examples/example.go

2017-11-16T08:09:08+13:00 WARN     : Warning level log message
2017-11-16T08:09:08+13:00 ERROR    : Error level log message
2017-11-16T08:09:08+13:00 CRITICAL : Critical level log message
2017-11-16T08:09:08+13:00 DEBUG    : You can see this message, because we changed level to DEBUG.
2017-11-16T08:09:08+13:00 INFO     : Reached end of recursion at level 10
2017-11-16T08:09:08+13:00 INFO     : About to change log output. Check /tmp/rlog-output.log...
2017-11-16T08:09:08+13:00 INFO     : Back to stderr

Usage with SubLoggers

import (
	"github.com/lab259/rlog/v2"
	"math/rand"
	"sync"
	"time"
)

func main() {
	rlog.Info("Starting system")
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func(logger rlog.Logger) {
			defer wg.Done()
			ms := rand.Intn(50)
			logger.Infof("Going to sleep for %dms", ms)
			time.Sleep(time.Millisecond * time.Duration(ms))
			logger.Info("Exiting ...")
		}(rlog.WithFields(rlog.Fields{
			"i": i,
		}))
	}
	rlog.Info("Waiting routines ...")
	wg.Wait()
	rlog.Info("OK!")
}

This example will start 100 goroutines with a random sleep in it. Each, goroutine receives a rlog.Logger with the i in its context.

The output of this example looks like:

date="2019-01-03T01:03:04Z" level="INFO" msg="Starting system"
date="2019-01-03T01:03:04Z" level="INFO" i="3" msg="Going to sleep for 31ms"
date="2019-01-03T01:03:04Z" level="INFO" i="1" msg="Going to sleep for 37ms"
...
date="2019-01-03T01:03:04Z" level="INFO" i="82" msg="Going to sleep for 23ms"
date="2019-01-03T01:03:04Z" level="INFO" i="89" msg="Going to sleep for 3ms"
rlog.Info("Waiting routines ...")
date="2019-01-03T01:03:04Z" level="INFO" i="60" msg="Exiting ..."
date="2019-01-03T01:03:04Z" level="INFO" i="86" msg="Exiting ..."
date="2019-01-03T01:03:04Z" level="INFO" i="69" msg="Exiting ..."
...
date="2019-01-03T01:03:05Z" level="INFO" i="54" msg="Exiting ..."
date="2019-01-03T01:03:05Z" level="INFO" msg="OK!"

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultLogger *logger

Functions

func AcquireOutput

func AcquireOutput() []byte

func Critical

func Critical(a ...interface{})

Critical prints a message if RLOG_LEVEL is set to CRITICAL or lower.

func Criticalf

func Criticalf(format string, a ...interface{})

Criticalf prints a message if RLOG_LEVEL is set to CRITICAL or lower, with formatting.

func Debug

func Debug(a ...interface{})

Debug prints a message if RLOG_LEVEL is set to DEBUG.

func Debugf

func Debugf(format string, a ...interface{})

Debugf prints a message if RLOG_LEVEL is set to DEBUG, with formatting.

func Error

func Error(a ...interface{})

Error prints a message if RLOG_LEVEL is set to ERROR or lower.

func Errorf

func Errorf(format string, a ...interface{})

Errorf prints a message if RLOG_LEVEL is set to ERROR or lower, with formatting.

func Info

func Info(a ...interface{})

Info prints a message if RLOG_LEVEL is set to INFO or lower.

func Infof

func Infof(format string, a ...interface{})

Infof prints a message if RLOG_LEVEL is set to INFO or lower, with formatting.

func NewDefaultFormatter

func NewDefaultFormatter(file *os.File) *defaultFormatter

func NewLogger

func NewLogger(config Config) (*logger, error)

NewLogger initializes a new instance loading all configuration set in the `config` argument. Then, the new instance is returned ready for use.

func Printf

func Printf(format string, a ...interface{})

Printf prints a message if RLOG_LEVEL is set to INFO or lower, with formatting. Printf shouldn't be used except for backward compatibility with standard log package, directly using Infof is preferred way.

func Println

func Println(a ...interface{})

Println prints a message if RLOG_LEVEL is set to INFO or lower. Println shouldn't be used except for backward compatibility with standard log package, directly using Info is preferred way.

func ReleaseOutput

func ReleaseOutput(data []byte)

func SetOutput

func SetOutput(writer io.Writer)

SetOutput re-wires the log output to a new io.Writer. By default rlog logs to os.Stderr, but this function can be used to direct the output somewhere else. If output to two destinations was specified via environment variables then this will change it back to just one output.

func Trace

func Trace(traceLevel int, a ...interface{})

func Tracef

func Tracef(traceLevel int, format string, a ...interface{})

Tracef prints trace messages, with formatting.

func Warn

func Warn(a ...interface{})

Warn prints a message if RLOG_LEVEL is set to WARN or lower.

func Warnf

func Warnf(format string, a ...interface{})

Warnf prints a message if RLOG_LEVEL is set to WARN or lower, with formatting.

Types

type Color

type Color func(a ...interface{}) string

type Config

type Config struct {
	// Formatter will adjust the output according with the selection.
	//
	// For more information, please refer to the `Formatter` interface.
	Formatter string
	// What log level. String, since filters are allowed
	LogLevel string
	// What trace level. String, since filters are allowed
	TraceLevel string

	// Name of logfile
	LogFile string

	// Name of logstream: stdout, stderr or NONE
	LogStream string
	// Flag to determine if date/time is logged at all
	LogNoTime bool
	// CallerInfo is a flag to determine if caller info is logged
	ShowCallerInfo bool
	// Flag to determine if goroute ID shows in caller info
	ShowGoroutineID bool
	// contains filtered or unexported fields
}

rlogConfig captures the entire configuration of rlog, as supplied by a user via environment variables and/or config files. This still requires checking and translation into more easily used config items. All values therefore are stored as simple strings here.

func (*Config) LoadFromEnv

func (config *Config) LoadFromEnv(prefix string)

LoadFromEnv loads the configuration from the env variables.

If the prefix is empty, it uses the RLOG prefix.

func (*Config) LoadFromFile

func (config *Config) LoadFromFile(fileName string) error

LoadFromFile load the configuration from a file.

type Entry

type Entry struct {
	Time        string
	CallerInfo  EntryCallerInfo
	Level       Level
	TraceLevel  int
	FieldsCache string
	Fields      FieldsArr
	Message     string
}

func (*Entry) Reset

func (entry *Entry) Reset()

type EntryCallerInfo

type EntryCallerInfo struct {
	PID          int
	GID          uint64
	FileName     string
	Line         int
	FunctionName string
}

type Fields

type Fields map[string]interface{}

type FieldsArr

type FieldsArr []interface{}

type Level

type Level int

func (Level) Bytes

func (level Level) Bytes() []byte

func (Level) String

func (level Level) String() string

type LogFormatter

type LogFormatter interface {
	FormatField(key string, data interface{}) string
	FormatFields(FieldsArr) string
	Separator() string
	Format(entry *Entry) []byte
}

type Logger

type Logger interface {
	WithPrefix(prefix string) Logger
	WithField(name string, value interface{}) Logger
	WithFields(fields Fields) Logger
	WithFieldsArr(fields ...interface{}) Logger
	Formatter() LogFormatter
	BasicLog(logLevel Level, traceLevel int, additionalInformation string, fields FieldsArr, format string, a ...interface{})
	Trace(level int, a ...interface{})
	Tracef(level int, format string, a ...interface{})
	Debug(a ...interface{})
	Debugf(format string, a ...interface{})
	Info(a ...interface{})
	Infof(format string, a ...interface{})
	Println(a ...interface{})
	Printf(format string, a ...interface{})
	Warn(a ...interface{})
	Warnf(format string, a ...interface{})
	Error(a ...interface{})
	Errorf(format string, a ...interface{})
	Critical(a ...interface{})
	Criticalf(format string, a ...interface{})
}

Logger is the interface that represents a logging unit.

func WithField

func WithField(name string, value interface{}) Logger

WithField returns a new sublogger with the new field in the context.

func WithFields

func WithFields(fields Fields) Logger

WithFields returns a new sublogger with the new fields in the context.

type TextFormatter

type TextFormatter struct{}

func (*TextFormatter) Format

func (formatter *TextFormatter) Format(entry *Entry) []byte

func (*TextFormatter) FormatField

func (formatter *TextFormatter) FormatField(key string, data interface{}) string

func (*TextFormatter) FormatFields

func (formatter *TextFormatter) FormatFields(fields FieldsArr) string

func (*TextFormatter) Separator

func (formatter *TextFormatter) Separator() string

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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