ktlogging

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2023 License: Apache-2.0 Imports: 10 Imported by: 2

README

lib-logging-golang

A wrapper around (currently! can change!) the popular go.uber.org/zap logging library and on top of that brings

  • configurability from yaml/json config (Python style)
  • hierarchical logging
  • bringing fmt.Printf() style .Info("log message with %v", value) logging signature - which will be only evaluated into a string if log event is not filtered out
  • concept of "global labels" - set of key-value papirs which are always logged with every log event
  • builder style to add custom labels (zap.Fields) to particular log events

Get and install

go get github.com/keytiles/lib-logging-golang

Usage

Here is a simple example (you also find this as a running code in the example folder!)

import (
    ...
	"github.com/keytiles/lib-logging-golang"
	...
)


func main() {

	// === init the logging

	cfgErr := ktlogging.InitFromConfig("./log-config.yaml")
	if cfgErr != nil {
		panic(fmt.Sprintf("Oops! it looks configuring logging failed :-( error was: %v", cfgErr))
	}

	// === global labels

	ktlogging.SetGlobalLabels(buildGlobalLabels())

	// manipulating the GlobalLabels later is also possible
	globalLabels := ktlogging.GetGlobalLabels()
	globalLabels = append(globalLabels, ktlogging.FloatLabel("myVersion", 5.2))
	ktlogging.SetGlobalLabels(globalLabels)

	// === and now let's use the initialized logging!

	// most simple usage
	ktlogging.With("root").Info("very simple info message")

	// message constructued with parameters (will be really evaluated into a string if log event is not filtered out)
	ktlogging.With("root").Info("just an info level message - sent at %v", time.Now())

	// message with only one custom label
	ktlogging.With("root").WithLabel(ktlogging.StringLabel("myKey", "myValue")).Info("just an info level message - sent at %v", time.Now())

	// message with multiple labels
	ktlogging.With("root").WithLabels([]ktlogging.Label{ktlogging.IntLabel("myIntKey", 5), ktlogging.BoolLabel("myBoolKey", true)}).Info("just an info level message - sent at %v", time.Now())

	// and combined also works - multiple labels and one custom
	ktlogging.With("root").WithLabel(ktlogging.StringLabel("myKey", "myValue")).WithLabels([]ktlogging.Label{ktlogging.IntLabel("myIntKey", 5), ktlogging.BoolLabel("myBoolKey", true)}).Info("just an info level message - sent at %v", time.Now())

	// hierarchical logging - we only have "controller" configured (log-config.yaml) so this one will fall back in runtime
	ktlogging.With("controller.something").Info("not visible as logger level is 'warn'")
	ktlogging.With("controller.something").Warn("visible controller log")

	// get a Logger once - and then just use it in all subsequent logs
	// this way you can create package-private Logger instances e.g.
	logger := ktlogging.GetLogger("main")
	logger.Info("with logger instance")
	labels := []ktlogging.Label{ktlogging.StringLabel("key", "value")}
	logger.WithLabels(labels).Info("one more message tagged with 'key=value'")

	// check conditionally if a log event we intend to do on a certain level would be fired or not
	// this way we can omit efforts taken into assembling a log event which later would be simply just dropped anyways
	if logger.IsDebugEnabled() {
		myDebugMsg := "for example"
		myDebugMsg = myDebugMsg + " if we do stuff"
		myDebugMsg = myDebugMsg + " to compile a Debug log message"
		myDebugMsg = myDebugMsg + " this way just done if makes sense"
		logger.Debug(myDebugMsg)
	}
	// the above methods also consider if the Logger has any configured output (handler) or not
	// and return false if however the log level is good but currently the Logger does not output anywhere
	// take a look into the 'log-config.yaml'! this Logger is configured on "debug" level but no handlers attached...
	noOutputLogger := ktlogging.GetLogger("no_handler")
	if noOutputLogger.IsInfoEnabled() {
		// you will never get in here...
	} else {
		// but always here!
		fmt.Println("logger 'no_handler' IsInfoEnabled() returned FALSE")
	}
	if noOutputLogger.IsErrorEnabled() {
		// similarly, you will never get in here either
	} else {
		// but always here!
		fmt.Println("logger 'no_handler' IsErrorEnabled() returned FALSE")
	}
	noOutputLogger.Info("this message will NOT appear")

	// you can also check if a specific logger "is silent" currently either because of the log level or not having configured outputs...
	if noOutputLogger.IsSilent() {
		// you would now get in here as this logger does not have any output (handler)
		fmt.Println("logger 'no_handler' IsSilent() returned TRUE")
	}
	silentLevelLogger := ktlogging.GetLogger("silent_level")
	silentLevelLogger.Error("this message will NOT appear")
	if silentLevelLogger.IsSilent() {
		// you would now get in here too as this logger's log level is "none" at the moment
		fmt.Println("logger 'silent_level' IsSilent() returned TRUE as well")
	}
	if !ktlogging.GetLogger("main").IsSilent() {
		// you would now get in here as 'main' logger is obviously not "silent"
		fmt.Println("logger 'main' IsSilent() returned FALSE - obviously...")
	}
}

// builds and returns labels we want to add to all log events (this is just an example!!)
func buildGlobalLabels() []ktlogging.Label {
	var globalLabels = []ktlogging.Label{}
	appName := os.Getenv("CONTAINER_NAME")
	appVer := os.Getenv("CONTAINER_VERSION")
	host := os.Getenv("HOSTNAME")

	if appName == "" {
		appName = "?"
	}
	globalLabels = append(globalLabels, ktlogging.StringLabel("appName", appName))
	if appVer == "" {
		appVer = "?"
	}
	globalLabels = append(globalLabels, ktlogging.StringLabel("appVer", appVer))
	if host == "" {
		host = "?"
	}
	globalLabels = append(globalLabels, ktlogging.StringLabel("host", host))

	return globalLabels
}

Config file

We are using the Python style log config as that is simple yet effective at the same time.

Take a look into /example/log-config.yaml file!

This basically consists of two sections:

  • loggers - is a map of Logger instances you want to create.
    So each Logger is named (by the key) and you can assign a specific log level (error|warning|info|debug) and list of handlers (see below) to where this Logger will forward to each log events passed the level filtering
  • handlers - is a map of configured outputs. Each Handler is a named (by the key) entity and can represent outputting to STDOUT (console), file or other. For Handlers you can control the encoding format can be 'json' or 'console'

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func InitFromConfig

func InitFromConfig(cfgPath string) error

Initializing the logging from the .yaml or .json config file available on the given path

func SetGlobalLabels

func SetGlobalLabels(labels []Label)

you can change the GlobalLabels with this - the key-value pairs attached to all log events

Types

type ConfigModel

type ConfigModel struct {
	Loggers  map[string]LoggerConfigModel  `json:"loggers" yaml:"loggers"`
	Handlers map[string]HandlerConfigModel `json:"handlers" yaml:"handlers"`
}

for json/yaml config file parsing - this is root level object

type HandlerConfigModel

type HandlerConfigModel struct {
	Level       string   `json:"level" yaml:"level"`
	Encoding    string   `json:"encoding" yaml:"encoding"`
	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
}

for json/yaml config file parsing - this is the entries in /handlers path

type Label

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

A Label can carry a certain key-value pair. Regarding the value the atomic JSON data types are supported: string, bool, numeric values. Complex structures like array/object are not as we do not want to bring this complexity as a key-value into central persistence layers (like Elastic Search or similar). You can marshal complex structures into a JSON string before logging and just log the string representation

func BoolLabel

func BoolLabel(key string, val bool) Label

constructs a Boolean label with the given key and value.

func FloatLabel

func FloatLabel(key string, val float64) Label

constructs a Float label with the given key and value.

func GetGlobalLabels

func GetGlobalLabels() []Label

returns the current GlobalLabels - key-value pairs attached to all log events

func IntLabel

func IntLabel(key string, val int64) Label

constructs an Integer label with the given key and value.

func StringLabel

func StringLabel(key string, val string) Label

constructs a String label with the given key and value.

type LabelType

type LabelType uint8

A LabelType indicates the data type the Label carries

const (
	// UnknownType is the default Label type. Attempting to add it to an encoder will panic.
	UnknownType LabelType = iota
	// BoolType indicates that the Label carries a bool.
	BoolType
	// FloatType indicates that the Label carries a float64.
	FloatType
	// IntType indicates that the Label carries an int64.
	IntType
	// StringType indicates that the Label carries a string.
	StringType
)

type LogEvent

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

func (LogEvent) Debug

func (le LogEvent) Debug(message string, messageParams ...any)

Fires a log event on Debug level

func (LogEvent) Error

func (le LogEvent) Error(message string, messageParams ...any)

Fires a log event on Error level

func (LogEvent) Info

func (le LogEvent) Info(message string, messageParams ...any)

Fires a log event on Info level

func (LogEvent) Warn

func (le LogEvent) Warn(message string, messageParams ...any)

Fires a log event on Warning level

func (LogEvent) WithLabel

func (le LogEvent) WithLabel(label Label) LogEvent

func (LogEvent) WithLabels

func (le LogEvent) WithLabels(labels []Label) LogEvent

type LogLevel

type LogLevel uint8
const (
	NoneLevel    LogLevel = 0
	ErrorLevel   LogLevel = 1
	WarningLevel LogLevel = 2
	InfoLevel    LogLevel = 3
	DebugLevel   LogLevel = 4
)

type Logger

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

func GetLogger

func GetLogger(loggerName string) *Logger

returns a Logger with the given name - if does not exist then a new instance is created with this name and registered note: Loggers are hierarchical

func With

func With(loggerName string) *Logger

just a shortcut to the GetLogger method - for builder style readability stuff

func (*Logger) Debug

func (l *Logger) Debug(message string, messageParams ...any)

Wrapper around .Log() function - firing a log event on Debug level

func (*Logger) Error

func (l *Logger) Error(message string, messageParams ...any)

Wrapper around .Log() function - firing a log event on Error level

func (*Logger) GetHandlers

func (l *Logger) GetHandlers() map[string]*zap.Logger

returns the attached Handlers

func (*Logger) GetLevel

func (l *Logger) GetLevel() LogLevel

returns the level

func (*Logger) GetName

func (l *Logger) GetName() string

returns the name of the Logger - this can not change after instantiation

func (*Logger) Info

func (l *Logger) Info(message string, messageParams ...any)

Wrapper around .Log() function - firing a log event on Info level

func (*Logger) IsDebugEnabled added in v1.1.0

func (l *Logger) IsDebugEnabled() bool

returns TRUE if Logger would output Debug level logs due to its current configuration - FALSE otherwise

func (*Logger) IsErrorEnabled added in v1.1.0

func (l *Logger) IsErrorEnabled() bool

returns TRUE if Logger would output Error level logs due to its current configuration - FALSE otherwise

func (*Logger) IsInfoEnabled added in v1.1.0

func (l *Logger) IsInfoEnabled() bool

returns TRUE if Logger would output Info level logs due to its current configuration - FALSE otherwise

func (*Logger) IsSilent added in v1.1.0

func (l *Logger) IsSilent() bool

Returns TRUE if Logger would not output anything due to its current configuration. This is either because it's log level is None or does not have any (output) handlers at the moment

func (*Logger) IsWarningEnabled added in v1.1.0

func (l *Logger) IsWarningEnabled() bool

returns TRUE if Logger would output Warning level logs due to its current configuration - FALSE otherwise

func (*Logger) Log

func (l *Logger) Log(level LogLevel, message string, messageParams ...any)

logs the given message resolved with (optional) messageParams (Printf() style) on the given log level in case the the message is filtered out due to configured log level then the message string is not built at all

func (*Logger) Warn

func (l *Logger) Warn(message string, messageParams ...any)

Wrapper around .Log() function - firing a log event on Warning level

func (*Logger) WithLabel

func (l *Logger) WithLabel(label Label) LogEvent

Decorates the upcoming LogEvent (when you invoke .info(), .error() etc method the LogEvent is fired) with the given label. If you have multiple labels to add consider using .WithLabels() method instead. Please note: the label will be just used in the upcoming LogEvent and after that forgotten!

func (*Logger) WithLabels

func (l *Logger) WithLabels(labels []Label) LogEvent

Decorates the upcoming LogEvent (when you invoke .info(), .error() etc method the LogEvent is fired) with the given labels. Please note: the labels will be just used in the upcoming LogEvent and after that forgotten!

type LoggerConfigModel

type LoggerConfigModel struct {
	Name         string   `json:"name" yaml:"name"`
	Level        string   `json:"level" yaml:"level"`
	HandlerNames []string `json:"handlers" yaml:"handlers"`
}

for json/yaml config file parsing - this is the entries in /loggers path

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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