Tiny structured logging abstraction or facade for various logging libraries, allowing the end user to plug in the desired logging library in main.go

YALA - Yet Another Logging Abstraction for Go

Build Go Reference Go Report Card codecov Project Status: Active – The project has reached a stable, usable state and is being actively developed.

Tiny structured logging abstraction or facade for various logging libraries, allowing the end user to plug in the desired logging library in main.go.

Supported logging libraries (via adapters)

logrus, zap, zerolog, glog, log15, standard log and console

When to use?

  • If you are a package/module/library author
  • And you want to participate in the end user logging system (log messages using the logger provided by the end user)
  • You don't want to add dependency to any specific logging library to your code
  • You don't want to manually inject logger to every possible place where you want to log something (such as function, struct etc.)
  • If you need a nice and elegant API with a bunch of useful functions, but at the same time you don't want your end users to spend hours on writing their own logging adapter.

Installation

# Add yala to your Go module:
go get github.com/elgopher/yala        

Please note that at least Go 1.17 is required.

How to use

Choose logger - global or local?

Global logger can be accessed from everywhere in your library and can be reconfigured anytime. Local logger is a logger initialized only once and used locally, for example inside the function.

Use global logger

package lib // this is your package, part of module/library etc.

import (
	"context"
	"errors"

	"github.com/elgopher/yala/logger"
)

// define global logger, no need to initialize it (by default nothing is logged)
var log logger.Global

// Provide a public function for setting adapter. It will be called in main.go
func SetLoggerAdapter(adapter logger.Adapter) {
	log.SetAdapter(adapter)
}

func Function(ctx context.Context) {
	log.Debug(ctx, "Debug message")
	log.With("field_name", "value").Info(ctx, "Message with field")
	log.WithError(errors.New("some")).Error(ctx, "Message with error")
}

Specify adapter - a real logger implementation.

package main

import (
	"context"

	"github.com/elgopher/yala/adapter/console"
	"lib"
)

// End user decides what library to plug in.
func main() {
	adapter := console.StdoutAdapter() // will print messages to console
	lib.SetLoggerAdapter(adapter)

	ctx := context.Background()
	lib.Function(ctx)
}

Why context.Context is a parameter?

context.Context can very useful in transiting request-scoped tags or even entire logger. A logger.Adapter implementation might use them making possible to log messages instrumented with tags. Thanks to that your library can trully participate in the incoming request.

Use local logger

Logging is a special kind of dependency. It is used all over the place. Adding it as an explicit dependency to every function, struct etc. can be cumbersome. Still though, you have an option to use local logger by injecting logger.Adapter into your library:

// your library code:
func NewLibrary(adapter logger.Adapter) YourLib {
	// create a new local logger which provides similar API to the global logger
	localLogger := logger.Local{Adapter: adapter}     
	return YourLib{localLogger: localLogger}
}

type YourLib struct {
	localLogger logger.Local
}

func (l YourLib) Method(ctx context.Context) {
	l.localLogger.Debug(ctx, "message from local logger")
}


// end user code
adapter := console.StdoutAdapter()
lib := NewLibrary(adapter)

How to use existing adapters

Writing your own adapter

Just implement logger.Adapter interface:

type MyAdapter struct{}

func (MyAdapter) Log(context.Context, logger.Entry) {
    // here you can do whatever you want with the log entry 
}

Difference between Logger and Adapter

  • Logger is used by package/module/library author
  • Adapter is an interface to be implemented by adapters. They use real logging libraries under the hood.
  • So, why two abstractions? Simply because the smaller the Adapter interface, the easier it is to implement it. On the other hand, from library perspective, more methods means API which is easier to use.
  • Here is the architecture from the package perspective:

More examples

Advanced recipes

Why just don't create my own abstraction instead of using yala?

Yes, you can also create your own. Very often it is just an interface with a single method, like this:

type ImaginaryLogger interface {
    Log(context.Context, Entry)
}

But there are limitations for such solution:

  • such interface alone is not very easy to use in your package/module/library. You just have to write way too much boilerplate code.
  • someone who is using your package is supposed to write implementation of this interface (or you can provide prebuilt implementation for various logging libraries). In both cases this cost time and effort.
  • it is not obvious how logging API should look like. Someone would argue that is better to have a much more complicated interface like this:
type AnotherImaginaryLogger interface {
	With(field string, value interface{}) AnotherImaginaryLogger
	WithError(err error) AnotherImaginaryLogger
	Info(context.Context, string)
	Debug(context.Context, string)
	Warn(context.Context, string)
	Error(context.Context, string)
}

Unfortunately such interface is much harder to implement, than interface with a single method.

But yala is just another API. Why it is unique?

  • yala is designed for the ease of use. And by that I mean ease of use for everyone - developer logging messages, developer writing adapter and end user configuring the adapter
  • yala is using context.Context in each method call, making possible to use sophisticated request-scoped logging

YALA limitations

  • even though your package will be independent of any specific logging implementation, you still have to import github.com/elgopher/yala/logger. This package is relatively small though, compared to real logging libraries (about ~200 lines of production code) and it does not import any external libraries.
  • yala is not optimized for extreme performance, because this would hurt the developer experience and readability of the created code. Any intermediary API ads overhead - global synchronized variables, wrapper code and even polymorphism slow down the execution a bit. The overhead varies, but it is usually a matter of tens of nanoseconds per call.
Owner
Jacek Olszak
El Gopher
Jacek Olszak
Comments
  • Move context.Context parameter to Info method

    Move context.Context parameter to Info method

    context.Context should not be retained in other structures, because it makes user programs more complicated. context.Context should be passed to log methods such as Info, Debug, Warn and Error.

  • Add new ***Fields methods

    Add new ***Fields methods

    Right now developer can use fluent-interface API to log message with fields, for example:

    log.With("key1","value").
        With("key2","value").
        Info(ctx, "message")
    

    For some such design is not intuitive. They prefer to pass fields directly as an Info method parameter. Preferably a map. For example:

    log.InfoFields(ctx, "message", logger.Fields{
        "key1": "value",
        "key2": "value",
    })
    

    Also, the performance of fluent-interface design is not very high. Each With method call creates a new instance of logger with additional field which costs 1 allocation (for normal logger) and 2 allocations (for global one). Most of the time, such logger will be discarded soon (in the very same method), so allocation could be avoided.

    The proposed new logger methods can simplify the use of logger and improve the performance.

    Here are the changes:

    • new logger.Fields type which is a just a map[string]interface{}
    • both loggers (logger.Logger and logger.Global) have new methods for directly passing fields:
      • DebugFields(ctx context.Context, msg string, fields Fields)
      • InfoFields(ctx context.Context, msg string, fields Fields)
      • WarnFields(ctx context.Context, msg string, fields Fields)
      • ErrorFields(ctx context.Context, msg string, fields Fields)
    • both loggers have new methods for passing an error too:
      • ErrorCause(ctx context.Context, msg string, cause error)
      • ErrorCauseFields(ctx context.Context, msg string, cause error, fields Fields)
    • both loggers have new methods for creating a child logger with multiple fields at once:
      • WithFields(Fields)
    • logger.Entry has a new method WithFields(Fields)

    All the changes are backwards compatible.

  • Revert

    Revert "Remove Entry.With method"

    This reverts commit 80d8c30e

    It turns out that that change was unsafe from concurrency perspective. There were data races for With method of both loggers.

  • [logrus] Accept also *logrus.Logger in logrus adapter

    [logrus] Accept also *logrus.Logger in logrus adapter

    So far, only *logrus.Entry was accepted. But sometimes end user would prefer to pass *logrus.Logger directly.

    This commit also renames Adapter.Entry field to Adapter.Logger.

  • Add Global.WithSkippedCallerFrame

    Add Global.WithSkippedCallerFrame

    This method is similar to logger.Logger.WithSkippedCallerFrame()

    WithSkippedCallerFrame creates a new child logger with one more skipped caller frame. This function is handy when you want to write your own logging helpers.

  • [refactor] Simplify Global.SetAdapter

    [refactor] Simplify Global.SetAdapter

    Use adapterValue() method to get adapter atomic.Value instead of if statement. Even though this simplification adds few nanos overhead, it makes code easier to reason about and gets rid of duplicated code.

  • Support older versions of Go

    Support older versions of Go

    Is your feature request related to a problem? Please describe. Currenty yala requires Go 1.17. But some developers use older versions. It would be nice to support Go versions starting with 1.15 (at least)

    Describe the solution you'd like Yala uses atomic.Value.CompareAndSwap() method which requires Go 1.17. Maybe this method can be ported to library itself?

    Describe alternatives you've considered Maybe use different atomic struct for storing logger inside logger.Global?

Tracetest - Trace-based testing. End-to-end tests powered by your OpenTelemetry Traces.
Tracetest - Trace-based testing. End-to-end tests powered by your OpenTelemetry Traces.

End-to-end tests powered by OpenTelemetry. For QA, Dev, & Ops. Live Demo | Documentation | Twitter | Discord | Blog Click on the image or this link to

Jan 3, 2023
Hierarchical, leveled, and structured logging library for Go

spacelog Please see http://godoc.org/github.com/spacemonkeygo/spacelog for info License Copyright (C) 2014 Space Monkey, Inc. Licensed under the Apach

Apr 27, 2021
Minimal structured logging library for Go
Minimal structured logging library for Go

slog slog is a minimal structured logging library for Go. Install go get cdr.dev/slog Features Minimal API First class context.Context support First c

Dec 29, 2022
YALA - Yet Another Logging Abstraction for Go

YALA - Yet Another Logging Abstraction for Go Tiny structured logging abstraction with adapters for most popular logging Go libraries and easy way to

Dec 7, 2022
Structured logging package for Go.
Structured logging package for Go.

Package log implements a simple structured logging API inspired by Logrus, designed with centralization in mind. Read more on Medium. Handlers apexlog

Dec 24, 2022
Simple, configurable and scalable Structured Logging for Go.

log Log is a simple, highly configurable, Structured Logging library Why another logging library? There's allot of great stuff out there, but also tho

Sep 26, 2022
Structured, composable logging for Go
Structured, composable logging for Go

log15 Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is mo

Dec 18, 2022
Structured, pluggable logging for Go.
Structured, pluggable logging for Go.

Logrus Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. Logrus is in maintenance-mode. We wi

Jan 9, 2023
Structured Logging Made Easy
Structured Logging Made Easy

Structured Logging Made Easy Features Dependency Free Simple and Clean Interface Consistent Writer IOWriter, io.Writer wrapper FileWriter, rotating &

Jan 3, 2023
Blazing fast, structured, leveled logging in Go.

⚡ zap Blazing fast, structured, leveled logging in Go. Installation go get -u go.uber.org/zap Note that zap only supports the two most recent minor ve

Jan 7, 2023
Logrus is a structured, pluggable logging for Go.
Logrus is a structured, pluggable logging for Go.

Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger.

May 25, 2021
Fully asynchronous, structured, pluggable logging for Go.

logr Logr is a fully asynchronous, contextual logger for Go. It is very much inspired by Logrus but addresses two issues: Logr is fully asynchronous,

Dec 28, 2022
structured logging helper

Logart Logart is a structured logging tool that aims to simplify logging to a database It is not yet in stable state, but is used in production and ac

Apr 24, 2021
Go-metalog - Standard API for structured logging

Metalog is a standard API for structured logging and adapters for its implementa

Jan 20, 2022
Simple example of creating an `LD_PRELOAD` library in Go that hooks LibC's main function.

LD_PRELOAD in Go Simple example of creating an LD_PRELOAD library in Go that hooks LibC's main function. Code hooks __libc_start_main to run before th

Nov 9, 2022
Logger abstraction in Go

lax Logger abstraction in Go. Besides implementing own logger interface, all adapters also implement pgx.Logger interface and may implement more inter

Jan 5, 2022
Structured log interface

Structured log interface Package log provides the separation of the logging interface from its implementation and decouples the logger backend from yo

Sep 26, 2022
A minimal and extensible structured logger

⚠️ PRE-RELEASE ⚠️ DO NOT IMPORT THIS MODULE YOUR PROJECT WILL BREAK package log package log provides a minimal interface for structured logging in ser

Jan 7, 2023
Search and analysis tooling for structured logs

Zed The Zed system provides an open-source, cloud-native, and searchable data lake for semi-structured and structured data. Zed lakes utilize a supers

Jan 5, 2023