ulogger
athreadsafeloggerwithcoloredoutput
logger.h
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.h
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 #pragma once
28 
29 #include "colors.h"
30 #include <pthread.h>
31 #include <stdbool.h>
32 #include <string.h>
33 
34 /*!
35  * @brief strips leading path from __FILE__
36  */
37 #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
38 
39 /*!
40  * @brief used to emit a standard INFO log
41  * @param thl an instance of thread_logger, passing anything other than an
42  * initialized thread_logger will result in undefined benhavior
43  * @param msg the actual message to log
44  */
45 #define LOG_INFO(thl, msg) \
46  thl->log(thl, 0, msg, LOG_LEVELS_INFO, __FILENAME__, __LINE__);
47 
48 /*!
49  * @brief used to emit a standard WARN log
50  * @param thl an instance of thread_logger, passing anything other than an
51  * initialized thread_logger will result in undefined benhavior
52  * @param msg the actual message to log
53  */
54 #define LOG_WARN(thl, msg) \
55  thl->log(thl, 0, msg, LOG_LEVELS_WARN, __FILENAME__, __LINE__);
56 
57 /*!
58  * @brief used to emit a standard ERROR log
59  * @param thl an instance of thread_logger, passing anything other than an
60  * initialized thread_logger will result in undefined benhavior
61  * @param msg the actual message to log
62  */
63 #define LOG_ERROR(thl, msg) \
64  thl->log(thl, 0, msg, LOG_LEVELS_ERROR, __FILENAME__, __LINE__);
65 
66 /*!
67  * @brief used to emit a standard DEBUG log
68  * @param thl an instance of thread_logger, passing anything other than an
69  * initialized thread_logger will result in undefined benhavior
70  * @param msg the actual message to log
71  * @note if logger is created without debug enabled, this is a noop
72  */
73 #define LOG_DEBUG(thl, msg) \
74  thl->log(thl, 0, msg, LOG_LEVELS_DEBUG, __FILENAME__, __LINE__);
75 
76 /*!
77  * @brief used to emit a printf INFO log
78  * @param thl an instance of thread_logger, passing anything other than an
79  * initialized thread_logger will result in undefined benhavior
80  * @param msg the actual message to log
81  * @param msg the printf styled message to format
82  * @param ... the arguments to use for formatting
83  */
84 #define LOGF_INFO(thl, msg, ...) \
85  thl->logf(thl, 0, LOG_LEVELS_INFO, __FILENAME__, __LINE__, msg, __VA_ARGS__);
86 
87 /*!
88  * @brief used to emit a printf WARN log
89  * @param thl an instance of thread_logger, passing anything other than an
90  * initialized thread_logger will result in undefined benhavior
91  * @param msg the actual message to log
92  * @param msg the printf styled message to format
93  * @param ... the arguments to use for formatting
94  */
95 #define LOGF_WARN(thl, msg, ...) \
96  thl->logf(thl, 0, LOG_LEVELS_WARN, __FILENAME__, __LINE__, msg, __VA_ARGS__);
97 
98 /*!
99  * @brief used to emit a printf ERROR log
100  * @param thl an instance of thread_logger, passing anything other than an
101  * initialized thread_logger will result in undefined benhavior
102  * @param msg the actual message to log
103  * @param msg the printf styled message to format
104  * @param ... the arguments to use for formatting
105  */
106 #define LOGF_ERROR(thl, msg, ...) \
107  thl->logf(thl, 0, LOG_LEVELS_ERROR, __FILENAME__, __LINE__, msg, __VA_ARGS__);
108 
109 /*!
110  * @brief used to emit a printf DEBUG log
111  * @param thl an instance of thread_logger, passing anything other than an
112  * initialized thread_logger will result in undefined benhavior
113  * @param msg the printf styled message to format
114  * @param ... the arguments to use for formatting
115  * @note if logger is created without debug enabled, this is a noop
116  */
117 #define LOGF_DEBUG(thl, msg, ...) \
118  thl->logf(thl, 0, LOG_LEVELS_DEBUG, __FILENAME__, __LINE__, msg, __VA_ARGS__);
119 
120 /*!
121  * @brief like LOG_INFO except for file logging
122 */
123 #define fLOG_INFO(fhl, msg) \
124  fhl->thl->log(fhl->thl, fhl->fd, msg, LOG_LEVELS_INFO, __FILENAME__, __LINE__);
125 
126 /*!
127  * @brief like LOG_WARN except for file logging
128 */
129 #define fLOG_WARN(fhl, msg) \
130  fhl->thl->log(fhl->thl, fhl->fd, msg, LOG_LEVELS_WARN, __FILENAME__, __LINE__);
131 
132 /*!
133  * @brief like LOG_ERROR except for file logging
134 */
135 #define fLOG_ERROR(fhl, msg) \
136  fhl->thl->log(fhl->thl, fhl->fd, msg, LOG_LEVELS_ERROR, __FILENAME__, __LINE__);
137 
138 /*!
139  * @brief like LOG_DEBUG except for file logging
140 */
141 #define fLOG_DEBUG(fhl, msg) \
142  fhl->thl->log(fhl->thl, fhl->fd, msg, LOG_LEVELS_DEBUG, __FILENAME__, __LINE__);
143 
144 /*!
145  * @brief like LOGF_INFO except for file logging
146 */
147 #define fLOGF_INFO(fhl, msg, ...) \
148  fhl->thl->logf(fhl->thl, fhl->fd, LOG_LEVELS_INFO, __FILENAME__, __LINE__, msg, __VA_ARGS__);
149 
150 /*!
151  * @brief like LOGF_WARN except for file logging
152 */
153 #define fLOGF_WARN(fhl, msg, ...) \
154  fhl->thl->logf(fhl->thl, fhl->fd, LOG_LEVELS_WARN, __FILENAME__, __LINE__, msg, __VA_ARGS__);
155 
156 /*!
157  * @brief like LOGF_ERROR except for file logging
158 */
159 #define fLOGF_ERROR(fhl, msg, ...) \
160  fhl->thl->logf(fhl->thl, fhl->fd, LOG_LEVELS_ERROR, __FILENAME__, __LINE__, msg, __VA_ARGS__);
161 
162 /*!
163  * @brief like LOGF_DEBUG except for file logging
164 */
165 #define fLOGF_DEBUG(fhl, msg, ...) \
166  fhl->thl->logf(fhl->thl, fhl->fd, LOG_LEVELS_DEBUG, __FILENAME__, __LINE__, msg, __VA_ARGS__);
167 
168 #ifdef __cplusplus
169 extern "C" {
170 #endif
171 
172 /*! @struct base struct used by the thread_logger
173  */
174 struct thread_logger;
175 
176 /*! @typedef specifies log_levels, typically used when determining function
177  * invocation by log_fn
178  */
179 typedef enum {
180  /*! indicates the message we are logging is of type info (color green) */
182  /*! indicates the message we are logging is of type warn (color yellow) */
184  /*! indicates the message we are logging is of type error (color red) */
186  /*! indicates the message we are logging is of type debug (color soft red) */
189 
190 /*! @typedef signature of pthread_mutex_unlock and pthread_mutex_lock used by the
191  * thread_logger
192  * @param mx pointer to a pthread_mutex_t type
193  */
194 typedef int (*mutex_fn)(pthread_mutex_t *mx);
195 
196 #ifdef __cplusplus
197 /*! @typedef signature used by the thread_logger for log_fn calls
198  * @param thl pointer to an instance of thread_logger
199  * @param file_descriptor file descriptor to write log messages to, if 0 then only
200  * stdout is used
201  * @param message the actual message we want to log
202  * @param level the log level to use (effects color used)
203  */
204 typedef void (*log_fn)(struct thread_logger *thl, int file_descriptor,
205  const char *message, LOG_LEVELS level, const char *file,
206  int line);
207 #else
208 /*! @typedef signature used by the thread_logger for log_fn calls
209  * @param thl pointer to an instance of thread_logger
210  * @param file_descriptor file descriptor to write log messages to, if 0 then only
211  * stdout is used
212  * @param message the actual message we want to log
213  * @param level the log level to use (effects color used)
214  */
215 typedef void (*log_fn)(struct thread_logger *thl, int file_descriptor, char *message,
216  LOG_LEVELS level, char *file, int line);
217 #endif
218 
219 #ifdef __cplusplus
220 /*! @typedef signatured used by the thread_logger for printf style log_fn calls
221  * @param thl pointer to an instance of thread_logger
222  * @param file_descriptor file descriptor to write log messages to, if 0 then only
223  * stdout is used
224  * @param level the log level to use (effects color used)
225  * @param message format string like `%sFOO%sBAR`
226  * @param ... values to supply to message
227  */
228 typedef void (*log_fnf)(struct thread_logger *thl, int file_descriptor,
229  LOG_LEVELS level, const char *file, int line,
230  const char *message, ...);
231 #else
232 /*! @typedef signatured used by the thread_logger for printf style log_fn calls
233  * @param thl pointer to an instance of thread_logger
234  * @param file_descriptor file descriptor to write log messages to, if 0 then only
235  * stdout is used
236  * @param level the log level to use (effects color used)
237  * @param message format string like `%sFOO%sBAR`
238  * @param ... values to supply to message
239  */
240 typedef void (*log_fnf)(struct thread_logger *thl, int file_descriptor,
241  LOG_LEVELS level, char *file, int line, char *message, ...);
242 #endif
243 
244 /*! @typedef a thread safe logger
245  * @brief guards all log calls with a mutex lock/unlock
246  * recommended usage is to call thread_logger:log(instance_of_thread_logger,
247  * char*_of_your_log_message, log_level) alternatively you can call the `*_log`
248  * functions directly
249  */
250 typedef struct thread_logger {
251  bool debug; /*! @brief indicates whether we will action on debug logs */
252  pthread_mutex_t mutex; /*! @brief used for synchronization across threads */
253  mutex_fn lock; /*! @brief helper function for pthread_mutex_lock */
254  mutex_fn unlock; /*! @brief helper function for pthread_mutex_unlock */
255  log_fn log; /*! @brief function that gets called for all regular logging */
256  log_fnf
257  logf; /*! @brief function that gets called for all printf style logging */
259 
260 /*! @typedef a wrapper around thread_logger that enables file logging
261  * @brief like thread_logger but also writes to a file
262  * @todo
263  * - enable log rotation
264  */
265 typedef struct file_logger {
266  int fd; /*! @brief the file descriptor used for sending log information to */
267  thread_logger *thl; /*! @brief the underlying threadsafe logger used for
268  sycnhronization and the actual logging */
270 
271 /*! @brief returns a new thread safe logger
272  * if with_debug is false, then all debug_log calls will be ignored
273  * @param with_debug whether to enable debug logging, if false debug log calls will
274  * be ignored
275  */
276 thread_logger *new_thread_logger(bool with_debug);
277 
278 #ifdef __cplusplus
279 /*! @brief returns a new file_logger
280  * Calls new_thread_logger internally
281  * @param output_file the file we will dump logs to. created if not exists and is
282  * appended to
283  */
284 file_logger *new_file_logger(const char *output_file, bool with_debug);
285 #else
286 /*! @brief returns a new file_logger
287  * Calls new_thread_logger internally
288  * @param output_file the file we will dump logs to. created if not exists and is
289  * appended to
290  */
291 file_logger *new_file_logger(char *output_file, bool with_debug);
292 #endif
293 
294 /*! @brief free resources for the threaded logger
295  * @param thl the thread_logger instance to free memory for
296  */
298 
299 /*! @brief free resources for the file ogger
300  * @param fhl the file_logger instance to free memory for. also frees memory for the
301  * embedded thread_logger and closes the open file
302  */
303 void clear_file_logger(file_logger *fhl);
304 
305 /*! @brief main function you should call, which will delegate to the appopriate *_log
306  * function
307  * @param thl pointer to an instance of thread_logger
308  * @param file_descriptor file descriptor to write log messages to, if 0 then only
309  * stdout is used
310  * @param message the actual message we want to log
311  * @param level the log level to use (effects color used)
312  */
313 void log_func(thread_logger *thl, int file_descriptor, char *message,
314  LOG_LEVELS level, char *file, int line);
315 
316 /*! @brief like log_func but for formatted logs
317  * @param thl pointer to an instance of thread_logger
318  * @param file_descriptor file descriptor to write log messages to, if 0 then only
319  * stdout is used
320  * @param level the log level to use (effects color used)
321  * @param message format string like `<percent-sign>sFOO<percent-sign>sBAR`
322  * @param ... values to supply to message
323  */
324 void logf_func(thread_logger *thl, int file_descriptor, LOG_LEVELS level, char *file,
325  int line, char *message, ...);
326 
327 /*! @brief logs a debug styled message - called by log_fn
328  * @param thl pointer to an instance of thread_logger
329  * @param file_descriptor file descriptor to write log messages to in addition to
330  * stdout logging. if 0 only stdout is used
331  * @param message the actuall message to log
332  */
333 void debug_log(thread_logger *thl, int file_descriptor, char *message);
334 
335 /*! @brief logs a warned styled message - called by log_fn
336  * @param thl pointer to an instance of thread_logger
337  * @param file_descriptor file descriptor to write log messages to in addition to
338  * stdout logging. if 0 only stdout is used
339  * @param message the actuall message to log
340  */
341 void warn_log(thread_logger *thl, int file_descriptor, char *message);
342 
343 /*! @brief logs an error styled message - called by log_fn
344  * @param thl pointer to an instance of thread_logger
345  * @param file_descriptor file descriptor to write log messages to in addition to
346  * stdout logging. if 0 only stdout is used
347  * @param message the actuall message to log
348  */
349 void error_log(thread_logger *thl, int file_descriptor, char *message);
350 
351 /*! @brief logs an info styled message - called by log_fn
352  * @param thl pointer to an instance of thread_logger
353  * @param file_descriptor file descriptor to write log messages to in addition to
354  * stdout logging. if 0 only stdout is used
355  * @param message the actuall message to log
356  */
357 void info_log(thread_logger *thl, int file_descriptor, char *message);
358 
359 /*! @brief used to write a log message to file although this really means a file
360  * descriptor
361  * @param thl pointer to an instance of thread_logger
362  * @param file_descriptor file descriptor to write log messages to in addition to
363  * stdout logging. if 0 only stdout is used
364  * @param message the actuall message to log
365  */
366 int write_file_log(int file_descriptor, char *message);
367 
368 /*! @brief returns a timestamp of format `Jul 06 10:12:20 PM`
369  * @warning providing an input buffer whose length isnt at least 76 bytes will result
370  * in undefined behavior
371  * @param date_buffer the buffer to write the timestamp into
372  * @param date_buffer_len the size of the buffer
373  */
374 void get_time_string(char *date_buffer, size_t date_buffer_len);
375 
376 #ifdef __cplusplus
377 }
378 #endif
clear_file_logger
void clear_file_logger(file_logger *fhl)
free resources for the file ogger
Definition: logger.c:333
file_logger::fd
int fd
Definition: logger.h:266
thread_logger
struct thread_logger thread_logger
clear_thread_logger
void clear_thread_logger(thread_logger *thl)
free resources for the threaded logger
Definition: logger.c:322
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
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
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
log_fnf
void(* log_fnf)(struct thread_logger *thl, int file_descriptor, LOG_LEVELS level, char *file, int line, char *message,...)
Definition: logger.h:240
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
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
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
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::mutex
pthread_mutex_t mutex
indicates whether we will action on debug logs
Definition: logger.h:252
thread_logger::debug
bool debug
Definition: logger.h:251
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
LOG_LEVELS
LOG_LEVELS
Definition: logger.h:179
thread_logger
Definition: logger.h:250
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
log_fn
void(* log_fn)(struct thread_logger *thl, int file_descriptor, char *message, LOG_LEVELS level, char *file, int line)
Definition: logger.h:215
colors.h
macros and utilities for printing color to stdout from https://www.quora.com/How-do-I-print-a-colored...
LOG_LEVELS_INFO
@ LOG_LEVELS_INFO
Definition: logger.h:181
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
file_logger
struct file_logger file_logger
file_logger
Definition: logger.h:265
LOG_LEVELS_WARN
@ LOG_LEVELS_WARN
Definition: logger.h:183
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
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
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
mutex_fn
int(* mutex_fn)(pthread_mutex_t *mx)
Definition: logger.h:194