-
Notifications
You must be signed in to change notification settings - Fork 76
Expand file tree
/
Copy pathlogger.go
More file actions
179 lines (156 loc) · 5 KB
/
Copy pathlogger.go
File metadata and controls
179 lines (156 loc) · 5 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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package ghostferry
import (
"fmt"
"io"
"os"
"strings"
"sync/atomic"
)
// Logger is the interface for structured logging throughout ghostferry.
// It is designed to be backend-agnostic, allowing swapping logrus for
// zerolog, zap, or any other structured logger without changing consuming code.
type Logger interface {
Debug(args ...any)
Debugf(format string, args ...any)
Info(args ...any)
Infof(format string, args ...any)
Warn(args ...any)
Warnf(format string, args ...any)
Error(args ...any)
Errorf(format string, args ...any)
Panicf(format string, args ...any)
WithField(key string, value any) Logger
WithFields(fields Fields) Logger
WithError(err error) Logger
}
// Fields is a map of key-value pairs for structured logging context.
type Fields map[string]any
// LogLevel represents the severity level for logging.
type LogLevel int
const (
LogLevelDebug LogLevel = iota
LogLevelInfo
LogLevelWarn
LogLevelError
)
// ParseLogLevel converts a string to a LogLevel.
// It returns the parsed level and true on success, or LogLevelInfo and false
// if the string is not recognized.
func ParseLogLevel(s string) (LogLevel, bool) {
switch strings.ToLower(s) {
case "debug":
return LogLevelDebug, true
case "info":
return LogLevelInfo, true
case "warn", "warning":
return LogLevelWarn, true
case "error":
return LogLevelError, true
default:
return LogLevelInfo, false
}
}
// LogBackendType identifies a logging backend implementation.
type LogBackendType string
const (
// LogBackendLogrus selects the logrus logging backend (default).
LogBackendLogrus LogBackendType = "logrus"
// LogBackendZerolog selects the zerolog logging backend.
LogBackendZerolog LogBackendType = "zerolog"
)
// activeBackend holds the currently selected logging backend.
// It is accessed atomically to be safe under the Go memory model, even though
// in practice the backend is always configured before goroutines are launched.
var activeBackend atomic.Value // stores LogBackendType
func init() {
// Set the default before anything else runs.
activeBackend.Store(LogBackendLogrus)
if backend := os.Getenv("GHOSTFERRY_LOG_BACKEND"); backend != "" {
SetLogBackend(LogBackendType(backend))
}
if level := os.Getenv("GHOSTFERRY_LOG_LEVEL"); level != "" {
if parsed, ok := ParseLogLevel(level); ok {
SetLogLevel(parsed)
} else {
fmt.Fprintf(os.Stderr, "ghostferry: unknown log level %q from GHOSTFERRY_LOG_LEVEL, ignoring\n", level)
}
}
}
// getBackend returns the currently active backend type.
func getBackend() LogBackendType {
return activeBackend.Load().(LogBackendType)
}
// SetLogBackend switches the active logging backend.
// This should be called once at program startup, before any loggers are created.
// If an unknown backend is specified, a warning is printed to stderr and the
// backend falls back to logrus.
func SetLogBackend(backend LogBackendType) {
switch backend {
case LogBackendLogrus, LogBackendZerolog:
activeBackend.Store(backend)
default:
fmt.Fprintf(os.Stderr, "ghostferry: unknown log backend %q, falling back to %q\n", backend, LogBackendLogrus)
activeBackend.Store(LogBackendLogrus)
}
}
// GetLogBackend returns the currently active logging backend.
func GetLogBackend() LogBackendType {
return getBackend()
}
// --- Public factory functions (dispatch to active backend) ---
// LogWithField creates a new Logger with a single key-value field.
// This is the primary way components create their tagged loggers.
func LogWithField(key string, value any) Logger {
if getBackend() == LogBackendZerolog {
return zerologWithField(key, value)
}
return logrusWithField(key, value)
}
// LogWithFields creates a new Logger with multiple key-value fields.
func LogWithFields(fields Fields) Logger {
if getBackend() == LogBackendZerolog {
return zerologWithFields(fields)
}
return logrusWithFields(fields)
}
// LogWithError creates a new Logger with an error field.
func LogWithError(err error) Logger {
if getBackend() == LogBackendZerolog {
return zerologWithError(err)
}
return logrusWithError(err)
}
// NewDefaultLogger creates a Logger from the active backend's default configuration.
// Used as a fallback when no logger is provided.
func NewDefaultLogger() Logger {
if getBackend() == LogBackendZerolog {
return newZerologDefaultLogger()
}
return newLogrusDefaultLogger()
}
// SetLogLevel sets the global log level for the active backend.
func SetLogLevel(level LogLevel) {
if getBackend() == LogBackendZerolog {
setZerologLevel(level)
return
}
setLogrusLevel(level)
}
// SetLogJSONFormatter configures the active backend to output JSON.
// For zerolog this is a no-op since JSON is the default format.
func SetLogJSONFormatter() {
if getBackend() == LogBackendZerolog {
setZerologJSONFormatter()
return
}
setLogrusJSONFormatter()
}
// SetLogOutput sets the output writer for the active backend.
// This is primarily useful for testing (capturing log output).
func SetLogOutput(w io.Writer) {
if getBackend() == LogBackendZerolog {
setZerologOutput(w)
return
}
setLogrusOutput(w)
}