yala

module
v0.10.1 Latest Latest
Warning

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

Go to latest
Published: Jan 23, 2022 License: MIT

README

YALA - Yet Another Logging Abstraction for Go

Go Go Reference

Tiny structured logging abstraction or facade with adapters for most popular Go logging libraries and an easy way to create a new adapter.

Supported logging implementations

logrus, zap, zerolog, glog, log15 and standard fmt and log packages

When to use?

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

Installation

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

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/jacekolszak/yala/logger"
)

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

var ErrSome = errors.New("ErrSome")

func Function(ctx context.Context) {
	Logger.Debug(ctx, "Debug message")
	Logger.With(ctx, "field_name", "value").Info("Message with field")
	Logger.WithError(ctx, ErrSome).Error("Message with error")
}
Specify adapter - a real logger implementation.
package main

import (
	"context"

	"github.com/jacekolszak/yala/adapter/printer"
	"lib"
)

// End user decides what library to plug in.
func main() {
	adapter := printer.StdoutAdapter() // will use fmt.Println
	lib.Logger.SetAdapter(adapter)     // set the 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)         
    return YourLib{localLogger: localLogger}
}

type YourLib struct {
	localLogger logger.LocalLogger
}

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

// end user code
adapter := printer.StdoutAdapter()
lib := NewLibrary(adapter)
More examples
Advanced examples
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.
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 module/library/package
  • someone who is using your module is supposed to write his own adapter of this interface (or you can provide adapters which of course takes your valuable time)
  • it is not obvious how logging API should look like
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 anyone - developer who logs 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 module will be independent of any specific logging implementation, you still have to import github.com/jacekolszak/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.

Directories

Path Synopsis
adapter
contextadapter
Package contextadapter provides logger.Adapter implementation which lookups logger in the context.Context and wrap it inside adapter.
Package contextadapter provides logger.Adapter implementation which lookups logger in the context.Context and wrap it inside adapter.
logfmt
Package logfmt provides functions encoding logger.Field using logfmt format, for example: "field=value".
Package logfmt provides functions encoding logger.Field using logfmt format, for example: "field=value".
merge
Package merge provides logger.Adapter implementation which merges each logger.Entry with context.Context using provided function.
Package merge provides logger.Adapter implementation which merges each logger.Entry with context.Context using provided function.
Package logger provides structured logging abstraction or facade, to be used by code which is not aware what logging library is used by end user.
Package logger provides structured logging abstraction or facade, to be used by code which is not aware what logging library is used by end user.

Jump to

Keyboard shortcuts

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