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