ulogger
athreadsafeloggerwithcoloredoutput
logger.c
Go to the documentation of this file.
1 // Copyright 2020 Bonedaddy (Alexandre Trottier)
2 //
3 // licensed under GNU AFFERO GENERAL PUBLIC LICENSE;
4 // you may not use this file except in compliance with the License;
5 // You may obtain the license via the LICENSE file in the repository root;
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License.
12 
13 /*! @file logger.c
14  * @brief a thread safe logger with optional printf style logging
15  * @details allows writing color coded logs to stdout, with optional file output as
16  * well. timestamps all logs, and provides optional printf style logging
17  * @note logf_func has a bug where some format is respected and others are not,
18  * consider the following from a `%s%s` format:
19  * - [error - Jul 06 10:01:07 PM] one<insert-tab-here>two
20  * - [warn - Jul 06 10:01:07 PM] one two
21  * @note warn, and info appear to not respect format, while debug and error do
22  * @todo
23  * - buffer logs and use a dedicated thread for writing (avoid blocking locks)
24  * - handling system signals (exit, kill, etc...)
25  */
26 
27 #include "logger.h"
28 #include <fcntl.h>
29 #include <pthread.h>
30 #include <stdarg.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include <unistd.h>
37 
38 #ifdef __cplusplus
39 extern "C" {
40 #endif
41 
42 /*! @brief returns a new thread safe logger
43  * if with_debug is false, then all debug_log calls will be ignored
44  * @param with_debug whether to enable debug logging, if false debug log calls will
45  * be ignored
46  */
47 thread_logger *new_thread_logger(bool with_debug) {
48 
49  thread_logger *thl = malloc(sizeof(thread_logger));
50  if (thl == NULL) {
51  printf("failed to malloc thread_logger\n");
52  return NULL;
53  }
54 
55  thl->lock = pthread_mutex_lock;
56  thl->unlock = pthread_mutex_unlock;
57  thl->log = log_func;
58  thl->logf = logf_func;
59  thl->debug = with_debug;
60  pthread_mutex_init(&thl->mutex, NULL);
61 
62  return thl;
63 }
64 
65 /*! @brief returns a new file_logger
66  * Calls new_thread_logger internally
67  * @param output_file the file we will dump logs to. created if not exists and is
68  * appended to
69  */
70 file_logger *new_file_logger(char *output_file, bool with_debug) {
71 
72  thread_logger *thl = new_thread_logger(with_debug);
73  if (thl == NULL) {
74  // dont printf log here since new_thread_logger handles that
75  return NULL;
76  }
77 
78  file_logger *fhl = malloc(sizeof(file_logger));
79  if (fhl == NULL) {
80  // free thl as it is not null
81  free(thl);
82  printf("failed to malloc file_logger\n");
83  return NULL;
84  }
85 
86  // append to file, create if not exist, sync write files
87  // TODO(bonedaddy): try to use O_DSYNC for data integrity sync
88  int file_descriptor =
89  open(output_file, O_WRONLY | O_CREAT | O_SYNC | O_APPEND, 0640);
90  if (file_descriptor <= 0) {
91  // free thl as it is not null
92  free(thl);
93  // free fhl as it is not null
94  free(fhl);
95  printf("failed to run posix open function\n");
96  return NULL;
97  }
98 
99  fhl->fd = file_descriptor;
100  fhl->thl = thl;
101 
102  return fhl;
103 }
104 
105 /*! @brief used to write a log message to file although this really means a file
106  * descriptor
107  * @param thl pointer to an instance of thread_logger
108  * @param file_descriptor file descriptor to write log messages to in addition to
109  * stdout logging. if 0 only stdout is used
110  * @param message the actuall message to log
111  */
112 int write_file_log(int file_descriptor, char *message) {
113 
114  char msg[strlen(message) + 2]; // 2 for \n
115  memset(msg, 0, sizeof(msg));
116 
117  strcat(msg, message);
118  strcat(msg, "\n");
119 
120  int response = write(file_descriptor, msg, strlen(msg));
121  if (response == -1) {
122  printf("failed to write file log message");
123  } else {
124  // this branch will be triggered if write doesnt fail
125  // so overwrite the response to 0 as we want to return 0 to indicate
126  // no error was received, and returning response directly would return the
127  // number of bytes written
128  response = 0;
129  }
130 
131  return response;
132 }
133 
134 /*! @brief like log_func but for formatted logs
135  * @param thl pointer to an instance of thread_logger
136  * @param file_descriptor file descriptor to write log messages to, if 0 then only
137  * stdout is used
138  * @param level the log level to use (effects color used)
139  * @param message format string like `<percent-sign>sFOO<percent-sign>sBAR`
140  * @param ... values to supply to message
141  */
142 void logf_func(thread_logger *thl, int file_descriptor, LOG_LEVELS level, char *file,
143  int line, char *message, ...) {
144 
145  va_list args;
146  va_start(args, message);
147  char msg[sizeof(args) + (strlen(message) * 2)];
148  memset(msg, 0, sizeof(msg));
149 
150  int response = vsnprintf(msg, sizeof(msg), message, args);
151  if (response < 0) {
152  free(msg);
153  printf("failed to vsprintf\n");
154  return;
155  }
156 
157  log_func(thl, file_descriptor, msg, level, file, line);
158 }
159 
160 /*! @brief main function you should call, which will delegate to the appopriate *_log
161  * function
162  * @param thl pointer to an instance of thread_logger
163  * @param file_descriptor file descriptor to write log messages to, if 0 then only
164  * stdout is used
165  * @param message the actual message we want to log
166  * @param level the log level to use (effects color used)
167  */
168 void log_func(thread_logger *thl, int file_descriptor, char *message,
169  LOG_LEVELS level, char *file, int line) {
170 
171  char time_str[76];
172  memset(time_str, 0, sizeof(time_str));
173 
174  get_time_string(time_str, 76);
175 
176  char location_info[strlen(file) + sizeof(line) + 4];
177  memset(location_info, 0, sizeof(location_info));
178 
179  sprintf(location_info, " %s:%i", file, line);
180 
181  char
182  date_msg[strlen(time_str) + strlen(message) + 2 + sizeof(location_info) + 6];
183  memset(date_msg, 0, sizeof(date_msg));
184 
185  strcat(date_msg, time_str);
186  strcat(date_msg, " -");
187  strcat(date_msg, location_info);
188  strcat(date_msg, "] ");
189  strcat(date_msg, message);
190 
191  switch (level) {
192  case LOG_LEVELS_INFO:
193  info_log(thl, file_descriptor, date_msg);
194  break;
195  case LOG_LEVELS_WARN:
196  warn_log(thl, file_descriptor, date_msg);
197  break;
198  case LOG_LEVELS_ERROR:
199  error_log(thl, file_descriptor, date_msg);
200  break;
201  case LOG_LEVELS_DEBUG:
202  debug_log(thl, file_descriptor, date_msg);
203  break;
204  }
205 }
206 
207 /*! @brief logs an info styled message - called by log_fn
208  * @param thl pointer to an instance of thread_logger
209  * @param file_descriptor file descriptor to write log messages to in addition to
210  * stdout logging. if 0 only stdout is used
211  * @param message the actuall message to log
212  */
213 void info_log(thread_logger *thl, int file_descriptor, char *message) {
214 
215  // 2, 1 for null terminator and 1 for space after ]
216  size_t msg_size = strlen(message) + strlen("[info - ") + 2;
217  char msg[msg_size];
218  memset(msg, 0, sizeof(msg));
219 
220  thl->lock(&thl->mutex);
221 
222  strcat(msg, "[info - ");
223  strcat(msg, message);
224 
225  if (file_descriptor != 0) {
226  write_file_log(file_descriptor, msg);
227  }
228 
230 
231  thl->unlock(&thl->mutex);
232 }
233 
234 /*! @brief logs a warned styled message - called by log_fn
235  * @param thl pointer to an instance of thread_logger
236  * @param file_descriptor file descriptor to write log messages to in addition to
237  * stdout logging. if 0 only stdout is used
238  * @param message the actuall message to log
239  */
240 void warn_log(thread_logger *thl, int file_descriptor, char *message) {
241 
242  // 2, 1 for null terminator and 1 for space after ]
243  size_t msg_size = strlen(message) + strlen("[warn - ") + 2;
244  char msg[msg_size];
245  memset(msg, 0, sizeof(msg));
246 
247  thl->lock(&thl->mutex);
248 
249  strcat(msg, "[warn - ");
250  strcat(msg, message);
251 
252  if (file_descriptor != 0) {
253  write_file_log(file_descriptor, msg);
254  }
255 
257 
258  thl->unlock(&thl->mutex);
259 }
260 
261 /*! @brief logs an error styled message - called by log_fn
262  * @param thl pointer to an instance of thread_logger
263  * @param file_descriptor file descriptor to write log messages to in addition to
264  * stdout logging. if 0 only stdout is used
265  * @param message the actuall message to log
266  */
267 void error_log(thread_logger *thl, int file_descriptor, char *message) {
268 
269  // 2, 1 for null terminator and 1 for space after ]
270  size_t msg_size = strlen(message) + strlen("[error - ") + 2;
271  char msg[msg_size];
272  memset(msg, 0, sizeof(msg));
273 
274  thl->lock(&thl->mutex);
275 
276  strcat(msg, "[error - ");
277  strcat(msg, message);
278 
279  if (file_descriptor != 0) {
280  write_file_log(file_descriptor, msg);
281  }
282 
284 
285  thl->unlock(&thl->mutex);
286 }
287 
288 /*! @brief logs a debug styled message - called by log_fn
289  * @param thl pointer to an instance of thread_logger
290  * @param file_descriptor file descriptor to write log messages to in addition to
291  * stdout logging. if 0 only stdout is used
292  * @param message the actuall message to log
293  */
294 void debug_log(thread_logger *thl, int file_descriptor, char *message) {
295 
296  if (thl->debug == false) {
297  return;
298  }
299 
300  // 2, 1 for null terminator and 1 for space after ]
301  size_t msg_size = strlen(message) + strlen("[debug - ") + 2;
302  char msg[msg_size];
303  memset(msg, 0, sizeof(msg));
304 
305  thl->lock(&thl->mutex);
306 
307  strcat(msg, "[debug - ");
308  strcat(msg, message);
309 
310  if (file_descriptor != 0) {
311  write_file_log(file_descriptor, msg);
312  }
313 
315 
316  thl->unlock(&thl->mutex);
317 }
318 
319 /*! @brief free resources for the threaded logger
320  * @param thl the thread_logger instance to free memory for
321  */
323 
324  pthread_mutex_lock(&thl->mutex); // lock before destroying
325  pthread_mutex_destroy(&thl->mutex);
326  free(thl);
327 }
328 
329 /*! @brief free resources for the file ogger
330  * @param fhl the file_logger instance to free memory for. also frees memory for the
331  * embedded thread_logger and closes the open file
332  */
334 
335  close(fhl->fd);
336  clear_thread_logger(fhl->thl);
337  free(fhl);
338 }
339 
340 /*! @brief returns a timestamp of format `Jul 06 10:12:20 PM`
341  * @warning providing an input buffer whose length isnt at least 76 bytes will result
342  * in undefined behavior
343  * @param date_buffer the buffer to write the timestamp into
344  * @param date_buffer_len the size of the buffer
345  */
346 void get_time_string(char *date_buffer, size_t date_buffer_len) {
347 
348  strftime(date_buffer, date_buffer_len, "%b %d %r",
349  localtime(&(time_t){time(NULL)}));
350 }
351 
352 #ifdef __cplusplus
353 }
354 #endif
error_log
void error_log(thread_logger *thl, int file_descriptor, char *message)
logs an error styled message - called by log_fn
Definition: logger.c:267
file_logger::fd
int fd
Definition: logger.h:266
clear_thread_logger
void clear_thread_logger(thread_logger *thl)
free resources for the threaded logger
Definition: logger.c:322
info_log
void info_log(thread_logger *thl, int file_descriptor, char *message)
logs an info styled message - called by log_fn
Definition: logger.c:213
COLORS_RED
@ COLORS_RED
Definition: colors.h:44
logger.h
a thread safe logger with optional printf style logging
LOG_LEVELS_DEBUG
@ LOG_LEVELS_DEBUG
Definition: logger.h:187
thread_logger::lock
mutex_fn lock
used for synchronization across threads
Definition: logger.h:253
clear_file_logger
void clear_file_logger(file_logger *fhl)
free resources for the file ogger
Definition: logger.c:333
LOG_LEVELS_ERROR
@ LOG_LEVELS_ERROR
Definition: logger.h:185
file_logger::thl
thread_logger * thl
the file descriptor used for sending log information to
Definition: logger.h:267
get_time_string
void get_time_string(char *date_buffer, size_t date_buffer_len)
returns a timestamp of format Jul 06 10:12:20 PM
Definition: logger.c:346
COLORS_GREEN
@ COLORS_GREEN
Definition: colors.h:46
thread_logger::unlock
mutex_fn unlock
helper function for pthread_mutex_lock
Definition: logger.h:254
thread_logger::log
log_fn log
helper function for pthread_mutex_unlock
Definition: logger.h:255
thread_logger::logf
log_fnf logf
function that gets called for all regular logging
Definition: logger.h:257
COLORS_SOFT_RED
@ COLORS_SOFT_RED
Definition: colors.h:45
log_func
void log_func(thread_logger *thl, int file_descriptor, char *message, LOG_LEVELS level, char *file, int line)
main function you should call, which will delegate to the appopriate *_log function
Definition: logger.c:168
thread_logger::mutex
pthread_mutex_t mutex
indicates whether we will action on debug logs
Definition: logger.h:252
write_file_log
int write_file_log(int file_descriptor, char *message)
used to write a log message to file although this really means a file descriptor
Definition: logger.c:112
logf_func
void logf_func(thread_logger *thl, int file_descriptor, LOG_LEVELS level, char *file, int line, char *message,...)
like log_func but for formatted logs
Definition: logger.c:142
thread_logger::debug
bool debug
Definition: logger.h:251
LOG_LEVELS
LOG_LEVELS
Definition: logger.h:179
new_file_logger
file_logger * new_file_logger(char *output_file, bool with_debug)
returns a new file_logger Calls new_thread_logger internally
Definition: logger.c:70
thread_logger
Definition: logger.h:250
COLORS_YELLOW
@ COLORS_YELLOW
Definition: colors.h:47
LOG_LEVELS_INFO
@ LOG_LEVELS_INFO
Definition: logger.h:181
warn_log
void warn_log(thread_logger *thl, int file_descriptor, char *message)
logs a warned styled message - called by log_fn
Definition: logger.c:240
file_logger
Definition: logger.h:265
LOG_LEVELS_WARN
@ LOG_LEVELS_WARN
Definition: logger.h:183
debug_log
void debug_log(thread_logger *thl, int file_descriptor, char *message)
logs a debug styled message - called by log_fn
Definition: logger.c:294
print_colored
void print_colored(COLORS color, char *message)
prints message to stdout with the given color
Definition: colors.c:77
new_thread_logger
thread_logger * new_thread_logger(bool with_debug)
returns a new thread safe logger if with_debug is false, then all debug_log calls will be ignored
Definition: logger.c:47