Skip to content
/ golog Public

Fork of https://github.com/apex/log that comply with SOCIFI's standards.

License

Notifications You must be signed in to change notification settings

socifi/golog

Repository files navigation

Golog

Golog is palindrome. Palindromes are cool! Like A Santa at Nasa. or Flee to me, remote elf.

Based on Apex log with few tweaks. Unless otherwise noted (mainly in pkg.go), this package should be thread safe. Just for the record the architecture leaves almost all thread safety on handlers so if you are uncertain, consult the documentation and code of the handlers you use.

Functionality

Autogenerated levels

This package contains python script for automatic log level generation. By default, the script uses log levels that comply with RFC 5424 levels (debug, info, notice, warning, error, critical, alert, emergency) but can be easily tweaked to generate entirely custom level names and level numbers. Just alter the level names and level codes at the top of the file gen_levels.py and run it. It will take care about everything else (unless you deleted guideline comments).

Hooks

This package allows registering of hooks which will be run on every field in a log entry to check specific things in field name and then apply some function to its content. The rationale behind this is that some pieces of information might be very useful in troubleshooting some problems but you might want to review information sent over network (e.g. logging passwords is very bad practice).

What the hooks are good for

The hooks are useful for many things. The most important use-case is for filtering data passed to logging service. DSN hook will, for example, check if a field name contains dsn (case insensitive). If it does the hook will then try to convert DSN interface to string and hide password in this string. This feature is entirely opt-in and might be included by importing module DSN as such:

import(
	// ...
	"github.com/socifi/golog"
	_ "github.com/socifi/golog/hook/dsn"
	// ...
)

How to write a custom hook

The hook is an interface with two functions. First one is Check(string) bool which takes field name as a parameter and returns if the field should be processed or not. The other function is Sanitize(interface{}) interface{} which takes a field value and does some processing on it and returns processed interface which will be saved or sent to logging service.

Example implementations of hooks are in folder hook/.

package hook

import (
	"github.com/socifi/golog"
	"strings"
)

type hook struct{}

var h hook

// Register hook to the mechanism
func init() {
	log.RegisterSanitizeHook(h)
}

func (dsn) Check(s string) bool {
	// string.Contains was chosen for simplicity, any logic can be here
	if strings.Contains(strings.ToLower(s), "password") {
		return true
	}
	return false
}

func (dsn) Sanitize(v interface{}) interface{} {
	// In case it's impossible to convert to chosen type return original value and do nothing with it
	s, ok := v.(string)
	if !ok {
		return v
	}

	// Again any logic (e.g. regex) can be here, this is for simplicity
	s = "***"

	return s
}

Atexit

Inspired by atexit module golog also offers the possibility to register a function which will be run on system exit. However this functionality has the same caveat as atexit, the program needs to be closed with any Exit(code int) function in this module. This function as well as AddExitHandler(handler func()) which registers functions to be run on program exit is defined for both module, logger and entry.

A side note: To access local variables use closures:

	// ...
	a := "something"
	log.AddExitHandler( func() {
		fmt.Println(a)
	})
	// ...

	// End the program
	log.Exit(0)

Simple logger initialization from config json

There is also initialization script which allows a simple load of configuration needed by the logger. This feature is now limited to JSON and elastic handlers and might be changed in the future to allow better interoperability with e.g. Viper. We, therefore, cannot guarantee any backward compatibility in this module! But for now the example usage is the following:

package main

import (
	"encoding/json"
	"fmt"
	"github.com/socifi/golog/handlers/init"
	_ "github.com/socifi/golog/hook/dsn"
	"io/ioutil"
	"os"
)

type autoGenerated struct {
	/* Your other config here... */
	Logging loginit.LogConfig `json:"logging"`
}

func main() {
	raw, err := ioutil.ReadFile("./config.json")
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	var c autoGenerated
	json.Unmarshal(raw, &c)
	fmt.Printf("%#v\n", c)
	logger := loginit.Init(c.Logging)
	logger.WithField("dsn", "host=localhost port=5432 user=postgres password=postgres dbname=postgres sslmode=disable").Info("trial")
}

Handlers

Some handlers which support a fixed number of log levels only have been discarded and only the following were kept. PR for simple text handler which will be able to process any number of levels is welcome.

  • discard – discards all logs
  • es – Elasticsearch handler
  • init – Initialization script
  • json – JSON output handler
  • kinesis – AWS Kinesis handler
  • level – level filter handler
  • logfmt – logfmt plain-text formatter
  • memory – in-memory handler for tests
  • multi – fan-out to multiple handlers
  • papertrail – Papertrail handler