emme coverage


Directory: src/
File: src/log.c
Date: 2025-08-24 07:42:18
Exec Total Coverage
Lines: 0 104 0.0%
Functions: 0 6 0.0%
Branches: 0 60 0.0%

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