logg

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2021 License: MIT Imports: 7 Imported by: 0

README

 __        ______     _______   _______
|  |      /  __  \   /  _____| /  _____|
|  |     |  |  |  | |  |  __  |  |  __
|  |     |  |  |  | |  | |_ | |  | |_ |
|  `----.|  `--'  | |  |__| | |  |__| |
|_______| \______/   \______|  \______|

Package logg is merely a wrapper around github.com/rs/zerolog. The primary goal is to abstract structured logging for an application while providing a simpler API. It's rather opinionated, and offers a limited feature set.

The feature set is:

  • provide timestamps
  • unique event ids
  • leveled logging (just error and info)
  • emit JSON

Usage

Call the Configure function as early as possible in your application. This initializes a "root" logger, which functions like a prototype for all subsequent events. Things initialized are the output sinks and an optional "version" field. The "version" is just some application versioning metadata, which may be useful if you want to know something about your application's source code.

Use the Errorf, Infof functions to log at error, info levels respectively. To add more event-specific fields to a logging entry, call New and then call one of the Emitter methods. Use the Emitter.WithID method if you need a unique tracing ID.

See more in the godoc examples.

Event shape

These top-level fields are always present:

  • level: string, either "info", "error"
  • time: string, an rfc3339 timestamp of emission in system's timezone.
  • message: string, what happened

These top-level fields may or may not be present, depending on configuration and how the event is emitted:

  • error: string, an error message. only when the event is emitted with an Error level.
  • version: map[string]string, optional versioning metadata from your application. will only be present when this data is passed in to the Configure function.

Example events

Info level

{
  "level":"info",
  "time":"2021-09-22T08:59:52-07:00",
  "message":"TestLogger/empty_sinks",
  "data":{
    "alfa": "anything",
    "bravo": {
      "bool": true,
      "duration": 1234,
      "float": 1.23,
      "int": 10,
      "string": "nevada"
    }
  }
}

Error level

{
  "level":"error",
  "error":"pq: duplicate key value violates unique constraint \"unique_index_on_foos_bar_id\"",
  "time":"2021-09-22T08:47:11-07:00",
  "message":"database library error",
  "data":{
    "code":"23505",
    "message":"duplicate key value violates unique constraint \"unique_index_on_foos_bar_id\"",
    "table":"foos"
  }
}

Versioning metadata can be added with the Configure function, which must be invoked before the first invocation of any library function.

{
  "level":"info",
  "time":"2021-09-22T08:59:52-07:00",
  "message":"TestLogger/empty_sinks",
  "version":{
    "branch_name":"main",
    "go_version":"v1.17",
    "commit_hash":"deadbeef"
  },
  "data":{
    "alfa": "anything",
    "bravo": {
      "bool": true,
      "duration": 1234,
      "float": 1.23,
      "int": 10,
      "string": "nevada"
    }
  }
}

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Configure

func Configure(w io.Writer, version map[string]string, moreSinks ...io.Writer)

Configure initializes a root logger from with all subsequent logging events are derived, provided there are no previous writes to the log. If there are any log writes before configuration, then all writes will go to os.Stderr by default. So, it's best to call this function as early as possible in your application.

Configure will set up a prototype logger to write to w, include version metadata and may optionally write to moreSinks. The output destination(s) cannot be changed once this function is called.

The version parameter may be empty, but it's recommended to put some metadata here so you can associate an event with the source code version.

Example
package main

import (
	"os"

	"github.com/rafaelespinoza/logg"
)

func main() {
	logg.Configure(
		os.Stderr,
		map[string]string{"branch_name": "main", "build_time": "20060102T150415", "commit_hash": "deadbeef"},
	)
}
Output:

Example (MultipleSinks)
package main

import (
	"io"
	"os"

	"github.com/rafaelespinoza/logg"
)

func main() {
	// Write to standard error as well as some file.
	var file io.Writer

	logg.Configure(
		os.Stderr,
		map[string]string{"branch_name": "main", "build_time": "20060102T150415", "commit_hash": "1337d00d"},
		file,
	)
}
Output:

Example (PossiblyUnintendedConfiguration)
package main

import (
	"io"

	"github.com/rafaelespinoza/logg"
)

func main() {
	// This example shows how the first usage of logg, that is not Configure,
	// may unintentionally set up your logger.
	logg.Infof("these writes go")
	logg.Infof("to standard error")
	logg.Infof("by default")

	// Then your code attempts to configure the logger to write to someSocket.
	var someSocket io.Writer
	logg.Configure(
		someSocket,
		map[string]string{"branch_name": "main", "build_time": "20060102T150415", "commit_hash": "feedface"},
	)

	// Whoops, these logging event will continue to go to standard error. This
	// may not be what you want. The solution would be to call Configure before
	// emitting any kind of event.
	logg.Infof("hello, is there")
	logg.Infof("anybody out there?")
}
Output:

func CtxWithID

func CtxWithID(ctx context.Context) context.Context

CtxWithID returns a new context with an ID. If the ID already existed in the context, then the new context has the same ID as before.

Example
package main

import (
	"context"

	"github.com/rafaelespinoza/logg"
)

func main() {
	// Create a new context to ensure the same tracing ID on each subsequent
	// tracing event.
	ctxA := logg.CtxWithID(context.Background())
	alfa := logg.New(map[string]interface{}{"a": "A"}).WithID(ctxA)
	alfa.Infof("altoona")
	alfa.Infof("athletic")

	// Attempting to create a logger using the same context would yield the same
	// tracing ID on each event.
	ctxB := logg.CtxWithID(ctxA)
	bravo := logg.New(map[string]interface{}{"b": "B"}).WithID(ctxB)
	bravo.Infof("boston")
	bravo.Infof("boisterous")

	// If you need another tracing ID, then use a brand-new context as the
	// parent context create create another context (using a
	// brand-new context as the parent)
	ctxC := logg.CtxWithID(context.Background())
	charlie := logg.New(map[string]interface{}{"c": "C"}).WithID(ctxC)
	charlie.Infof("chicago")
	charlie.Infof("chewbacca")
}
Output:

func Errorf

func Errorf(err error, msg string, args ...interface{})

Errorf writes msg to the log at level error and additionally writes err to an error field. If msg is a format string and there are args, then it works like fmt.Printf.

func Infof

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

Infof writes msg to the log at level info. If msg is a format string and there are args, then it works like fmt.Printf.

Types

type Emitter

type Emitter interface {
	Infof(msg string, args ...interface{})
	Errorf(err error, msg string, args ...interface{})
	WithID(ctx context.Context) Emitter
	WithData(fields map[string]interface{}) Emitter
}

An Emitter emitter writes to the log at info or error levels.

func New

func New(fields map[string]interface{}, sinks ...io.Writer) Emitter

New initializes a logger Emitter type and configures it so each event emission outputs fields at the data key. If sinks is empty or the first sink is nil, then it writes to the same destination as the root logger. If sinks is non-empty then it duplicates the root logger and writes to sinks.

Example
package main

import (
	"github.com/rafaelespinoza/logg"
)

func main() {
	// Create a logger without any data fields.
	logger := logg.New(nil)

	// do stuff ...

	logger.Infof("no data fields here")
}
Output:

Example (MultipleSinks)
package main

import (
	"io"
	"time"

	"github.com/rafaelespinoza/logg"
)

func main() {
	// This logger will emit events to multiple destinations.
	var file, socket io.Writer

	dataFields := map[string]interface{}{
		"bravo":   true,
		"delta":   234 * time.Millisecond,
		"foxtrot": float64(1.23),
		"india":   10,
	}
	logger := logg.New(dataFields, file, socket)

	// do stuff ...

	logger.Infof("hello")
	logger.Infof("world")
}
Output:

Example (WithData)
package main

import (
	"time"

	"github.com/rafaelespinoza/logg"
)

func main() {
	// Initialize the logger with data fields.
	logger := logg.New(map[string]interface{}{
		"bravo":   true,
		"delta":   234 * time.Millisecond,
		"foxtrot": float64(1.23),
		"india":   10,
	})

	// do stuff ...

	logger.Infof("hello")
	logger.Infof("world")
}
Output:

Example (WithID)
package main

import (
	"context"

	"github.com/rafaelespinoza/logg"
)

func main() {
	// Set up a logger a tracing ID.
	logger := logg.New(nil).WithID(context.Background())

	// do stuff ...

	logger.Infof("hello")
	logger.Infof("world")
}
Output:

Jump to

Keyboard shortcuts

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