Categorygithub.com/j-keck/plog
repositorypackage
0.7.0
Repository: https://github.com/j-keck/plog.git
Documentation: pkg.go.dev

# Packages

No description provided by the author

# README

#+TITLE: plog (pico log) - golang logging library #+PROPERTY: header-args :eval never-export

[[https://pkg.go.dev/github.com/j-keck/plog][file:https://godoc.org/github.com/j-keck/plog?status.svg]] [[https://github.com/j-keck/plog/actions][file:https://github.com/j-keck/plog/workflows/test/badge.svg]]

  • Intro

pico log - because it's so small and has zero dependencies.

** Why?

I want a logging library with

  • zero dependencies
  • easy to use
  • can emit log messages per go channel

** Non Goals

  • blazing fast
  • a lot of features

** Features

  • console logging

  • log over go channel

  • optional global logger instance (see [[#create-a-global-logger-instance][Create a global logger instance]])

  • log output format configurable \ (see [[#custom-log-format][Custom log format]], [[#log-output-format-per-program-arguments][Log output format per program arguments]] for examples)

  • helper functions to set log level per command line arguments \ (see [[#set-loglevel-per-program-arguments][Set LogLevel per program arguments]] for examples)

  • Usage

/After each code block you will see an output example./

** Create a console logger instance

You create a console logger per plog.NewDefaultConsoleLogger or plog.NewConsoleLogger.

*** plog.NewDefaultConsoleLogger()

This function creates a console logger where each log column are separated with " | " and three log columns: the timestamp of the log event, the log level for the message and the log message.

#+BEGIN_SRC go -r :tangle examples/console.go :exports both package main

import "github.com/j-keck/plog"

func main() { log := plog.NewDefaultConsoleLogger()

  log.Info("startup")
  log.Debug("change to debug level")
  log.SetLevel(plog.Debug)
  log.Debug("level changed")
  log.Infof("2 + 2 = %d", 2 + 2)

} #+END_SRC

#+RESULTS: : Wed Feb 19 09:34:16 CET 2020 | INFO | startup : Wed Feb 19 09:34:16 CET 2020 | DEBUG | level changed : Wed Feb 19 09:34:16 CET 2020 | INFO | 2 + 2 = 4

*** plog.NewConsoleLogger(separator string, formatters ...plog.Formatter)

This function creates a logger with a custom log output format.

The first argument is a separator with separates each log column, the rest are "column formatters". These describes which columns and how this columns are formatted (see [[#formatters][Formatters]] for more information).

#+BEGIN_SRC go -r :tangle examples/console-custom-format.go :exports both package main

import  "github.com/j-keck/plog"

func main() {
    log := plog.NewConsoleLogger(
        " - ",
        plog.Level,
        plog.TimestampMillis,
        plog.Message,
    )

    // set log prefix and suffix
    log.SetLogPrefix("[").SetLogSuffix("]")

    log.Info("startup")
    log.Debug("change to debug level")
    log.SetLevel(plog.Debug)
    log.Debug("level changed")
    log.Infof("2 + 2 = %d", 2 + 2)
}

#+END_SRC

#+RESULTS: : [ INFO - Feb 19 09:34:17.009 - startup] : [DEBUG - Feb 19 09:34:17.010 - level changed] : [ INFO - Feb 19 09:34:17.010 - 2 + 2 = 4]

** Create a global logger instance

You create a singleton global logger instance per plog.GlobalLogger. So you can configure the log output only once, and every other plog.GlobalLogger instance have the same logging configuration.

When you create a global logger, you must attach a console or stream logger to it. If not, a warning is logged (this can be disabled with plog.DropUnhandledMessages).

plog.GlobalLogger is a broadcast logger, so you can add many consumer.

#+BEGIN_SRC go :tangle examples/global-logger.go :eval no package main

import "github.com/j-keck/plog"

func main() { // initialize the logger log := plog.GlobalLogger().Add(plog.NewDefaultConsoleLogger()) log.Info("in 'main'")

other() }

func other() { log := plog.GlobalLogger() log.Info("in 'other'") } #+END_SRC

#+BEGIN_SRC shell :results output :exports both go run examples/global-logger.go #+END_SRC

#+RESULTS: : Wed Feb 19 09:34:17 CET 2020 | INFO | in 'main' : Wed Feb 19 09:34:17 CET 2020 | INFO | in 'other'

  • API

Each logger instance have the following functions:

| SetLevel(LogLevel) | Set the log level. From plog.Trace to plog.Error | | IsTraceEnabled() | Checks if the Trace level is enabled | | IsDebugEnabled() | Checks if the Debug level is enabled | | IsInfoEnabled() | Checks if the Info level is enabled | | IsNoteEnabled() | Checks if the Note level is enabled | | IsWarnEnabled() | Checks if the Warn level is enabled | | IsErrorEnabled() | Checks if the Error level is enabled | | Trace(string) / Tracef(string, ...{}interface) | Trace logging | | Debug(string) / Debugf(string, ...{}interface) | Debug logging | | Info(string) / Infof(string, ...{}interface) | Info logging | | Note(string) / Notef(string, ...{}interface) | Note (Notifications) logging | | Warn(string) / Warnf(string, ...{}interface) | Warn logging | | Error(string) / Errorf(string, ...{}interface) | Error logging | | Fatal(string) / Fatalf(string, ...{}interface) | Fatal logging |

Where the log functions act like fmt.Print(string) and fmt.Printf(string, ...{}interface).

*** Console logger API

The consoleLogger has the following additional functions:

| SetStdout(io.Writer) | Redirect stdout | | SetStderr(io.Writer) | Redirect stderr | | SetLogPrefix(string) | Prepend the given string on each log message | | SetLogSuffix(string) | Append the given string on each log message | | AddLogFormatter(Formatter) | Add an log formatter to format the log message |

*** Stream logger API

The streamLogger has the following additional functions:

| SetStderr(io.Writer) | Redirect stderr | | Subscribe(bufferSize int) <-chan LogMessage | Get a go channel where the log messages are emitted | | WaitForSubscribers(timeout time.Duration) | Blocks till all subscribers have received all messages |

*** Broadcast logger API The broadcastLogger has the following additional functions:

| Add(Logger) | Add a logger which receives the log messages | | Reset() | Reset removes all attached logger instances |

** Set the log level per programm arguments

plog provides two helper functions to configure the LogLevel per program arguments:

  • plog.FlagDebugVar(p *LogLevel, name string, usage string)
  • plog.FlagTraceVar(p *LogLevel, name string, usage string)

see [[#set-loglevel-per-program-arguments][Set LogLevel per program arguments]] for a example.

** Formatters

Formatters describes which and how each log column are logged.

To define the format of the log message, you can use predefined formatters or construct your own.

***** Predefined formatter

#+BEGIN_SRC go :imports '("github.com/j-keck/plog" "time" "fmt" "strings") :exports results msg := plog.LogMessage{plog.Info, time.Now(), "go_srcfile", 33, "Test"} show := func(name string, formatter plog.Formatter) { fmt.Printf("%-46s | '%s'\n", name, formatter.Format(&msg)) } fmt.Printf("%-46s | example output\n%s\n", "formatter", strings.Repeat("-", 80)) show("plog.Level", plog.Level) show("plog.Timestamp", plog.Timestamp) show("plog.TimestampMillis", plog.TimestampMillis) show("plog.TimestampUnixDate", plog.TimestampUnixDate) show("plog.Location", plog.Location) show("plog.File", plog.File) show("plog.Line", plog.Line) show("plog.Message", plog.Message) #+END_SRC

#+RESULTS: #+begin_example formatter | example output

plog.Level | ' INFO' plog.Timestamp | 'Feb 19 09:34:17' plog.TimestampMillis | 'Feb 19 09:34:17.855' plog.TimestampUnixDate | 'Wed Feb 19 09:34:17 CET 2020' plog.Location | ' go_srcfile:33 ' plog.File | ' go_srcfile' plog.Line | '33 ' plog.Message | 'Test' #+end_example

***** Custom Columns

A custom formatter expects a format string, which describes how each log column are formatted.

The TimestampFmt formatter used time.Format(format string) to format the timestamp column. See the [[https://golang.org/pkg/time/#Time.Format][time.Format]] api for a description.

The LineFmt formatter expects a %d in his format where the line number should be inserted.

All other formatters expects a %s where the value should be inserted.

#+BEGIN_SRC go :imports '("github.com/j-keck/plog" "time" "fmt" "strings") :exports results msg := plog.LogMessage{plog.Info, time.Now(), "go_srcfile", 33, "Test"} show := func(name string, formatter plog.Formatter) { fmt.Printf("%-46s | '%s'\n", name, formatter.Format(&msg)) } fmt.Printf("%-46s | example output\n%s\n", "formatter examples", strings.Repeat("-", 80)) show("plog.LevelFmt("%10s")", plog.LevelFmt("(%10s)")) show("plog.TimestampFmt("15:04:05.000")", plog.TimestampFmt("15:04:05.000")) show("plog.TimestampFmt("2006-01-02T15:04:05Z07:00")", plog.TimestampFmt("2006-01-02T15:04:05Z07:00")) show("plog.LocationFmt("[file: %s, line: %d]")", plog.LocationFmt("[file: %s, line: %d]")) show("plog.FileFmt("<%s>")", plog.FileFmt("<%s>")) show("plog.LineFmt("[%d]")", plog.LineFmt("[%d]"))

#+END_SRC

#+RESULTS: : formatter examples | example output : -------------------------------------------------------------------------------- : plog.LevelFmt("%10s") | '( INFO)' : plog.TimestampFmt("15:04:05.000") | '09:34:18.269' : plog.TimestampFmt("2006-01-02T15:04:05Z07:00") | '2020-02-19T09:34:18+01:00' : plog.LocationFmt("[file: %s, line: %d]") | '[file: go_srcfile, line: 33]' : plog.FileFmt("<%s>") | '<go_srcfile>' : plog.LineFmt("[%d]") | '[33]'

  • Examples

** Custom log format

#+BEGIN_SRC go :tangle examples/logformat.go :eval no package main

import "github.com/j-keck/plog"

func main() {
    log := plog.NewConsoleLogger(" - ",
        plog.LevelFmt("(%-5s)"),
        plog.TimestampFmt("2006-01-02T15:04:05Z07:00"),
        plog.MessageFmt("%-20s"),
        plog.LocationFmt("%s[%d]"),

    )
    log.SetLogPrefix("[").SetLogSuffix("]")

    log.Info("startup")
    log.Debug("change to debug level")
    log.SetLevel(plog.Debug)
    log.Debug("level changed")
    log.Infof("2 + 2 = %d", 2 + 2)
}

#+END_SRC

#+BEGIN_SRC shell :results output :exports both go run examples/logformat.go #+END_SRC

#+RESULTS: : [(INFO ) - 2020-02-19T09:34:18+01:00 - startup - logformat[16]] : [(DEBUG) - 2020-02-19T09:34:18+01:00 - level changed - logformat[19]] : [(INFO ) - 2020-02-19T09:34:18+01:00 - 2 + 2 = 4 - logformat[20]]

** Log output format per program arguments

#+BEGIN_SRC go :tangle examples/log-output-format-per-args.go :eval no package main

import "github.com/j-keck/plog" import "flag"

func main() { // // flags // logTs := flag.Bool("log-timestamps", false, "log messages with timestamps") logLocation := flag.Bool("log-location", false, "log messages with caller location") flag.Parse()

  //
  // initialize / configure the logger
  //
  log := plog.NewConsoleLogger(" | ")

  // timestamp only when '-log-timestamps' flag is given
  if *logTs {
      log.AddLogFormatter(plog.TimestampUnixDate)
  }

  // log level
  log.AddLogFormatter(plog.Level)

  // location only when '-log-location' flag is given
  if *logLocation {
      log.AddLogFormatter(plog.Location)
  }

  // log message
  log.AddLogFormatter(plog.Message)


  //
  // action
  //
  log.Info("startup")
  log.Debug("change to debug level")
  log.SetLevel(plog.Debug)
  log.Debug("level changed")
  log.Infof("2 + 2 = %d", 2 + 2)

} #+END_SRC

#+BEGIN_SRC shell :results output :exports results run() { echo $(printf "=%.0s" {1..80}); echo "j@main:~ ⟩ $@"; $@; echo;}

run go run examples/log-output-format-per-args.go run go run examples/log-output-format-per-args.go -log-timestamps run go run examples/log-output-format-per-args.go -log-location run go run examples/log-output-format-per-args.go -log-timestamps -log-location #+END_SRC

#+RESULTS: #+begin_example

j@main:~ ⟩ go run examples/log-output-format-per-args.go INFO | startup DEBUG | level changed INFO | 2 + 2 = 4

================================================================================ j@main:~ ⟩ go run examples/log-output-format-per-args.go -log-timestamps Wed Feb 19 09:34:19 CET 2020 | INFO | startup Wed Feb 19 09:34:19 CET 2020 | DEBUG | level changed Wed Feb 19 09:34:19 CET 2020 | INFO | 2 + 2 = 4

================================================================================ j@main:~ ⟩ go run examples/log-output-format-per-args.go -log-location INFO | log-output-format-per-args:40 | startup DEBUG | log-output-format-per-args:43 | level changed INFO | log-output-format-per-args:44 | 2 + 2 = 4

================================================================================ j@main:~ ⟩ go run examples/log-output-format-per-args.go -log-timestamps -log-location Wed Feb 19 09:34:20 CET 2020 | INFO | log-output-format-per-args:40 | startup Wed Feb 19 09:34:20 CET 2020 | DEBUG | log-output-format-per-args:43 | level changed Wed Feb 19 09:34:20 CET 2020 | INFO | log-output-format-per-args:44 | 2 + 2 = 4

#+end_example

** Set LogLevel per program arguments

#+BEGIN_SRC go :tangle examples/loglevel-per-args.go :eval no package main

import "github.com/j-keck/plog" import "flag"

func main() { log := plog.NewDefaultConsoleLogger()

  logLevel := plog.Info
  plog.FlagDebugVar(&logLevel,  "v", "debug")
  plog.FlagTraceVar(&logLevel, "vv", "trace")
  flag.Parse()

  log.SetLevel(logLevel)

  log.Info("info")
  log.Debug("debug")
  log.Trace("trace")

} #+END_SRC

#+BEGIN_SRC shell :results output :exports results run() { echo $(printf "=%.0s" {1..80}); echo "j@main:~ ⟩ $@"; $@; echo;}

run go run examples/loglevel-per-args.go run go run examples/loglevel-per-args.go -v run go run examples/loglevel-per-args.go -vv #+END_SRC

#+RESULTS: #+begin_example

j@main:~ ⟩ go run examples/loglevel-per-args.go Wed Feb 19 09:34:20 CET 2020 | INFO | info

================================================================================ j@main:~ ⟩ go run examples/loglevel-per-args.go -v Wed Feb 19 09:34:20 CET 2020 | INFO | info Wed Feb 19 09:34:20 CET 2020 | DEBUG | debug

================================================================================ j@main:~ ⟩ go run examples/loglevel-per-args.go -vv Wed Feb 19 09:34:21 CET 2020 | INFO | info Wed Feb 19 09:34:21 CET 2020 | DEBUG | debug Wed Feb 19 09:34:21 CET 2020 | TRACE | trace

#+end_example

** Log over a go channel

plog.NewStreamLogger() creates a new streaming logger. With Subscribe(bufferSize int) <-chan LogMessage you get a go channel where the log messages are emitted.

#+BEGIN_SRC go :tangle examples/stream.go :eval no package main

import "github.com/j-keck/plog" import "fmt" import "time"

func main() { log := plog.NewStreamLogger() logC := log.Subscribe(10)

  log.Info("startup")
  log.Debug("change to debug level")
  log.SetLevel(plog.Debug)
  log.Debug("level changed")
  log.Infof("2 + 2 = %d", 2 + 2)

  go func() {
    for msg := range logC {
      fmt.Printf("%s: %s\n", msg.Level, msg.Message)
    }
  }()

  log.WaitForSubscribers(100 * time.Millisecond)

} #+END_SRC

#+BEGIN_SRC shell :results output :exports both go run examples/stream.go #+END_SRC

#+RESULTS: : INFO: startup : DEBUG: level changed : INFO: 2 + 2 = 4

** Broadcast log messages to multiple receivers.

To simplify the example, only console loggers are used, but you can also use stream loggers.

#+BEGIN_SRC go :tangle examples/broadcast.go :eval no package main

import "github.com/j-keck/plog"

func main() { log := plog.NewBroadcastLogger( plog.NewDefaultConsoleLogger(), plog.NewDefaultConsoleLogger(), plog.NewDefaultConsoleLogger(), )

  log.Info("startup")
  log.Debug("change to debug level")
  log.SetLevel(plog.Debug)
  log.Debug("level changed")
  log.Infof("2 + 2 = %d", 2 + 2)

} #+END_SRC

#+BEGIN_SRC shell :results output :exports both go run examples/broadcast.go #+END_SRC

#+RESULTS: : Wed Feb 19 09:34:22 CET 2020 | INFO | startup : Wed Feb 19 09:34:22 CET 2020 | INFO | startup : Wed Feb 19 09:34:22 CET 2020 | INFO | startup : Wed Feb 19 09:34:22 CET 2020 | DEBUG | level changed : Wed Feb 19 09:34:22 CET 2020 | DEBUG | level changed : Wed Feb 19 09:34:22 CET 2020 | DEBUG | level changed : Wed Feb 19 09:34:22 CET 2020 | INFO | 2 + 2 = 4 : Wed Feb 19 09:34:22 CET 2020 | INFO | 2 + 2 = 4 : Wed Feb 19 09:34:22 CET 2020 | INFO | 2 + 2 = 4