Coverage Report

Created: 2026-10-17 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/event-log.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "str.h"
6
#include "event-filter.h"
7
#include "lib-event-private.h"
8
9
unsigned int event_filter_replace_counter = 1;
10
11
static struct event_filter *global_debug_log_filter = NULL;
12
static struct event_filter *global_core_log_filter = NULL;
13
static ARRAY(struct event_filter *) global_debug_send_filters;
14
static bool global_log_filters_have_source_location = FALSE;
15
16
#undef event_want_level
17
#undef e_error
18
void e_error(struct event *event,
19
       const char *source_filename, unsigned int source_linenum,
20
       const char *fmt, ...)
21
0
{
22
0
  if (!event_want_level(event, LOG_TYPE_ERROR,
23
0
            source_filename, source_linenum)) {
24
0
    event_send_abort(event);
25
0
    return;
26
0
  }
27
0
  struct event_log_params params = {
28
0
    .log_type = LOG_TYPE_ERROR,
29
0
    .source_filename = source_filename,
30
0
    .source_linenum = source_linenum,
31
0
  };
32
0
  va_list args;
33
34
0
  va_start(args, fmt);
35
0
  T_BEGIN {
36
0
    event_logv(event, &params, fmt, args);
37
0
  } T_END;
38
0
  va_end(args);
39
0
}
40
41
#undef e_warning
42
void e_warning(struct event *event,
43
         const char *source_filename, unsigned int source_linenum,
44
         const char *fmt, ...)
45
0
{
46
0
  if (!event_want_level(event, LOG_TYPE_WARNING,
47
0
            source_filename, source_linenum)) {
48
0
    event_send_abort(event);
49
0
    return;
50
0
  }
51
0
  struct event_log_params params = {
52
0
    .log_type = LOG_TYPE_WARNING,
53
0
    .source_filename = source_filename,
54
0
    .source_linenum = source_linenum,
55
0
  };
56
0
  va_list args;
57
58
0
  va_start(args, fmt);
59
0
  T_BEGIN {
60
0
    event_logv(event, &params, fmt, args);
61
0
  } T_END;
62
0
  va_end(args);
63
0
}
64
65
#undef e_info
66
void e_info(struct event *event,
67
      const char *source_filename, unsigned int source_linenum,
68
      const char *fmt, ...)
69
1.88k
{
70
1.88k
  if (!event_want_level(event, LOG_TYPE_INFO,
71
1.88k
            source_filename, source_linenum)) {
72
0
    event_send_abort(event);
73
0
    return;
74
0
  }
75
1.88k
  struct event_log_params params = {
76
1.88k
    .log_type = LOG_TYPE_INFO,
77
1.88k
    .source_filename = source_filename,
78
1.88k
    .source_linenum = source_linenum,
79
1.88k
  };
80
1.88k
  va_list args;
81
82
1.88k
  va_start(args, fmt);
83
1.88k
  T_BEGIN {
84
1.88k
    event_logv(event, &params, fmt, args);
85
1.88k
  } T_END;
86
1.88k
  va_end(args);
87
1.88k
}
88
89
#undef e_debug
90
void e_debug(struct event *event,
91
       const char *source_filename, unsigned int source_linenum,
92
       const char *fmt, ...)
93
0
{
94
0
  struct event_log_params params = {
95
0
    .log_type = LOG_TYPE_DEBUG,
96
0
    .source_filename = source_filename,
97
0
    .source_linenum = source_linenum,
98
0
  };
99
0
  va_list args;
100
101
0
  va_start(args, fmt);
102
0
  T_BEGIN {
103
0
    event_logv(event, &params, fmt, args);
104
0
  } T_END;
105
0
  va_end(args);
106
0
}
107
108
#undef e_log
109
void e_log(struct event *event, enum log_type level,
110
     const char *source_filename, unsigned int source_linenum,
111
     const char *fmt, ...)
112
0
{
113
0
  struct event_log_params params = {
114
0
    .log_type = level,
115
0
    .source_filename = source_filename,
116
0
    .source_linenum = source_linenum,
117
0
  };
118
0
  va_list args;
119
120
0
  va_start(args, fmt);
121
0
  T_BEGIN {
122
0
    event_logv(event, &params, fmt, args);
123
0
  } T_END;
124
0
  va_end(args);
125
0
}
126
127
struct event_get_log_message_context {
128
  const struct event_log_params *params;
129
130
  string_t *log_prefix;
131
  const char *message;
132
  unsigned int type_pos;
133
134
  bool replace_prefix:1;
135
  bool str_out_done:1;
136
};
137
138
static inline void ATTR_FORMAT(2, 0)
139
event_get_log_message_str_out(struct event_get_log_message_context *glmctx,
140
            const char *fmt, va_list args)
141
1.88k
{
142
1.88k
  const struct event_log_params *params = glmctx->params;
143
1.88k
  string_t *str_out = params->base_str_out;
144
145
  /* The message is appended once in full, rather than incremental during
146
     the recursion. */
147
148
1.88k
  if (glmctx->str_out_done || str_out == NULL)
149
1.88k
    return;
150
151
  /* append the current log prefix to the string buffer */
152
0
  if (params->base_str_prefix != NULL && !glmctx->replace_prefix)
153
0
    str_append(str_out, params->base_str_prefix);
154
0
  str_append_str(str_out, glmctx->log_prefix);
155
156
0
  if (glmctx->message != NULL) {
157
    /* a child event already constructed a message */
158
0
    str_append(str_out, glmctx->message);
159
0
  } else {
160
0
    va_list args_copy;
161
162
    /* construct message from format and arguments */
163
0
    VA_COPY(args_copy, args);
164
0
    str_vprintfa(str_out, fmt, args_copy);
165
0
    va_end(args_copy);
166
0
  }
167
168
  /* finished with the string buffer */
169
0
  glmctx->str_out_done = TRUE;
170
0
}
171
172
static bool ATTR_FORMAT(4, 0)
173
event_get_log_message(struct event *event,
174
          struct event_get_log_message_context *glmctx,
175
          unsigned int prefixes_dropped,
176
          const char *fmt, va_list args)
177
7.54k
{
178
7.54k
  const struct event_log_params *params = glmctx->params;
179
7.54k
  const char *prefix = event->log_prefix;
180
7.54k
  bool ret = FALSE;
181
182
  /* Reached the base event? */
183
7.54k
  if (event == params->base_event) {
184
    /* Append the message to the provided string buffer. */
185
0
    event_get_log_message_str_out(glmctx, fmt, args);
186
    /* Insert the base send prefix */
187
0
    if (params->base_send_prefix != NULL) {
188
0
      str_insert(glmctx->log_prefix, 0,
189
0
           params->base_send_prefix);
190
0
      ret = TRUE;
191
0
    }
192
0
  }
193
194
  /* Call the message amendment callback for this event if there is one.
195
   */
196
7.54k
  if (event->log_message_callback != NULL) {
197
0
    const char *in_message;
198
199
    /* construct the log message composed by children and arguments
200
     */
201
0
    if (glmctx->message == NULL) {
202
0
      str_vprintfa(glmctx->log_prefix, fmt, args);
203
0
      in_message = str_c(glmctx->log_prefix);
204
0
    } else if (str_len(glmctx->log_prefix) == 0) {
205
0
      in_message = glmctx->message;
206
0
    } else {
207
0
      str_append(glmctx->log_prefix, glmctx->message);
208
0
      in_message = str_c(glmctx->log_prefix);
209
0
    }
210
211
    /* reformat the log message */
212
0
    glmctx->message = event->log_message_callback(
213
0
      event->log_message_callback_context,
214
0
      glmctx->params->log_type, in_message);
215
0
    if (glmctx->message == str_c(glmctx->log_prefix)) {
216
      /* The log message returned the input log_prefix
217
         pointer. However, it's going to become modified, so
218
         it needs to be duplicated. */
219
0
      glmctx->message = t_strdup(str_c(glmctx->log_prefix));
220
0
    }
221
222
    /* continue with a cleared prefix buffer (as prefix is now part
223
       of *message_r). */
224
0
    str_truncate(glmctx->log_prefix, 0);
225
0
    ret = TRUE;
226
0
  }
227
228
7.54k
  if (event->log_prefix_callback != NULL) {
229
0
    prefix = event->log_prefix_callback(
230
0
      event->log_prefix_callback_context);
231
0
  }
232
7.54k
  if (event->log_prefix_replace) {
233
    /* this event replaces all parent log prefixes */
234
0
    glmctx->replace_prefix = TRUE;
235
0
    glmctx->type_pos = (prefix == NULL ? 0 : strlen(prefix));
236
0
    event_get_log_message_str_out(glmctx, fmt, args);
237
0
  }
238
7.54k
  if (prefix != NULL) {
239
3.77k
    if (event->log_prefix_replace || prefixes_dropped == 0) {
240
1.88k
      str_insert(glmctx->log_prefix, 0, prefix);
241
1.88k
      ret = TRUE;
242
1.88k
    } else if (prefixes_dropped > 0) {
243
1.88k
      prefixes_dropped--;
244
1.88k
    }
245
3.77k
  }
246
7.54k
  if (event->parent == NULL) {
247
1.88k
    event_get_log_message_str_out(glmctx, fmt, args);
248
1.88k
    if (params->base_event == NULL &&
249
1.88k
        params->base_send_prefix != NULL &&
250
0
        !glmctx->replace_prefix) {
251
0
      str_insert(glmctx->log_prefix, 0,
252
0
           params->base_send_prefix);
253
0
      ret = TRUE;
254
0
    }
255
5.65k
  } else if (!event->log_prefix_replace &&
256
5.65k
       (!params->no_send || !glmctx->str_out_done)) {
257
5.65k
    prefixes_dropped += event->log_prefixes_dropped;
258
5.65k
    if (event_get_log_message(event->parent, glmctx,
259
5.65k
            prefixes_dropped, fmt, args))
260
0
      ret = TRUE;
261
5.65k
  }
262
7.54k
  return ret;
263
7.54k
}
264
265
void event_log(struct event *event, const struct event_log_params *params,
266
         const char *fmt, ...)
267
0
{
268
0
  va_list args;
269
270
0
  va_start(args, fmt);
271
0
  event_logv(event, params, fmt, args);
272
0
  va_end(args);
273
0
}
274
275
#undef event_want_log_level
276
bool event_want_log_level(struct event *event, enum log_type level,
277
        const char *source_filename,
278
        unsigned int source_linenum)
279
2.08M
{
280
2.08M
  struct failure_context ctx = { .type = LOG_TYPE_DEBUG };
281
282
2.08M
  if (event->forced_never_debug && level == LOG_TYPE_DEBUG)
283
0
    return FALSE;
284
2.08M
  if (level >= event->min_log_level) {
285
    /* Always log when level is at least this high */
286
3.77k
    return TRUE;
287
3.77k
  }
288
289
2.08M
  if (event->debug_level_checked_filter_counter == event_filter_replace_counter) {
290
    /* Log filters haven't changed since we last checked this, so
291
       we can rely on the last cached value. FIXME: this doesn't
292
       work correctly if event changes and the change affects
293
       whether the filters would match. */
294
1.55M
    return event->sending_debug_log;
295
1.55M
  }
296
  /* With source_location filters the result can be different for each
297
     source location, so it can't be cached. */
298
525k
  if (!global_log_filters_have_source_location) {
299
525k
    event->debug_level_checked_filter_counter =
300
525k
      event_filter_replace_counter;
301
525k
  }
302
303
525k
  if (event->forced_debug) {
304
    /* Debugging is forced for this event (and its children) */
305
0
    event->sending_debug_log = TRUE;
306
525k
  } else if (global_debug_log_filter != NULL &&
307
0
       event_filter_match_source(global_debug_log_filter, event,
308
0
               source_filename, source_linenum, &ctx)) {
309
    /* log_debug filter matched */
310
0
    event->sending_debug_log = TRUE;
311
525k
  } else if (global_core_log_filter != NULL &&
312
0
       event_filter_match_source(global_core_log_filter, event,
313
0
               source_filename, source_linenum, &ctx)) {
314
    /* log_core_filter matched */
315
0
    event->sending_debug_log = TRUE;
316
525k
  } else {
317
525k
    event->sending_debug_log = FALSE;
318
525k
  }
319
525k
  return event->sending_debug_log;
320
2.08M
}
321
322
bool event_want_level(struct event *event, enum log_type level,
323
          const char *source_filename,
324
          unsigned int source_linenum)
325
2.08M
{
326
2.08M
  if (event_want_log_level(event, level, source_filename, source_linenum))
327
1.88k
    return TRUE;
328
329
  /* see if debug send filtering matches */
330
2.08M
  struct event_filter *filter;
331
2.08M
  array_foreach_elem(&global_debug_send_filters, filter) {
332
0
    struct failure_context ctx = { .type = LOG_TYPE_DEBUG };
333
334
0
    if (filter != NULL &&
335
0
        event_filter_match_source(filter, event,
336
0
                source_filename, source_linenum,
337
0
                &ctx))
338
0
      return TRUE;
339
0
  }
340
2.08M
  return FALSE;
341
2.08M
}
342
343
static void ATTR_FORMAT(3, 0)
344
event_logv_params(struct event *event, const struct event_log_params *params,
345
      const char *fmt, va_list args)
346
1.88k
{
347
1.88k
  struct event_get_log_message_context glmctx;
348
349
1.88k
  struct failure_context ctx = {
350
1.88k
    .type = params->log_type,
351
1.88k
  };
352
1.88k
  bool abort_after_event = FALSE;
353
354
1.88k
  i_assert(!params->no_send || params->base_str_out != NULL);
355
356
1.88k
  if (global_core_log_filter != NULL &&
357
0
      event_filter_match_source(global_core_log_filter, event,
358
0
              event->source_filename,
359
0
              event->source_linenum, &ctx))
360
0
    abort_after_event = TRUE;
361
362
1.88k
  i_zero(&glmctx);
363
1.88k
  glmctx.params = params;
364
1.88k
  glmctx.log_prefix = t_str_new(64);
365
1.88k
  if (!event_get_log_message(event, &glmctx, 0, fmt, args)) {
366
    /* keep log prefix as it is */
367
0
    if (params->base_str_out != NULL && !glmctx.str_out_done) {
368
0
      va_list args_copy;
369
370
0
      VA_COPY(args_copy, args);
371
0
      str_vprintfa(params->base_str_out, fmt, args_copy);
372
0
      va_end(args_copy);
373
0
    }
374
0
    if (!params->no_send)
375
0
      event_vsend(event, &ctx, fmt, args);
376
1.88k
  } else if (params->no_send) {
377
    /* don't send the event */
378
1.88k
  } else if (glmctx.replace_prefix) {
379
    /* event overrides the log prefix (even if it's "") */
380
0
    ctx.log_prefix = str_c(glmctx.log_prefix);
381
0
    ctx.log_prefix_type_pos = glmctx.type_pos;
382
0
    if (glmctx.message != NULL)
383
0
      event_send(event, &ctx, "%s", glmctx.message);
384
0
    else
385
0
      event_vsend(event, &ctx, fmt, args);
386
1.88k
  } else {
387
    /* append to log prefix, but don't fully replace it */
388
1.88k
    if (glmctx.message != NULL)
389
0
      str_append(glmctx.log_prefix, glmctx.message);
390
1.88k
    else
391
1.88k
      str_vprintfa(glmctx.log_prefix, fmt, args);
392
1.88k
    event_send(event, &ctx, "%s", str_c(glmctx.log_prefix));
393
1.88k
  }
394
1.88k
  if (abort_after_event)
395
0
    abort();
396
1.88k
}
397
398
void event_logv(struct event *event, const struct event_log_params *params,
399
    const char *fmt, va_list args)
400
1.88k
{
401
1.88k
  const char *orig_source_filename = event->source_filename;
402
1.88k
  unsigned int orig_source_linenum = event->source_linenum;
403
1.88k
  int old_errno = errno;
404
405
1.88k
  if (params->source_filename != NULL) {
406
1.88k
    event_set_source(event, params->source_filename,
407
1.88k
         params->source_linenum, TRUE);
408
1.88k
  }
409
410
1.88k
  (void)event_want_log_level(event, params->log_type,
411
1.88k
           event->source_filename,
412
1.88k
           event->source_linenum);
413
414
1.88k
  event_ref(event);
415
1.88k
  event_logv_params(event, params, fmt, args);
416
1.88k
  event_set_source(event, orig_source_filename,
417
1.88k
       orig_source_linenum, TRUE);
418
1.88k
  event_unref(&event);
419
1.88k
  errno = old_errno;
420
1.88k
}
421
422
struct event *event_set_forced_debug(struct event *event, bool force)
423
12.2k
{
424
12.2k
  if (force)
425
0
    event->forced_debug = TRUE;
426
12.2k
  event_recalculate_debug_level(event);
427
12.2k
  return event;
428
12.2k
}
429
430
struct event *event_unset_forced_debug(struct event *event)
431
0
{
432
0
  event->forced_debug = FALSE;
433
0
  event_recalculate_debug_level(event);
434
0
  return event;
435
0
}
436
437
struct event *event_set_forced_never_debug(struct event *event, bool force)
438
0
{
439
0
  event->forced_never_debug = force;
440
0
  return event;
441
0
}
442
443
static void event_global_log_filters_updated(void)
444
132k
{
445
132k
  global_log_filters_have_source_location =
446
132k
    (global_debug_log_filter != NULL &&
447
0
     event_filter_has_source_location(global_debug_log_filter)) ||
448
132k
    (global_core_log_filter != NULL &&
449
0
     event_filter_has_source_location(global_core_log_filter));
450
132k
  event_filter_replace_counter++;
451
132k
}
452
453
void event_set_global_debug_log_filter(struct event_filter *filter)
454
0
{
455
0
  event_unset_global_debug_log_filter();
456
0
  global_debug_log_filter = filter;
457
0
  event_filter_ref(global_debug_log_filter);
458
0
  event_global_log_filters_updated();
459
0
}
460
461
struct event_filter *event_get_global_debug_log_filter(void)
462
0
{
463
0
  return global_debug_log_filter;
464
0
}
465
466
void event_unset_global_debug_log_filter(void)
467
66.3k
{
468
66.3k
  event_filter_unref(&global_debug_log_filter);
469
66.3k
  event_global_log_filters_updated();
470
66.3k
}
471
472
struct event_filter **event_global_debug_send_filter_register(void)
473
0
{
474
0
  return array_append_space(&global_debug_send_filters);
475
0
}
476
477
void event_global_debug_send_filter_updated(void)
478
0
{
479
0
  event_filter_replace_counter++;
480
0
}
481
482
void event_set_global_core_log_filter(struct event_filter *filter)
483
0
{
484
0
  event_unset_global_core_log_filter();
485
0
  global_core_log_filter = filter;
486
0
  event_filter_ref(global_core_log_filter);
487
0
  event_global_log_filters_updated();
488
0
}
489
490
struct event_filter *event_get_global_core_log_filter(void)
491
0
{
492
0
  return global_core_log_filter;
493
0
}
494
495
void event_unset_global_core_log_filter(void)
496
66.3k
{
497
66.3k
  event_filter_unref(&global_core_log_filter);
498
66.3k
  event_global_log_filters_updated();
499
66.3k
}
500
501
void event_log_init(void)
502
66.3k
{
503
66.3k
  i_array_init(&global_debug_send_filters, 2);
504
66.3k
}
505
506
void event_log_deinit(void)
507
66.3k
{
508
66.3k
  struct event_filter *filter;
509
66.3k
  array_foreach_elem(&global_debug_send_filters, filter)
510
0
    event_filter_unref(&filter);
511
66.3k
  array_free(&global_debug_send_filters);
512
66.3k
}