Line data Source code
1 : #include "logger.h"
2 : #include "raii.h"
3 : #include <stdarg.h>
4 : #include <time.h>
5 : #include <string.h>
6 : #include <stdlib.h>
7 : #include <unistd.h>
8 : #include <sys/stat.h>
9 : #include <dirent.h>
10 :
11 : #define MAX_LOG_SIZE (5 * 1024 * 1024) // 5MB
12 : #define MAX_ROTATED_LOGS 5
13 :
14 : static FILE *g_log_fp = NULL;
15 : static LogLevel g_log_level = LOG_INFO;
16 : static char *g_log_path = NULL;
17 : static int g_log_stderr = 1;
18 :
19 : /** @brief Converts a LogLevel enum to its string representation. */
20 20433 : static const char* level_to_str(LogLevel level) {
21 20433 : switch (level) {
22 18405 : case LOG_DEBUG: return "DEBUG";
23 1880 : case LOG_INFO: return "INFO";
24 145 : case LOG_WARN: return "WARN";
25 3 : case LOG_ERROR: return "ERROR";
26 0 : default: return "UNKNOWN";
27 : }
28 : }
29 :
30 : /**
31 : * @brief Rotates log files: session.log → session.log.1, dropping session.log.5.
32 : */
33 0 : static void rotate_logs() {
34 0 : if (!g_log_path) return;
35 :
36 : // session.log.5 -> deleted
37 : // session.log.4 -> session.log.5
38 : // ...
39 : // session.log -> session.log.1
40 :
41 : char old_name[1024], new_name[1024];
42 :
43 : // Remove the oldest log
44 0 : snprintf(old_name, sizeof(old_name), "%s.%d", g_log_path, MAX_ROTATED_LOGS);
45 0 : unlink(old_name);
46 :
47 : // Rotate existing logs
48 0 : for (int i = MAX_ROTATED_LOGS - 1; i >= 1; i--) {
49 0 : snprintf(old_name, sizeof(old_name), "%s.%d", g_log_path, i);
50 0 : snprintf(new_name, sizeof(new_name), "%s.%d", g_log_path, i + 1);
51 0 : rename(old_name, new_name);
52 : }
53 :
54 : // Move current log
55 0 : snprintf(new_name, sizeof(new_name), "%s.1", g_log_path);
56 0 : rename(g_log_path, new_name);
57 : }
58 :
59 472 : int logger_init(const char *log_file_path, LogLevel level) {
60 472 : g_log_level = level;
61 472 : g_log_path = strdup(log_file_path);
62 :
63 : // Check size and rotate if necessary
64 : struct stat st;
65 472 : if (stat(g_log_path, &st) == 0 && st.st_size > MAX_LOG_SIZE) {
66 0 : rotate_logs();
67 : }
68 :
69 472 : g_log_fp = fopen(g_log_path, "a");
70 472 : if (!g_log_fp) {
71 0 : free(g_log_path);
72 0 : g_log_path = NULL;
73 0 : return -1;
74 : }
75 :
76 472 : logger_log(LOG_INFO, "Logging initialized. Level: %s", level_to_str(level));
77 472 : return 0;
78 : }
79 :
80 19974 : void logger_log(LogLevel level, const char *format, ...) {
81 19974 : if (level < g_log_level || !g_log_fp) return;
82 :
83 19961 : time_t now = time(NULL);
84 19961 : struct tm *tm_info = localtime(&now);
85 : char time_str[26];
86 19961 : strftime(time_str, 26, "%Y-%m-%d %H:%M:%S", tm_info);
87 :
88 : va_list args;
89 :
90 : // Log to file
91 19961 : fprintf(g_log_fp, "[%s] [%s] ", time_str, level_to_str(level));
92 19961 : va_start(args, format);
93 19961 : vfprintf(g_log_fp, format, args);
94 19961 : va_end(args);
95 19961 : fprintf(g_log_fp, "\n");
96 19961 : fflush(g_log_fp);
97 :
98 : // Also log to stderr if ERROR and enabled
99 19961 : if (level == LOG_ERROR && g_log_stderr) {
100 3 : fprintf(stderr, "ERROR: ");
101 3 : va_start(args, format);
102 3 : vfprintf(stderr, format, args);
103 3 : va_end(args);
104 3 : fprintf(stderr, "\n");
105 : }
106 : }
107 :
108 375 : void logger_close(void) {
109 375 : if (g_log_fp) {
110 375 : fclose(g_log_fp);
111 375 : g_log_fp = NULL;
112 : }
113 375 : if (g_log_path) {
114 375 : free(g_log_path);
115 375 : g_log_path = NULL;
116 : }
117 375 : }
118 :
119 0 : void logger_set_stderr(int enable) {
120 0 : g_log_stderr = enable;
121 0 : }
122 :
123 0 : int logger_clean_logs(const char *log_dir) {
124 0 : RAII_DIR DIR *dir = opendir(log_dir);
125 0 : if (!dir) return -1;
126 :
127 : struct dirent *entry;
128 0 : while ((entry = readdir(dir)) != NULL) {
129 0 : if (entry->d_type == DT_REG && strstr(entry->d_name, "session.log")) {
130 : char path[1024];
131 0 : snprintf(path, sizeof(path), "%s/%s", log_dir, entry->d_name);
132 0 : unlink(path);
133 : }
134 : }
135 0 : return 0;
136 : }
|