Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate GORM Logger with Zerolog and Add Configuration Options for Logging and Performance #2040

Merged
merged 3 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,23 @@ ephemeral_node_inactivity_timeout: 30m
database:
type: sqlite

# Enable debug mode. Set log.level to "debug".
nadongjun marked this conversation as resolved.
Show resolved Hide resolved
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:
path: /var/lib/headscale/db.sqlite
Expand Down
5 changes: 3 additions & 2 deletions hscontrol/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
// TODO(kradalby): Integrate this with zerolog
var dbLogger logger.Interface
if cfg.Debug {
dbLogger = logger.Default
dbLogger = util.NewDBLogWrapper(&log.Logger, cfg.SlowThreshold, cfg.SkipErrRecordNotFound, cfg.ParameterizedQueries)
} else {
dbLogger = logger.Default.LogMode(logger.Silent)
}
Expand All @@ -447,7 +447,8 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
db, err := gorm.Open(
sqlite.Open(cfg.Sqlite.Path),
&gorm.Config{
Logger: dbLogger,
PrepareStmt: cfg.PrepareStmt,
Logger: dbLogger,
},
)

Expand Down
19 changes: 17 additions & 2 deletions hscontrol/types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ type DatabaseConfig struct {
Type string
Debug bool

// Type sets the gorm configuration
SlowThreshold time.Duration
SkipErrRecordNotFound bool
ParameterizedQueries bool
PrepareStmt bool

Sqlite SqliteConfig
Postgres PostgresConfig
}
Expand Down Expand Up @@ -450,6 +456,11 @@ func GetDatabaseConfig() DatabaseConfig {

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_ {
case DatabaseSqlite, DatabasePostgres:
break
Expand All @@ -461,8 +472,12 @@ func GetDatabaseConfig() DatabaseConfig {
}

return DatabaseConfig{
Type: type_,
Debug: debug,
Type: type_,
Debug: debug,
SkipErrRecordNotFound: skipErrRecordNotFound,
SlowThreshold: slowThreshold,
ParameterizedQueries: parameterizedQueries,
PrepareStmt: prepareStmt,
nadongjun marked this conversation as resolved.
Show resolved Hide resolved
Sqlite: SqliteConfig{
Path: util.AbsolutePathFromConfigPath(
viper.GetString("database.sqlite.path"),
Expand Down
75 changes: 75 additions & 0 deletions hscontrol/util/log.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package util

import (
"context"
"errors"
"time"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
gormLogger "gorm.io/gorm/logger"
"tailscale.com/types/logger"
)

Expand All @@ -14,3 +21,71 @@ func TSLogfWrapper() logger.Logf {
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
}