100 lines
2.7 KiB
C
100 lines
2.7 KiB
C
/*
|
|
* log.c - Non-blocking timestamped stderr logger
|
|
*
|
|
* Formats messages into a pipe with O_NONBLOCK so the hot path never
|
|
* blocks on I/O. A background thread drains the pipe and writes to
|
|
* stderr. When the pipe buffer is full messages are silently dropped.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include "log.h"
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stdatomic.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
static int log_pipe[2] = {-1, -1};
|
|
static pthread_t log_thread;
|
|
static atomic_bool log_running = false;
|
|
|
|
/* Background thread: drains the pipe and writes each chunk to stderr.
|
|
Spins on EAGAIN with 100us sleep when the pipe is empty. */
|
|
static void *log_worker(void *arg) {
|
|
(void)arg;
|
|
char buf[4096];
|
|
while (atomic_load(&log_running)) {
|
|
ssize_t n = read(log_pipe[0], buf, sizeof(buf));
|
|
if (n > 0) {
|
|
write(STDERR_FILENO, buf, (size_t)n);
|
|
} else if (n < 0) {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
usleep(100);
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Create a non-blocking pipe and start the background drain thread. */
|
|
void log_init(void) {
|
|
if (pipe2(log_pipe, O_NONBLOCK) != 0) {
|
|
log_pipe[0] = log_pipe[1] = -1;
|
|
return;
|
|
}
|
|
atomic_store(&log_running, true);
|
|
pthread_create(&log_thread, NULL, log_worker, NULL);
|
|
}
|
|
|
|
/* Stop the worker thread and close both pipe ends. */
|
|
void log_shutdown(void) {
|
|
atomic_store(&log_running, false);
|
|
if (log_thread) {
|
|
pthread_join(log_thread, NULL);
|
|
}
|
|
if (log_pipe[0] >= 0) { close(log_pipe[0]); log_pipe[0] = -1; }
|
|
if (log_pipe[1] >= 0) { close(log_pipe[1]); log_pipe[1] = -1; }
|
|
}
|
|
|
|
/* Non-blocking log write. Formats a timestamped message into the pipe.
|
|
Falls back to sync stderr write if the pipe has not been initialised. */
|
|
void log_write(const char *fmt, ...) {
|
|
if (log_pipe[1] < 0) {
|
|
/* fallback: sync write to stderr */
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
return;
|
|
}
|
|
|
|
char ts[32];
|
|
time_t t = time(NULL);
|
|
struct tm tm;
|
|
localtime_r(&t, &tm);
|
|
strftime(ts, sizeof(ts), "[%Y/%m/%d %H:%M:%S] ", &tm);
|
|
|
|
char buf[1536];
|
|
int ts_len = (int)strlen(ts);
|
|
memcpy(buf, ts, (size_t)ts_len);
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
int msg_len = vsnprintf(buf + ts_len, sizeof(buf) - (size_t)ts_len, fmt, ap);
|
|
va_end(ap);
|
|
|
|
int total = ts_len + (msg_len > 0 ? msg_len : 0);
|
|
if (total > 0) {
|
|
write(log_pipe[1], buf, (size_t)total);
|
|
}
|
|
}
|