// Package tracelog provides a tracer that acts as a traditional logger.
package tracelog import ( ) // LogLevel represents the pgx logging level. See LogLevel* constants for // possible values. type LogLevel int // The values for log levels are chosen such that the zero value means that no // log level was specified. const ( LogLevelTrace = LogLevel(6) LogLevelDebug = LogLevel(5) LogLevelInfo = LogLevel(4) LogLevelWarn = LogLevel(3) LogLevelError = LogLevel(2) LogLevelNone = LogLevel(1) ) func ( LogLevel) () string { switch { case LogLevelTrace: return "trace" case LogLevelDebug: return "debug" case LogLevelInfo: return "info" case LogLevelWarn: return "warn" case LogLevelError: return "error" case LogLevelNone: return "none" default: return fmt.Sprintf("invalid level %d", ) } } // Logger is the interface used to get log output from pgx. type Logger interface { // Log a message at the given level with data key/value pairs. data may be nil. Log(ctx context.Context, level LogLevel, msg string, data map[string]any) } // LoggerFunc is a wrapper around a function to satisfy the pgx.Logger interface type LoggerFunc func(ctx context.Context, level LogLevel, msg string, data map[string]any) // Log delegates the logging request to the wrapped function func ( LoggerFunc) ( context.Context, LogLevel, string, map[string]any) { (, , , ) } // LogLevelFromString converts log level string to constant // // Valid levels: // // trace // debug // info // warn // error // none func ( string) (LogLevel, error) { switch { case "trace": return LogLevelTrace, nil case "debug": return LogLevelDebug, nil case "info": return LogLevelInfo, nil case "warn": return LogLevelWarn, nil case "error": return LogLevelError, nil case "none": return LogLevelNone, nil default: return 0, errors.New("invalid log level") } } func ( []any) []any { := make([]any, 0, len()) for , := range { switch v := .(type) { case []byte: if len() < 64 { = hex.EncodeToString() } else { = fmt.Sprintf("%x (truncated %d bytes)", [:64], len()-64) } case string: if len() > 64 { := 0 for := 0; < 64; += { _, = utf8.DecodeRuneInString([:]) } if len() > { = fmt.Sprintf("%s (truncated %d bytes)", [:], len()-) } } } = append(, ) } return } // TraceLogConfig holds the configuration for key names type TraceLogConfig struct { TimeKey string } // DefaultTraceLogConfig returns the default configuration for TraceLog func () *TraceLogConfig { return &TraceLogConfig{ TimeKey: "time", } } // TraceLog implements pgx.QueryTracer, pgx.BatchTracer, pgx.ConnectTracer, pgx.CopyFromTracer, pgxpool.AcquireTracer, // and pgxpool.ReleaseTracer. Logger and LogLevel are required. Config will be automatically initialized on the // first use if nil. type TraceLog struct { Logger Logger LogLevel LogLevel Config *TraceLogConfig ensureConfigOnce sync.Once } // ensureConfig initializes the Config field with default values if it is nil. func ( *TraceLog) () { .ensureConfigOnce.Do( func() { if .Config == nil { .Config = DefaultTraceLogConfig() } }, ) } type ctxKey int const ( _ ctxKey = iota tracelogQueryCtxKey tracelogBatchCtxKey tracelogCopyFromCtxKey tracelogConnectCtxKey tracelogPrepareCtxKey tracelogAcquireCtxKey ) type traceQueryData struct { startTime time.Time sql string args []any } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TraceQueryStartData) context.Context { return context.WithValue(, tracelogQueryCtxKey, &traceQueryData{ startTime: time.Now(), sql: .SQL, args: .Args, }) } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TraceQueryEndData) { .ensureConfig() := .Value(tracelogQueryCtxKey).(*traceQueryData) := time.Now() := .Sub(.startTime) if .Err != nil { if .shouldLog(LogLevelError) { .log(, , LogLevelError, "Query", map[string]any{"sql": .sql, "args": logQueryArgs(.args), "err": .Err, .Config.TimeKey: }) } return } if .shouldLog(LogLevelInfo) { .log(, , LogLevelInfo, "Query", map[string]any{"sql": .sql, "args": logQueryArgs(.args), .Config.TimeKey: , "commandTag": .CommandTag.String()}) } } type traceBatchData struct { startTime time.Time } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TraceBatchStartData) context.Context { return context.WithValue(, tracelogBatchCtxKey, &traceBatchData{ startTime: time.Now(), }) } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TraceBatchQueryData) { if .Err != nil { if .shouldLog(LogLevelError) { .log(, , LogLevelError, "BatchQuery", map[string]any{"sql": .SQL, "args": logQueryArgs(.Args), "err": .Err}) } return } if .shouldLog(LogLevelInfo) { .log(, , LogLevelInfo, "BatchQuery", map[string]any{"sql": .SQL, "args": logQueryArgs(.Args), "commandTag": .CommandTag.String()}) } } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TraceBatchEndData) { .ensureConfig() := .Value(tracelogBatchCtxKey).(*traceBatchData) := time.Now() := .Sub(.startTime) if .Err != nil { if .shouldLog(LogLevelError) { .log(, , LogLevelError, "BatchClose", map[string]any{"err": .Err, .Config.TimeKey: }) } return } if .shouldLog(LogLevelInfo) { .log(, , LogLevelInfo, "BatchClose", map[string]any{.Config.TimeKey: }) } } type traceCopyFromData struct { startTime time.Time TableName pgx.Identifier ColumnNames []string } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TraceCopyFromStartData) context.Context { return context.WithValue(, tracelogCopyFromCtxKey, &traceCopyFromData{ startTime: time.Now(), TableName: .TableName, ColumnNames: .ColumnNames, }) } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TraceCopyFromEndData) { .ensureConfig() := .Value(tracelogCopyFromCtxKey).(*traceCopyFromData) := time.Now() := .Sub(.startTime) if .Err != nil { if .shouldLog(LogLevelError) { .log(, , LogLevelError, "CopyFrom", map[string]any{"tableName": .TableName, "columnNames": .ColumnNames, "err": .Err, .Config.TimeKey: }) } return } if .shouldLog(LogLevelInfo) { .log(, , LogLevelInfo, "CopyFrom", map[string]any{"tableName": .TableName, "columnNames": .ColumnNames, "err": .Err, .Config.TimeKey: , "rowCount": .CommandTag.RowsAffected()}) } } type traceConnectData struct { startTime time.Time connConfig *pgx.ConnConfig } func ( *TraceLog) ( context.Context, pgx.TraceConnectStartData) context.Context { return context.WithValue(, tracelogConnectCtxKey, &traceConnectData{ startTime: time.Now(), connConfig: .ConnConfig, }) } func ( *TraceLog) ( context.Context, pgx.TraceConnectEndData) { .ensureConfig() := .Value(tracelogConnectCtxKey).(*traceConnectData) := time.Now() := .Sub(.startTime) if .Err != nil { if .shouldLog(LogLevelError) { .Logger.Log(, LogLevelError, "Connect", map[string]any{ "host": .connConfig.Host, "port": .connConfig.Port, "database": .connConfig.Database, .Config.TimeKey: , "err": .Err, }) } return } if .Conn != nil { if .shouldLog(LogLevelInfo) { .log(, .Conn, LogLevelInfo, "Connect", map[string]any{ "host": .connConfig.Host, "port": .connConfig.Port, "database": .connConfig.Database, .Config.TimeKey: , }) } } } type tracePrepareData struct { startTime time.Time name string sql string } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TracePrepareStartData) context.Context { return context.WithValue(, tracelogPrepareCtxKey, &tracePrepareData{ startTime: time.Now(), name: .Name, sql: .SQL, }) } func ( *TraceLog) ( context.Context, *pgx.Conn, pgx.TracePrepareEndData) { .ensureConfig() := .Value(tracelogPrepareCtxKey).(*tracePrepareData) := time.Now() := .Sub(.startTime) if .Err != nil { if .shouldLog(LogLevelError) { .log(, , LogLevelError, "Prepare", map[string]any{"name": .name, "sql": .sql, "err": .Err, .Config.TimeKey: }) } return } if .shouldLog(LogLevelInfo) { .log(, , LogLevelInfo, "Prepare", map[string]any{"name": .name, "sql": .sql, .Config.TimeKey: , "alreadyPrepared": .AlreadyPrepared}) } } type traceAcquireData struct { startTime time.Time } func ( *TraceLog) ( context.Context, *pgxpool.Pool, pgxpool.TraceAcquireStartData) context.Context { return context.WithValue(, tracelogAcquireCtxKey, &traceAcquireData{ startTime: time.Now(), }) } func ( *TraceLog) ( context.Context, *pgxpool.Pool, pgxpool.TraceAcquireEndData) { .ensureConfig() := .Value(tracelogAcquireCtxKey).(*traceAcquireData) := time.Now() := .Sub(.startTime) if .Err != nil { if .shouldLog(LogLevelError) { .Logger.Log(, LogLevelError, "Acquire", map[string]any{"err": .Err, .Config.TimeKey: }) } return } if .Conn != nil { if .shouldLog(LogLevelDebug) { .log(, .Conn, LogLevelDebug, "Acquire", map[string]any{.Config.TimeKey: }) } } } func ( *TraceLog) ( *pgxpool.Pool, pgxpool.TraceReleaseData) { if .shouldLog(LogLevelDebug) { // there is no context on the TraceRelease callback .log(context.Background(), .Conn, LogLevelDebug, "Release", map[string]any{}) } } func ( *TraceLog) ( LogLevel) bool { return .LogLevel >= } func ( *TraceLog) ( context.Context, *pgx.Conn, LogLevel, string, map[string]any) { if == nil { = map[string]any{} } := .PgConn() if != nil { := .PID() if != 0 { ["pid"] = } } .Logger.Log(, , , ) }