Skip to content

Commit

Permalink
basic logger library
Browse files Browse the repository at this point in the history
tests are cleaned up
  • Loading branch information
nofun97 committed Jan 7, 2020
1 parent 193ce02 commit cad78a9
Show file tree
Hide file tree
Showing 13 changed files with 1,282 additions and 0 deletions.
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/anz-bank/pkg

go 1.13

require (
github.com/marcelocantos/frozen v0.4.0
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/testify v1.4.0
)
32 changes: 32 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/marcelocantos/frozen v0.4.0 h1:GzYtr+BWZBTGpYeXZvACr3HUDN0wZKDg5tApuKdt59E=
github.com/marcelocantos/frozen v0.4.0/go.mod h1:kMkkLrWmAufGtJqXshM37prrD5O3ovVmb1pvG6mXfvY=
github.com/marcelocantos/hash v0.2.0 h1:pHimAPlPACKciZw0VeMbj8qmca7VmG6w8fAuUOi9gZo=
github.com/marcelocantos/hash v0.2.0/go.mod h1:ZpakDB4Kjclkjg4f6wxrlINDjQ/Wxpky/0bvbwyET7A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220220014-0732a990476f h1:72l8qCJ1nGxMGH26QVBVIxKd/D34cfGt0OvrPtpemyY=
golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
39 changes: 39 additions & 0 deletions log/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package log

import (
"context"

"github.com/anz-bank/pkg/log/loggers"
)

type loggerContextKey int

const loggerKey loggerContextKey = iota

// WithLogger adds a copy of the logger to the context
func WithLogger(ctx context.Context, logger loggers.Logger) context.Context {
return context.WithValue(ctx, loggerKey, logger.Copy())
}

// The fields setup in WithField and WithFields are for context-specific fields
// Fields will be logged alphabetically

// WithField adds a single field in the scope of the context
func WithField(ctx context.Context, key string, val interface{}) context.Context {
return context.WithValue(ctx, loggerKey,
getCopiedLogger(ctx).PutField(key, val))
}

// WithFields adds multiple fields in the scope of the context
func WithFields(ctx context.Context, fields MultipleFields) context.Context {
return context.WithValue(ctx, loggerKey,
getCopiedLogger(ctx).PutFields(fromFields([]Fields{fields})))
}

// Logger is a way to access the API of the logger inside the context
func Logger(ctx context.Context, fields ...Fields) loggers.Logger {
if len(fields) == 0 {
return getCopiedLogger(ctx)
}
return getCopiedLogger(ctx).PutFields(fromFields(fields))
}
143 changes: 143 additions & 0 deletions log/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package log

import (
"context"
"testing"

"github.com/anz-bank/pkg/log/loggers"
"github.com/anz-bank/pkg/log/testutil"
"github.com/marcelocantos/frozen"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestWithLogger(t *testing.T) {
t.Parallel()

logger := loggers.NewMockLogger()
logger.On("Copy").Return(logger).Once()
WithLogger(context.Background(), logger)

require.True(t, logger.AssertExpectations(t))
}

func TestWithField(t *testing.T) {
cases := testutil.GenerateSingleFieldCases()
for _, c := range cases {
c := c
t.Run("TestWithField"+" "+c.Name, func(tt *testing.T) {
tt.Parallel()

logger := loggers.NewMockLogger()
// this is done so that the mock logger is of the same reference, for testing purposes
// in real use, it will return a different logger
logger.On("Copy").Return(logger)
logger.On("PutField", c.Key, c.Val).Return(logger)
ctx := context.WithValue(context.Background(), loggerKey, logger)
WithField(ctx, c.Key, c.Val)
logger = ctx.Value(loggerKey).(*loggers.MockLogger)

assert.True(tt, logger.AssertExpectations(tt))
})
}
}

func TestWithFields(t *testing.T) {
cases := testutil.GenerateMultipleFieldsCases()
for _, c := range cases {
c := c
t.Run("TestWithFields"+" "+c.Name, func(tt *testing.T) {
tt.Parallel()

logger := loggers.NewMockLogger()
setLogSpecificFieldAssertion(logger, c.Fields)

ctx := context.WithValue(context.Background(), loggerKey, logger)
WithFields(ctx, testutil.ConvertToGoMap(c.Fields))
logger = ctx.Value(loggerKey).(*loggers.MockLogger)

assert.True(tt, logger.AssertExpectations(tt))
})
}
}

func TestLogger(t *testing.T) {
tests := []struct {
name string
fields []Fields
}{
{
name: "Empty fields",
fields: []Fields{},
},
{
name: "Single fields only",
fields: []Fields{
NewField("test", 1),
NewField("test again", 2),
NewField("another test", 3),
},
},
{
name: "Multiple fields only",
fields: []Fields{
FromMap(
map[string]interface{}{
"test": '1',
"test2": 2,
},
),
FromMap(
map[string]interface{}{
"test3": 3,
"test4": 4,
},
),
},
},
{
name: "Mixed Fields",
fields: []Fields{
NewField("test", 1),
FromMap(
map[string]interface{}{
"test3": 3,
"test4": 4,
},
),
NewField("another test", 2),
},
},
}

for _, ts := range tests {
ts := ts
t.Run(ts.name, func(tt *testing.T) {
tt.Parallel()

logger := loggers.NewMockLogger()
setLogSpecificFieldAssertion(logger, fromFields(ts.fields))

ctx := context.WithValue(context.Background(), loggerKey, logger)
Logger(ctx, ts.fields...)
assert.True(tt, logger.AssertExpectations(tt))
})
}
}

func setLogSpecificFieldAssertion(logger *loggers.MockLogger, fields frozen.Map) {
// set to return the same logger for testing purposes, in real case it will return
// a copied logger
logger.On("Copy").Return(logger)
if fields.Count() != 0 {
logger.On(
"PutFields",
mock.MatchedBy(
func(arg frozen.Map) bool {
return fields.Equal(arg)
},
),
).Return(logger)
}
}
139 changes: 139 additions & 0 deletions log/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Examples of Usage

## Setup

```go
package main

import (
"context"

"github.com/anz-bank/pkg/log"
"github.com/anz-bank/pkg/log/loggers"
)

func main() {
// User is expected to choose a logger and add it to the context using the library's API
ctx := context.Background()

// this is a logger based on the logrus standard logger
logger := loggers.NewStandardLogger()

// WithLogger returns a new context
ctx = log.WithLogger(ctx, logger)
}
```

That's all in setup, now logging can be used by using the context.

## Usage

```go
import (
"github.com/anz-bank/pkg/log"
)

func stuffToLog(ctx context.Context) {
// logging requires the context variable so it must be given to any function that requires it
log.Logger(ctx).Debug("Debug")
log.Logger(ctx).Print("Print")
log.Logger(ctx).Trace("Trace")
log.Logger(ctx).Warn("Warn")
log.Logger(ctx).Error("Error")
log.Logger(ctx).Fatal("Fatal")
log.Logger(ctx).Panic("Panic")

// Expected to log
// (time in RFC3339Nano Format) (Level) (Message)
//
// Example:
// 2019-12-12T08:23:59.210878+11:00 PRINT Hello There
//
// Each API also has its Format counterpart (Debugf, Printf, Tracef, Warnf, Errorf, Fatalf, Panicf)
}
```

Fields are also supported in the logging. There are two kinds of fields, context-level fields and log-level fields.

```go
import (
"github.com/anz-bank/pkg/log"
)


// With fields, it is expected to log
// (time in RFC3339Nano Format) (Fields) (Level) (Message)
//
// Fields will be logged ALPHABETICALLY. If the same key field is added to the context logger,
// it will replace the existing value that corresponds to that key.
//
// Example:
// 2019-12-12T08:23:59.210878+11:00 random=stuff very=random PRINT Hello There
//
// Each API also has its Format counterpart (Debugf, Printf, Tracef, Warnf, Errorf, Fatalf, Panicf)


func logWithField(ctx context.Context) {
// context-level field adds fields to the context and creates a new context
ctx = log.WithField(ctx, "random", "stuff")
ctx = log.WithFields(ctx, map[string]interface{}{
"just": "stuff",
"stuff": 1
})

// or
ctx = log.WithFields(ctx, MultipleFields{
"just": "stuff",
"stuff": 1
})

// Any log at this point will have fields and to any function that uses the same context
// just=stuff random=stuff stuff=1
contextLevelField(ctx)
logLevelField(ctx)
}

func contextLevelField(ctx context.Context) {
// This is expected to log something like
// 2019-12-12T08:23:59.210878+11:00 just=stuff random=stuff stuff=1 WARN Warn
log.Logger(ctx).Warn("Warn")
}

func logLevelField(ctx context.Context) {

// Log level fields are fields that are not stored into the context logger
// Log level fields will add fields on top of the existing context level fields
// If an existing key exists in the stored field, it will replace the value
// This is expected to log something like
// 2019-12-12T08:23:59.210878+11:00 just=stuff more=random stuff random=stuff stuff=1 very=random WARN Warn


// You can add multiple fields at once through FromMap API or just use MultipleFields struct
log.Logger(ctx,
FromMap(map[string]interface{}{
"more": "random stuff",
"very": "random"
}
)).Warn("Warn")

// or

log.Logger(
ctx,
MultipleFields{
"more": "random stuff",
"very": "random"
}
).Warn("Warn")

// You can also add single field through the API NewField
log.Logger(ctx,
NewField("more", "random stuff"),
NewField("very", "random"),
).Warn("Warn")

// As long as context logger is not modified, it will log again only the context level fields
contextLevelField(ctx)
}

```
Loading

0 comments on commit cad78a9

Please sign in to comment.