Categorygithub.com/daihasso/slogging
modulepackage
1.1.0
Repository: https://github.com/daihasso/slogging.git
Documentation: pkg.go.dev

# README

Slogging

A simple logging framework for go that strives to be familiar while still being powerful.

Table Of Contents

Basic Usage

Using the slogging package directly you can just call Info, Debug, Warn, or Error to use the singleton root logger. Like so:

package main

import (
    "github.com/daihasso/slogging"
)

func main() {
    logging.Info("Hello world!")
}

This would result in something like following log:

{"log_level":"INFO","message":"Hello world!","timestamp":1552169765}

The default format for the root logger is JSON. This is chosen with the idea of deployments to production in mind. You may want to change this to something a little more readable for local testing. You can that like so:

package main

import (
    "github.com/daihasso/slogging"
)

func main() {
    logging.GetRootLogger().SetFormat(logging.Standard)
    logging.Info("Hello world!")
}

This would result in the much more straightforward log-line:

2019-03-09T14:59:50 INFO Hello world!

* NOTE: Changing the root logger directly can be dangerous if you're using multiple logger instances as new loggers are based on the root logger. Keep this in mind when changing the root logger.

Creating a new logger

If you want a specialized instance of a Logger you can get one like so:

package main

import (
    "os"
    "strings"

    "github.com/daihasso/slogging"
)

func main() {
    myBuf := new(strings.Builder)
    newLogger := logging.NewLogger(
        "MyCustomLogger",
        logging.WithFormat(logging.StandardExtended),
        logging.WithWriters(os.Stdout, myBuf),
        logging.WithLogLevel(logging.ERROR),
    )
    
    newLogger.Error("Just kidding, no error!")
}

This would result in something like the following being outputted to stdout and to myBuf:

timestamp           | log_level | message
2019-03-09T14:59:50 | ERROR     | Just kidding, no error!

Retrieving Loggers By Identifier

Every logger has an identifier (accessable via logger.Identifier()) which is entered into a global registry in the slogging framework. This means if you want to retrieve a given logger somewhere else in your code.

package main

import (
    "os"
    "strings"

    "github.com/daihasso/slogging"
)

func main() {
    myLogger, ok := logging.GetLogger(MyLogger)
    if myLogger == nil {
        panic("MyLogger didn't exist!!!")
    }
    
    myLogger.Info(
}

func init() {
    logging.NewLogger(
        "MyLogger",
        logging.WithFormat(logging.Standard),
        logging.WithLogLevel(logging.DEBUG),
    )
}

Logging Extras

Sometimes you don't just want to log a message, you also want to log some extra data. With slogging, that's relatively straightforward:

package main

import (
    "os"
    "strings"

    "github.com/daihasso/slogging"
)

func main() {
    logging.GetRootLogger().SetFormat(logging.Standard)
    logging.Info("Started app.", logging.Extras{
        "app_name": "Logging Test App"
    })
}

This would result in a log like the following:

2019-03-09T14:59:50 INFO app_name="Logging Test App" Started app.

* NOTE: Generally the provided keys are used as-is however; for Standard format variants \r, \n and whitespace is removed and replaced with _.

Your value for your extras doesn't even have to be a string! The provided value will be marshalled into the log format (For Standard variants fmt.Sprint is used to format the value).

package main

import (
    "os"
    "strings"

    "github.com/daihasso/slogging"
)

type MyStruct struct {
    Name string
}

func main() {
    logging.GetRootLogger().SetFormat(logging.Standard)
    
    myStruct := MyStruct{
        Name: "Structicus",
    }
    logging.Info("Started app.", logging.Extras{
        "app_name": "Logging Test App",
        "my_struct": myStruct,
    })
}
2019-03-09T15:42:20 INFO app_name="Logging Test App" test="{Structicus}" Started app.

Default Extras

Sometimes you want all logs for a logger to have a set of default Extras that they log along with your message. This is where default extras come in.

package main

import (
    "os"
    "strings"

    "github.com/daihasso/slogging"
)

func main() {
    newLogger := logging.NewLogger(
        "MyLoggerWithDefaultExtras",
        logging.WithFormat(logging.Standard),
        logging.WithDefaultExtras(
            StaticExtras(Extras{
                "app_name": "MyApp",
            }),
        ),
    )
    newLogger.Info("Started app.")
}

This would result in the following log line.

2019-03-09T15:52:13 INFO app_name="MyApp" Started app.

Global Default Extras

Default Extras can also be set on a global scale--meaning every single logger will evaluate these before logging. This can be done like so:

package main

import (
    "os"
    "strings"

    "github.com/daihasso/slogging"
)

func main() {
    logging.AddGlobalExtras(
        StaticExtras(Extras{
            "app_name": "MyGlobalApp",
        }),
    ),
    newLogger := logging.NewLogger(
        "MyLogger",
        logging.WithFormat(logging.Standard),
    )
    newLogger.Info("Started app.")
}

This would result in the following log line.

2019-03-09T15:52:13 INFO app_name="MyGlobalApp" Started app.

Advanced Usage

There are actually two types of default Extras; StaticExtras and FunctionalExtras. The former takes extras in the format you would normally for a specific log line as discussed above and the latter takes in a function that is evaluated at log-time. Let's see FunctionlExtras in action.

package main

import (
    "os"
    "strings"

    "github.com/daihasso/slogging"
)

func main() {
    logCount := 0
    newLogger := logging.NewLogger(
        "MyLoggerWithSharedExtras",
        logging.WithFormat(logging.Standard),
        logging.WithDefaultExtras(
            Functional(ExtrasFuncs{
                "total_log_statements": func() (interface{}, error) {
                    logCount++
                    return logCount, nil
                },
            }),
        ),
    )
    newLogger.Info("Started app.")
    newLogger.Info("Quitting app.")
}

This would result in the following logs:

2019-03-09T15:55:24 INFO total_log_statements="1" Started app.
2019-03-09T15:55:25 INFO total_log_statements="2" Quitting app.

It's really quite powerful when used properly.

Logging formats

Three formats are currently supported:

  • JSON
  • Standard
  • Standard Extended

JSON Example

{"log_level":"INFO","message":"Hello world!","extra_key":"extra_value","timestamp":1552172390}

Standard Example

2019-03-09T14:59:50 INFO extra_key="extra_value" Hello world!

Standard Extended Example

timestamp           | log_level | extra_key   | message
2019-03-09T14:59:50 | INFO      | extra_value | Hello World!

Compatibility

You may find yourself needing to provide a Logger to a library that expects a io.Writer or a *log.Logger. For this slogging provides the PseudoWriter.

package main

import (
    "os"
    "strings"

    "github.com/daihasso/slogging"
)

func main() {
    pseudoWriter := logging.NewPseudoWriter(
        logging.ERROR, logging.GetRootLogger(),
    )

    server := http.Server{
        Addr: "localhost:8080",
        ErrorLog: log.New(pseudoWriter, "", 0),
    }
    
    server.ListenAndServe()
}

The PseudoWriter is an ultra simple wrapper which simply wraps your logger and logs to the provided LogLevel when Write is called on it.

# Functions

AddGlobalExtras appends the provided extras to the global extras.
CloneLogger creates a new logger basing it off the provided logger.
Debug uses the root logger to log to debug level.
Error uses the root logger to log to error level.
Exception uses the root logger to log an error at error level.
Extra is a convinience function for creating an Extras map it has some overhead of generating several extras that will have to be combined but it may be prefereable at times.
FormatFromString takes a string and returns a format for it.
FunctionalExtras adds all items in a key, value (function) map that will append: key: value() to the log for each item in the map.
GetGlobalExtras returns the global extras.
GetLogger get an existing logger by its identifier.
GetLogLevelsForString will get the appropriate loglevels for a string log level representation.
GetRootLogger gets the root logger.
Info uses the root logger to log to info level.
No description provided by the author
NewLogger creates a new logger basing it off the default/root logger.
NewPsuedoWriter wraps a logger with the Write functionality wich writes out logs at a specified log level.
SetGlobalExtras sets the global extras.
SetRootLogger will use the provided identifier as the default logger for future log calls.
SetRootLoggerExisting will use the provided identifier as the default logger for future log calls.
StaticExtras adds all items in a key, value (Extras) map that will append: key: value to the log for each item in the map.
Warn uses the root logger to log to warn level.
WithDefaultExtras provides one or many Extras that will be logged for every log statement for this Logger.
WithFormat sets the new Logger's format to the provided format.
WithLogLevel sets this Logger's log level to the provided LogLevel.
WithLogWriters sets the provided Logger's writers to the provided writers.

# Constants

Definition of LogLevels for a logger.
Definition of LogLevels for a logger.
Definition of LogLevels for a logger.
Definition of all the available LogFormats known by this framework.
Definition of all the available LogFormats known by this framework.
Definition of all the available LogFormats known by this framework.
Definition of all the available LogFormats known by this framework.
Definition of all the available LogFormats known by this framework.
Definition of LogLevels for a logger.
Definition of LogLevels for a logger.

# Variables

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Structs

Logger is a logger instance that provides a unified interface for logging data.
PseudoWriter is a wrapper for JSONLogger for things that need a writer to output.

# Type aliases

No description provided by the author
No description provided by the author
No description provided by the author
LogFormat is a representation of what format a log should output.
LoggerOption is an option used when creating a new Logger.
LogLevel is a representation of the logging level for a logger.go:generate stringer -type=LogLevel.
No description provided by the author