/logger

✏️ A Go wrapper for Logrus, Errors, Mongo and Facebook Workplace giving you extremely detailed log reports.

Primary LanguageGoMIT LicenseMIT

Logger Logo

made-with-Go Go Report Card Maintainability Test codecov go.dev reference

✏️ Logger

A Go wrapper for Logrus, Errors, Mongo and Facebook Workplace giving you extremely detailed log reports. This package is designed to be used with github.com/ainsleyclark/errors for error reporting with codes, messages and more.

Overview

  • ✅ Log a wide variety of log levels.
  • ✅ Logs with custom errors featuring codes, messages and lifelines.
  • ✅ Beautiful middleware and formatters for entries (see screenshots below).
  • ✅ Facebook Workplace integration, any error marked as INTERNAL will be sent to WP.
  • ✅ Mongo integration, if specified log entries will be sent to a Mongo collection.

Why?

Detailed and verbose logging is important to any application or API. This package aims to make it easy for APIs to log errors to a central location, using a Logrus Hook.

Workplace

Installation

go get -u github.com/ainsleyclark/logger

Quick Start

Get started with the Logger by calling logger.New() and creating new options. The service is required, this is the name of your currently running app.

func QuickStart() error {
	err := logger.New(context.TODO(), logger.NewOptions().Service("service"))
	if err != nil {
		return err
	}

	logger.Trace("Trace Entry")
	logger.Debug("Debug Entry")
	logger.Info("Info Entry")
	logger.Warn("Warn Entry")
	logger.Error("Error Entry")
	logger.WithError(errors.NewInternal(errors.New("error"), "message", "op")).Error()
	logger.Fatal("Fatal Entry")

	return nil
}

Outputs:

Logger Entries

Fields

Fields allow you to log out key value pairs to the logger that will appear under data. The simplest way to use the logger is simply the package-level exported logger.

func Fields() {
	logger.WithFields(types.Fields{
		"animal": "walrus",
	}).Info("A walrus appears")
}

Errors

This package is designed to work with [github.com/ainsleyclark/errors][https://github.com/ainsleyclark/errors] as such the WithError function can be used to log deatiled and rich error data.

func WithError() {
	logger.WithError(errors.NewInternal(errors.New("error"), "message", "op")).Error()
}

Middleware

Middleware is provided out of the box in the form of a fire hook. Upon receiving a request from the API, calling logger.Firewill send the log entry to stdout with detailed request information and meta.

func Middleware(r *http.Request) {
	logger.Fire(logger.FireHook{
		Request:      r,
		Status:       http.StatusOK,
		Message:      "Message from API",
		Data:         map[string]any{},
		RequestTime:  time.Now(),
		ResponseTime: time.Now(),
		Latency:      100,
	})
}

Recipes

Simple

Creates a simple logger with stdout.

func Simple() error {
	opts := logger.NewOptions().
		Service("service").
		Prefix("prefix").
		DefaultStatus("status")

	err := logger.New(context.Background(), opts)
	if err != nil {
		return err
	}

	logger.Info("Hello from Logger!")

	return nil
}

WithWorkplace

Create a logger with Facebook Workplace integration. A token and a thread are required to send any error code that has been marked as errors.INTERNAL to thread ID passed.

func WithWorkplace() error {
	opts := logger.NewOptions().
		Service("api").
		WithWorkplaceNotifier("token", "thread", nil, nil)

	err := logger.New(context.Background(), opts)
	if err != nil {
		return err
	}

	logger.Info("Hello from Logger!")

	return nil
}

Workplace CallBack

You can pass a function to WithWorkplaceNotifier as the second argument which is a callback function to determine if the log entry should be sent to a thread, an example is below:

func WithWorkplaceReport() {
	// Don't send the message to Workplace if there is no error.
	workplaceCallBack := func(entry types.Entry) bool {
		if !entry.HasError() {
			return false
		}
		return true
	}

	_ = logger.NewOptions().
	Service("api").
	WithWorkplaceNotifier("token", "thread", workplaceCallBack, nil)

	// etc
}

Workplace Formatter

You can pass a function to WithWorkplaceNotifier as the third argument which is a callback function to write the message to Workplace. This is where you can customise the message easily and return a formatted string.

func WithWorkplaceReport() {
	// Format the message with the supplied arguments.
	workplaceCallBack := func(entry types.Entry) bool {
		if !entry.HasError() {
			return false
		}
		return true
	}

	_ = logger.NewOptions().
	Service("api").
	WithWorkplaceNotifier("token", "thread", workplaceCallBack, nil)

	// etc
}

WithSlack

Create a logger with Facebook Slack integration. A token and a channel are required to send any error code that has been marked as errors.INTERNAL to thread ID passed.

Note All formatting and callbacks are available with Slack. See above for more details.

func WithSlack() error {
	opts := logger.NewOptions().
		Service("api").
		WithSlackNotifier("token", "#channel", nil, nil)

	err := logger.New(context.Background(), opts)
	if err != nil {
		return err
	}

	logger.Info("Hello from Logger!")

	return nil
}

WithMongo

Create a logger with Mongo integration. All logs are sent to the collection passed using github.com/ainsleyclark/mogurs.

func WithMongo() error {
	clientOptions := options.Client().
		ApplyURI(os.Getenv("MONGO_CONNECTION")).
		SetServerAPIOptions(options.ServerAPI(options.ServerAPIVersion1))

	client, err := mongo.Connect(context.Background(), clientOptions)
	if err != nil {
		log.Fatalln(err)
	}

	opts := logger.NewOptions().
		Service("api").
		WithMongoCollection(client.Database("logs").Collection("col"))

	err = logger.New(context.Background(), opts)
	if err != nil {
		return err
	}

	logger.Info("Hello from Logger!")

	return nil
}

Mongo CallBack

You can pass a function to WithWorkplaceNotifier as the second argument which is a callback function to determine if the log entry should be stored within Mongo, an example is below:

func WithMongoReport() {
	// Don't send the message to Mongo if there is no error.
	mongoCallBack := func(entry types.Entry) bool {
		if !entry.HasError() {
			return false
		}
		return true
	}

	client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(os.Getenv("MONGO_CONNECTION")))
	if err != nil {
		log.Fatalln(err)
	}

	_ = logger.NewOptions().
	Service("api").
	WithMongoCollection(client.Database("logs").Collection("col"), mongoCallBack)

	// etc
}

KitchenSink

Boostrap all Log integrations.

func KitchenSink() error {
	clientOptions := options.Client().
		ApplyURI(os.Getenv("MONGO_CONNECTION")).
		SetServerAPIOptions(options.ServerAPI(options.ServerAPIVersion1))

	client, err := mongo.Connect(context.Background(), clientOptions)
	if err != nil {
		log.Fatalln(err)
	}

	opts := logger.NewOptions().
		Service("service").
		Prefix("prefix").
		DefaultStatus("status").
		WithWorkplaceNotifier("token", "thread").
		WithSlackNotifier("token", "#channel").
		WithMongoCollection(client.Database("logs").Collection("col"))

	err = logger.New(context.Background(), opts)
	if err != nil {
		return err
	}

	logger.Info("Hello from Logger!")

	return nil
}

Contributing

Please feel free to make a pull request if you think something should be added to this package!

Credits

Shout out to the incredible Maria Letta for her excellent Gopher illustrations.

Licence

Code Copyright 2022 Errors. Code released under the MIT Licence.