| 233 | } |
| 234 | |
| 235 | static void emit_line(const char *line) { |
| 236 | /* Load ONCE: re-reading the global between the test and the call would |
| 237 | * let a concurrent cbm_log_set_sink turn a checked pointer into a NULL |
| 238 | * call. */ |
| 239 | cbm_log_sink_fn sink = atomic_load_explicit(&g_log_sink, memory_order_relaxed); |
| 240 | if (sink) { |
| 241 | sink(line); |
| 242 | if (atomic_load_explicit(&g_log_sink_mode, memory_order_relaxed) == CBM_LOG_SINK_REPLACE) { |
| 243 | return; |
| 244 | } |
| 245 | } |
| 246 | (void)fprintf(stderr, "%s\n", line); |
| 247 | if (atomic_load_explicit(&g_log_crash_durable, memory_order_relaxed)) { |
| 248 | /* The line is complete here and the stream lock is released, so a |
| 249 | * process that dies on the very next instruction still leaves this |
| 250 | * line on disk. Free when stderr is unbuffered; one write() per line |
| 251 | * when setvbuf was refused. */ |
| 252 | (void)fflush(stderr); |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | void cbm_log(CBMLogLevel level, const char *msg, ...) { |
| 257 | if (level < atomic_load_explicit(&g_log_level, memory_order_relaxed)) { |
no outgoing calls
no test coverage detected