165 lines
4.1 KiB
Go
165 lines
4.1 KiB
Go
// see https://github.com/orandin/slog-gorm/tree/master
|
|
|
|
package gormlogs
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
gormlogger "gorm.io/gorm/logger"
|
|
"gorm.io/gorm/utils"
|
|
)
|
|
|
|
type LogType string
|
|
|
|
const (
|
|
ErrorLogType LogType = "sql_error"
|
|
SlowQueryLogType LogType = "slow_query"
|
|
DefaultLogType LogType = "default"
|
|
|
|
SourceField = "file"
|
|
ErrorField = "error"
|
|
QueryField = "query"
|
|
DurationField = "duration"
|
|
SlowQueryField = "slow_query"
|
|
RowsField = "rows"
|
|
)
|
|
|
|
// New creates a new logger for gorm.io/gorm
|
|
func New(options ...Option) *logger {
|
|
l := logger{
|
|
ignoreRecordNotFoundError: true,
|
|
errorField: ErrorField,
|
|
sourceField: SourceField,
|
|
|
|
// log levels
|
|
logLevel: map[LogType]slog.Level{
|
|
ErrorLogType: slog.LevelError,
|
|
SlowQueryLogType: slog.LevelWarn,
|
|
DefaultLogType: slog.LevelInfo,
|
|
},
|
|
}
|
|
|
|
// Apply options
|
|
for _, option := range options {
|
|
option(&l)
|
|
}
|
|
|
|
if l.slogger == nil {
|
|
// If no slogger is defined, use the default Logger
|
|
l.slogger = slog.Default()
|
|
}
|
|
|
|
return &l
|
|
}
|
|
|
|
type logger struct {
|
|
slogger *slog.Logger
|
|
ignoreTrace bool
|
|
ignoreRecordNotFoundError bool
|
|
traceAll bool
|
|
slowThreshold time.Duration
|
|
logLevel map[LogType]slog.Level
|
|
contextKeys map[string]string
|
|
|
|
sourceField string
|
|
errorField string
|
|
}
|
|
|
|
// LogMode log mode
|
|
func (l logger) LogMode(_ gormlogger.LogLevel) gormlogger.Interface {
|
|
// log level is set by slog
|
|
return l
|
|
}
|
|
|
|
// Info logs info
|
|
func (l logger) Info(ctx context.Context, msg string, args ...any) {
|
|
l.log(l.slogger.InfoContext, ctx, msg, args...)
|
|
}
|
|
|
|
// Warn logs warn messages
|
|
func (l logger) Warn(ctx context.Context, msg string, args ...any) {
|
|
l.log(l.slogger.WarnContext, ctx, msg, args...)
|
|
}
|
|
|
|
// Error logs error messages
|
|
func (l logger) Error(ctx context.Context, msg string, args ...any) {
|
|
l.log(l.slogger.ErrorContext, ctx, msg, args...)
|
|
}
|
|
|
|
// log adds context attributes and logs a message with the given slog function
|
|
func (l logger) log(f func(ctx context.Context, msg string, args ...any), ctx context.Context, msg string, args ...any) {
|
|
|
|
// Append context attributes
|
|
args = l.appendContextAttributes(ctx, args)
|
|
|
|
// Call slog
|
|
f(ctx, msg, args...)
|
|
}
|
|
|
|
// Trace logs sql message
|
|
func (l logger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
|
if l.ignoreTrace {
|
|
return // Silent
|
|
}
|
|
|
|
elapsed := time.Since(begin)
|
|
switch {
|
|
case err != nil && (!errors.Is(err, gorm.ErrRecordNotFound) || !l.ignoreRecordNotFoundError):
|
|
sql, rows := fc()
|
|
|
|
// Append context attributes
|
|
attributes := l.appendContextAttributes(ctx, []any{
|
|
slog.Any(l.errorField, err),
|
|
slog.String(QueryField, sql),
|
|
slog.Duration(DurationField, elapsed),
|
|
slog.Int64(RowsField, rows),
|
|
slog.String(l.sourceField, utils.FileWithLineNum()),
|
|
})
|
|
|
|
l.slogger.Log(ctx, l.logLevel[ErrorLogType], err.Error(), attributes...)
|
|
|
|
case l.slowThreshold != 0 && elapsed > l.slowThreshold:
|
|
sql, rows := fc()
|
|
|
|
// Append context attributes
|
|
attributes := l.appendContextAttributes(ctx, []any{
|
|
slog.Bool(SlowQueryField, true),
|
|
slog.String(QueryField, sql),
|
|
slog.Duration(DurationField, elapsed),
|
|
slog.Int64(RowsField, rows),
|
|
slog.String(l.sourceField, utils.FileWithLineNum()),
|
|
})
|
|
l.slogger.Log(ctx, l.logLevel[SlowQueryLogType], fmt.Sprintf("slow sql query [%s >= %v]", elapsed, l.slowThreshold), attributes...)
|
|
|
|
case l.traceAll:
|
|
sql, rows := fc()
|
|
|
|
// Append context attributes
|
|
attributes := l.appendContextAttributes(ctx, []any{
|
|
slog.String(QueryField, sql),
|
|
slog.Duration(DurationField, elapsed),
|
|
slog.Int64(RowsField, rows),
|
|
slog.String(l.sourceField, utils.FileWithLineNum()),
|
|
})
|
|
|
|
l.slogger.Log(ctx, l.logLevel[DefaultLogType], fmt.Sprintf("SQL query executed [%s]", elapsed), attributes...)
|
|
}
|
|
}
|
|
|
|
func (l logger) appendContextAttributes(ctx context.Context, args []any) []any {
|
|
if args == nil {
|
|
args = []any{}
|
|
}
|
|
for k, v := range l.contextKeys {
|
|
if value := ctx.Value(v); value != nil {
|
|
args = append(args, slog.Any(k, value))
|
|
}
|
|
}
|
|
return args
|
|
}
|