7#include <ascii-chat/common.h>
8#include <ascii-chat/options/options.h>
9#include <ascii-chat/options/rcu.h>
10#include <ascii-chat/platform/abstraction.h>
11#include <ascii-chat/platform/system.h>
12#include <ascii-chat/util/path.h>
13#include <ascii-chat/util/string.h>
14#include <ascii-chat/util/time.h>
15#include <ascii-chat/util/utf8.h>
25#include <ascii-chat/log/logging.h>
26#include <ascii-chat/log/format.h>
27#include <ascii-chat/log/colorize.h>
28#include <ascii-chat/log/mmap.h>
29#include <ascii-chat/log/grep.h>
30#include <ascii-chat/log/json.h>
31#include <ascii-chat/platform/terminal.h>
32#include <ascii-chat/options/colorscheme.h>
33#include <ascii-chat/platform/thread.h>
34#include <ascii-chat/platform/mutex.h>
35#include <ascii-chat/network/packet.h>
36#include <ascii-chat/video/ansi.h>
55#pragma GCC diagnostic push
56#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
57static struct log_context_t {
59 _Atomic
int json_file;
62 char filename[LOG_MSG_BUFFER_SIZE];
63 _Atomic
size_t current_size;
64 _Atomic
bool terminal_output_enabled;
65 _Atomic
bool level_manually_set;
66 _Atomic
bool force_stderr;
67 _Atomic
bool terminal_locked;
68 _Atomic uint64_t terminal_owner_thread;
69 _Atomic
unsigned int flush_delay_ms;
70 mutex_t rotation_mutex;
71 _Atomic
bool rotation_mutex_initialized;
72 log_template_t *format;
73 log_template_t *format_console_only;
74 _Atomic
bool has_custom_format;
78 .level = DEFAULT_LOG_LEVEL,
82 .terminal_output_enabled =
true,
83 .level_manually_set =
false,
84 .force_stderr =
false,
85 .terminal_locked =
false,
86 .terminal_owner_thread = 0,
88 .rotation_mutex_initialized =
false,
90 .format_console_only = NULL,
91 .has_custom_format =
false,
93#pragma GCC diagnostic pop
139 if (strlen(result) != 5) {
140 fprintf(stderr,
"ERROR: get_level_string_padded() returned non-5-char string: '%s' (len=%zu)\n", result,
146#define LOG_COLOR_COUNT 8
154#define LOGGING_INTERNAL_ERROR(error, message, ...) \
156 asciichat_set_errno_with_message(error, NULL, 0, NULL, message, ##__VA_ARGS__); \
157 static const char *msg_header = "CRITICAL LOGGING SYSTEM ERROR: "; \
158 safe_fprintf(stderr, "%s %s\n", colored_string(LOG_COLOR_ERROR, msg_header), message); \
159 platform_write(g_log.file, msg_header, strlen(msg_header)); \
160 platform_write(g_log.file, message, strlen(message)); \
161 platform_write(g_log.file, "\n", 1); \
162 platform_print_backtrace(0); \
165#define LOGGING_INTERNAL_ERROR(error, message, ...) \
167 asciichat_set_errno_with_message(error, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__); \
168 static const char *msg_header = "CRITICAL LOGGING SYSTEM ERROR: "; \
169 safe_fprintf(stderr, "%s %s\n", colored_string(LOG_COLOR_ERROR, msg_header), message); \
170 platform_write(g_log.file, msg_header, strlen(msg_header)); \
171 platform_write(g_log.file, message, strlen(message)); \
172 platform_write(g_log.file, "\n", 1); \
173 platform_print_backtrace(0); \
178static terminal_capabilities_t g_terminal_caps = {0};
179static bool g_terminal_caps_initialized =
false;
180static bool g_terminal_caps_detecting =
false;
183static compiled_color_scheme_t g_compiled_colors = {0};
184static bool g_log_colorscheme_initialized =
false;
189static bool g_shutdown_saved_terminal_output =
true;
190static bool g_shutdown_in_progress =
false;
196 time_t seconds = (time_t)(ts_ns / NS_PER_SEC_INT);
197 long nanoseconds = (long)(ts_ns % NS_PER_SEC_INT);
202 size_t len = strftime(time_buf, 32,
"%H:%M:%S", &tm_info);
203 if (len == 0 || len >= 32) {
209 long microseconds = nanoseconds / 1000;
210 if (microseconds < 0)
212 if (microseconds > 999999)
213 microseconds = 999999;
215 int result =
safe_snprintf(time_buf + len, 32 - len,
".%06ld", microseconds);
216 if (result < 0 || result >= (
int)(32 - len)) {
221 return len + (size_t)result;
231 va_copy(args_copy,
args);
241 char *message = SAFE_MALLOC(size + 1,
char *);
262static int truncate_at_whole_line(
char *buffer,
int current_len,
size_t max_len) {
263 if (current_len < 0 || (
size_t)current_len <= max_len) {
268 int truncate_pos = (int)max_len - 1;
269 while (truncate_pos > 0 && buffer[truncate_pos] !=
'\n') {
274 if (truncate_pos > 0 && buffer[truncate_pos] ==
'\n') {
276 buffer[truncate_pos] =
'\0';
281 truncate_pos = (int)max_len - 1;
282 buffer[truncate_pos] =
'\0';
293static void validate_log_message_utf8(
const char *message,
const char *source) {
294 if (!message || !source) {
301 safe_fprintf(stderr,
"[WARN] Invalid UTF-8 detected in %s\n", source);
302 safe_fprintf(stderr,
"[DEBUG] Invalid UTF-8 data: %s\n", message);
312static log_level_t parse_log_level_from_env(
void) {
313 const char *env_level = SAFE_GETENV(
"LOG_LEVEL");
315 return DEFAULT_LOG_LEVEL;
339 log_warn(
"Invalid LOG_LEVEL: %s", env_level);
340 return DEFAULT_LOG_LEVEL;
347static void rotate_log_locked(
void) {
348 int file = atomic_load(&g_log.file);
349 size_t current_size = atomic_load(&g_log.current_size);
351 if (file < 0 || file == STDERR_FILENO || strlen(g_log.filename) == 0) {
355 if (current_size < MAX_LOG_SIZE) {
360 atomic_store(&g_log.file, -1);
365 safe_fprintf(stderr,
"Failed to open log file for tail rotation: %s\n", g_log.filename);
367 int fd =
platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
368 atomic_store(&g_log.file, fd);
369 atomic_store(&g_log.current_size, 0);
374 size_t keep_size = MAX_LOG_SIZE * 2 / 3;
375 if (current_size < keep_size) {
378 int fd =
platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
379 atomic_store(&g_log.file, fd);
380 atomic_store(&g_log.current_size, 0);
383 if (lseek(read_file, (off_t)(current_size - keep_size), SEEK_SET) == (off_t)-1) {
386 int fd =
platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
387 atomic_store(&g_log.file, fd);
388 atomic_store(&g_log.current_size, 0);
400 int result =
safe_snprintf(temp_filename,
sizeof(temp_filename),
"%s.tmp", g_log.filename);
401 if (result <= 0 || result >= (
int)
sizeof(temp_filename)) {
404 int fd =
platform_open(g_log.filename, O_CREAT | O_RDWR | O_APPEND, FILE_PERM_PRIVATE);
405 atomic_store(&g_log.file, fd);
409 int temp_file =
platform_open(temp_filename, O_CREAT | O_WRONLY | O_TRUNC, FILE_PERM_PRIVATE);
413 int fd =
platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
414 atomic_store(&g_log.file, fd);
415 atomic_store(&g_log.current_size, 0);
423 while ((bytes_read =
platform_read(read_file, buffer,
sizeof(buffer))) > 0) {
424 ssize_t written =
platform_write(temp_file, buffer, (
size_t)bytes_read);
425 if (written != bytes_read) {
428 unlink(temp_filename);
430 int fd =
platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
431 atomic_store(&g_log.file, fd);
432 atomic_store(&g_log.current_size, 0);
435 new_size += (size_t)bytes_read;
442 if (rename(temp_filename, g_log.filename) != 0) {
443 unlink(temp_filename);
445 int fd =
platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
446 atomic_store(&g_log.file, fd);
447 atomic_store(&g_log.current_size, 0);
452 int new_fd =
platform_open(g_log.filename, O_CREAT | O_RDWR | O_APPEND, FILE_PERM_PRIVATE);
455 atomic_store(&g_log.file, STDERR_FILENO);
456 g_log.filename[0] =
'\0';
457 atomic_store(&g_log.current_size, 0);
460 atomic_store(&g_log.file, new_fd);
461 atomic_store(&g_log.current_size, new_size);
463 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
469 if (log_msg_len > 0 && log_msg_len < (
int)
sizeof(
log_msg)) {
475static void maybe_rotate_log(
void) {
477 if (!atomic_load(&g_log.rotation_mutex_initialized)) {
483 size_t used = 0, capacity = 0;
486 if (used > capacity * 9 / 10) {
487 mutex_lock(&g_log.rotation_mutex);
489 mutex_unlock(&g_log.rotation_mutex);
496 size_t current_size = atomic_load(&g_log.current_size);
497 if (current_size < MAX_LOG_SIZE) {
502 mutex_lock(&g_log.rotation_mutex);
504 mutex_unlock(&g_log.rotation_mutex);
507void log_init(
const char *filename, log_level_t level,
bool force_stderr,
bool use_mmap) {
509 if (!atomic_load(&g_log.rotation_mutex_initialized)) {
511 atomic_store(&g_log.rotation_mutex_initialized,
true);
515 atomic_store(&g_log.force_stderr, force_stderr);
516 bool preserve_terminal_output = atomic_load(&g_log.terminal_output_enabled);
519 int old_file = atomic_load(&g_log.file);
520 if (atomic_load(&g_log.initialized) && old_file >= 0 && old_file != STDERR_FILENO) {
522 atomic_store(&g_log.file, -1);
526 const char *env_level_str = SAFE_GETENV(
"LOG_LEVEL");
528 atomic_store(&g_log.level, (
int)parse_log_level_from_env());
530 atomic_store(&g_log.level, (
int)level);
533 atomic_store(&g_log.level_manually_set,
false);
534 atomic_store(&g_log.current_size, 0);
537 SAFE_STRNCPY(g_log.filename, filename,
sizeof(g_log.filename) - 1);
542 if (mmap_result == ASCIICHAT_OK) {
543 atomic_store(&g_log.file, -1);
546 if (preserve_terminal_output) {
547 safe_fprintf(stderr,
"Mmap logging failed for %s, using stderr only (lock-free)\n", filename);
549 atomic_store(&g_log.file, STDERR_FILENO);
550 g_log.filename[0] =
'\0';
554 int fd =
platform_open(filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
555 atomic_store(&g_log.file, (fd >= 0) ? fd : STDERR_FILENO);
557 if (preserve_terminal_output) {
558 safe_fprintf(stderr,
"Failed to open log file: %s\n", filename);
560 g_log.filename[0] =
'\0';
564 atomic_store(&g_log.file, STDERR_FILENO);
565 g_log.filename[0] =
'\0';
571 atomic_store(&g_log.initialized,
true);
572 atomic_store(&g_log.terminal_output_enabled, preserve_terminal_output);
575 if (g_terminal_caps_initialized && !g_terminal_caps.detection_reliable) {
576 g_terminal_caps_initialized =
false;
601 if (g_log.format_console_only) {
603 g_log.format_console_only = NULL;
605 atomic_store(&g_log.has_custom_format,
false);
608 int old_file = atomic_load(&g_log.file);
609 if (old_file >= 0 && old_file != STDERR_FILENO) {
612 atomic_store(&g_log.file, -1);
613 atomic_store(&g_log.initialized,
false);
616 if (atomic_load(&g_log.rotation_mutex_initialized)) {
618 atomic_store(&g_log.rotation_mutex_initialized,
false);
623 atomic_store(&g_log.level, (
int)level);
624 atomic_store(&g_log.level_manually_set,
true);
628 return (log_level_t)atomic_load(&g_log.level);
636 if (
enabled && opts && opts->quiet) {
639 atomic_store(&g_log.terminal_output_enabled,
enabled);
643 return atomic_load(&g_log.terminal_output_enabled);
647 atomic_store(&g_log.force_stderr,
enabled);
651 return atomic_load(&g_log.force_stderr);
655 atomic_store(&g_log.json_file, fd);
660 int old_file = atomic_load(&g_log.file);
661 if (old_file >= 0 && old_file != STDERR_FILENO) {
665 atomic_store(&g_log.file, STDERR_FILENO);
666 g_log.filename[0] =
'\0';
675 if (g_log.format_console_only) {
677 g_log.format_console_only = NULL;
681 const char *format_to_use = (format_str && format_str[0] !=
'\0') ? format_str : OPT_LOG_TEMPLATE_DEFAULT;
682 bool is_custom = (format_str && format_str[0] !=
'\0');
686 if (!parsed_format) {
687 log_error(
"Failed to parse log format: %s", format_to_use);
688 return SET_ERRNO(ERROR_INVALID_STATE,
"Invalid log format string");
692 if (console_only && is_custom) {
694 if (!default_format) {
696 log_error(
"Failed to parse default log format");
697 return SET_ERRNO(ERROR_INVALID_STATE,
"Failed to parse default format");
699 g_log.format = default_format;
700 g_log.format_console_only = parsed_format;
702 g_log.format = parsed_format;
703 g_log.format_console_only = NULL;
706 atomic_store(&g_log.has_custom_format, is_custom);
711 bool previous_state = atomic_exchange(&g_log.terminal_locked,
true);
713 return previous_state;
717 atomic_store(&g_log.terminal_locked, previous_state);
718 if (!previous_state) {
719 atomic_store(&g_log.terminal_owner_thread, 0);
724 atomic_store(&g_log.flush_delay_ms, delay_ms);
730 int file = atomic_load(&g_log.file);
731 if (file >= 0 && file != STDERR_FILENO && strlen(g_log.filename) > 0) {
733 if (fstat(file, &st) == 0 && st.st_size > MAX_LOG_SIZE) {
734 atomic_store(&g_log.current_size, (
size_t)st.st_size);
743static void write_to_log_file_atomic(
const char *buffer,
int length) {
744 if (length <= 0 || buffer == NULL) {
748 if (length > MAX_LOG_SIZE) {
752 int file = atomic_load(&g_log.file);
753 if (file < 0 || file == STDERR_FILENO) {
759 const char *write_buf = stripped ? stripped : buffer;
760 size_t write_len = stripped ? strlen(stripped) : (size_t)length;
765 atomic_fetch_add(&g_log.current_size, (
size_t)written);
777static int format_log_header(
char *buffer,
size_t buffer_size, log_level_t level,
const char *timestamp,
778 const char *file,
int line,
const char *func,
bool use_colors, uint64_t time_nanoseconds) {
779 const log_template_t *format = g_log.format;
803static void write_to_terminal_atomic(log_level_t level,
const char *timestamp,
const char *file,
int line,
804 const char *func,
const char *fmt, va_list
args, uint64_t time_nanoseconds) {
807 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
811 char msg_buffer[LOG_MSG_BUFFER_SIZE];
813 va_copy(args_copy,
args);
814 int msg_len =
safe_vsnprintf(msg_buffer,
sizeof(msg_buffer), fmt, args_copy);
818 char *stripped_msg = NULL;
819 const char *clean_msg = msg_buffer;
821 if (msg_len > 0 && msg_len < (
int)
sizeof(msg_buffer)) {
823 clean_msg = stripped_msg ? stripped_msg : msg_buffer;
827 char colored_log_line[LOG_MSG_BUFFER_SIZE + 512];
828 char plain_log_line[LOG_MSG_BUFFER_SIZE + 512];
831 int plain_len =
log_template_apply(g_log.format, plain_log_line,
sizeof(plain_log_line), level, timestamp, file, line,
836 if (use_colors && plain_len > 0) {
837 colored_len =
log_template_apply(g_log.format, colored_log_line,
sizeof(colored_log_line), level, timestamp, file,
840 if (colored_len <= 0) {
842 colored_len = plain_len;
843 if (plain_len > 0 && plain_len < (
int)
sizeof(colored_log_line)) {
844 memcpy(colored_log_line, plain_log_line, (
size_t)plain_len + 1);
849 colored_len = plain_len;
850 if (plain_len > 0 && plain_len < (
int)
sizeof(colored_log_line)) {
851 memcpy(colored_log_line, plain_log_line, (
size_t)plain_len + 1);
856 if (colored_len > 0 && colored_len < (
int)
sizeof(colored_log_line)) {
859 }
else if (plain_len > 0 && plain_len < (
int)
sizeof(plain_log_line)) {
865 bool is_enabled = atomic_load(&g_log.terminal_output_enabled);
869 SAFE_FREE(stripped_msg);
875 if (atomic_load(&g_log.terminal_locked)) {
876 uint64_t owner = atomic_load(&g_log.terminal_owner_thread);
880 SAFE_FREE(stripped_msg);
887 if (msg_len <= 0 || msg_len >= (
int)
sizeof(msg_buffer)) {
889 (void)vfprintf(output_stream, fmt,
args);
891 (void)fflush(output_stream);
893 SAFE_FREE(stripped_msg);
899 if (colored_len <= 0 || colored_len >= (
int)
sizeof(colored_log_line) || plain_len <= 0 ||
900 plain_len >= (
int)
sizeof(plain_log_line)) {
903 (void)fflush(output_stream);
905 SAFE_FREE(stripped_msg);
912 size_t match_start = 0, match_len = 0;
915 SAFE_FREE(stripped_msg);
925 if (match_len > 0 && colors != NULL) {
927 const char *highlighted_line =
grep_highlight_colored(colored_log_line, plain_log_line, match_start, match_len);
940 SAFE_FREE(stripped_msg);
943 (void)fflush(output_stream);
946void log_msg(log_level_t level,
const char *file,
int line,
const char *func,
const char *fmt, ...) {
948 if (!atomic_load(&g_log.initialized)) {
951 if (level < (log_level_t)atomic_load(&g_log.level)) {
963 char msg_buffer[LOG_MMAP_MSG_BUFFER_SIZE];
969 msg_len = truncate_at_whole_line(msg_buffer, msg_len,
sizeof(msg_buffer));
973 validate_log_message_utf8(msg_buffer,
"mmap log message");
978 if (atomic_load(&g_log.terminal_output_enabled) && !atomic_load(&g_log.terminal_locked)) {
979 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
985 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
990 bool use_colors =
true;
996 char header_buffer[512];
997 int header_len = format_log_header(header_buffer,
sizeof(header_buffer), level, time_buf, file, line, func,
998 use_colors, time_ns);
1000 if (header_len >= 0 && header_len < (
int)
sizeof(header_buffer)) {
1008 safe_fprintf(output_stream,
"%s%s%s%s\n", header_buffer, colors[LOG_COLOR_RESET], colorized_msg,
1009 colors[LOG_COLOR_RESET]);
1011 safe_fprintf(output_stream,
"%s%s\n", header_buffer, colorized_msg);
1014 safe_fprintf(output_stream,
"%s%s\n", header_buffer, msg_buffer);
1016 (void)fflush(output_stream);
1024 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1028 char log_buffer[LOG_MSG_BUFFER_SIZE];
1030 va_start(
args, fmt);
1031 int header_len = format_log_header(log_buffer,
sizeof(log_buffer), level, time_buf, file, line, func,
false, time_ns);
1032 if (header_len < 0 || header_len >= (
int)
sizeof(log_buffer)) {
1037 int msg_len = header_len;
1038 int formatted_len =
safe_vsnprintf(log_buffer + header_len,
sizeof(log_buffer) - (
size_t)header_len, fmt,
args);
1039 if (formatted_len < 0) {
1045 msg_len += formatted_len;
1047 msg_len = truncate_at_whole_line(log_buffer, msg_len,
sizeof(log_buffer));
1049 if (msg_len > 0 && msg_len < (
int)
sizeof(log_buffer) - 1) {
1050 if (log_buffer[msg_len - 1] !=
'\n') {
1051 log_buffer[msg_len++] =
'\n';
1052 log_buffer[msg_len] =
'\0';
1057 validate_log_message_utf8(log_buffer,
"leveled log message");
1060 const char *user_message = log_buffer + header_len;
1063 char json_message_buf[LOG_MSG_BUFFER_SIZE];
1064 const char *json_message = user_message;
1065 size_t user_msg_len = strlen(user_message);
1068 if (user_msg_len > 0 && user_message[user_msg_len - 1] ==
'\n') {
1072 int msg_content_len = msg_len - header_len;
1073 if (msg_content_len > 1 && log_buffer[msg_len - 2] !=
'\n') {
1075 if (user_msg_len - 1 <
sizeof(json_message_buf)) {
1076 memcpy(json_message_buf, user_message, user_msg_len - 1);
1077 json_message_buf[user_msg_len - 1] =
'\0';
1078 json_message = json_message_buf;
1084 int json_fd = atomic_load(&g_log.json_file);
1085 bool json_format_enabled = (json_fd >= 0);
1088 if (json_format_enabled) {
1090 log_json_write(json_fd, level, time_ns, file, line, func, json_message);
1092 if (atomic_load(&g_log.terminal_output_enabled)) {
1094 log_json_write(console_fd, level, time_ns, file, line, func, json_message);
1099 int file_fd = atomic_load(&g_log.file);
1100 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1101 write_to_log_file_atomic(log_buffer, msg_len);
1105 va_list args_terminal;
1106 va_start(args_terminal, fmt);
1107 write_to_terminal_atomic(level, time_buf, file, line, func, fmt, args_terminal, time_ns);
1108 va_end(args_terminal);
1112void log_terminal_msg(log_level_t level,
const char *file,
int line,
const char *func,
const char *fmt, ...) {
1114 if (!atomic_load(&g_log.initialized)) {
1118 if (level < (log_level_t)atomic_load(&g_log.level)) {
1123 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1128 va_start(
args, fmt);
1129 write_to_terminal_atomic(level, time_buf, file, line, func, fmt,
args, time_ns);
1134 if (!atomic_load(&g_log.initialized)) {
1142 char log_buffer[LOG_MSG_BUFFER_SIZE];
1144 va_start(
args, fmt);
1153 msg_len = truncate_at_whole_line(log_buffer, msg_len,
sizeof(log_buffer));
1156 validate_log_message_utf8(log_buffer,
"plain text log");
1163 int file_fd = atomic_load(&g_log.file);
1164 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1166 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1170 char header_buffer[512];
1171 int header_len = format_log_header(header_buffer,
sizeof(header_buffer), LOG_INFO, time_buf,
"lib/log/logging.c",
1172 0,
"log_plain_msg",
false, time_ns);
1174 if (header_len > 0) {
1175 write_to_log_file_atomic(header_buffer, header_len);
1177 write_to_log_file_atomic(log_buffer, msg_len);
1178 write_to_log_file_atomic(
"\n", 1);
1183 if (!atomic_load(&g_log.terminal_output_enabled)) {
1186 if (atomic_load(&g_log.terminal_locked)) {
1187 uint64_t owner = atomic_load(&g_log.terminal_owner_thread);
1194 int json_fd = atomic_load(&g_log.json_file);
1195 bool json_format_enabled = (json_fd >= 0);
1197 if (json_format_enabled) {
1201 log_json_write(console_fd, LOG_INFO, time_ns, __FILE__, __LINE__,
"log_plain_msg", log_buffer);
1205 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
1214 (void)fflush(output_stream);
1219static void log_plain_stderr_internal_atomic(
const char *fmt, va_list
args,
bool add_newline) {
1220 char log_buffer[LOG_MSG_BUFFER_SIZE];
1228 msg_len = truncate_at_whole_line(log_buffer, msg_len,
sizeof(log_buffer));
1231 validate_log_message_utf8(log_buffer,
"stderr log");
1238 int file_fd = atomic_load(&g_log.file);
1239 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1241 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1245 char header_buffer[512];
1246 int header_len = format_log_header(header_buffer,
sizeof(header_buffer), LOG_INFO, time_buf,
"lib/log/logging.c",
1247 0,
"log_plain_stderr_msg",
false, time_ns);
1249 if (header_len > 0) {
1250 write_to_log_file_atomic(header_buffer, header_len);
1252 write_to_log_file_atomic(log_buffer, msg_len);
1254 write_to_log_file_atomic(
"\n", 1);
1260 if (!atomic_load(&g_log.terminal_output_enabled)) {
1263 if (atomic_load(&g_log.terminal_locked)) {
1264 uint64_t owner = atomic_load(&g_log.terminal_owner_thread);
1285 (void)fflush(stderr);
1289 if (!atomic_load(&g_log.initialized)) {
1297 va_start(
args, fmt);
1298 log_plain_stderr_internal_atomic(fmt,
args,
true);
1303 if (!atomic_load(&g_log.initialized)) {
1311 va_start(
args, fmt);
1312 log_plain_stderr_internal_atomic(fmt,
args,
false);
1317 if (!atomic_load(&g_log.initialized)) {
1321 char log_buffer[LOG_MSG_BUFFER_SIZE];
1323 va_start(
args, fmt);
1332 msg_len = truncate_at_whole_line(log_buffer, msg_len,
sizeof(log_buffer));
1335 validate_log_message_utf8(log_buffer,
"file-only log");
1341 int file_fd = atomic_load(&g_log.file);
1342 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1343 write_to_log_file_atomic(log_buffer, msg_len);
1344 write_to_log_file_atomic(
"\n", 1);
1349static const char *log_network_direction_label(remote_log_direction_t direction) {
1350 switch (direction) {
1351 case REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT:
1352 return "server→client";
1353 case REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER:
1354 return "client→server";
1360static asciichat_error_t log_network_message_internal(
socket_t sockfd,
const struct crypto_context_t *crypto_ctx,
1361 log_level_t level, remote_log_direction_t direction,
1362 const char *file,
int line,
const char *func,
const char *fmt,
1365 return SET_ERRNO(ERROR_INVALID_PARAM,
"Format string is NULL");
1369 va_copy(args_copy,
args);
1374 asciichat_error_t current_error = GET_ERRNO();
1375 if (current_error == ASCIICHAT_OK) {
1376 current_error = SET_ERRNO(ERROR_MEMORY,
"Failed to format network log message");
1378 return current_error;
1381 asciichat_error_t send_result = ASCIICHAT_OK;
1382 if (sockfd == INVALID_SOCKET_VALUE) {
1383 send_result = SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid socket descriptor");
1384 log_msg(LOG_WARN, file, line, func,
"Skipping remote log message: invalid socket descriptor");
1386 send_result =
packet_send_remote_log(sockfd, (
const crypto_context_t *)crypto_ctx, level, direction, 0, formatted);
1387 if (send_result != ASCIICHAT_OK) {
1388 log_msg(LOG_WARN, file, line, func,
"Failed to send remote log message: %s", asciichat_error_string(send_result));
1392 const char *direction_label = log_network_direction_label(direction);
1393 log_msg(level, file, line, func,
"[NET %s] %s", direction_label, formatted);
1395 SAFE_FREE(formatted);
1400 remote_log_direction_t direction,
const char *fmt, ...) {
1402 va_start(
args, fmt);
1403 asciichat_error_t result =
1404 log_network_message_internal(sockfd, crypto_ctx, level, direction, NULL, 0, NULL, fmt,
args);
1410 remote_log_direction_t direction,
const char *file,
int line,
const char *func,
1411 const char *fmt, ...) {
1413 va_start(
args, fmt);
1414 asciichat_error_t result =
1415 log_network_message_internal(sockfd, crypto_ctx, level, direction, file, line, func, fmt,
args);
1425static void init_terminal_capabilities(
void) {
1428 if (g_terminal_caps_detecting) {
1432 if (!g_terminal_caps_initialized) {
1434 g_terminal_caps_detecting =
true;
1441 g_terminal_caps.color_level = TERM_COLOR_16;
1442 g_terminal_caps.capabilities = TERM_CAP_COLOR_16;
1443 g_terminal_caps.color_count = 16;
1444 g_terminal_caps.detection_reliable =
false;
1445 g_terminal_caps_initialized =
true;
1448 g_terminal_caps_detecting =
false;
1455 if (g_terminal_caps_detecting) {
1462 if (!g_terminal_caps_initialized || !g_terminal_caps.detection_reliable) {
1463 g_terminal_caps_detecting =
true;
1465 g_terminal_caps_detecting =
false;
1466 g_terminal_caps_initialized =
true;
1469 log_debug(
"Terminal capabilities: color_level=%d, capabilities=0x%x, utf8=%s, fps=%d", g_terminal_caps.color_level,
1470 g_terminal_caps.capabilities, g_terminal_caps.utf8_support ?
"yes" :
"no", g_terminal_caps.desired_fps);
1479 init_terminal_capabilities();
1481 if (!g_log_colorscheme_initialized) {
1486 if (!g_log_colorscheme_initialized) {
1493 if (g_terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1494 return (
const char **)g_compiled_colors.codes_truecolor;
1495 }
else if (g_terminal_caps.color_level >= TERM_COLOR_256) {
1496 return (
const char **)g_compiled_colors.codes_256;
1498 return (
const char **)g_compiled_colors.codes_16;
1504 if (colors == NULL) {
1507 if (color >= 0 && color <= LOG_COLOR_RESET) {
1508 return colors[color];
1510 return colors[LOG_COLOR_RESET];
1519 if (g_terminal_caps_detecting) {
1524 if (!atomic_load(&g_log.initialized)) {
1528 if (g_log_colorscheme_initialized) {
1544 static bool first_compile =
true;
1545 if (first_compile) {
1546 memset(&g_compiled_colors, 0,
sizeof(g_compiled_colors));
1547 first_compile =
false;
1553 terminal_color_mode_t mode;
1554 if (g_terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1555 mode = TERM_COLOR_TRUECOLOR;
1556 }
else if (g_terminal_caps.color_level >= TERM_COLOR_256) {
1557 mode = TERM_COLOR_256;
1559 mode = TERM_COLOR_16;
1563 g_log_colorscheme_initialized =
true;
1567 if (result != ASCIICHAT_OK) {
1568 log_debug(
"Failed to compile color scheme: %d", result);
1584 terminal_color_mode_t mode;
1585 if (g_terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1586 mode = TERM_COLOR_TRUECOLOR;
1587 }
else if (g_terminal_caps.color_level >= TERM_COLOR_256) {
1588 mode = TERM_COLOR_256;
1590 mode = TERM_COLOR_16;
1596 g_log_colorscheme_initialized =
true;
1600 if (result != ASCIICHAT_OK) {
1601 log_debug(
"Failed to compile color scheme: %d", result);
1615 return SET_ERRNO(ERROR_INVALID_PARAM,
"log_path is required");
1620 if (result != ASCIICHAT_OK) {
1624 log_info(
"Lock-free mmap logging enabled: %s", log_path);
1625 return ASCIICHAT_OK;
1631 log_info(
"Lock-free mmap logging disabled");
1640 if (g_shutdown_in_progress) {
1645 g_shutdown_saved_terminal_output = atomic_load(&g_log.terminal_output_enabled);
1646 atomic_store(&g_log.terminal_output_enabled,
false);
1647 g_shutdown_in_progress =
true;
1651 if (!g_shutdown_in_progress) {
1656 atomic_store(&g_log.terminal_output_enabled, g_shutdown_saved_terminal_output);
1657 g_shutdown_in_progress =
false;
1681 if (!plain_line || !colored_buf || buf_size < 128) {
1685 static char work_buffer[LOG_MSG_BUFFER_SIZE + 1024];
1689 const char *p = plain_line;
1696 const char *timestamp_start = p;
1700 while (*p && *p !=
']') {
1707 size_t timestamp_len = p - timestamp_start;
1709 if (timestamp_len >=
sizeof(timestamp) || timestamp_len == 0) {
1712 SAFE_STRNCPY(timestamp, timestamp_start, timestamp_len);
1713 timestamp[timestamp_len] =
'\0';
1717 while (*p && *p ==
' ') {
1726 const char *level_start = p;
1727 while (*p && *p !=
']') {
1733 size_t level_len = p - level_start;
1735 if (level_len >=
sizeof(level_str) || level_len == 0) {
1739 strncpy(level_str, level_start, level_len);
1740 level_str[level_len] =
'\0';
1743 log_level_t level = LOG_INFO;
1744 if (strstr(level_str,
"DEV") || strstr(level_str,
"DEBUG")) {
1746 }
else if (strstr(level_str,
"INFO")) {
1748 }
else if (strstr(level_str,
"WARN")) {
1750 }
else if (strstr(level_str,
"ERROR")) {
1752 }
else if (strstr(level_str,
"FATAL")) {
1759 while (*p && *p ==
' ') {
1765 if (*p ==
'[' && strncmp(p,
"[tid:", 5) == 0) {
1767 char *tid_end = NULL;
1768 tid = strtoull(p, &tid_end, 10);
1769 if (!tid_end || *tid_end !=
']') {
1772 while (*p && *p !=
']') {
1782 while (*p && *p ==
' ') {
1789 const char *file_start = p;
1790 while (*p && *p !=
':') {
1796 size_t file_len = p - file_start;
1802 strncpy(
file_path, file_start, file_len);
1808 const char *line_start = p;
1809 while (*p && isdigit((
unsigned char)*p)) {
1812 if (p == line_start) {
1815 line_num = (int)strtol(line_start, NULL, 10);
1818 while (*p && (*p ==
' ' || *p ==
'\t' || *p ==
'\n' || *p ==
'\r')) {
1823 if (strncmp(p,
"in ", 3) == 0) {
1825 }
else if (*p ==
'i' && *(p + 1) ==
'n' && (*(p + 2) ==
' ' || *(p + 2) ==
'\t')) {
1828 while (*p && (*p ==
' ' || *p ==
'\t')) {
1837 const char *func_start = p;
1838 while (*p && *p !=
'(') {
1841 if (*p !=
'(' || func_start == p) {
1844 size_t func_len = p - func_start;
1847 while (func_len > 0 && (func_start[func_len - 1] ==
' ' || func_start[func_len - 1] ==
'\t')) {
1851 char func_name[256];
1852 if (func_len >=
sizeof(func_name)) {
1853 func_len =
sizeof(func_name) - 1;
1857 strncpy(func_name, func_start, func_len);
1859 func_name[func_len] =
'\0';
1864 while (*p && *p !=
')') {
1873 while (*p && (*p ==
' ' || *p ==
':' || *p ==
'\t')) {
1878 const char *message = p;
1884 static bool warned_once =
false;
1886 log_debug(
"WARNING: log_recolor_plain_entry() called but colors not initialized - returning plain text");
1889 size_t len = strlen(plain_line);
1890 if (len >= buf_size) {
1893 SAFE_STRNCPY(colored_buf, plain_line, buf_size - 1);
1894 colored_buf[buf_size - 1] =
'\0';
1899 const char *level_color = colors[level];
1900 const char *reset = colors[LOG_COLOR_RESET];
1901 const char *file_color = colors[1];
1902 const char *line_color = colors[6];
1903 const char *func_color = colors[0];
1904 const char *tid_color = colors[6];
1907 "[%s%s%s] [%s%s%s] [tid:%s%llu%s] %s%s%s:%s%d%s in %s%s%s(): %s", level_color, timestamp,
1908 reset, level_color, level_str, reset, tid_color, (
unsigned long long)tid, reset, file_color,
1909 file_path, reset, line_color, line_num, reset, func_color, func_name, reset, message);
1911 if (len <= 0 || len >= (
int)
sizeof(work_buffer)) {
1918 "[%s%s%s] [%s%s%s] [tid:%s%llu%s] %s%s%s:%s%d%s in %s%s%s(): %s", level_color, timestamp, reset,
1919 level_color, level_str, reset, tid_color, (
unsigned long long)tid, reset, file_color,
file_path,
1920 reset, line_color, line_num, reset, func_color, func_name, reset, colorized_msg);
1922 if (len <= 0 || len >= (
int)
sizeof(work_buffer) || len >= (
int)buf_size) {
1926 SAFE_STRNCPY(colored_buf, work_buffer, buf_size - 1);
1927 colored_buf[buf_size - 1] =
'\0';
1932void log_console_impl(log_level_t level,
const char *file,
int line,
const char *func,
const char *message) {
1943 int json_fd = atomic_load(&g_log.json_file);
1944 bool use_json = (json_fd >= 0);
1948 extern void log_json_async_safe(
int fd, log_level_t level,
const char *file,
int line,
const char *func,
1949 const char *message);
1953 size_t msg_len = strlen(message);
1955 if (msg_len == 0 || message[msg_len - 1] !=
'\n') {
1971 return (
void *)g_log.format;
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all data to file descriptor with automatic retry on transient errors.
char * ansi_strip_escapes(const char *input, size_t input_len)
const char * colorize_log_message(const char *message)
Colorize a log message for terminal output.
const color_scheme_t * colorscheme_get_active_scheme(void)
terminal_background_t detect_terminal_background(void)
asciichat_error_t colorscheme_compile_scheme(const color_scheme_t *scheme, terminal_color_mode_t mode, terminal_background_t background, compiled_color_scheme_t *compiled)
void colorscheme_cleanup_compiled(compiled_color_scheme_t *compiled)
mutex_t g_colorscheme_mutex
ASCIICHAT_API bool g_color_flag_value
ASCIICHAT_API bool g_color_flag_passed
bool shutdown_is_requested(void)
void platform_log_hook(log_level_t level, const char *message)
bool grep_should_output(const char *log_line, size_t *match_start, size_t *match_len)
bool enabled
Is filtering active?
int buffer_size
Size of circular buffer.
const char * grep_highlight_colored(const char *colored_text, const char *plain_text, size_t match_start, size_t match_len)
void log_json_write(int fd, log_level_t level, uint64_t time_nanoseconds, const char *file, int line, const char *func, const char *message)
void log_json_async_safe(int fd, log_level_t level, const char *file, int line, const char *func, const char *message)
Async-safe JSON logging for signal handlers.
void log_truncate_if_large(void)
void log_plain_stderr_nonewline_msg(const char *fmt,...)
void log_plain_msg(const char *fmt,...)
bool log_get_terminal_output(void)
asciichat_error_t log_enable_mmap(const char *log_path)
void log_init_colors(void)
void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
void log_shutdown_end(void)
void log_set_force_stderr(bool enabled)
log_level_t log_get_level(void)
const char ** log_get_color_array(void)
const char * log_level_color(log_color_t color)
void log_set_flush_delay(unsigned int delay_ms)
char * format_message(const char *format, va_list args)
void log_cleanup_colors(void)
Clean up compiled color scheme.
void log_shutdown_begin(void)
void log_disable_file_output(void)
bool log_get_force_stderr(void)
void log_set_level(log_level_t level)
size_t log_recolor_plain_entry(const char *plain_line, char *colored_buf, size_t buf_size)
Recolor a plain (non-colored) log line with proper ANSI codes.
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
asciichat_error_t log_net_message(socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *file, int line, const char *func, const char *fmt,...)
bool log_lock_terminal(void)
asciichat_error_t log_network_message(socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *fmt,...)
asciichat_error_t log_set_format(const char *format_str, bool console_only)
size_t get_current_time_formatted(char *time_buf)
asciichat_error_t log_enable_mmap_sized(const char *log_path, size_t max_size)
void log_set_json_output(int fd)
void log_file_msg(const char *fmt,...)
void log_disable_mmap(void)
void log_set_terminal_output(bool enabled)
void * log_get_template(void)
Get the current log format template.
void log_set_color_scheme(const color_scheme_t *scheme)
#define LOGGING_INTERNAL_ERROR(error, message,...)
const char * get_level_string_padded(log_level_t level)
Get padded level string for consistent alignment.
void log_plain_stderr_msg(const char *fmt,...)
void log_redetect_terminal_capabilities(void)
void log_unlock_terminal(bool previous_state)
void log_console_impl(log_level_t level, const char *file, int line, const char *func, const char *message)
void log_terminal_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
void server_status_log_append(const char *message)
char file_path[PLATFORM_MAX_PATH_LENGTH]
void log_mmap_rotate(void)
bool log_mmap_is_active(void)
void log_mmap_write(int level, const char *file, int line, const char *func, const char *fmt,...)
bool log_mmap_get_usage(size_t *used, size_t *capacity)
void log_mmap_destroy(void)
asciichat_error_t log_mmap_init_simple(const char *log_path, size_t max_size)
asciichat_error_t packet_send_remote_log(socket_t sockfd, const crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, uint16_t flags, const char *message)
const options_t * options_get(void)
int safe_fprintf(FILE *stream, const char *format,...)
Safe formatted output to file stream.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Safe formatted string printing with va_list.
#define PLATFORM_MAX_PATH_LENGTH
int mutex_init(mutex_t *mutex)
asciichat_thread_t asciichat_thread_self(void)
uint64_t asciichat_thread_current_id(void)
int mutex_destroy(mutex_t *mutex)
bool utf8_is_valid(const char *str)
uint64_t time_get_realtime_ns(void)
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
int platform_close(int fd)
int platform_open(const char *pathname, int flags,...)
ssize_t platform_read(int fd, void *buf, size_t count)
ssize_t platform_write(int fd, const void *buf, size_t count)