emme coverage


Directory: src/
File: src/log.c
Date: 2026-03-27 20:24:50
Exec Total Coverage
Lines: 78 113 69.0%
Functions: 5 6 83.3%
Branches: 32 66 48.5%

Line Branch Exec Source
1 #include "log.h"
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <stdarg.h>
5 #include <string.h>
6 #include <pthread.h>
7 #include <time.h>
8 #include <stdatomic.h>
9 #include <unistd.h>
10 #include <sys/stat.h>
11
12 #define MAX_LOG_ENTRY_SIZE 1024
13
14 /* Struttura per ciascun messaggio di log */
15 typedef struct {
16 LogLevel level;
17 char message[MAX_LOG_ENTRY_SIZE];
18 struct timespec timestamp;
19 } LogEntry;
20
21 /* Variabili statiche per la gestione del ring buffer e del logging */
22 static LogEntry *log_buffer = NULL;
23 static atomic_size_t log_head; // indice di scrittura
24 static atomic_size_t log_tail; // indice di lettura
25 static size_t log_buffer_size = 0;
26 static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;
27
28 /* Variabili di configurazione */
29 static LoggingConfig current_config;
30 static FILE *log_fp = NULL;
31 static pthread_t logger_thread;
32 static atomic_int logger_running = ATOMIC_VAR_INIT(0);
33
34 /* Variabili per il rollover */
35 static struct timespec last_rollover;
36 static size_t current_file_size = 0;
37
38 /* Funzione di rollover: rinomina il file attuale e ne crea uno nuovo */
39 static void rollover_log_file(void) {
40 if (!(current_config.appender_flags & APPENDER_FILE))
41 return;
42
43 // Chiudi il file corrente
44 if (log_fp) {
45 fclose(log_fp);
46 log_fp = NULL;
47 }
48
49 // Costruisci il nome di backup con timestamp
50 char backup_filename[512];
51 char timebuf[64];
52 struct tm tm_info;
53 localtime_r(&last_rollover.tv_sec, &tm_info);
54 strftime(timebuf, sizeof(timebuf), "%Y%m%d%H%M%S", &tm_info);
55 snprintf(backup_filename, sizeof(backup_filename), "%s.%s.bak", current_config.file, timebuf);
56
57 // Rinomina il file attuale in backup
58 rename(current_config.file, backup_filename);
59
60 // Riapri un nuovo file di log
61 log_fp = fopen(current_config.file, "a");
62 current_file_size = 0;
63 clock_gettime(CLOCK_REALTIME, &last_rollover);
64 }
65
66 /* Verifica se occorre effettuare il rollover in base a dimensione o intervallo */
67 42 static void check_rollover(const char *formatted_message) {
68
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
42 if (!(current_config.appender_flags & APPENDER_FILE))
69 return;
70
71 42 int do_rollover = 0;
72 42 size_t msg_len = strlen(formatted_message);
73
74 // Verifica la dimensione
75
2/4
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
42 if (current_config.rollover_size > 0 && (current_file_size + msg_len) >= current_config.rollover_size) {
76 do_rollover = 1;
77 }
78 // Verifica il rollover giornaliero
79
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if (current_config.rollover_daily) {
80 time_t now_t;
81 42 time(&now_t);
82 struct tm now_tm, last_tm;
83 42 localtime_r(&now_t, &now_tm);
84 42 localtime_r(&last_rollover.tv_sec, &last_tm);
85 // Se il giorno dell'anno è cambiato, effettua il rollover
86
2/4
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
42 if (now_tm.tm_yday != last_tm.tm_yday || now_tm.tm_year != last_tm.tm_year) {
87 do_rollover = 1;
88 }
89 }
90
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
42 if (do_rollover) {
91 rollover_log_file();
92 }
93 }
94
95 /* Funzione del thread di logging: estrae dal buffer e scrive sugli appender */
96 8 static void *logger_thread_func(void *arg) {
97 (void)arg;
98 8 clock_gettime(CLOCK_REALTIME, &last_rollover);
99
3/4
✓ Branch 0 taken 2509 times.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
2517 while (atomic_load(&logger_running) || (atomic_load(&log_head) != atomic_load(&log_tail))) {
100 2509 size_t tail = atomic_load_explicit(&log_tail, memory_order_relaxed);
101 2509 size_t head = atomic_load_explicit(&log_head, memory_order_acquire);
102
2/2
✓ Branch 0 taken 2467 times.
✓ Branch 1 taken 42 times.
2509 if (tail == head) {
103 /* Buffer vuoto: attesa breve per evitare busy-wait */
104 2467 usleep(1000);
105 2467 continue;
106 }
107 42 LogEntry entry = log_buffer[tail % log_buffer_size];
108
109 /* Formatta il timestamp */
110 char timebuf[64];
111 struct tm tm_info;
112 42 localtime_r(&(entry.timestamp.tv_sec), &tm_info);
113 42 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &tm_info);
114
115 /* Costruisce il messaggio formattato */
116 char formatted_message[2048];
117
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if (current_config.format == LOG_FORMAT_PLAIN) {
118 42 snprintf(formatted_message, sizeof(formatted_message),
119 "[%s.%03ld] [%d] %s\n",
120 42 timebuf, entry.timestamp.tv_nsec / 1000000, entry.level, entry.message);
121 } else { // LOG_FORMAT_JSON
122 snprintf(formatted_message, sizeof(formatted_message),
123 "{\"timestamp\": \"%s.%03ld\", \"level\": %d, \"message\": \"%s\"}\n",
124 timebuf, entry.timestamp.tv_nsec / 1000000, entry.level, entry.message);
125 }
126
127 /* Scrive sugli appender configurati */
128
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if (current_config.appender_flags & APPENDER_FILE) {
129 42 check_rollover(formatted_message);
130
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if (log_fp) {
131 42 fputs(formatted_message, log_fp);
132 42 fflush(log_fp);
133 42 current_file_size += strlen(formatted_message);
134 }
135 }
136
1/2
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
42 if (current_config.appender_flags & APPENDER_CONSOLE) {
137 42 fputs(formatted_message, stdout);
138 }
139 42 atomic_fetch_add(&log_tail, 1);
140 }
141 8 return NULL;
142 }
143
144 /* Inizializza il modulo di logging */
145 8 int log_init(const LoggingConfig *config) {
146
147 // Initialize last_rollover with the current time
148
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (clock_gettime(CLOCK_REALTIME, &last_rollover) != 0)
149 {
150 perror("Failed to initialize last_rollover");
151 return -1;
152 }
153
154
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!config)
155 return -1;
156
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
8 if ((config->appender_flags & APPENDER_FILE) && config->file[0] == '\0')
157 return -1;
158 8 current_config = *config;
159
160 /* Alloca il buffer di log */
161
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 log_buffer_size = (config->buffer_size > 0) ? config->buffer_size : 1024;
162 8 log_buffer = malloc(sizeof(LogEntry) * log_buffer_size);
163
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!log_buffer)
164 return -1;
165 8 atomic_init(&log_head, 0);
166 8 atomic_init(&log_tail, 0);
167
168 /* Inizializza file di log se richiesto */
169
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (current_config.appender_flags & APPENDER_FILE) {
170 8 log_fp = fopen(current_config.file, "a");
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (!log_fp) {
172 free(log_buffer);
173 return -1;
174 }
175 /* Determina la dimensione corrente del file */
176 struct stat st;
177
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 if (stat(current_config.file, &st) == 0) {
178 8 current_file_size = st.st_size;
179 } else {
180 current_file_size = 0;
181 }
182 }
183
184 8 atomic_store(&logger_running, 1);
185
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (pthread_create(&logger_thread, NULL, logger_thread_func, NULL) != 0) {
186 if (log_fp)
187 fclose(log_fp);
188 free(log_buffer);
189 return -1;
190 }
191 8 return 0;
192 }
193
194 /* Funzione per inviare messaggi di log */
195 42 void log_message(LogLevel level, const char *format, ...) {
196
2/4
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 42 times.
42 if (!log_buffer || !atomic_load(&logger_running))
197 return;
198
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
42 if (level < current_config.level)
199 return;
200
201 42 pthread_mutex_lock(&log_lock);
202 42 size_t head = atomic_load_explicit(&log_head, memory_order_relaxed);
203 42 size_t tail = atomic_load_explicit(&log_tail, memory_order_relaxed);
204 /* Se il buffer è pieno, scarta il messaggio per non bloccare */
205
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
42 if (head - tail >= log_buffer_size) {
206 pthread_mutex_unlock(&log_lock);
207 return;
208 }
209
210 42 LogEntry *entry = &log_buffer[head % log_buffer_size];
211 42 entry->level = level;
212 42 clock_gettime(CLOCK_REALTIME, &entry->timestamp);
213
214 va_list args;
215 42 va_start(args, format);
216 42 vsnprintf(entry->message, MAX_LOG_ENTRY_SIZE, format, args);
217 42 va_end(args);
218
219 42 atomic_store_explicit(&log_head, head + 1, memory_order_release);
220 42 pthread_mutex_unlock(&log_lock);
221 }
222
223 /* Chiude il modulo di logging e libera le risorse */
224 8 void log_shutdown(void) {
225 8 atomic_store(&logger_running, 0);
226 8 pthread_join(logger_thread, NULL);
227
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (log_buffer) {
228 8 free(log_buffer);
229 8 log_buffer = NULL;
230 }
231
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (log_fp) {
232 8 fclose(log_fp);
233 8 log_fp = NULL;
234 }
235 8 }
236