-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlogger.go
More file actions
138 lines (124 loc) · 3.42 KB
/
logger.go
File metadata and controls
138 lines (124 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package norm
import (
"encoding/hex"
"fmt"
"log"
"reflect"
"strings"
"time"
)
// Field represents a structured logging field
type Field struct {
Key string
Value any
}
// LogMode controls verbosity of ORM logging
type LogMode int
const (
// LogSilent disables all logs unless a chain explicitly enables debug
LogSilent LogMode = iota
// LogError logs only errors
LogError
// LogWarn logs warnings and errors (unused for now but reserved)
LogWarn
// LogInfo logs queries and errors
LogInfo
// LogDebug logs everything at debug level
LogDebug
)
type Logger interface {
Debug(msg string, fields ...Field)
Info(msg string, fields ...Field)
Warn(msg string, fields ...Field)
Error(msg string, fields ...Field)
}
// NoopLogger is a default no-op logger
type NoopLogger struct{}
func (NoopLogger) Debug(msg string, fields ...Field) {}
func (NoopLogger) Info(msg string, fields ...Field) {}
func (NoopLogger) Warn(msg string, fields ...Field) {}
func (NoopLogger) Error(msg string, fields ...Field) {}
// StdLogger logs to the standard library logger
type StdLogger struct{}
func (StdLogger) Debug(msg string, fields ...Field) { stdLogPrint("DEBUG", msg, fields...) }
func (StdLogger) Info(msg string, fields ...Field) { stdLogPrint("INFO", msg, fields...) }
func (StdLogger) Warn(msg string, fields ...Field) { stdLogPrint("WARN", msg, fields...) }
func (StdLogger) Error(msg string, fields ...Field) { stdLogPrint("ERROR", msg, fields...) }
func stdLogPrint(level string, msg string, fields ...Field) {
for _, f := range fields {
if f.Key == "stmt" {
if s, ok := f.Value.(string); ok && s != "" {
log.Printf("%s", s)
return
}
}
}
log.Printf("[%s] %s %s", level, msg, formatFields(fields))
}
func formatFields(fields []Field) string {
if len(fields) == 0 {
return ""
}
parts := make([]string, 0, len(fields))
for _, f := range fields {
var val string
if s, ok := f.Value.(string); ok {
val = s
} else {
val = fmt.Sprintf("%v", f.Value)
}
parts = append(parts, f.Key+"="+val)
}
return strings.Join(parts, " ")
}
// inlineSQL returns a paste-ready SQL with all $n placeholders inlined as SQL literals and a trailing semicolon
func inlineSQL(query string, args []any) string {
if len(args) == 0 {
qs := strings.TrimSpace(query)
if strings.HasSuffix(qs, ";") {
return query
}
return query + ";"
}
inlined := query
for i := len(args); i >= 1; i-- {
ph := fmt.Sprintf("$%d", i)
lit := sqlLiteral(args[i-1])
inlined = strings.ReplaceAll(inlined, ph, lit)
}
qs := strings.TrimSpace(inlined)
if strings.HasSuffix(qs, ";") {
return inlined
}
return inlined + ";"
}
func sqlLiteral(v any) string {
if v == nil {
return "NULL"
}
switch t := v.(type) {
case string:
return "'" + escapeSQLString(t) + "'"
case []byte:
// Represent bytea as decode(hex,'hex') for easy psql paste
return "decode('" + strings.ToUpper(hex.EncodeToString(t)) + "','hex')"
case bool:
if t {
return "TRUE"
}
return "FALSE"
case time.Time:
return "'" + t.Format(time.RFC3339Nano) + "'"
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
return fmt.Sprintf("%v", v)
}
return "'" + escapeSQLString(fmt.Sprintf("%v", v)) + "'"
}
func escapeSQLString(s string) string {
return strings.ReplaceAll(s, "'", "''")
}