logger

package
v0.166.2 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2023 License: Apache-2.0 Imports: 20 Imported by: 0

README

package logger

Package logger provides tooling for structured logging. With logger, you can use context to add logging details to your call stack.

How to use

package main

import (
	"context"

	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()

	// You can add details to the context; thus, every logging call using this context will inherit the details.
	ctx = logger.ContextWith(ctx, logger.Fields{
		"foo": "bar",
		"baz": "qux",
	})

	// You can use your own Logger instance or the logger.Default logger instance if you plan to log to the STDOUT. 
	logger.Info(ctx, "foo", logger.Fields{
		"userID":    42,
		"accountID": 24,
	})
}

example output when snake case key format is defined (default):

{
  "account_id": 24,
  "baz": "qux",
  "foo": "bar",
  "level": "info",
  "message": "foo",
  "timestamp": "2023-04-02T16:00:00+02:00",
  "user_id": 42
}

example output when camel key format is defined:

{
  "accountID": 24,
  "baz": "qux",
  "foo": "bar",
  "level": "info",
  "message": "foo",
  "timestamp": "2023-04-02T16:00:00+02:00",
  "userID": 42
}

How to configure:

logger.Logger can be configured through its struct fields; please see the documentation for more details. To configure the default logger, simply configure it from your main.

package main

import "github.com/adamluzsi/frameless/pkg/logger"

func main() {
	logger.Default.MessageKey = "msg"
	logger.Default.TimestampKey = "ts"
}

Key string case style consistency in your logs

Using the logger package can help you maintain historical consistency in your logging key style and avoid mixed string case styles. This is particularly useful since many log collecting systems rely on an append-only strategy, and any inconsistency could potentially cause issues with your alerting scripts that rely on certain keys. So, by using the logger package, you can ensure that your logging is clean and consistent, making it easier to manage and troubleshoot any issues that may arise.

The default key format is snake_case, but you can change it easily by supplying a formetter in the KeyFormatter Logger field.

package main

import (
	"github.com/adamluzsi/frameless/pkg/logger"
	"github.com/adamluzsi/frameless/pkg/stringcase"
)

func main() {
	logger.Default.KeyFormatter = stringcase.ToKebab
}

Security concerns

It is crucial to make conscious decisions about what we log from a security standpoint because logging too much information can potentially expose sensitive data about users. On the other hand, by logging what is necessary and relevant for operations, we can avoid security and compliance issues. Following this practice can reduce the attack surface of our system and limit the potential impact of a security breach. Additionally, logging too much information can make detecting and responding to security incidents more difficult, as the noise of unnecessary data can obscure the signal of actual threats.

The logger package has a safety mechanism to prevent exposure of sensitive data; it requires you to register any DTO or Entity struct type with the logging package before you can use it as a logging field.

package mydomain

import "github.com/adamluzsi/frameless/pkg/logger"

type MyEntity struct {
	ID               string
	NonSensitiveData string
	SensitiveData    string
}

var _ = logger.RegisterFieldType(func(ent MyEntity) logger.LoggingDetail {
	return logger.Fields{
		"id":   ent.ID,
		"data": ent.NonSensitiveData,
	}
})

ENV based Configuration

You can set the default's loggers level with the following env values:

  • LOG_LEVEL
  • LOGGER_LEVEL
  • LOGGING_LEVEL
logging level env value env short format value
Debug Level debug d
Info Level info i
Warn Level warn w
Error Level error e
Fatal Level fatal f
Fatal Level critical c

Logging performance optimisation by using an async logging strategy

If your application requires high performance and uses a lot of concurrent actions, using an async logging strategy can provide the best of both worlds. This means the application can have great performance that is not hindered by logging calls, while also being able to observe and monitor its behavior effectively.

----------------- no concurrency heavy concurrency
sync logging 5550 ns/op 54930 ns/op
async logging 700.7 ns/op 1121 ns/op

tested on MacBook Pro with M1 when writing into a file

$ go test -bench BenchmarkLogger_AsyncLogging -run -

package main

import (
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	defer logger.AsyncLogging()() // from here on, logging will be async

	logger.Info(nil, "this is logged asynchronously")
}

Helping the TDD Flow during Testing

using the logger.LogWithTB helper method will turn your logs into structured testing log events making it easier to investigate unexpected behaviours using the DEBUG logs.

myfile.go

package mypkg

import (
	"github.com/adamluzsi/frameless/pkg/logger"
)

func MyFunc() {
	logger.Debug(nil, "the logging message", logger.Field("bar", 24))
}

myfile_test.go

package mypkg_test

import (
	"testing"

	"mypkg"

	"github.com/adamluzsi/frameless/pkg/logger"
)

func TestMyFunc(t *testing.T) {
	logger.LogWithTB(t) // logs are redirected to use *testing.T.Log  

	mypkg.MyFunc()
}

will produce the following output:

=== RUN   TestLogWithTB_spike
	myfile.go:11: the logging message | level:debug bar:24
	--- PASS: TestLogWithTB_spike (0.00s)
	PASS

Documentation

Overview

Package logger provides tooling for structured logging. With logger, you can use context to add logging details to your call stack.

Example (WithDetails)
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	logger.Info(ctx, "foo", logger.Fields{
		"userID":    42,
		"accountID": 24,
	})
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AsyncLogging added in v0.138.0

func AsyncLogging() func()
Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	defer logger.AsyncLogging()()
	logger.Info(ctx, "this log message is written out asynchronously")
}
Output:

func ContextWith added in v0.135.0

func ContextWith(ctx context.Context, lds ...LoggingDetail) context.Context
Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()

	ctx = logger.ContextWith(ctx, logger.Fields{
		"foo": "bar",
		"baz": "qux",
	})

	logger.Info(ctx, "message") // will have details from the context
}
Output:

func Debug

func Debug(ctx context.Context, msg string, ds ...LoggingDetail)
Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	logger.Debug(ctx, "foo")
}
Output:

func Error

func Error(ctx context.Context, msg string, ds ...LoggingDetail)
Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	logger.Error(ctx, "foo")
}
Output:

func Fatal

func Fatal(ctx context.Context, msg string, ds ...LoggingDetail)
Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	logger.Fatal(ctx, "foo")
}
Output:

func Info

func Info(ctx context.Context, msg string, ds ...LoggingDetail)
Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	logger.Info(ctx, "foo")
}
Output:

func LogWithTB added in v0.150.0

func LogWithTB(tb testingTB)

LogWithTB pipes all application log generated during the test execution through the testing.TB's Log method. LogWithTB meant to help debugging your application during your TDD flow.

Example
package main

import (
	"github.com/adamluzsi/frameless/pkg/logger"
	"testing"
)

func main() {
	var tb testing.TB

	logger.LogWithTB(tb)

	// somewhere in your application
	logger.Debug(nil, "the logging message", logger.Field("bar", 24))

}
Output:

func RegisterFieldType added in v0.135.0

func RegisterFieldType[T any](mapping func(T) LoggingDetail) any
Example
package main

import (
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	type MyEntity struct {
		ID               string
		NonSensitiveData string
		SensitiveData    string
	}

	// at package level
	var _ = logger.RegisterFieldType(func(ent MyEntity) logger.LoggingDetail {
		return logger.Fields{
			"id":   ent.ID,
			"data": ent.NonSensitiveData,
		}
	})
}
Output:

func Warn

func Warn(ctx context.Context, msg string, ds ...LoggingDetail)
Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	logger.Warn(ctx, "foo")
}
Output:

Types

type Details

type Details = Fields

Details

DEPRECATED: use logging.Fields instead

type Fields added in v0.135.1

type Fields map[string]any
Example
package main

import (
	"context"

	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	logger.Error(context.Background(), "msg", logger.Fields{
		"key1": "value",
		"key2": "value",
	})
}
Output:

type Level added in v0.142.0

type Level string
const (
	LevelDebug Level = "debug"
	LevelInfo  Level = "info"
	LevelWarn  Level = "warn"
	LevelError Level = "error"
	LevelFatal Level = "fatal"
)

func (Level) String added in v0.142.0

func (ll Level) String() string

type Logger

type Logger struct {
	Out io.Writer

	// Level is the logging level.
	// The default Level is LevelInfo.
	Level Level

	Separator string

	MessageKey   string
	LevelKey     string
	TimestampKey string

	// MarshalFunc is used to serialise the logging message event.
	// When nil it defaults to JSON format.
	MarshalFunc func(any) ([]byte, error)
	// KeyFormatter will be used to format the logging field keys
	KeyFormatter func(string) string

	// Hijack will hijack the logging and instead of letting it logged out to the Out,
	// the logging will be done with the Hijack function.
	// This is useful if you want to use your own choice of logging,
	// but also packages that use this logging package.
	Hijack func(level Level, msg string, fields Fields)
	// contains filtered or unexported fields
}
var Default Logger

func (*Logger) AsyncLogging added in v0.138.0

func (l *Logger) AsyncLogging() func()

AsyncLogging will change the logging strategy from sync to async. This makes the log calls such as Logger.Info not wait on io operations. The AsyncLogging function call is where the async processing will happen, You should either run it in a separate goroutine, or use it with the tasker package. After the AsyncLogging returned, the logger returns to log synchronously.

Example
package main

import (
	"context"
	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	l := logger.Logger{}
	defer l.AsyncLogging()()
	l.Info(ctx, "this log message is written out asynchronously")
}
Output:

func (*Logger) Debug

func (l *Logger) Debug(ctx context.Context, msg string, ds ...LoggingDetail)

func (*Logger) Error

func (l *Logger) Error(ctx context.Context, msg string, ds ...LoggingDetail)

func (*Logger) Fatal

func (l *Logger) Fatal(ctx context.Context, msg string, ds ...LoggingDetail)

func (*Logger) Info

func (l *Logger) Info(ctx context.Context, msg string, ds ...LoggingDetail)

func (*Logger) Warn

func (l *Logger) Warn(ctx context.Context, msg string, ds ...LoggingDetail)

type LoggingDetail added in v0.135.0

type LoggingDetail interface {
	// contains filtered or unexported methods
}

func ErrField added in v0.135.0

func ErrField(err error) LoggingDetail
Example
package main

import (
	"context"
	"errors"

	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	ctx := context.Background()
	err := errors.New("boom")

	logger.Error(ctx, "task failed successfully", logger.ErrField(err))
}
Output:

func Field added in v0.135.0

func Field(key string, value any) LoggingDetail
Example
package main

import (
	"context"

	"github.com/adamluzsi/frameless/pkg/logger"
)

func main() {
	logger.Error(context.Background(), "msg",
		logger.Field("key1", "value"),
		logger.Field("key2", "value"))
}
Output:

type StubOutput added in v0.150.0

type StubOutput interface {
	io.Reader
	String() string
	Bytes() []byte
}

func Stub

func Stub(tb testingTB) StubOutput

Stub the logger.Default and return the buffer where the logging output will be recorded. Stub will restore the logger.Default after the test.

Example
package main

import (
	"github.com/adamluzsi/frameless/pkg/logger"
	"strings"
	"testing"
)

func main() {
	var tb testing.TB
	buf := logger.Stub(tb) // stub will clean up after itself when the test is finished
	logger.Info(nil, "foo")
	strings.Contains(buf.String(), "foo") // true
}
Output:

Jump to

Keyboard shortcuts

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