or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

associations.mdclause.mddatabase-operations.mdhooks.mdindex.mdlogger.mdmigrations.mdquery-building.mdschema.mdtransactions.md
tile.json

logger.mddocs/

Logger

GORM provides a flexible logging interface for tracking SQL queries, errors, and slow queries. The gorm.io/gorm/logger package includes a default logger implementation and interfaces for custom loggers.

Logger Package

import "gorm.io/gorm/logger"

Logger Interface

type Interface interface {
    LogMode(LogLevel) Interface
    Info(context.Context, string, ...interface{})
    Warn(context.Context, string, ...interface{})
    Error(context.Context, string, ...interface{})
    Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)
}

Log Levels

type LogLevel int

const (
    Silent LogLevel = iota + 1  // No logging
    Error                        // Log errors only
    Warn                         // Log warnings and errors
    Info                         // Log info, warnings, and errors
)

Usage:

import "gorm.io/gorm/logger"

// Set log level globally
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: logger.Default.LogMode(logger.Info),
})

// Set log level for session
db.Session(&gorm.Session{
    Logger: logger.Default.LogMode(logger.Silent),
})

// Enable debug mode (same as Info level)
db.Debug().Find(&users)

Logger Configuration

type Config struct {
    SlowThreshold             time.Duration  // Slow SQL threshold
    Colorful                  bool           // Enable colored output
    IgnoreRecordNotFoundError bool           // Don't log ErrRecordNotFound
    ParameterizedQueries      bool           // Log parameterized queries
    LogLevel                  LogLevel       // Log level
}

Creating a Logger

// Writer interface for logger output
type Writer interface {
    Printf(string, ...interface{})
}

// Create new logger
func New(writer Writer, config Config) Interface

Usage:

import (
    "log"
    "os"
    "time"
    "gorm.io/gorm/logger"
)

// Create custom logger
newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    logger.Config{
        SlowThreshold:             200 * time.Millisecond, // Slow SQL threshold
        LogLevel:                  logger.Info,            // Log level
        IgnoreRecordNotFoundError: true,                   // Ignore ErrRecordNotFound
        Colorful:                  true,                   // Enable color
        ParameterizedQueries:      false,                  // Log with actual values
    },
)

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: newLogger,
})

Built-in Loggers

var (
    // Default logger writes to stdout with colors
    Default Interface

    // Discard logger that ignores all logs
    Discard Interface

    // Recorder logger that records SQL for testing
    Recorder Interface
)

Usage:

import "gorm.io/gorm/logger"

// Use default logger
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: logger.Default,
})

// Disable all logging
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: logger.Discard,
})

// Use recorder for testing
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: logger.Recorder,
})

// Get recorded SQL
sql := db.Statement.SQL.String()

Color Constants

ANSI color codes for terminal output.

const (
    Reset       = "\033[0m"
    Red         = "\033[31m"
    Green       = "\033[32m"
    Yellow      = "\033[33m"
    Blue        = "\033[34m"
    Magenta     = "\033[35m"
    Cyan        = "\033[36m"
    White       = "\033[37m"
    BlueBold    = "\033[34;1m"
    MagentaBold = "\033[35;1m"
    RedBold     = "\033[31;1m"
    YellowBold  = "\033[33;1m"
)

Logger Methods

LogMode

Set the log level and return a new logger instance.

logger := logger.Default.LogMode(logger.Info)
db.Session(&gorm.Session{Logger: logger})

Info

Log informational messages.

logger.Info(ctx, "Custom info message: %s", "details")

Warn

Log warning messages.

logger.Warn(ctx, "Custom warning: %s", "warning details")

Error

Log error messages.

logger.Error(ctx, "Custom error: %v", err)

Trace

Log SQL execution trace (called automatically by GORM).

// Automatically called by GORM for each query
logger.Trace(ctx, begin, func() (string, int64) {
    return sql, rowsAffected
}, err)

Custom Logger Implementation

Implement the logger interface for custom logging behavior.

import (
    "context"
    "fmt"
    "time"
    "gorm.io/gorm/logger"
)

type CustomLogger struct {
    LogLevel logger.LogLevel
}

func (l *CustomLogger) LogMode(level logger.LogLevel) logger.Interface {
    newLogger := *l
    newLogger.LogLevel = level
    return &newLogger
}

func (l *CustomLogger) Info(ctx context.Context, msg string, data ...interface{}) {
    if l.LogLevel >= logger.Info {
        fmt.Printf("[INFO] "+msg+"\n", data...)
    }
}

func (l *CustomLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
    if l.LogLevel >= logger.Warn {
        fmt.Printf("[WARN] "+msg+"\n", data...)
    }
}

func (l *CustomLogger) Error(ctx context.Context, msg string, data ...interface{}) {
    if l.LogLevel >= logger.Error {
        fmt.Printf("[ERROR] "+msg+"\n", data...)
    }
}

func (l *CustomLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
    if l.LogLevel <= logger.Silent {
        return
    }

    elapsed := time.Since(begin)
    sql, rows := fc()

    if err != nil && l.LogLevel >= logger.Error {
        fmt.Printf("[ERROR] SQL: %s, Duration: %v, Error: %v\n", sql, elapsed, err)
    } else if l.LogLevel >= logger.Info {
        fmt.Printf("[SQL] %s, Duration: %v, Rows: %d\n", sql, elapsed, rows)
    }
}

// Use custom logger
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: &CustomLogger{LogLevel: logger.Info},
})

Logging Specific Operations

Session-level Logging

// Enable logging for specific query
db.Debug().Where("age > ?", 18).Find(&users)

// Custom logger for specific session
customLogger := logger.Default.LogMode(logger.Info)
db.Session(&gorm.Session{
    Logger: customLogger,
}).Find(&users)

// Silent mode for specific query
db.Session(&gorm.Session{
    Logger: logger.Default.LogMode(logger.Silent),
}).Create(&user)

Conditional Logging

logLevel := logger.Silent
if os.Getenv("DEBUG") == "true" {
    logLevel = logger.Info
}

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: logger.Default.LogMode(logLevel),
})

Slow Query Logging

Configure threshold for slow query warnings.

newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags),
    logger.Config{
        SlowThreshold: 200 * time.Millisecond,  // Log queries slower than 200ms
        LogLevel:      logger.Warn,              // Only log slow queries
    },
)

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: newLogger,
})

Parameterized Queries

Control whether to log parameterized queries or queries with actual values.

// Log with actual values (default)
newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags),
    logger.Config{
        ParameterizedQueries: false,
    },
)
// Output: SELECT * FROM users WHERE age = 18

// Log with placeholders
newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags),
    logger.Config{
        ParameterizedQueries: true,
    },
)
// Output: SELECT * FROM users WHERE age = ?

Ignoring RecordNotFound Errors

newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags),
    logger.Config{
        IgnoreRecordNotFoundError: true,  // Don't log ErrRecordNotFound
        LogLevel:                  logger.Error,
    },
)

db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: newLogger,
})

// ErrRecordNotFound will not be logged
var user User
db.First(&user, 9999)  // No error log if not found

Context-aware Logging

Pass context through queries for request tracking.

// Create context with request ID
ctx := context.WithValue(context.Background(), "request_id", "abc-123")

// Context-aware logger
type ContextLogger struct {
    logger.Interface
}

func (l *ContextLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
    requestID := ctx.Value("request_id")
    sql, rows := fc()
    elapsed := time.Since(begin)

    fmt.Printf("[%v] SQL: %s, Duration: %v, Rows: %d\n",
        requestID, sql, elapsed, rows)

    // Call original Trace
    l.Interface.Trace(ctx, begin, fc, err)
}

// Use with context
db.WithContext(ctx).Find(&users)

Structured Logging

Integrate with structured logging libraries.

import (
    "context"
    "time"
    "github.com/sirupsen/logrus"
    "gorm.io/gorm/logger"
)

type LogrusLogger struct {
    Logger *logrus.Logger
    LogLevel logger.LogLevel
}

func (l *LogrusLogger) LogMode(level logger.LogLevel) logger.Interface {
    newLogger := *l
    newLogger.LogLevel = level
    return &newLogger
}

func (l *LogrusLogger) Info(ctx context.Context, msg string, data ...interface{}) {
    if l.LogLevel >= logger.Info {
        l.Logger.WithContext(ctx).Infof(msg, data...)
    }
}

func (l *LogrusLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
    if l.LogLevel >= logger.Warn {
        l.Logger.WithContext(ctx).Warnf(msg, data...)
    }
}

func (l *LogrusLogger) Error(ctx context.Context, msg string, data ...interface{}) {
    if l.LogLevel >= logger.Error {
        l.Logger.WithContext(ctx).Errorf(msg, data...)
    }
}

func (l *LogrusLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
    if l.LogLevel <= logger.Silent {
        return
    }

    elapsed := time.Since(begin)
    sql, rows := fc()

    fields := logrus.Fields{
        "sql":      sql,
        "duration": elapsed,
        "rows":     rows,
    }

    if err != nil {
        fields["error"] = err
        l.Logger.WithContext(ctx).WithFields(fields).Error("SQL Error")
    } else {
        l.Logger.WithContext(ctx).WithFields(fields).Info("SQL Trace")
    }
}

// Use Logrus logger
logrusLogger := logrus.New()
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: &LogrusLogger{
        Logger:   logrusLogger,
        LogLevel: logger.Info,
    },
})

Testing with Recorder

Use the recorder logger for testing SQL generation.

import (
    "testing"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
    "gorm.io/driver/sqlite"
)

func TestQuery(t *testing.T) {
    db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
        Logger: logger.Recorder,
    })
    if err != nil {
        t.Fatal(err)
    }

    // Perform query
    var users []User
    db.Where("age > ?", 18).Find(&users)

    // Check SQL was generated correctly
    sql := db.Statement.SQL.String()
    if !strings.Contains(sql, "WHERE age > ?") {
        t.Errorf("Expected WHERE clause in SQL: %s", sql)
    }
}

Best Practices

Production Logging

// Use appropriate log level
productionLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags),
    logger.Config{
        SlowThreshold:             500 * time.Millisecond,
        LogLevel:                  logger.Warn,  // Only errors and slow queries
        IgnoreRecordNotFoundError: true,
        Colorful:                  false,  // Disable colors in production
        ParameterizedQueries:      true,   // Don't log sensitive data
    },
)

Development Logging

// Verbose logging for development
devLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags),
    logger.Config{
        SlowThreshold:             200 * time.Millisecond,
        LogLevel:                  logger.Info,  // Log all queries
        IgnoreRecordNotFoundError: false,
        Colorful:                  true,  // Enable colors
        ParameterizedQueries:      false, // Show actual values
    },
)

Utility Functions

Helper functions for logging and SQL debugging.

// ExplainSQL generates SQL string with parameters interpolated for debugging
// WARNING: Do NOT execute the returned SQL - it's for logging only and may have SQL injection vulnerabilities
func ExplainSQL(sql string, numericPlaceholder *regexp.Regexp, escaper string, avars ...interface{}) string

// NewSlogLogger creates a new logger using Go's structured logging (log/slog)
func NewSlogLogger(logger *slog.Logger, config Config) Interface

// RecorderParamsFilter is a params filter for SQL recorder
var RecorderParamsFilter func(ctx context.Context, sql string, params ...interface{}) (string, []interface{})

Usage:

import (
    "log/slog"
    "os"
    "regexp"
    "gorm.io/gorm/logger"
)

// Use slog-based logger
slogger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    Logger: logger.NewSlogLogger(slogger, logger.Config{
        LogLevel:                  logger.Info,
        SlowThreshold:             200 * time.Millisecond,
        IgnoreRecordNotFoundError: true,
    }),
})

// Use ExplainSQL for debugging
sql := "SELECT * FROM users WHERE id = ? AND name = ?"
params := []interface{}{1, "Alice"}
explainedSQL := logger.ExplainSQL(sql, nil, "'", params...)
fmt.Println(explainedSQL) // SELECT * FROM users WHERE id = 1 AND name = 'Alice'

Sensitive Data

// Filter sensitive data from logs
type FilteredLogger struct {
    logger.Interface
}

func (l *FilteredLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
    sql, rows := fc()

    // Filter sensitive data
    sql = strings.ReplaceAll(sql, "password", "***")
    sql = strings.ReplaceAll(sql, "token", "***")

    // Log filtered SQL
    elapsed := time.Since(begin)
    fmt.Printf("SQL: %s, Duration: %v, Rows: %d\n", sql, elapsed, rows)
}