logger

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 22, 2019 License: MIT Imports: 14 Imported by: 40

README

go-logger

go-logger is a logging library based on node-bunyan.

The output is compatible with the bunyan log reader application from that node package.

Build Status Coverage Status

Usage

You first start by creating a Logger object that will operate on a Stream.

package main

import "github.com/gildas/go-logger"

var Log = logger.Create("myapp")

Then, you can log messages to the same levels from node-bunyan:

Log.Tracef("This is a message at the trace level for %s", myObject)
Log.Debugf("This is a message at the debug level for %s", myObject)
Log.Infof("This is a message at the trace level for %s", myObject)
Log.Warnf("This is a message at the warn level for %s", myObject)
Log.Errorf("This is a message at the error level for %s", myObject, err)
Log.Fatalf("This is a message at the fatal level for %s", myObject, err)

Note the err variable (must implement the error interface) used with the last two log calls. By just adding it to the list of arguments while not mentioning it in the format string will tell the Logger to spit that error in a bunyan Record field.

More generally, Record fields can be logged like this:

Log.Record("myObject", myObject).Infof("Another message about my object")
Log.Recordf("myObject", "format %s %+v". myObject.ID(), myObject).Infof("This record uses a formatted value")

log := Log.Record("dynamic", func() interface{} { return myObject.Callme() })

log.Infof("This is here")
log.Infof("That is there")

In the last example, the code myObject.Callme() will be executed each time log is used to write a message. This is used, as an example, to add a timestamp to the log's Record.

In addition to the Bunyan core fields, this library adds a couple of Record Fields:

  • topic can be used for stuff like types or general topics (e.g.: "http")
  • scope can be used to scope logging within a topic, like a func or a portion of code.

When the Logger is created its topic and scope are set to "main".

Here is a simple example how Record fields can be used with a type:

type Stuff struct {
    Field1 string
    Field2 int
    Logger *logger.Logger // So Stuff carries its own logger
}

func (s *Stuff) SetLogger(l *logger.Logger) {
    s.Logger = l.Topic("stuff").Scope("stuff")
}

func (s Stuff) DoSomething(other *OtherStuff) error {
    log := s.Logger.Scope("dosomething")

    log.Record("other", other).Infof("Need to do something")
    if err := someFunc(s, other); err != nil {
        log.Errorf("Something went wrong with other", err)
        return err
    }
    return nil
}

The call to Record(key, value) creates a new Logger object. So, they are like Russian dolls when it comes down to actually writing the log message to the output stream. In other words, Record are collected from their parent's Logger back to the original Logger.

For example:

var Log   = logger.Create("test")
var child = Log.Record("key1", "value1").Record("key2", "value2")

child will actually be something like Logger(Logger(Logger(Stream to stdout))). Though we added only 2 records.

Therefore, to optimize the number of Logger objects that are created, there are some convenience methods that can be used:

func (s stuff) DoSomethingElse(other *OtherStuff) {
    log := s.Logger.Child("new_topic", "new_scope", "id", other.ID(), "key1", "value1")

    log.Infof("I am logging this stuff")

    log.Records("key2", "value2", "key3", 12345).Warnf("Aouch that hurts!")
}

The Child method will create one Logger that has a Record containing a topic, a scope, 2 keys (id and key1) with their values.

The Records method will create one Logger that has 2 keys (key2 and key3) with their values.

For example, with these methods:

var Log    = logger.Create("test")
var child1 = Log.Child("topic", "scope", "key2", "value2", "key3", "value3")
var child2 = child1.Records("key2", "value21", "key4", "value4")

child1 will be something like Logger(Logger(Stream to stdout)). Though we added 2 records.
child2 will be something like Logger(Logger(Logger(Stream to stdout))). Though we added 1 record to the 2 records added previously.

Stream objects

A Stream is where the Logger actually writes its Record data.

When creating a Logger, you can specify the destination it will write to:

var Log = logger.Create("myapp", "file://path/to/myapp.log")
var Log = logger.Create("myapp", "/path/to/myapp.log")
var Log = logger.Create("myapp", "./localpath/to/myapp.log")
var Log = logger.Create("myapp", "stackdriver")
var Log = logger.Create("myapp", "gcp")
var Log = logger.Create("myapp", "/path/to/myapp.log", "gcp")
var Log = logger.Create("myapp", "nil")

The first three Logger will write to a file, the fourth to Google Stackdriver, the fifth to Google Cloud Platform (GCP), the sixth to a file and GCP, and the seventh to nowhere (i.e. logs do not get written at all).

By default, when creating the Logger with:

var Log = logger.Create("myapp")

The Logger will write to the standard output or the destination specified in the environment variable LOG_DESTINATION.

You can also create a Logger by passing it a Stream object (these are equivalent to the previous code):

var Log = logger.Create("myapp", &logger.FileStream{Path: "/path/to/myapp.log"})
var Log = logger.Create("myapp", &logger.StackDriverStream{})
var Log = logger.Create("myapp", &logger.GCPStream{})
var Log = logger.Create("myapp", &logger.NilStream{})
var Log = logger.Create("myapp", &logger.FileStream{Path: "/path/to/myapp.log"}, &logger.GCPStream{})

A few notes:

  • logger.CreateWithStream can also be used to create with one or more streams.
    (Backward compatibility)
  • logger.CreateWithDestination can also be used to create with one or more destinations.
    (Backward compatibility)
  • the StackDriverStream needs a ProjectID parameter or the value of the environment variable PROJECT_ID.
    It can use a LogID (see Google's StackDriver documentation).
  • NilStream is a Stream that does not write anything, all messages are lost.
  • MultiStream is a Stream than can write to several streams.
  • All Stream types, except NilStream and MultiStream can use a FilterLevel. When set, Record objects that have a Level below the FilterLevel are not written to the Stream. This allows to log only stuff above Warn for instance. The FilterLevel can be set via the environment variable LOG_LEVEL.
  • StdoutStream and FileStream are buffered by default. Data is written from every LOG_FLUSHFREQUENCY (default 5 minutes) or when the Record's Level is at least ERROR.

You can also create a Logger with a combination of destinations and streams, AND you can even add some records right away:

var Log = logger.Create("myapp",
    &logger.FileStream{Path: "/path/to/myapp.log"},
    "stackdriver",
    NewRecord().Set("key", "value")
)

You can also write your own Stream by implementing the logger.Streamer interface and create the Logger like this:

var Log = logger.Create("myapp", &MyStream{})

The following convenience methods can be used when creating a Logger from another one (received from arguments, for example):

var Log = logger.CreateIfNil(OtherLogger, "myapp")
var Log = logger.Create("myapp", OtherLogger)

If OtherLogger is nil, the new Logger will write to the NilStream().

var Log = logger.Must(logger.FromContext(context))

Must can be used to create a Logger from a method that returns *Logger, error, if there is an error, Mustwill panic.

FromContext can be used to retrieve a Logger from a GO context. (This is used in the next paragraph)
log.ToContext will store the Logger to the given GO context.

HTTP Usage

It is possible to pass Logger objects to http.Handler. When doing so, the Logger will automatically write the request identifier ("X-Request-Id" HTTP Header), remote host, user agent, when the request starts and when the request finishes along with its duration.

The request identifier is attached every time the log writes in a Record.

Here is an example:

package main

import (
    "net/http"
	"github.com/gildas/go-logger"
	"github.com/gorilla/mux"
)

func MyHandler() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Extracts the Logger from the request's context
        //  Note: Use logger.Must only when you know there is a Logger as it will panic otherwise
        log := logger.Must(logger.FromContext(r.Context()))

        log.Infof("Now we are logging inside this http Handler")
    })
}

func main() {
    log := logger.Create("myapp")
    router := mux.NewRouter()
    router.Methods("GET").Path("/").Handler(log.HttpHandler()(MyHandler()))
}

Thanks

Special thanks to @chakrit for his chakrit/go-bunyan that inspired me. In fact earlier versions were wrappers around his library.

Well, we would not be anywhere without the original work of @trentm and the original trentm/node-bunyan. Many, many thanks!

Documentation

Index

Examples

Constants

View Source
const ContextKey key = iota + 12583

ContextKey is the key for logger child stored in Context

Variables

View Source
var VERSION = "1.1.0" + commit

VERSION is the version of this application

Functions

func GetFlushFrequencyFromEnvironment

func GetFlushFrequencyFromEnvironment() time.Duration

GetFlushFrequencyFromEnvironment fetches the flush frequency from the environment

func Gettid

func Gettid() int

Gettid returns the current thread identifier

Types

type FileStream

type FileStream struct {
	*json.Encoder
	Path        string
	FilterLevel Level
	Unbuffered  bool
	// contains filtered or unexported fields
}

FileStream is the Stream that writes to a file

Any record with a level < FilterLevel will be written

func (*FileStream) Flush

func (stream *FileStream) Flush()

Flush flushes the stream (makes sure records are actually written)

implements logger.Stream

func (*FileStream) ShouldWrite

func (stream *FileStream) ShouldWrite(level Level) bool

ShouldWrite tells if the given level should be written to this stream

implements logger.Stream

func (FileStream) String

func (stream FileStream) String() string

String gets a string version

implements the fmt.Stringer interface

func (*FileStream) Write

func (stream *FileStream) Write(record Record) (err error)

Write writes the given Record

implements logger.Stream

type GCPStream

type GCPStream struct {
	*json.Encoder
	FilterLevel Level
}

GCPStream is the Stream that writes to the standard output

func (*GCPStream) Flush

func (stream *GCPStream) Flush()

Flush flushes the stream (makes sure records are actually written)

implements logger.Stream

func (*GCPStream) ShouldWrite

func (stream *GCPStream) ShouldWrite(level Level) bool

ShouldWrite tells if the given level should be written to this stream

implements logger.Stream

func (GCPStream) String

func (stream GCPStream) String() string

String gets a string version

implements the fmt.Stringer interface

func (*GCPStream) Write

func (stream *GCPStream) Write(record Record) error

Write writes the given Record

implements logger.Stream

type Level

type Level byte
const (
	NEVER Level = iota * 10
	TRACE
	DEBUG
	INFO
	WARN
	ERROR
	FATAL
	ALWAYS = 255
)

func GetLevelFromEnvironment

func GetLevelFromEnvironment() Level

GetLevelFromEnvironment

func GetLevelFromRecord

func GetLevelFromRecord(record Record) Level

func ParseLevel

func ParseLevel(value string) Level

ParseLevel converts a string into a Level

func (Level) ShouldWrite

func (level Level) ShouldWrite(filter Level) bool

ShouldWrite tells if the current level is writeable when compared to the given filter level

func (Level) String

func (level Level) String() string

String gets a string version

implements the fmt.Stringer interface

type Logger

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

Logger is a Logger that creates Bunyan's compatible logs (see: https://github.com/trentm/node-bunyan)

func Create

func Create(name string, parameters ...interface{}) *Logger

Create creates a new Logger

func CreateIfNil

func CreateIfNil(logger *Logger, name string) *Logger

CreateIfNil creates a new Logger if the given Logger is nil, otherwise return the said Logger

func CreateWithDestination

func CreateWithDestination(name string, destinations ...string) *Logger

CreateWithDestination creates a new Logger streaming to the given destination(s)

func CreateWithStream

func CreateWithStream(name string, streams ...Streamer) *Logger

CreateWithStream creates a new Logger streaming to the given stream or list of streams

func FromContext

func FromContext(context context.Context) (*Logger, error)

FromContext retrieves the Logger stored in the context

func Must

func Must(log *Logger, err error) *Logger

Must returns the given logger or panics if there is an error or if the Logger is nil

func (*Logger) Child

func (log *Logger) Child(topic, scope interface{}, params ...interface{}) *Logger

Child creates a child Logger with a topic, a scope, and records

func (*Logger) Debugf

func (log *Logger) Debugf(msg string, args ...interface{})

Debugf traces a message at the DEBUG Level

func (*Logger) Errorf

func (log *Logger) Errorf(msg string, args ...interface{})

Errorf traces a message at the ERROR Level If the last argument is an error, a Record is added and the error string is added to the message

func (*Logger) Fatalf

func (log *Logger) Fatalf(msg string, args ...interface{})

Fatalf traces a message at the FATAL Level If the last argument is an error, a Record is added and the error string is added to the message

func (*Logger) Flush

func (log *Logger) Flush()

Flush flushes the stream (makes sure records are actually written)

implements logger.Stream

func (*Logger) GetRecord

func (log *Logger) GetRecord(key string) interface{}

GetRecord returns the Record field value for a given key

func (*Logger) HttpHandler

func (l *Logger) HttpHandler() func(http.Handler) http.Handler

HttpHandler function will wrap an http handler with extra logging information

func (*Logger) Infof

func (log *Logger) Infof(msg string, args ...interface{})

Infof traces a message at the INFO Level

func (*Logger) Record

func (log *Logger) Record(key string, value interface{}) *Logger

Record adds the given Record to the Log

func (*Logger) Recordf

func (log *Logger) Recordf(key, value string, args ...interface{}) *Logger

Recordf adds the given Record with formatted arguments

func (*Logger) Records

func (log *Logger) Records(params ...interface{}) *Logger

Records adds key, value pairs as Record objects E.g.: log.Records("key1", value1, "key2", value2)

The key should be castable to a string
If the last value is missing, its key is ignored

func (*Logger) Scope

func (log *Logger) Scope(value interface{}) *Logger

Scope sets the Scope if this Logger

func (*Logger) ShouldWrite

func (log *Logger) ShouldWrite(level Level) bool

ShouldWrite tells if the given level should be written to this stream

implements logger.Stream

func (Logger) String

func (log Logger) String() string

String gets a string version

implements the fmt.Stringer interface

func (*Logger) ToContext

func (l *Logger) ToContext(parent context.Context) context.Context

ToContext stores the Logger in the given context

func (*Logger) Topic

func (log *Logger) Topic(value interface{}) *Logger

Topic sets the Topic of this Logger

func (*Logger) Tracef

func (log *Logger) Tracef(msg string, args ...interface{})

Tracef traces a message at the TRACE Level

func (*Logger) Warnf

func (log *Logger) Warnf(msg string, args ...interface{})

Warnf traces a message at the WARN Level

func (*Logger) Write

func (log *Logger) Write(record Record) error

Write writes the given Record

implements logger.Stream

type MultiStream

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

MultiStream is the Stream that writes to several streams

func (*MultiStream) Flush

func (stream *MultiStream) Flush()

Flush flushes the stream (makes sure records are actually written)

implements logger.Stream

func (*MultiStream) ShouldWrite

func (stream *MultiStream) ShouldWrite(level Level) bool

ShouldWrite tells if the given level should be written to this stream

implements logger.Stream

func (MultiStream) String

func (stream MultiStream) String() string

String gets a string version

implements the fmt.Stringer interface

func (*MultiStream) Write

func (stream *MultiStream) Write(record Record) error

Write writes the given Record

implements logger.Stream

type NilStream

type NilStream struct {
}

NilStream is the Stream that writes nowhere

func (*NilStream) Flush

func (stream *NilStream) Flush()

Flush flushes the stream (makes sure records are actually written)

implements logger.Stream

func (*NilStream) ShouldWrite

func (stream *NilStream) ShouldWrite(level Level) bool

ShouldWrite tells if the given level should be written to this stream

implements logger.Stream

func (NilStream) String

func (stream NilStream) String() string

String gets a string version

implements the fmt.Stringer interface

func (*NilStream) Write

func (stream *NilStream) Write(record Record) error

Write writes the given Record

implements logger.Stream

type Record

type Record map[string]interface{}

Record is the map that contains all records of a log entry

If the value at a key is a func() interface the func will be called when the record is marshaled

func NewRecord

func NewRecord() Record

NewRecord creates a new empty record

func (Record) MarshalJSON

func (record Record) MarshalJSON() ([]byte, error)

MarshalJSON marshals this into JSON

func (Record) Merge

func (record Record) Merge(source Record) Record

Merge merges a source Record into this Record

values already set in this record cannot be overriden

func (Record) Set

func (record Record) Set(key string, value interface{}) Record

Set the key and value if not yet set

func (*Record) UnmarshalJSON

func (record *Record) UnmarshalJSON(payload []byte) error

UnmarshalJSON unmarshals JSON into this

type StackDriverStream

type StackDriverStream struct {
	LogID       string
	ProjectID   string
	FilterLevel Level
	// contains filtered or unexported fields
}

GCPStream is the Stream that writes to the standard output

func (*StackDriverStream) Flush

func (stream *StackDriverStream) Flush()

Flush flushes the stream (makes sure records are actually written)

implements logger.Stream

func (*StackDriverStream) ShouldWrite

func (stream *StackDriverStream) ShouldWrite(level Level) bool

ShouldWrite tells if the given level should be written to this stream

implements logger.Stream

func (StackDriverStream) String

func (stream StackDriverStream) String() string

String gets a string version

implements the fmt.Stringer interface

func (*StackDriverStream) Write

func (stream *StackDriverStream) Write(record Record) (err error)

Write writes the given Record

implements logger.Stream

type StderrStream

type StderrStream struct {
	*json.Encoder
	FilterLevel Level
}

StderrStream is the Stream that writes to the standard output

func (*StderrStream) Flush

func (stream *StderrStream) Flush()

Flush flushes the stream (makes sure records are actually written)

implements logger.Stream

func (*StderrStream) ShouldWrite

func (stream *StderrStream) ShouldWrite(level Level) bool

ShouldWrite tells if the given level should be written to this stream

implements logger.Stream

func (StderrStream) String

func (stream StderrStream) String() string

String gets a string version

implements the fmt.Stringer interface

func (*StderrStream) Write

func (stream *StderrStream) Write(record Record) error

Write writes the given Record

implements logger.Stream

type StdoutStream

type StdoutStream struct {
	*json.Encoder
	FilterLevel Level
	Unbuffered  bool
	// contains filtered or unexported fields
}

StdoutStream is the Stream that writes to the standard output

Example
stream := &logger.StdoutStream{}
record := logger.NewRecord().Set("bello", "banana").Set("だれ", "Me")

err := stream.Write(record)
if err != nil {
	os.Stdout.WriteString(err.Error() + "\n")
}
stream.Flush()
Output:

{"bello":"banana","だれ":"Me"}

func (*StdoutStream) Flush

func (stream *StdoutStream) Flush()

Flush flushes the stream (makes sure records are actually written)

implements logger.Stream

func (*StdoutStream) ShouldWrite

func (stream *StdoutStream) ShouldWrite(level Level) bool

ShouldWrite tells if the given level should be written to this stream

implements logger.Stream

func (StdoutStream) String

func (stream StdoutStream) String() string

String gets a string version

implements the fmt.Stringer interface

func (*StdoutStream) Write

func (stream *StdoutStream) Write(record Record) error

Write writes the given Record

implements logger.Stream

type Streamer

type Streamer interface {
	Write(record Record) error
	ShouldWrite(level Level) bool
	Flush()
}

Streamer is the interface a Logger writes to

func CreateStreamWithDestination added in v1.1.0

func CreateStreamWithDestination(destinations ...string) Streamer

CreateStreamWithDestination creates a new Streamer from a list of strings

Jump to

Keyboard shortcuts

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