libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
log.c
Go to the documentation of this file.
1/*
2 * This file is part of the Aaru Data Preservation Suite.
3 * Copyright (c) 2019-2026 Natalia Portillo.
4 *
5 * This library is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 2.1 of the
8 * License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE)
20#define _POSIX_C_SOURCE 200112L // localtime_r
21#endif
22
23#include <stdarg.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <time.h>
28
29#ifdef _WIN32
30#include <windows.h>
31#else
32#include <sys/time.h>
33#endif
34
35#include "log.h"
36
37#define LOG_STATE_UNINITIALIZED 0
38#define LOG_STATE_INITIALIZING 1
39#define LOG_STATE_READY 2
40
41#define LOG_LEVEL_NONE (-1)
42
43#define LOG_STACK_BUFFER_SIZE 1024
44
45static volatile long log_state = LOG_STATE_UNINITIALIZED;
46static FILE *log_stream = NULL;
48
49/* Minimal portable atomics so the one-time initialization is race-free without pthreads. */
50#if defined(_MSC_VER)
51static int log_state_claim(void) { return InterlockedCompareExchange(&log_state, LOG_STATE_INITIALIZING, LOG_STATE_UNINITIALIZED) == LOG_STATE_UNINITIALIZED; }
52static long log_state_load(void) { return InterlockedCompareExchange(&log_state, 0, 0); }
53static void log_state_store(long value) { InterlockedExchange(&log_state, value); }
54#elif defined(__GNUC__) || defined(__clang__)
55static int log_state_claim(void)
56{
57 long expected = LOG_STATE_UNINITIALIZED;
58 return __atomic_compare_exchange_n(&log_state, &expected, LOG_STATE_INITIALIZING, 0, __ATOMIC_ACQ_REL,
59 __ATOMIC_ACQUIRE);
60}
61static long log_state_load(void) { return __atomic_load_n(&log_state, __ATOMIC_ACQUIRE); }
62static void log_state_store(long value) { __atomic_store_n(&log_state, value, __ATOMIC_RELEASE); }
63#else
64static int log_state_claim(void)
65{
66 if(log_state != LOG_STATE_UNINITIALIZED) return 0;
68 return 1;
69}
70static long log_state_load(void) { return log_state; }
71static void log_state_store(long value) { log_state = value; }
72#endif
73
74static int log_strcasecmp(const char *a, const char *b)
75{
76 while(*a && *b)
77 {
78 int ca = (unsigned char)*a++;
79 int cb = (unsigned char)*b++;
80 if(ca >= 'A' && ca <= 'Z') ca += 'a' - 'A';
81 if(cb >= 'A' && cb <= 'Z') cb += 'a' - 'A';
82 if(ca != cb) return ca - cb;
83 }
84 return (unsigned char)*a - (unsigned char)*b;
85}
86
93static void log_init(void)
94{
95 const char *level_env;
96 const char *file_env;
97
98 if(log_state_load() == LOG_STATE_READY) return;
99
100 if(!log_state_claim())
101 {
102 // Another thread is initializing; the window is a getenv() and an fopen().
104 return;
105 }
106
107 level_env = getenv("AARUFORMAT_LOG_LEVEL");
108 if(level_env != NULL)
109 {
110 if(log_strcasecmp(level_env, "none") == 0 || log_strcasecmp(level_env, "off") == 0)
112 else if(log_strcasecmp(level_env, "fatal") == 0)
114 else
116 }
117
118 log_stream = stderr;
119 file_env = getenv("AARUFORMAT_LOG_FILE");
120 if(file_env != NULL && *file_env != '\0' && log_level != LOG_LEVEL_NONE)
121 {
122 FILE *file = fopen(file_env, "a");
123 if(file != NULL) log_stream = file;
124 }
125
127}
128
130static const char *log_short_path(const char *file)
131{
132 const char *p;
133 const char *base = file;
134
135 for(p = file; *p; p++)
136 {
137 if((*p == '/' || *p == '\\') && strncmp(p + 1, "src", 3) == 0 && (p[4] == '/' || p[4] == '\\'))
138 base = p + 5;
139 }
140
141 if(base != file) return base;
142
143 for(p = file; *p; p++)
144 if(*p == '/' || *p == '\\') base = p + 1;
145
146 return base;
147}
148
149static int log_timestamp(char *buf, size_t size)
150{
151#ifdef _WIN32
152 SYSTEMTIME st;
153 GetLocalTime(&st);
154 return snprintf(buf, size, "%02u:%02u:%02u.%03u", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
155#else
156 struct timeval tv;
157 struct tm tm;
158 time_t secs;
159
160 gettimeofday(&tv, NULL);
161 secs = tv.tv_sec;
162 if(localtime_r(&secs, &tm) == NULL) return snprintf(buf, size, "??:??:??.???");
163 return snprintf(buf, size, "%02d:%02d:%02d.%03ld", tm.tm_hour, tm.tm_min, tm.tm_sec, (long)(tv.tv_usec / 1000));
164#endif
165}
166
173void aaru_log_write(int level, const char *file, int line, const char *func, const char *fmt, ...)
174{
175 char stack_buf[LOG_STACK_BUFFER_SIZE];
176 char *buf = stack_buf;
177 char timestamp[16];
178 const char *tag = level == AARU_LOG_FATAL ? "FATAL" : "TRACE";
179 int prefix_len;
180 int msg_len;
181 size_t total;
182 va_list args;
183 va_list args_copy;
184
185 log_init();
186
187 if(level > log_level) return;
188
189 log_timestamp(timestamp, sizeof(timestamp));
190
191 prefix_len = snprintf(NULL, 0, "%s [%s] %s:%d %s(): ", timestamp, tag, log_short_path(file), line, func);
192
193 va_start(args, fmt);
194 va_copy(args_copy, args);
195 msg_len = vsnprintf(NULL, 0, fmt, args_copy);
196 va_end(args_copy);
197
198 if(prefix_len < 0 || msg_len < 0)
199 {
200 va_end(args);
201 return;
202 }
203
204 total = (size_t)prefix_len + (size_t)msg_len + 2; // newline + terminator
205
206 if(total > sizeof(stack_buf))
207 {
208 buf = malloc(total);
209 if(buf == NULL)
210 {
211 va_end(args);
212 return;
213 }
214 }
215
216 snprintf(buf, total, "%s [%s] %s:%d %s(): ", timestamp, tag, log_short_path(file), line, func);
217 vsnprintf(buf + prefix_len, total - (size_t)prefix_len, fmt, args);
218 va_end(args);
219
220 buf[total - 2] = '\n';
221 buf[total - 1] = '\0';
222
223 fwrite(buf, 1, total - 1, log_stream);
224 fflush(log_stream);
225
226 if(buf != stack_buf) free(buf);
227}
228
230{
231 if(log_state_load() == LOG_STATE_READY && log_stream != NULL && log_stream != stderr) fclose(log_stream);
232
233 log_stream = NULL;
236}
static volatile long log_state
Definition log.c:45
#define LOG_STATE_INITIALIZING
Definition log.c:38
#define LOG_STACK_BUFFER_SIZE
Definition log.c:43
static long log_state_load(void)
Definition log.c:70
static int log_strcasecmp(const char *a, const char *b)
Definition log.c:74
#define LOG_LEVEL_NONE
Definition log.c:41
#define LOG_STATE_READY
Definition log.c:39
static void log_state_store(long value)
Definition log.c:71
static int log_timestamp(char *buf, size_t size)
Definition log.c:149
#define LOG_STATE_UNINITIALIZED
Definition log.c:37
static void log_init(void)
Resolves the logging configuration from the environment, exactly once per process.
Definition log.c:93
static const char * log_short_path(const char *file)
Strips the build-machine directory from FILE, keeping the path below src/ if present.
Definition log.c:130
static int log_state_claim(void)
Definition log.c:64
static FILE * log_stream
Definition log.c:46
static int log_level
Definition log.c:47
#define AARU_LOG_FATAL
Definition log.h:37
#define AARU_LOG_TRACE
Definition log.h:38
void aaru_log_reset(void)
Definition log.c:229
void aaru_log_write(int level, const char *file, int line, const char *func, const char *fmt,...)
Writes one log line.
Definition log.c:173