c-template
logger.c
Go to the documentation of this file.
1 /*! @file logger.c
2  * @brief a thread safe logger with optional printf style logging
3  * @details allows writing color coded logs to stdout, with optional file output as well. timestamps all logs, and provides optional printf style logging
4  * @note logf_func has a bug where some format is respected and others are not, consider the following from a `%s%s` format:
5  * - [error - Jul 06 10:01:07 PM] one<insert-tab-here>two
6  * - [warn - Jul 06 10:01:07 PM] one two
7  * @note warn, and info appear to not respect format, while debug and error do
8  * @todo
9  * - buffer logs and use a dedicated thread for writing (avoid blocking locks)
10  * - handling system signals (exit, kill, etc...)
11 */
12 
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <stdbool.h>
17 #include <pthread.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <fcntl.h>
21 #include <unistd.h>
22 #include "../../include/utils/logger.h"
23 
24 /*! @brief private function that returns a timestamp of format `Jul 06 10:12:20 PM`
25 */
26 char *get_time_string();
27 
28 thread_logger *new_thread_logger(bool with_debug) {
29  thread_logger *thl = malloc(sizeof(thread_logger));
30  if (thl == NULL) {
31  printf("failed to malloc thread_logger\n");
32  return NULL;
33  }
34  thl->lock = pthread_mutex_lock;
35  thl->unlock = pthread_mutex_unlock;
36  thl->log = log_func;
37  thl->logf = logf_func;
38  thl->debug = with_debug;
39  pthread_mutex_init(&thl->mutex, NULL);
40  return thl;
41 }
42 
43 file_logger *new_file_logger(char *output_file, bool with_debug) {
44  thread_logger *thl = new_thread_logger(with_debug);
45  if (thl == NULL) {
46  // dont printf log here since new_thread_logger handles that
47  return NULL;
48  }
49  file_logger *fhl = malloc(sizeof(file_logger));
50  if (fhl == NULL) {
51  // free thl as it is not null
52  free(thl);
53  printf("failed to malloc file_logger\n");
54  return NULL;
55  }
56  // append to file, create if not exist, sync write files
57  // TODO(bonedaddy): try to use O_DSYNC for data integrity sync
58  int file_descriptor = open(output_file, O_WRONLY | O_CREAT | O_SYNC | O_APPEND, 0640);
59  // TODO(bonedaddy): should this just be `< 0` ? `open` shouldn't return 0 but im unsure about removing the check for it
60  if (file_descriptor <= 0) {
61  // free thl as it is not null
62  free(thl);
63  // free fhl as it is not null
64  free(fhl);
65  printf("failed to run posix open function\n");
66  return NULL;
67  }
68  fhl->file_descriptor = file_descriptor;
69  fhl->thl = thl;
70  return fhl;
71 }
72 
73 int write_file_log(int file_descriptor, char *message) {
74  // 2 for \n
75  char *msg = calloc(sizeof(char), strlen(message) + 2);
76  if (msg == NULL) {
77  printf("failed to calloc msg\n");
78  return -1;
79  }
80  strcat(msg, message);
81  strcat(msg, "\n");
82  int response = write(file_descriptor, msg, strlen(msg));
83  if (response == -1) {
84  printf("failed to write file log message");
85  } else {
86  // this branch will be triggered if write doesnt fail
87  // so overwrite the response to 0 as we want to return 0 to indicate
88  // no error was received, and returning response directly would return the number of bytes written
89  response = 0;
90  }
91  free(msg);
92  return response;
93 }
94 
95 void logf_func(thread_logger *thl, int file_descriptor, LOG_LEVELS level, char *message, ...) {
96  va_list args;
97  va_start(args, message);
98  char *msg = malloc(sizeof(args) + strlen(message) + 1);
99  if (msg == NULL) {
100  return;
101  }
102  int response = vsprintf(msg, message, args);
103  if (response < 0) {
104  free(msg);
105  printf("failed to vsprintf\n");
106  return;
107  }
108  log_func(thl, file_descriptor, msg, level);
109  free(msg);
110 }
111 
112 void log_func(thread_logger *thl, int file_descriptor, char *message, LOG_LEVELS level) {
113  char *time_str = get_time_string();
114  if (time_str == NULL) {
115  // dont printf log as get_time_str does that
116  return;
117  }
118  char *date_msg = calloc(sizeof(char), strlen(time_str) + strlen(message) + 2);
119  if (date_msg == NULL) {
120  printf("failed to calloc date_msg\n");
121  return;
122  }
123  strcat(date_msg, time_str);
124  strcat(date_msg, message);
125  switch (level) {
126  case LOG_LEVELS_INFO:
127  info_log(thl, file_descriptor, date_msg);
128  break;
129  case LOG_LEVELS_WARN:
130  warn_log(thl, file_descriptor, date_msg);
131  break;
132  case LOG_LEVELS_ERROR:
133  error_log(thl, file_descriptor, date_msg);
134  break;
135  case LOG_LEVELS_DEBUG:
136  debug_log(thl, file_descriptor, date_msg);
137  break;
138  }
139  free(date_msg);
140  free(time_str);
141 }
142 
143 void info_log(thread_logger *thl, int file_descriptor, char *message) {
144  thl->lock(&thl->mutex);
145  // 2 = 1 for null terminator, 1 for space after ]
146  char *msg = calloc(sizeof(char), strlen(message) + strlen("[info - ") + (size_t)2);
147  if (msg == NULL) {
148  printf("failed to calloc info_log msg");
149  return;
150  }
151  strcat(msg, "[info - ");
152  strcat(msg, message);
153  if (file_descriptor != 0) {
154  write_file_log(file_descriptor, msg);
155  }
157  thl->unlock(&thl->mutex);
158  free(msg);
159 }
160 
161 void warn_log(thread_logger *thl, int file_descriptor, char *message) {
162  thl->lock(&thl->mutex);
163  // 2 = 1 for null terminator, 1 for space after ]
164  char *msg = calloc(sizeof(char), strlen(message) + strlen("[warn - ") + (size_t) 2);
165  if (msg == NULL) {
166  printf("failed to calloc warn_log msg");
167  return;
168  }
169  strcat(msg, "[warn - ");
170  strcat(msg, message);
171  if (file_descriptor != 0) {
172  // TODO(bonedaddy): decide if we want to copy
173  // char *cpy = malloc(strlen(msg)+1);
174  // strcpy(cpy, msg);
175  write_file_log(file_descriptor, msg);
176  // free(cpy);
177  }
179  thl->unlock(&thl->mutex);
180  free(msg);
181 }
182 
183 void error_log(thread_logger *thl, int file_descriptor, char *message) {
184  thl->lock(&thl->mutex);
185  // 2 = 1 for null terminator, 1 for space after ]
186  char *msg = calloc(sizeof(char), strlen(message) + strlen("[error - ") + (size_t)2);
187  if (msg == NULL) {
188  printf("failed to calloc error_log msg");
189  return;
190  }
191  strcat(msg, "[error - ");
192  strcat(msg, message);
193  if (file_descriptor != 0) {
194  write_file_log(file_descriptor, msg);
195  }
197  thl->unlock(&thl->mutex);
198  free(msg);
199 }
200 
201 void debug_log(thread_logger *thl, int file_descriptor, char *message) {
202  // unless debug enabled dont show
203  if (thl->debug == false) {
204  return;
205  }
206 
207  thl->lock(&thl->mutex);
208  // 2 = 1 for null terminator, 1 for space after ]
209  char *msg = calloc(sizeof(char), strlen(message) + strlen("[debug - ") + (size_t) 2);
210  if (msg == NULL) {
211  printf("failed to calloc debug_log msg");
212  return;
213  }
214  strcat(msg, "[debug - ");
215  strcat(msg, message);
216  if (file_descriptor != 0) {
217  write_file_log(file_descriptor, msg);
218  }
220  thl->unlock(&thl->mutex);
221  free(msg);
222 }
223 
225  free(thl);
226 }
227 
229  close(fhl->file_descriptor);
230  clear_thread_logger(fhl->thl);
231  free(fhl);
232 }
233 
235  char date[75];
236  strftime(date, sizeof date, "%b %d %r", localtime(&(time_t){time(NULL)}));
237  // 4 for [ ] and 1 for \0
238  char *msg = calloc(sizeof(char), sizeof(date) + 2);
239  if (msg == NULL) {
240  printf("failed to calloc get_time_string\n");
241  return NULL;
242  }
243  strcat(msg, "");
244  strcat(msg, date);
245  strcat(msg, "] ");
246  return msg;
247 }
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:183
clear_thread_logger
void clear_thread_logger(thread_logger *thl)
free resources for the threaded logger
Definition: logger.c:224
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:143
COLORS_RED
@ COLORS_RED
Definition: colors.h:26
LOG_LEVELS_DEBUG
@ LOG_LEVELS_DEBUG
Definition: logger.h:34
thread_logger::lock
mutex_fn lock
Definition: logger.h:67
clear_file_logger
void clear_file_logger(file_logger *fhl)
free resources for the file ogger
Definition: logger.c:228
LOG_LEVELS_ERROR
@ LOG_LEVELS_ERROR
Definition: logger.h:32
file_logger::thl
thread_logger * thl
Definition: logger.h:79
COLORS_GREEN
@ COLORS_GREEN
Definition: colors.h:26
thread_logger::unlock
mutex_fn unlock
Definition: logger.h:68
thread_logger::log
log_fn log
Definition: logger.h:69
thread_logger::logf
log_fnf logf
Definition: logger.h:70
COLORS_SOFT_RED
@ COLORS_SOFT_RED
Definition: colors.h:26
args
Definition: colors_test.c:12
thread_logger::mutex
pthread_mutex_t mutex
Definition: logger.h:66
write_file_log
int write_file_log(int file_descriptor, char *message)
used to write a log message to file
Definition: logger.c:73
thread_logger::debug
bool debug
Definition: logger.h:65
args
struct args args
LOG_LEVELS
LOG_LEVELS
Definition: logger.h:26
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:43
thread_logger
Definition: logger.h:63
get_time_string
char * get_time_string()
private function that returns a timestamp of format Jul 06 10:12:20 PM
Definition: logger.c:234
COLORS_YELLOW
@ COLORS_YELLOW
Definition: colors.h:26
logf_func
void logf_func(thread_logger *thl, int file_descriptor, LOG_LEVELS level, char *message,...)
like log_func but for formatted logs
Definition: logger.c:95
file_logger::file_descriptor
int file_descriptor
Definition: logger.h:80
LOG_LEVELS_INFO
@ LOG_LEVELS_INFO
Definition: logger.h:28
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:161
file_logger
Definition: logger.h:78
LOG_LEVELS_WARN
@ LOG_LEVELS_WARN
Definition: logger.h:30
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:201
log_func
void log_func(thread_logger *thl, int file_descriptor, char *message, LOG_LEVELS level)
main function you should call, which will delegate to the appopriate *_log function
Definition: logger.c:112
print_colored
void print_colored(COLORS color, char *message)
prints message to stdout with the given color
Definition: colors.c:43
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:28