20251209
This commit is contained in:
164
archive/bbs-go/server/internal/pkg/gormlogs/gorm.go
Normal file
164
archive/bbs-go/server/internal/pkg/gormlogs/gorm.go
Normal file
@@ -0,0 +1,164 @@
|
||||
// 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
|
||||
}
|
||||
76
archive/bbs-go/server/internal/pkg/gormlogs/gorm_options.go
Normal file
76
archive/bbs-go/server/internal/pkg/gormlogs/gorm_options.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// see https://github.com/orandin/slog-gorm/tree/master
|
||||
|
||||
package gormlogs
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Option func(l *logger)
|
||||
|
||||
// WithLogger defines a custom logger to use
|
||||
func WithLogger(log *slog.Logger) Option {
|
||||
return func(l *logger) {
|
||||
l.slogger = log
|
||||
}
|
||||
}
|
||||
|
||||
// WithSourceField defines the field to set the file name and line number of the current file
|
||||
func WithSourceField(field string) Option {
|
||||
return func(l *logger) {
|
||||
l.sourceField = field
|
||||
}
|
||||
}
|
||||
|
||||
// WithErrorField defines the field to set the error
|
||||
func WithErrorField(field string) Option {
|
||||
return func(l *logger) {
|
||||
l.errorField = field
|
||||
}
|
||||
}
|
||||
|
||||
// WithSlowThreshold defines the threshold above which a sql query is considered slow
|
||||
func WithSlowThreshold(threshold time.Duration) Option {
|
||||
return func(l *logger) {
|
||||
l.slowThreshold = threshold
|
||||
}
|
||||
}
|
||||
|
||||
// WithTraceAll enables mode which logs all SQL messages.
|
||||
func WithTraceAll() Option {
|
||||
return func(l *logger) {
|
||||
l.traceAll = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetLogLevel sets a new slog.Level for a LogType.
|
||||
func SetLogLevel(key LogType, level slog.Level) Option {
|
||||
return func(l *logger) {
|
||||
l.logLevel[key] = level
|
||||
}
|
||||
}
|
||||
|
||||
// WithRecordNotFoundError allows the slogger to log gorm.ErrRecordNotFound errors
|
||||
func WithRecordNotFoundError() Option {
|
||||
return func(l *logger) {
|
||||
l.ignoreRecordNotFoundError = false
|
||||
}
|
||||
}
|
||||
|
||||
// WithIgnoreTrace disables the tracing of SQL queries by the slogger
|
||||
func WithIgnoreTrace() Option {
|
||||
return func(l *logger) {
|
||||
l.ignoreTrace = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithContextValue adds a context value to the log
|
||||
func WithContextValue(slogAttrName, contextKey string) Option {
|
||||
return func(l *logger) {
|
||||
if l.contextKeys == nil {
|
||||
l.contextKeys = make(map[string]string, 0)
|
||||
}
|
||||
l.contextKeys[slogAttrName] = contextKey
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user