Integrate GORM Logger with Zerolog and Add Configuration Options for Logging and Performance (#2040)
* Integrate GORM logger with zerolog and add custom GORM configuration options * Add GormConfig struct to group GORM-related settings * Update debug mode instruction in config-example.yaml Co-authored-by: Kristoffer Dalby <kristoffer@dalby.cc> --------- Co-authored-by: Kristoffer Dalby <kristoffer@dalby.cc>
This commit is contained in:
parent
ac8491efec
commit
fdc034e8ae
4 changed files with 118 additions and 2 deletions
|
@ -140,6 +140,23 @@ ephemeral_node_inactivity_timeout: 30m
|
||||||
database:
|
database:
|
||||||
type: sqlite
|
type: sqlite
|
||||||
|
|
||||||
|
# Enable debug mode. This setting requires the log.level to be set to "debug" or "trace".
|
||||||
|
debug: false
|
||||||
|
|
||||||
|
# GORM configuration settings.
|
||||||
|
gorm:
|
||||||
|
# Enable prepared statements.
|
||||||
|
prepare_stmt: true
|
||||||
|
|
||||||
|
# Enable parameterized queries.
|
||||||
|
parameterized_queries: true
|
||||||
|
|
||||||
|
# Skip logging "record not found" errors.
|
||||||
|
skip_err_record_not_found: true
|
||||||
|
|
||||||
|
# Threshold for slow queries in milliseconds.
|
||||||
|
slow_threshold: 1000
|
||||||
|
|
||||||
# SQLite config
|
# SQLite config
|
||||||
sqlite:
|
sqlite:
|
||||||
path: /var/lib/headscale/db.sqlite
|
path: /var/lib/headscale/db.sqlite
|
||||||
|
|
|
@ -426,7 +426,7 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
|
||||||
// TODO(kradalby): Integrate this with zerolog
|
// TODO(kradalby): Integrate this with zerolog
|
||||||
var dbLogger logger.Interface
|
var dbLogger logger.Interface
|
||||||
if cfg.Debug {
|
if cfg.Debug {
|
||||||
dbLogger = logger.Default
|
dbLogger = util.NewDBLogWrapper(&log.Logger, cfg.Gorm.SlowThreshold, cfg.Gorm.SkipErrRecordNotFound, cfg.Gorm.ParameterizedQueries)
|
||||||
} else {
|
} else {
|
||||||
dbLogger = logger.Default.LogMode(logger.Silent)
|
dbLogger = logger.Default.LogMode(logger.Silent)
|
||||||
}
|
}
|
||||||
|
@ -447,6 +447,7 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
|
||||||
db, err := gorm.Open(
|
db, err := gorm.Open(
|
||||||
sqlite.Open(cfg.Sqlite.Path),
|
sqlite.Open(cfg.Sqlite.Path),
|
||||||
&gorm.Config{
|
&gorm.Config{
|
||||||
|
PrepareStmt: cfg.Gorm.PrepareStmt,
|
||||||
Logger: dbLogger,
|
Logger: dbLogger,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -120,11 +120,22 @@ type PostgresConfig struct {
|
||||||
ConnMaxIdleTimeSecs int
|
ConnMaxIdleTimeSecs int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GormConfig struct {
|
||||||
|
Debug bool
|
||||||
|
SlowThreshold time.Duration
|
||||||
|
SkipErrRecordNotFound bool
|
||||||
|
ParameterizedQueries bool
|
||||||
|
PrepareStmt bool
|
||||||
|
}
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
type DatabaseConfig struct {
|
||||||
// Type sets the database type, either "sqlite3" or "postgres"
|
// Type sets the database type, either "sqlite3" or "postgres"
|
||||||
Type string
|
Type string
|
||||||
Debug bool
|
Debug bool
|
||||||
|
|
||||||
|
// Type sets the gorm configuration
|
||||||
|
Gorm GormConfig
|
||||||
|
|
||||||
Sqlite SqliteConfig
|
Sqlite SqliteConfig
|
||||||
Postgres PostgresConfig
|
Postgres PostgresConfig
|
||||||
}
|
}
|
||||||
|
@ -486,6 +497,11 @@ func GetDatabaseConfig() DatabaseConfig {
|
||||||
|
|
||||||
type_ := viper.GetString("database.type")
|
type_ := viper.GetString("database.type")
|
||||||
|
|
||||||
|
skipErrRecordNotFound := viper.GetBool("database.gorm.skip_err_record_not_found")
|
||||||
|
slowThreshold := viper.GetDuration("database.gorm.slow_threshold") * time.Millisecond
|
||||||
|
parameterizedQueries := viper.GetBool("database.gorm.parameterized_queries")
|
||||||
|
prepareStmt := viper.GetBool("database.gorm.prepare_stmt")
|
||||||
|
|
||||||
switch type_ {
|
switch type_ {
|
||||||
case DatabaseSqlite, DatabasePostgres:
|
case DatabaseSqlite, DatabasePostgres:
|
||||||
break
|
break
|
||||||
|
@ -499,6 +515,13 @@ func GetDatabaseConfig() DatabaseConfig {
|
||||||
return DatabaseConfig{
|
return DatabaseConfig{
|
||||||
Type: type_,
|
Type: type_,
|
||||||
Debug: debug,
|
Debug: debug,
|
||||||
|
Gorm: GormConfig{
|
||||||
|
Debug: debug,
|
||||||
|
SkipErrRecordNotFound: skipErrRecordNotFound,
|
||||||
|
SlowThreshold: slowThreshold,
|
||||||
|
ParameterizedQueries: parameterizedQueries,
|
||||||
|
PrepareStmt: prepareStmt,
|
||||||
|
},
|
||||||
Sqlite: SqliteConfig{
|
Sqlite: SqliteConfig{
|
||||||
Path: util.AbsolutePathFromConfigPath(
|
Path: util.AbsolutePathFromConfigPath(
|
||||||
viper.GetString("database.sqlite.path"),
|
viper.GetString("database.sqlite.path"),
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
gormLogger "gorm.io/gorm/logger"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,3 +21,71 @@ func TSLogfWrapper() logger.Logf {
|
||||||
log.Debug().Caller().Msgf(format, args...)
|
log.Debug().Caller().Msgf(format, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DBLogWrapper struct {
|
||||||
|
Logger *zerolog.Logger
|
||||||
|
Level zerolog.Level
|
||||||
|
Event *zerolog.Event
|
||||||
|
SlowThreshold time.Duration
|
||||||
|
SkipErrRecordNotFound bool
|
||||||
|
ParameterizedQueries bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDBLogWrapper(origin *zerolog.Logger, slowThreshold time.Duration, skipErrRecordNotFound bool, parameterizedQueries bool) *DBLogWrapper {
|
||||||
|
l := &DBLogWrapper{
|
||||||
|
Logger: origin,
|
||||||
|
Level: origin.GetLevel(),
|
||||||
|
SlowThreshold: slowThreshold,
|
||||||
|
SkipErrRecordNotFound: skipErrRecordNotFound,
|
||||||
|
ParameterizedQueries: parameterizedQueries,
|
||||||
|
}
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBLogWrapperOption func(*DBLogWrapper)
|
||||||
|
|
||||||
|
func (l *DBLogWrapper) LogMode(gormLogger.LogLevel) gormLogger.Interface {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DBLogWrapper) Info(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.Logger.Info().Msgf(msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DBLogWrapper) Warn(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.Logger.Warn().Msgf(msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DBLogWrapper) Error(ctx context.Context, msg string, data ...interface{}) {
|
||||||
|
l.Logger.Error().Msgf(msg, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DBLogWrapper) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
||||||
|
elapsed := time.Since(begin)
|
||||||
|
sql, rowsAffected := fc()
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"duration": elapsed,
|
||||||
|
"sql": sql,
|
||||||
|
"rowsAffected": rowsAffected,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && !(errors.Is(err, gorm.ErrRecordNotFound) && l.SkipErrRecordNotFound) {
|
||||||
|
l.Logger.Error().Err(err).Fields(fields).Msgf("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.SlowThreshold != 0 && elapsed > l.SlowThreshold {
|
||||||
|
l.Logger.Warn().Fields(fields).Msgf("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Logger.Debug().Fields(fields).Msgf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DBLogWrapper) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) {
|
||||||
|
if l.ParameterizedQueries {
|
||||||
|
return sql, nil
|
||||||
|
}
|
||||||
|
return sql, params
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue