-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscoped_logger.v
More file actions
319 lines (270 loc) · 9.07 KB
/
scoped_logger.v
File metadata and controls
319 lines (270 loc) · 9.07 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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// Scoped logger for building wide events incrementally
module logger
import x.json2
import time
// new_request_logger creates a new request-scoped logger
// Auto-generates correlation_id if not provided in context
// Usage:
// mut req_log := logger.new_request_logger({
// 'method': json2.Any('TRADE_EXECUTED')
// 'path': json2.Any('/trading/execute')
// })
pub fn new_request_logger(initial_context ?map[string]json2.Any) &RequestLogger {
mut ctx := if ic := initial_context {
ic.clone()
} else {
map[string]json2.Any{}
}
// Extract or generate correlation ID
correlation_id := extract_correlation_from_context(ctx) or { generate_correlation_id() }
span_id := generate_correlation_id()
// Set correlation ID in context if not present
if 'correlation_id' !in ctx {
ctx['correlation_id'] = json2.Any(correlation_id)
}
return &RequestLogger{
context: ctx.clone()
start_time: time.now()
request_logs: []
field_configs: global_config.field_configs
has_error: false
has_warn: false
correlation_id: correlation_id
span_id: span_id
}
}
// new_child_request_logger creates a child logger with the same correlation ID
// This is useful for tracking related operations (e.g., order placement → execution)
pub fn new_child_request_logger(parent &RequestLogger, child_context ?map[string]json2.Any) &RequestLogger {
mut ctx := if cc := child_context {
cc.clone()
} else {
map[string]json2.Any{}
}
// Inherit correlation ID from parent
ctx['correlation_id'] = json2.Any(parent.correlation_id)
ctx['parent_span_id'] = json2.Any(parent.span_id)
child_span_id := generate_correlation_id()
return &RequestLogger{
context: ctx.clone()
start_time: time.now()
request_logs: []
field_configs: global_config.field_configs
has_error: false
has_warn: false
correlation_id: parent.correlation_id
span_id: child_span_id
parent_span_id: parent.span_id
}
}
// new_request_logger_with_correlation creates a logger with a specific correlation ID
// Useful when continuing a trace from an external source (webhook, message queue, etc.)
pub fn new_request_logger_with_correlation(correlation_id string, initial_context ?map[string]json2.Any) &RequestLogger {
mut ctx := if ic := initial_context {
ic.clone()
} else {
map[string]json2.Any{}
}
ctx['correlation_id'] = json2.Any(correlation_id)
span_id := generate_correlation_id()
return &RequestLogger{
context: ctx.clone()
start_time: time.now()
request_logs: []
field_configs: global_config.field_configs
has_error: false
has_warn: false
correlation_id: correlation_id
span_id: span_id
}
}
// set adds or updates context data by merging with existing context
pub fn (mut r RequestLogger) set(data map[string]json2.Any) {
r.context = deep_merge(data, r.context)
}
// set_field sets a single field in the context
pub fn (mut r RequestLogger) set_field(key string, value json2.Any) {
r.context[key] = value
}
// get_correlation_id returns the correlation ID for this request
pub fn (r &RequestLogger) get_correlation_id() string {
return r.correlation_id
}
// get_span_id returns the span ID for this specific operation
pub fn (r &RequestLogger) get_span_id() string {
return r.span_id
}
// error logs an error and marks the request as having an error
// The error details are added to the context
pub fn (mut r RequestLogger) error(err IError, error_context ?map[string]json2.Any) {
r.has_error = true
mut error_data := if ec := error_context {
ec.clone()
} else {
map[string]json2.Any{}
}
mut err_map := map[string]json2.Any{}
err_map['type'] = json2.Any(err.type_name())
err_map['message'] = json2.Any(err.msg())
err_map['code'] = json2.Any(err.code())
err_map['span_id'] = json2.Any(r.span_id) // Track which span had the error
error_data['error'] = json2.Any(err_map)
r.context = deep_merge(error_data, r.context)
}
// error_str logs an error message and marks the request as having an error
pub fn (mut r RequestLogger) error_str(message string, error_context ?map[string]json2.Any) {
r.has_error = true
mut error_data := if ec := error_context {
ec.clone()
} else {
map[string]json2.Any{}
}
mut err_map := map[string]json2.Any{}
err_map['type'] = json2.Any('Error')
err_map['message'] = json2.Any(message)
err_map['code'] = json2.Any(1)
err_map['span_id'] = json2.Any(r.span_id)
error_data['error'] = json2.Any(err_map)
r.context = deep_merge(error_data, r.context)
}
// info logs an info message and adds it to the request logs
pub fn (mut r RequestLogger) info(message string, info_context ?map[string]json2.Any) {
// Check if info level is enabled
if int(LogLevel.info) < int(global_config.level) {
return
}
r.request_logs << LogEntry{
level: .info
message: message
timestamp: time.now()
}
if ctx := info_context {
r.context = deep_merge(ctx, r.context)
}
}
// warn logs a warning message, marks the request as having a warning, and adds it to request logs
pub fn (mut r RequestLogger) warn(message string, warn_context ?map[string]json2.Any) {
// Check if warn level is enabled
if int(LogLevel.warn) < int(global_config.level) {
return
}
r.has_warn = true
r.request_logs << LogEntry{
level: .warn
message: message
timestamp: time.now()
}
if ctx := warn_context {
r.context = deep_merge(ctx, r.context)
}
}
// debug logs a debug message and adds it to the request logs
pub fn (mut r RequestLogger) debug(message string, debug_context ?map[string]json2.Any) {
// Check if debug level is enabled
if int(LogLevel.debug) < int(global_config.level) {
return
}
r.request_logs << LogEntry{
level: .debug
message: message
timestamp: time.now()
}
if ctx := debug_context {
r.context = deep_merge(ctx, r.context)
}
}
// emit finalizes the request logger and emits a wide event
// Returns the emitted event or none if logging is disabled
pub fn (mut r RequestLogger) emit(overrides ?map[string]json2.Any) ?WideEvent {
duration_ms := int(time.since(r.start_time).milliseconds())
// Determine level based on flags
level := if r.has_error {
LogLevel.error
} else if r.has_warn {
LogLevel.warn
} else {
LogLevel.info
}
// Merge overrides
mut final_context := r.context.clone()
if over := overrides {
final_context = deep_merge(over, final_context)
}
// Add duration fields
final_context['duration_ms'] = json2.Any(duration_ms)
final_context['duration'] = json2.Any(format_duration(duration_ms))
// Add correlation IDs to context for display
final_context['correlation_id'] = json2.Any(r.correlation_id)
final_context['_internal_span_id'] = json2.Any(r.span_id)
if psid := r.parent_span_id {
final_context['_internal_parent_span_id'] = json2.Any(psid)
}
// Add request logs if any exist
if r.request_logs.len > 0 {
mut logs_json := []json2.Any{}
for entry in r.request_logs {
logs_json << json2.Any({
'level': json2.Any(entry.level.str())
'message': json2.Any(entry.message)
'timestamp': json2.Any(entry.timestamp.format_rfc3339())
})
}
final_context['request_logs'] = json2.Any(logs_json)
}
return emit_wide_event(level, final_context)
}
// emit_with_level emits the event with a specific level override
pub fn (mut r RequestLogger) emit_with_level(level LogLevel, overrides ?map[string]json2.Any) ?WideEvent {
duration_ms := int(time.since(r.start_time).milliseconds())
// Merge overrides
mut final_context := r.context.clone()
if over := overrides {
final_context = deep_merge(over, final_context)
}
// Add duration fields
final_context['duration_ms'] = json2.Any(duration_ms)
final_context['duration'] = json2.Any(format_duration(duration_ms))
// Add correlation IDs
final_context['correlation_id'] = json2.Any(r.correlation_id)
final_context['_internal_span_id'] = json2.Any(r.span_id)
if psid := r.parent_span_id {
final_context['_internal_parent_span_id'] = json2.Any(psid)
}
// Add request logs if any exist
if r.request_logs.len > 0 {
mut logs_json := []json2.Any{}
for entry in r.request_logs {
logs_json << json2.Any({
'level': json2.Any(entry.level.str())
'message': json2.Any(entry.message)
'timestamp': json2.Any(entry.timestamp.format_rfc3339())
})
}
final_context['request_logs'] = json2.Any(logs_json)
}
return emit_wide_event(level, final_context)
}
// get_duration returns the duration since the logger was created
pub fn (r &RequestLogger) get_duration() time.Duration {
return time.since(r.start_time)
}
// get_duration_ms returns the duration in milliseconds
pub fn (r &RequestLogger) get_duration_ms() int {
return int(time.since(r.start_time).milliseconds())
}
// get_context returns a copy of the current context
pub fn (r &RequestLogger) get_context() map[string]json2.Any {
return r.context.clone()
}
// has_errors returns true if any errors were logged
pub fn (r &RequestLogger) has_errors() bool {
return r.has_error
}
// has_warnings returns true if any warnings were logged
pub fn (r &RequestLogger) has_warnings() bool {
return r.has_warn
}
// get_log_count returns the number of log entries
pub fn (r &RequestLogger) get_log_count() int {
return r.request_logs.len
}