Skip to content

Commit

Permalink
Initial functioning version
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleg Sklyar committed Oct 8, 2017
1 parent b9a84ca commit d6f4da7
Show file tree
Hide file tree
Showing 17 changed files with 624 additions and 220 deletions.
91 changes: 88 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,92 @@
# Structured logger interface
# Structured leveled log interface

Provides a clean decoupling from a particular logger implementation
via a simple and comprehensive logger interface.
Package `log` decouples the logger backend from your application. It defines
a simple, lightweight and comprehensive `Logger` and `Factory` interfaces which
can be used through your applications without any knowledge of the particular
implemeting backend and can be configured at the application wiring point to
bind a particular backend, such as Go's standard logger, `apex/log`, `logrus`,
with ease.

To complement the facade, the package `code.teris.io/util/log/std` provides an
implementation using the standard Go logger. The default log formatter for
this implementation uses colour coding for log levels and logs the date
leaving out the month and the year on the timestamp. However, the formatter
is fully configurable.

Similarly, the package `code.teris.io/util/log/apex` provides and implementation
using the `apex/log` logger backend.


## Interface details

The `Logger` interface defines a facade for a structured leveled log:

```go
type Logger interface {
Level(lvl LogLevel) Logger
Field(k string, v interface{}) Logger
Fields(data map[string]interface{}) Logger
WithError(err error) Logger
Log(msg string) Tracer
Logf(format string, v ...interface{}) Tracer
}
```

The `Factory` defines a facade for the creation of logger instances and setting the
log output threshold for newly created instances:

```go
type Factory interface {
New() Logger
Threshold(min LogLevel)
}
```

The package further defines three log levels differentiating between the (normally hidden)
`Debug`, (default) `Info` and (erroneous) `Error`.


## Usage

The log can be used both statically by binding a particular logger factory:

```go
func init() {
std.Use(os.Stderr, log.Info, std.DefaultFmtFun)
}

// elsewhere
logger := log.Level(log.Info).Field("key", "value")
logger.Log("message")
```

and dynamically by always going via a factory:

```go
factory := std.NewFactory(os.Stderr, log.Info, std.DefaultFmtFun)
logger := factory.Level(log.Info).Field("key", "value")
logger.Log("message")
```

By default a NoOp (no-operation) implementation is bound to the static factory.

## Tracing

To simplify debugging with execution time tracing, the `Log` and `Logf` methods
return a tracer that can be used to measure and log the execution time:

```go
logger := log.Level(log.Debug).Field("key", "value")

defer logger.Log("start").Trace()
// code to trace the execution time of
```

The above code snippet would output two log entries (provided the threshold permits)
the selected `Debug` level (her for the default formatter of the `std` logger):

08 16:31:42.023798 DBG start {key: value}
08 16:31:45.127619 DBG traced {duration: 3.103725832}, {key: value}

# License and copyright

Expand Down
33 changes: 33 additions & 0 deletions apex/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2017 Oleg Sklyar & teris.io.

package apex

import (
"code.teris.io/util/log"
alog "github.com/apex/log"
)

func NewFactory(root *alog.Logger) log.Factory {
return &factory{root: root, min: log.Unset}
}

func Use(root *alog.Logger) log.Factory {
factory := NewFactory(root)
log.SetFactory(factory)
return factory
}

type factory struct {
root *alog.Logger
min log.LogLevel
}

var _ log.Factory = (*factory)(nil)

func (f *factory) New() log.Logger {
return &logger{factory: f, lvl: log.Unset, ctx: alog.NewEntry(f.root)}
}

func (f *factory) Threshold(min log.LogLevel) {
f.min = min
}
54 changes: 0 additions & 54 deletions apex/handler.go

This file was deleted.

16 changes: 0 additions & 16 deletions apex/handler_test.go

This file was deleted.

62 changes: 62 additions & 0 deletions apex/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2017 Oleg Sklyar & teris.io

// Package apex provides a logger implementation using the github.com/apex/log backends.
package apex

import (
"fmt"
"time"

"code.teris.io/util/log"
alog "github.com/apex/log"
)

var _ log.Logger = (*logger)(nil)

type logger struct {
*factory
lvl log.LogLevel
ctx *alog.Entry
}

func (l *logger) Level(lvl log.LogLevel) log.Logger {
return &logger{factory: l.factory, lvl: lvl, ctx: l.ctx}
}

func (l *logger) Field(k string, v interface{}) log.Logger {
return &logger{factory: l.factory, lvl: l.lvl, ctx: l.ctx.WithField(k, v)}
}

func (l *logger) Fields(data map[string]interface{}) log.Logger {
fields := alog.Fields{}
for k, v := range data {
fields[k] = v
}
return &logger{factory: l.factory, lvl: l.lvl, ctx: l.ctx.WithFields(fields)}
}

func (l *logger) WithError(err error) log.Logger {
return &logger{factory: l.factory, lvl: l.lvl, ctx: l.ctx.WithError(err)}
}

func (l *logger) Log(msg string) log.Tracer {
if l.lvl >= l.factory.min {
switch {
case l.lvl <= log.Debug:
l.ctx.Debug(msg)
case l.lvl == log.Info:
l.ctx.Info(msg)
case l.lvl >= log.Error:
l.ctx.Error(msg)
}
}
return log.NewTracer(&logger{factory: l.factory, lvl: l.lvl, ctx: l.ctx}, time.Now())
}

func (l *logger) Logf(format string, v ...interface{}) log.Tracer {
if l.lvl >= l.factory.min {
return l.Log(fmt.Sprintf(format, v...))
} else {
return l.Log("")
}
}
21 changes: 21 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2017 Oleg Sklyar & teris.io.

// Package log defines the Logger interface for a structured leveled log, the Factory
// interface to create instances of the Logger and the Tracer interface used to trace
// and log the execution time since last Log.
//
// The package further defines three log levels differentiating between the (normally
// hidden) Debug, (default) Info and (erroneous) Error.
//
// The log can be used both statically by binding a particular logger factory, as in
//
// std.Use(os.Stderr, log.Info, std.DefaultFmtFun)
// logger := log.Level(log.Info).Field("key", "value")
// logger.Log("message")
//
// and dynamically by always going via a factory, as in
//
// factory := std.NewFactory(os.Stderr, log.Info, std.DefaultFmtFun)
// logger := factory.Level(log.Info).Field("key", "value")
// logger.Log("message")
package log
17 changes: 0 additions & 17 deletions factory.go

This file was deleted.

36 changes: 32 additions & 4 deletions logger.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,52 @@
// Copyright 2017 teris.io. License MIT
// Copyright (c) 2017 Oleg Sklyar & teris.io

package log

type Level int
// LogLevel defines level markers for log entries.
type LogLevel int

const (
// Unset level should not be output by logger implementation.
Unset = iota - 2
// Debug level marks detailed output for design purposes.
Debug
// Info level is the default log output marker.
Info
// Error level marks an error output.
Error
)

// Factory defines a utility to create new loggers and set the log level threshold.
type Factory interface {

//New creates a new logger.
New() Logger

// Threshold sets the minimum log level threshold for messages to be output.
Threshold(min LogLevel)
}

// Logger defines the logger interface.
type Logger interface {
With(k string, v interface{}) Logger

WithLevel(lvl Level) Logger
// Level creates a new logger instance from the current one setting its log level to the value supplied.
Level(lvl LogLevel) Logger

// Field creates a new logger instance from the current one adding a new field value.
Field(k string, v interface{}) Logger

// Fields creates a new logger instance from the current one adding a collection of field values.
Fields(data map[string]interface{}) Logger

// WithError creates a new logger instance from the current one adding an error
// and setting the level to Error.
WithError(err error) Logger

// Log outputs the log structure along with a message if the logger level is above or matching
// the threshold set in the factory.
Log(msg string) Tracer

// Logf outputs the log structure along with a formatted message if the logger level is above or
// matching the threshold set in the factory.
Logf(format string, v ...interface{}) Tracer
}
Loading

0 comments on commit d6f4da7

Please sign in to comment.