timber

package module
v0.0.0-...-1142ec8 Latest Latest
Warning

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

Go to latest
Published: Jun 21, 2013 License: MIT Imports: 18 Imported by: 0

README

Timber!

This is a logger implementation that supports multiple log levels, multiple output destinations with configurable formats and levels for each. It also supports granular output configuration to get more detailed logging for specific files/packages. Timber includes support for standard XML or JSON config files to get you started quickly. It's also easy to configure in code if you want to DIY.

Features

  • Log levels: Finest, Fine, Debug, Trace, Info, Warn, Error, Critical
  • External configuration via XML and JSON
  • Multiple log destinations (console, file, socket)
  • Configurable format per destination
  • Extensible and pluggable design (if you configure via code rather than XML)

Motivation

I like the log4go design with multiple configurable loggers, but it did not support adding formatters to all of the loggers (it is only supported for files). I thought about trying to contribute patches to log4go but some of the features I wanted would break complete backwards compatibility so I decided to do a rewrite from scratch.

I try to expose everything possible that I think might be useful for someone to replace or extend.

Usage

The easiest way to use Timber is to use configure the built-in singleton:

import (
	log "timber"
)

func main() {
	// load xml config, json also supported
	log.LoadConfiguration("timber.xml")
	log.Info("Timber!!!")
}

An example timber.xml and timber.json are included in the package. Timber does implement the interface of the go log package so replacing the log with Timber will work ok.

log.Close() should be called before your program exits to make sure all the buffers are drained and all messages are printed.

Design

Logger is the interface that is used for logging itself with methods like Warn, Critical, Error, etc. All of these functions expect a Printf-like arguments and syntax for the message.

LogFormatter is a generic interface for taking a LogRecord and formatting into a string to be logged. PatFormatter is the only included implementation of this interface.

LogWriter interface wraps an underlying Writer but doesn't allow errors to propagate. There are implementations for writing to files, sockets and the console.

Timber is a MultiLogger which just means that it implements the Logger interface but can log messages to multiple destinations. Each destination has a LogWriter, level and LogFormatter.

Global is the default unconfigured instance of Timber which may be configured and used or, less commonly, replaced with your own instance (be sure to call Global.Close() before replacing for proper cleanup).

Are you planning to wrap Timber in your own logger? Ever notice that if you wrap the go log package or log4go the source file that gets printed is always your wrapper? Timber.FileDepth sets how far up the stack to go to find the file you actually want. It's set to DefaultFileDepth so add your wrapper stack depth to that.

Completeness

  • Some of the runtime configuration changes have not been implemented, such as MultiLogger.SetLevel and MultiLogger.SetFormatter which change the Level or LogFormatter on-the-fly. Loggers may be added at any time with AddLogger but there is no way to delete loggers right now.

Compatibility

  • I don't support the log4go special handling of the first parameter and probably never will. Right now, all of the Logger methods just expect a Printf-like syntax. If there is demand, I may get the proc syntax in for delayed evaluation.
  • PatFormatter format codes are not the same as log4go
  • PatFormatter always adds a newline at the end of the string so if there's already one there, then you'll get 2 so using Timber to replace the go log package may look a bit messy depending on how you formatted your logging.

Documentation

Overview

This is a logger implementation that supports multiple log levels, multiple output destinations with configurable formats and levels for each. It also supports granular output configuration to get more detailed logging for specific files/packages. Timber includes support for standard XML or JSON config files to get you started quickly. It's also easy to configure in code if you want to DIY.

Basic use:

import "timber"
timber.LoadConfiguration("timber.xml")
timber.Debug("Debug message!")

IMPORTANT: timber has not default destination configured so log messages will be dropped until a destination is configured

It can be used as a drop-in replacement for the standard logger by changing the log import statement from:

import "log"

to

import log "timber"

It can also be used as the output of the standard logger with

log.SetFlags(0)
log.SetOutput(timber.Global)

Configuration in code is also simple:

timber.AddLogger(timber.ConfigLogger{
	LogWriter: new(timber.ConsoleWriter),
	Level:     timber.DEBUG,
	Formatter: timber.NewPatFormatter("[%D %T] [%L] %S %M"),
})

XML Config file:

<logging>
  <filter enabled="true">
	<tag>stdout</tag>
	<type>console</type>
	<!-- level is (:?FINEST|FINE|DEBUG|TRACE|INFO|WARNING|ERROR) -->
	<level>DEBUG</level>
  </filter>
  <filter enabled="true">
	<tag>file</tag>
	<type>file</type>
	<level>FINEST</level>
	<granular>
	  <level>INFO</level>
	  <path>path/to/package.FunctionName</path>
	</granular>
	<granular>
	  <level>WARNING</level>
	  <path>path/to/package</path>
	</granular>
	<property name="filename">log/server.log</property>
	<property name="format">server [%D %T] [%L] %M</property>
  </filter>
  <filter enabled="false">
	<tag>syslog</tag>
	<type>socket</type>
	<level>FINEST</level>
	<property name="protocol">unixgram</property>
	<property name="endpoint">/dev/log</property>
    <format name="pattern">%L %M</property>
  </filter>
</logging>

The <tag> is ignored.

To configure the pattern formatter all filters accept:

<format name="pattern">[%D %T] %L %M</format>

Pattern format specifiers (not the same as log4go!):

%T - Time: 17:24:05.333 HH:MM:SS.ms
%t - Time: 17:24:05 HH:MM:SS
%D - Date: 2011-12-25 yyyy-mm-dd
%d - Date: 2011/12/25 yyyy/mm/dd
%L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
%S - Source: full runtime.Caller line and line number
%s - Short Source: just file and line number
%x - Extra Short Source: just file without .go suffix
%M - Message
%% - Percent sign
%P - Caller Path: packagePath.CallingFunctionName
%p - Caller Path: packagePath

the string number prefixes are allowed e.g.: %10s will pad the source field to 10 spaces pattern defaults to %M Both log4go synatax of <property name="format"> and new <format name=type> are supported the property syntax will only ever support the pattern formatter To configure granulars:

  • Create one or many <granular> within a filter
  • Define a <level> and <path> within, where path can be path to package or path to package.FunctionName. Function name definitions override package paths.

Code Architecture: A MultiLogger <logging> which consists of many ConfigLoggers <filter>. ConfigLoggers have three properties: LogWriter <type>, Level (as a threshold) <level> and LogFormatter <format>.

In practice, this means that you define ConfigLoggers with a LogWriter (where the log prints to eg. socket, file, stdio etc), the Level threshold, and a LogFormatter which formats the message before writing. Because the LogFormatters and LogWriters are simple interfaces, it is easy to write your own custom implementations.

Once configured, you only deal with the "Logger" interface and use the log methods in your code

The motivation for this package grew from a need to make some changes to the functionality of log4go (which had already been integrated into a larger project). I tried to maintain compatiblity with log4go for the interface and configuration. The main issue I had with log4go was that each of logger types had incisistent and incompatible configuration. I looked at contributing changes to log4go, but I would have needed to break existing use cases so I decided to do a rewrite from scratch.

Index

Constants

View Source
const DefaultFileDepth int = 3

Default level passed to runtime.Caller by Timber, add to this if you wrap Timber in your own logging code

Variables

View Source
var DefaultSeverityMap = map[Level]syslog.Priority{
	NONE:     syslog.LOG_INFO,
	FINEST:   syslog.LOG_DEBUG,
	FINE:     syslog.LOG_DEBUG,
	DEBUG:    syslog.LOG_DEBUG,
	TRACE:    syslog.LOG_INFO,
	INFO:     syslog.LOG_INFO,
	WARNING:  syslog.LOG_WARNING,
	ERROR:    syslog.LOG_ERR,
	CRITICAL: syslog.LOG_CRIT,
}

Mapping from the timber levels to the syslog severity If you override this, make sure all the entries are in the map since the syslog.Priority zero value will cause a message at the Emergency severity

View Source
var Global = NewTimber()

Default Timber Instance (used for all the package level function calls)

View Source
var LevelStrings = [...]string{"", "FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"}

What gets printed for each Log level

View Source
var LongLevelStrings = []string{
	"NONE",
	"FINEST",
	"FINE",
	"DEBUG",
	"TRACE",
	"INFO",
	"WARNING",
	"ERROR",
	"CRITICAL",
}

Full level names

Functions

func AddLogger

func AddLogger(logger ConfigLogger) int

func Close

func Close()

func Critical

func Critical(arg0 interface{}, args ...interface{}) error

func Debug

func Debug(arg0 interface{}, args ...interface{})

func Error

func Error(arg0 interface{}, args ...interface{}) error

func Fatal

func Fatal(v ...interface{})

func Fatalf

func Fatalf(format string, v ...interface{})

func Fatalln

func Fatalln(v ...interface{})

func Fine

func Fine(arg0 interface{}, args ...interface{})

func Finest

func Finest(arg0 interface{}, args ...interface{})

Simple wrappers for Logger interface

func Info

func Info(arg0 interface{}, args ...interface{})

func LoadConfiguration

func LoadConfiguration(filename string)

func LoadJSONConfiguration

func LoadJSONConfiguration(filename string)

func LoadXMLConfiguration

func LoadXMLConfiguration(filename string)

func Log

func Log(lvl Level, arg0 interface{}, args ...interface{})

func Panic

func Panic(v ...interface{})

func Panicf

func Panicf(format string, v ...interface{})

func Panicln

func Panicln(v ...interface{})

func Print

func Print(v ...interface{})

func Printf

func Printf(format string, v ...interface{})

func Println

func Println(v ...interface{})

func Trace

func Trace(arg0 interface{}, args ...interface{})

func Warn

func Warn(arg0 interface{}, args ...interface{}) error

Types

type BufferedWriter

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

Use this of you need some buffering, or not

func NewBufferedWriter

func NewBufferedWriter(writer io.WriteCloser) (*BufferedWriter, error)

func (*BufferedWriter) Close

func (bw *BufferedWriter) Close()

func (*BufferedWriter) Flush

func (bw *BufferedWriter) Flush()

Force flush the buffer

func (*BufferedWriter) LogWrite

func (bw *BufferedWriter) LogWrite(msg string)

type ConfigLogger

type ConfigLogger struct {
	LogWriter LogWriter
	// Messages with level < Level will be ignored.  It's up to the implementor to keep the contract or not
	Level     Level
	Formatter LogFormatter
	Granulars map[string]Level
}

Container a single log format/destination

type ConsoleWriter

type ConsoleWriter func(string)

This uses the standard go logger to write the messages

func (ConsoleWriter) Close

func (c ConsoleWriter) Close()

func (ConsoleWriter) LogWrite

func (c ConsoleWriter) LogWrite(msg string)

type JSONConfig

type JSONConfig struct {
	Filters []JSONFilter
}

type JSONFilter

type JSONFilter struct {
	Enabled    bool
	Tag        string
	Type       string
	Level      string
	Format     JSONProperty
	Properties []JSONProperty
	Granulars  []JSONGranular
}

type JSONGranular

type JSONGranular struct {
	Level string `xml:"level"`
	Path  string `xml:"path"`
}

Granulars are overriding levels that can be either package paths or package path + function name

type JSONProperty

type JSONProperty struct {
	Name  string `xml:"name"`
	Value string `xml:"value"`
}

type Level

type Level int
const (
	NONE Level = iota // NONE to be used for standard go log impl's
	FINEST
	FINE
	DEBUG
	TRACE
	INFO
	WARNING
	ERROR
	CRITICAL
)

Log levels

type LogFormatter

type LogFormatter interface {
	Format(rec *LogRecord) string
}

Format a log message before writing

type LogRecord

type LogRecord struct {
	Level       Level
	Timestamp   time.Time
	SourceFile  string
	SourceLine  int
	Message     string
	FuncPath    string
	PackagePath string
}

This packs up all the message data and metadata. This structure will be passed to the LogFormatter

type LogWriter

type LogWriter interface {
	LogWrite(msg string)
	Close()
}

Interface required for a log writer endpoint. It's more or less a io.WriteCloser with no errors allowed to be returned and string instead of []byte.

TODO: Maybe this should just be a standard io.WriteCloser?

func NewFileWriter

func NewFileWriter(name string) (LogWriter, error)

This writer has a buffer that I don't ever bother to flush, so it may take a while to see messages

type Logger

type Logger interface {
	// match log4go interface to drop-in replace
	Finest(arg0 interface{}, args ...interface{})
	Fine(arg0 interface{}, args ...interface{})
	Debug(arg0 interface{}, args ...interface{})
	Trace(arg0 interface{}, args ...interface{})
	Info(arg0 interface{}, args ...interface{})
	Warn(arg0 interface{}, args ...interface{}) error
	Error(arg0 interface{}, args ...interface{}) error
	Critical(arg0 interface{}, args ...interface{}) error
	Log(lvl Level, arg0 interface{}, args ...interface{})

	// support standard log too
	Print(v ...interface{})
	Printf(format string, v ...interface{})
	Println(v ...interface{})
	Panic(v ...interface{})
	Panicf(format string, v ...interface{})
	Panicln(v ...interface{})
	Fatal(v ...interface{})
	Fatalf(format string, v ...interface{})
	Fatalln(v ...interface{})
}

This explicitly defines the contract for a logger Not really useful except for documentation for writing an separate implementation

type LoggerConfig

type LoggerConfig interface {
	// When set, messages with level < lvl will be ignored.  It's up to the implementor to keep the contract or not
	SetLevel(lvl Level)
	// Set the formatter for the log
	SetFormatter(formatter LogFormatter)
}

Not used

type MultiLogger

type MultiLogger interface {
	// returns an int that identifies the logger for future calls to SetLevel and SetFormatter
	AddLogger(logger ConfigLogger) int
	// dynamically change level or format
	SetLevel(index int, lvl Level)
	SetFormatter(index int, formatter LogFormatter)
	Close()
}

Allow logging to multiple places

type PatFormatter

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

func NewPatFormatter

func NewPatFormatter(format string) *PatFormatter

Format codes:

  %T - Time: 17:24:05.333 HH:MM:SS.ms
  %t - Time: 17:24:05 HH:MM:SS
  %D - Date: 2011-12-25 yyyy-mm-dd
  %d - Date: 2011/12/25
  %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
  %S - Source: full runtime.Caller line
  %s - Short Source: just file and line number
  %x - Extra Short Source: just file without .go suffix
  %M - Message
  %% - Percent sign
	 %P - Caller Path: package path + calling function name
	 %p - Caller Path: package path

the string number prefixes are allowed e.g.: %10s will pad the source field to 10 spaces

func (*PatFormatter) Format

func (pf *PatFormatter) Format(rec *LogRecord) string

LogFormatter interface

type SocketWriter

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

This should write to anything that you can write to with net.Dial

func NewSocketWriter

func NewSocketWriter(network, addr string) (*SocketWriter, error)

func (*SocketWriter) Close

func (sw *SocketWriter) Close()

func (*SocketWriter) LogWrite

func (sw *SocketWriter) LogWrite(msg string)

type SyslogFormatter

type SyslogFormatter struct {
	Hostname    string
	Tag         string
	Facility    syslog.Priority
	SeverityMap map[Level]syslog.Priority
	// contains filtered or unexported fields
}

Syslog formatter wraps a PatFormatter but adds the syslog protocol format to the message. Defaults: Facility: syslog.LOG_USER (1 << 3 for pre-go1.1 compatibility) Hostname: os.Hostname() Tag: os.Args[0]

func NewSyslogFormatter

func NewSyslogFormatter(format string) *SyslogFormatter

func (*SyslogFormatter) Format

func (sf *SyslogFormatter) Format(rec *LogRecord) string

type Timber

type Timber struct {

	// This value is passed to runtime.Caller to get the file name/line and may require
	// tweaking if you want to wrap the logger
	FileDepth int
	// contains filtered or unexported fields
}

The Timber instance is the concrete implementation of the logger interfaces. New instances may be created, but usually you'll just want to use the default instance in Global

NOTE: I don't supporting the log4go special handling of the first parameter based on type mainly cuz I don't think it's particularly useful (I kept passing a data string as the first param and expecting a Println-like output but that would always break expecting a format string) I also don't support the passing of the closure stuff

func NewTimber

func NewTimber() *Timber

Creates a new Timber logger that is ready to be configured With no subsequent configuration, nothing will be logged

func (*Timber) AddLogger

func (t *Timber) AddLogger(logger ConfigLogger) int

MultiLogger interface

func (*Timber) Close

func (t *Timber) Close()

MultiLogger interface

func (*Timber) Critical

func (t *Timber) Critical(arg0 interface{}, args ...interface{}) error

func (*Timber) Debug

func (t *Timber) Debug(arg0 interface{}, args ...interface{})

func (*Timber) Error

func (t *Timber) Error(arg0 interface{}, args ...interface{}) error

func (*Timber) Fatal

func (t *Timber) Fatal(v ...interface{})

func (*Timber) Fatalf

func (t *Timber) Fatalf(format string, v ...interface{})

func (*Timber) Fatalln

func (t *Timber) Fatalln(v ...interface{})

func (*Timber) Fine

func (t *Timber) Fine(arg0 interface{}, args ...interface{})

func (*Timber) Finest

func (t *Timber) Finest(arg0 interface{}, args ...interface{})

func (*Timber) Info

func (t *Timber) Info(arg0 interface{}, args ...interface{})

func (*Timber) LoadConfig

func (t *Timber) LoadConfig(filename string)

func (*Timber) LoadJSONConfig

func (t *Timber) LoadJSONConfig(filename string) error

Loads the configuration from an JSON file (as you were probably expecting)

func (*Timber) LoadXMLConfig

func (t *Timber) LoadXMLConfig(filename string) error

Loads the configuration from an XML file (as you were probably expecting)

func (*Timber) Log

func (t *Timber) Log(lvl Level, arg0 interface{}, args ...interface{})

func (*Timber) Panic

func (t *Timber) Panic(v ...interface{})

func (*Timber) Panicf

func (t *Timber) Panicf(format string, v ...interface{})

func (*Timber) Panicln

func (t *Timber) Panicln(v ...interface{})

func (*Timber) Print

func (t *Timber) Print(v ...interface{})

Print won't work well with a pattern_logger because it explicitly adds its own \n; so you'd have to write your own formatter to remove it

func (*Timber) Printf

func (t *Timber) Printf(format string, v ...interface{})

func (*Timber) Println

func (t *Timber) Println(v ...interface{})

Println won't work well either with a pattern_logger because it explicitly adds its own \n; so you'd have to write your own formatter to not have 2 \n's

func (*Timber) SetFormatter

func (t *Timber) SetFormatter(index int, formatter LogFormatter)

Not yet implemented

func (*Timber) SetLevel

func (t *Timber) SetLevel(index int, lvl Level)

Not yet implemented

func (*Timber) Trace

func (t *Timber) Trace(arg0 interface{}, args ...interface{})

func (*Timber) Warn

func (t *Timber) Warn(arg0 interface{}, args ...interface{}) error

func (*Timber) Write

func (t *Timber) Write(p []byte) (n int, err error)

This function allows a Timber instance to be used in the standard library log.SetOutput(). It is not a general Writer interface and assumes one message per call to Write. All messages are send at level INFO

type XMLConfig

type XMLConfig struct {
	XMLName xml.Name    `xml:"logging"`
	Filters []XMLFilter `xml:"filter"`
}

type XMLFilter

type XMLFilter struct {
	XMLName    xml.Name      `xml:"filter"`
	Enabled    bool          `xml:"enabled,attr"`
	Tag        string        `xml:"tag"`
	Type       string        `xml:"type"`
	Level      string        `xml:"level"`
	Format     XMLProperty   `xml:"format"`
	Properties []XMLProperty `xml:"property"`
	Granulars  []XMLGranular `xml:"granular"`
}

type XMLGranular

type XMLGranular struct {
	Level string `xml:"level"`
	Path  string `xml:"path"`
}

Granulars are overriding levels that can be either package paths or package path + function name

type XMLProperty

type XMLProperty struct {
	Name  string `xml:"name,attr"`
	Value string `xml:",chardata"`
}

match the log4go structure so i don't have to change my configs

Jump to

Keyboard shortcuts

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