package tracelog
import (
)
type LogLevel int
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", )
}
}
type Logger interface {
Log(ctx context.Context, level LogLevel, msg string, data map[string]any)
}
type LoggerFunc func(ctx context.Context, level LogLevel, msg string, data map[string]any)
func ( LoggerFunc) ( context.Context, LogLevel, string, map[string]any) {
(, , , )
}
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
}
type TraceLogConfig struct {
TimeKey string
}
func () *TraceLogConfig {
return &TraceLogConfig{
TimeKey: "time",
}
}
type TraceLog struct {
Logger Logger
LogLevel LogLevel
Config *TraceLogConfig
ensureConfigOnce sync.Once
}
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) {
.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(, , , )
}