gorm0log

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Apr 29, 2024 License: MPL-2.0 Imports: 8 Imported by: 0

README

Gorm logger using zerolog as backend

Go Reference GitHub go.mod Go version GitHub License

TL; DR

// setup your logger
w := zerolog.NewConsolewriter()
w.Out = os.Stdout
log.Logger = zerolog.New(w).Level(zerolog.InfoLevel)

// setup gorm
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
	Logger: &gorm0log.Logger{
    	Logger: log.Logger,
        Config: Config{
    		SlowThreshold: 3 * time.Second, // log sql queries slower than 3s
            ErrorLevel: gorm0log.DebugCommonErr, // log common errors at debug level
            ParameterizedQueries: true, // do not log value of parameters
        },
    },
})

db.Debug().Save(&User{Name: "John Doe"}) // dumps sql

var u User
db.Where("id", -1).Find(&user) // record not found, but ignored
db.Debug().Where("id", -1).Find(&user) // record not found error is logged

var x NonExistTable
db.Where("id", 1).Find(&x) // logs error

License

Mozilla Public License, v. 2.0

Documentation

Overview

Package gorm0log provides a gorm logger using zerolog as backend.

Thanks to zerolog, we have goo performance since most features are skipped if the log message is not visible.

If you queries gorm with context (gorm.DB.WithContext), the context can be handled by a function in Config named "Customize".

Default value of Config should be fairly enough for small projects.

gorm.DB.Debug swtiches underlying zerolog.Logger to Debug level, which shows every message but Trace level ones. Some features, controled by Config, can be set to customized level. So you can disable those messages by changing its level to UseTrace.

Example
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package main

import (
	"os"

	"github.com/glebarez/sqlite"
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"gorm.io/gorm"
)

type User struct {
	ID   int
	Name string
}

func initLog() {
	w := zerolog.NewConsoleWriter()
	w.NoColor = true
	w.Out = os.Stdout
	log.Logger = zerolog.New(w).Level(zerolog.WarnLevel)
}

func initDB() (*gorm.DB, error) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		TranslateError: true,
		Logger: &Logger{
			Logger: log.Logger,
		},
	})
	if err != nil {
		return nil, err
	}

	err = db.AutoMigrate(&User{})
	if err != nil {
		return nil, err
	}

	return db, nil
}

func SaveUser(db *gorm.DB, u *User) error {
	return db.Save(u).Error
}

func LoadUser(db *gorm.DB, uid int) (*User, error) {
	var u User
	err := db.Where("id", uid).First(&u).Error
	if err != nil {
		return nil, err
	}

	return &u, nil
}

func main() {
	initLog()
	db, err := initDB()
	if err != nil {
		log.Error().Err(err).Msg("cannot init db")
		return
	}

	// this line shows sql dump
	if err = SaveUser(db.Debug(), &User{Name: "John Doe"}); err != nil {
		log.Error().Err(err).Msg("cannot insert predefined record")
		return
	}

	// no log message
	u, err := LoadUser(db, 1)
	if err != nil {
		log.Error().Err(err).Msg("cannot load predefined record")
		return
	}
	if u.Name != "John Doe" {
		log.Error().Str("name", u.Name).Msg("unexpected record loaded")
		return
	}

	// record not found error is logged to error level by default
	if u, err = LoadUser(db, 2); err == nil {
		log.Error().Interface("user", u).Msg("unexpected user found for id#2")
		return
	}

	// change log level of record not found message to hide it
	tx := db.Session(&gorm.Session{
		Logger: &Logger{
			Logger: log.Logger,
			Config: Config{
				// logs to Debug level
				ErrorLevel: DebugCommonErr,
				// it's shortcut to
				// ErrorLevel: LogErrorAt(UseDebug, CommonError),
			},
		},
	})
	if u, err = LoadUser(tx, 3); err == nil {
		log.Error().Interface("user", u).Msg("unexpected user found for id#2")
		return
	}

	// change level setting of logger to log record not found error again
	if u, err = LoadUser(tx.Debug(), 4); err == nil {
		log.Error().Interface("user", u).Msg("unexpected user found for id#2")
		return
	}

	// disable record not found log, but dumps sql
	tx = db.Session(&gorm.Session{
		Logger: &Logger{
			Logger: log.Logger.Level(zerolog.DebugLevel),
			Config: Config{ErrorLevel: IgnoreCommonErr},
		},
	})
	if u, err = LoadUser(tx, 5); err == nil {
		log.Error().Interface("user", u).Msg("unexpected user found for id#2")
		return
	}

	// dump sql with source file info
	//
	// example output:
	// <nil> DBG dump sql affected_rows=1 source_file=/path/to/this/example_test.go source_line=53 sql="SELECT * FROM `users` WHERE `id` = 1 ORDER BY `users`.`id` LIMIT 1"
	tx = db.Session(&gorm.Session{
		Logger: &Logger{
			Config: Config{Customize: LogSource("_test.go")},
			// uncomment this to actually logs it
			// Logger: log.Logger.Level(zerolog.DebugLevel),
		},
	})
	if _, err = LoadUser(tx, 1); err != nil {
		log.Error().Err(err).Msg("cannot load predefined record")
		return
	}

}
Output:

<nil> DBG dump sql affected_rows=1 sql="INSERT INTO `users` (`name`) VALUES (\"John Doe\") RETURNING `id`"
<nil> ERR a sql error occured error="record not found" affected_rows=0 sql="SELECT * FROM `users` WHERE `id` = 2 ORDER BY `users`.`id` LIMIT 1"
<nil> DBG a sql error occured error="record not found" affected_rows=0 sql="SELECT * FROM `users` WHERE `id` = 4 ORDER BY `users`.`id` LIMIT 1"
<nil> DBG dump sql affected_rows=0 sql="SELECT * FROM `users` WHERE `id` = 5 ORDER BY `users`.`id` LIMIT 1"

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CommonError

func CommonError(err error) bool

CommonError detects if err is gorm.ErrDuplicatedKey or gorm.ErrRecordNotFound.

func DebugCommonErr

func DebugCommonErr(e error, l zerolog.Logger) *zerolog.Event

DebugCommonErr is shortcut of LogErrorAt(UseDebug, CommonError).

func DefaultLogLevelMap

func DefaultLogLevelMap(i logger.LogLevel) zerolog.Level

DefaultLogLevelMap enables sql dumping if you use logger.Info log mode.

It translates Gorm's logger.Info mode to zerolog.DebugLevel, so sql dumping is logged.

func Ignore

func Ignore(l zerolog.Logger) *zerolog.Event

Ignore denotes specified message is ignored.

func IgnoreCommonErr

func IgnoreCommonErr(e error, l zerolog.Logger) *zerolog.Event

IgnoreCommonErr is shortcut of LogErrorAt(UseTrace, CommonError).

func LogErrorAt

func LogErrorAt(level func(zerolog.Logger) *zerolog.Event, cmpErr func(error) bool) func(error, zerolog.Logger) *zerolog.Event

LogErrorAt creates a function to be used at ErrorLevel of Config. It compares error using cmpErr, use specified level to log it if matched, Error level otherwise.

func LogSource

func LogSource(keywords ...string) func(context.Context, *zerolog.Event)

LogSource creates a function to provide caller info.

It iterates the stack to find fist file that matches any of keywords, puts filename and line number to json field "source_file" and "source_line".

func UseDebug

func UseDebug(l zerolog.Logger) *zerolog.Event

UseDebug denotes specified message belongs to Debug level.

func UseError

func UseError(l zerolog.Logger) *zerolog.Event

UseError denotes specified message belongs to Error level.

func UseFatal

func UseFatal(l zerolog.Logger) *zerolog.Event

UseFatal denotes specified message belongs to Fatal level.

func UseInfo

func UseInfo(l zerolog.Logger) *zerolog.Event

UseInfo denotes specified message belongs to Info level.

func UseTrace

func UseTrace(l zerolog.Logger) *zerolog.Event

UseTrace denotes specified message belongs to Trace level.

func UseWarn

func UseWarn(l zerolog.Logger) *zerolog.Event

UseWarn denotes specified message belongs to Warn level.

func WithTimeTracking

func WithTimeTracking(i logger.LogLevel) zerolog.Level

WithTimeTracking enables time tracking info if you use logger.Info log mode.

It translates Gorm's logger.Info mode to zerolog.TraceLevel, so sql dumping and time tracking info are logged.

Types

type Config

type Config struct {
	// Duration threshold of slow log, 0 or less disables it.
	SlowThreshold time.Duration
	// Log level of slow sql messages, default to [UseWarn].
	SlowLevel func(zerolog.Logger) *zerolog.Event
	// Key used to show time tracking info, default to "duration"
	Duration string

	// Log level for special error, default to log every error at Error level.
	// You might use it to change log level of non-critical errors like
	// [gorm.ErrRecordNotFound] or [gorm.ErrDuplicatedKey]. Helpers are
	// provided, see [IgnoreCommonErr] and [DebugCommonErr].
	ErrorLevel func(error, zerolog.Logger) *zerolog.Event

	// Do not log value of parameters.
	ParameterizedQueries bool

	// Dump SQL
	// Log level of sql dumping messages, default to [UseDebug].
	DumpLevel func(zerolog.Logger) *zerolog.Event
	// Adds execution time info to sql dumping message.
	DumpWithDuration bool
	// Key used to show sql dump, default to "sql".
	SQL string
	// Key used to show affected rows, default to "affected_rows".
	AffectedRows string

	// A function to log extra info, context value or call stacks for example.
	// This function is called only if the message is visible.
	Customize func(context.Context, *zerolog.Event)
}

Config is a switch for extra features of Logger.

Default value is fairly enough for general use:

  • log general error as Error level
  • record not found error as Debug level
  • dump every sql as Debug level
  • slow log message as Warn level
  • disable slow log, as you should have your own slow threshold
  • common json key
  • ignore context values

By default, gorm.DB.Debug shows sql dump. This can be changed by setting DumpLevel to UseTrace in Config.

type Logger

type Logger struct {
	zerolog.Logger
	Config
}

Logger implements logger.Interface.

This implementation provides some customizable features, take a look at Config.

Using gorm.DB.Debug switches log level of zerolog.Logger to Debug level.

Zero value means a logger that:

func (*Logger) Error

func (l *Logger) Error(ctx context.Context, msg string, args ...any)

Error implements logger.Interface, to show a message at Error level.

func (*Logger) Info

func (l *Logger) Info(ctx context.Context, msg string, args ...any)

Info implements logger.Interface, to show a message at Info level.

func (*Logger) LogMode

func (l *Logger) LogMode(lv logger.LogLevel) logger.Interface

LogMode implements logger.Interface, to control which message is visible.

func (*Logger) ParamsFilter

func (l *Logger) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{})

ParamsFilter implements gorm.ParamsFilter to check if parameters should be shown.

func (*Logger) Trace

func (l *Logger) Trace(ctx context.Context, begin time.Time, f func() (string, int64), err error)

Trace implements logger.Ingerface. It is called every query by Gorm, so we can provide useful features like slow log or sql dump.

func (*Logger) Warn

func (l *Logger) Warn(ctx context.Context, msg string, args ...any)

Warn implements logger.Interface, to show a message at Warn level.

Jump to

Keyboard shortcuts

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