· 4 years ago · Feb 23, 2021, 01:34 AM
1#ifndef MINT_LOGGO_H
2#define MINT_LOGGO_H
3
4#include <stdlib.h>
5#include <stdint.h>
6#include <stdbool.h>
7#include <stdio.h>
8#include <string.h>
9#include <time.h>
10#include <math.h>
11
12/*
13 This a single header logging library.
14
15 The logger is highly configurable, easy to use and fast. It uses a queue for receiving messages
16 on consumer threads and a hashtable for logger lookup.
17
18 In order to use this you must do
19
20 #define MINT_LOGGO_IMPLEMENTATION
21 #include "mint_loggo.h"
22
23 In only one file to actual implement the logger.
24
25 Then in subsequent files you only have to
26 #include "mint_loggo.h"
27
28 to use it.
29*/
30
31#if defined(__unix__) || defined(linux) || defined(__APPLE__) || defined(MINT_LOGGO_USE_POSIX)
32 #include <pthread.h>
33 #include <unistd.h>
34 #define MINT_LOGGO_THREAD_TYPE pthread_t
35 #define MINT_LOGGO_THREAD_CREATE(id, func, param) pthread_create((id), NULL, (func), (param))
36 #define MINT_LOGGO_THREAD_JOIN(id) pthread_join((id), (NULL))
37 #define MINT_LOGGO_MUTEX_TYPE pthread_mutex_t
38 #define MINT_LOGGO_MUTEX_INIT(mutex) pthread_mutex_init(&(mutex), NULL)
39 #define MINT_LOGGO_MUTEX_DESTROY(mutex) pthread_mutex_destroy(&(mutex))
40 #define MINT_LOGGO_MUTEX_LOCK(mutex) pthread_mutex_lock(&(mutex))
41 #define MINT_LOGGO_MUTEX_UNLOCK(mutex) pthread_mutex_unlock(&(mutex))
42 #define MINT_LOGGO_COND_TYPE pthread_cond_t
43 #define MINT_LOGGO_COND_INIT(condition) pthread_cond_init(&(condition), NULL)
44 #define MINT_LOGGO_COND_DESTROY(condition) pthread_cond_destroy(&(condition))
45 #define MINT_LOGGO_COND_WAIT(condition, mutex) pthread_cond_wait(&(condition), &(mutex))
46 #define MINT_LOGGO_COND_SIGNAL(condition) pthread_cond_signal(&(condition))
47#elif defined(_WIN32) || defined(MINT_LOGGO_USE_WINDOWS)
48 #include <io.h>
49 #include <Windows.h>
50 #define MINT_LOGGO_THREAD_TYPE LPDWORD
51 #define MINT_LOGGO_THREAD_CREATE(id, func, param) CreateThread(NULL, 0, func, param, 0, id)
52 #define MINT_LOGGO_THREAD_JOIN(id) WaitForSingleObject((id), INFINITE)
53 #define MINT_LOGGO_MUTEX_TYPE LPCRITICAL_SECTION
54 #define MINT_LOGGO_MUTEX_INIT(mutex) InitializeCriticalSection((mutex))
55 #define MINT_LOGGO_MUTEX_DESTROY(mutex) DeleteCriticalSection((mutex))
56 #define MINT_LOGGO_MUTEX_LOCK(mutex) EnterCriticalSection((mutex))
57 #define MINT_LOGGO_MUTEX_UNLOCK(mutex) LeaveCriticalSection((mutex))
58 #define MINT_LOGGO_COND_TYPE PCONDITION_VARIABLE
59 #define MINT_LOGGO_COND_INIT(condition) InitializeConditionVariable((condition))
60 #define MINT_LOGGO_COND_DESTROY(condition) DeleteConditionVariable((condition))
61 #define MINT_LOGGO_COND_WAIT(condition, mutex) SleepConditionVariableCS((condition), (mutex), INFINITE)
62 #define MINT_LOGGO_COND_SIGNAL(condition) WakeConditionVariable((condition))
63#endif
64
65#ifdef MINT__DEBUG
66 #include <assert.h>
67#endif
68
69#define MINT_LOGGO_UNUSED(x) (void)(x)
70
71// Change this to change how Loggo is compiled in
72// Static would obviously make scope Loggo API methods to the
73// current translation unit which could be what you want
74// Defaults to extern API.
75#ifndef MINT_LOGGO_DEF
76 #ifdef MINT_LOGGO_DEF_STATIC
77 #define MINT_LOGGO_DEF static
78 #else
79 #define MINT_LOGGO_DEF extern
80 #endif
81#endif
82
83// Log Levels
84typedef enum {
85 MINT_LOGGO_LEVEL_DEBUG,
86 MINT_LOGGO_LEVEL_INFO,
87 MINT_LOGGO_LEVEL_WARN,
88 MINT_LOGGO_LEVEL_ERROR,
89 MINT_LOGGO_LEVEL_FATAL
90} Mint_Loggo_LogLevel;
91
92typedef int (*CloseHandler)(void*);
93typedef int (*WriteHandler)(char*, void*);
94typedef int (*FlushHandler)(void*);
95
96typedef struct {
97 void* handle;
98 CloseHandler close_handler;
99 WriteHandler write_handler;
100 FlushHandler flush_handler;
101} Mint_Loggo_LogHandler;
102
103// The user controls the format
104typedef struct {
105 Mint_Loggo_LogLevel level;
106 uint32_t queue_capacity;
107 bool colors;
108 bool flush;
109 char* time_format;
110 char* linesep;
111 char* linebeg;
112} Mint_Loggo_LogFormat;
113
114
115#ifdef __cplusplus
116extern "C" {
117#endif
118
119
120// API
121
122/*
123 * Init Logger in its own thread.
124 * If user_format is NULL then the defaults are used
125 * If user_handler is NULL then buffered stdout is used for logging
126 * name cannot be NULL
127 * Returns logger id on success or -1 for Failure
128 */
129MINT_LOGGO_DEF int32_t Mint_Loggo_CreateLogger(const char* name, Mint_Loggo_LogFormat* user_format, Mint_Loggo_LogHandler* user_handler);
130
131
132/*
133 * Delete logger waiting for all of its messages,
134 * This will also clean up the resources if its the last logger so there is no need to call DeleteLoggers
135 */
136MINT_LOGGO_DEF void Mint_Loggo_DeleteLogger(const char* name);
137
138
139/*
140 * Stop Logger threads and clean up handles.
141 * This is idempotent so it can be called multiple times
142 */
143 MINT_LOGGO_DEF void Mint_Loggo_DeleteLoggers();
144
145
146/*
147 * Pass messages to the log queue, the logging thread will accept messages,
148 * then use the handler methods (or defaults) to output logs
149 */
150MINT_LOGGO_DEF void Mint_Loggo_Log(const char* name, Mint_Loggo_LogLevel level, const char* msg);
151MINT_LOGGO_DEF void Mint_Loggo_Log2(const char* name, Mint_Loggo_LogLevel level, char* msg, bool free_string);
152
153// Loggo Handler methods
154
155// FILE* friends
156MINT_LOGGO_DEF int Mint_Loggo_StreamWrite(char* text, void* arg);
157MINT_LOGGO_DEF int Mint_Loggo_StreamClose(void* arg);
158MINT_LOGGO_DEF int Mint_Loggo_StreamFlush(void* arg);
159
160// Raw Descriptor IO
161MINT_LOGGO_DEF int Mint_Loggo_DescriptorWrite(char* text, void* arg);
162MINT_LOGGO_DEF int Mint_Loggo_DescriptorClose(void* arg);
163MINT_LOGGO_DEF int Mint_Loggo_DescriptorFlush(void* arg);
164
165// Do nothing
166MINT_LOGGO_DEF int Mint_Loggo_NullWrite(char* text, void* arg);
167MINT_LOGGO_DEF int Mint_Loggo_NullClose(void* arg);
168MINT_LOGGO_DEF int Mint_Loggo_NullFlush(void* arg);
169
170#ifdef __cplusplus
171}
172#endif
173
174// Convenience Macros for logging
175#ifdef MINT_LOGGO_USE_HELPERS
176 #define LOG_DEBUG(name, msg) Mint_Loggo_Log((name), MINT_LOGGO_LEVEL_DEBUG, (msg))
177 #define LOG_INFO(name, msg) Mint_Loggo_Log((name), MINT_LOGGO_LEVEL_INFO, (msg))
178 #define LOG_WARN(name, msg) Mint_Loggo_Log((name), MINT_LOGGO_LEVEL_WARN, (msg))
179 #define LOG_ERROR(name, msg) Mint_Loggo_Log((name), MINT_LOGGO_LEVEL_ERROR, (msg))
180 #define LOG_FATAL(name, msg) Mint_Loggo_Log((name), MINT_LOGGO_LEVEL_FATAL, (msg))
181
182 #define LOG2_DEBUG(name, msg, free_string) Mint_Loggo_Log2((name), MINT_LOGGO_LEVEL_DEBUG, (msg), (free_string))
183 #define LOG2_INFO(name, msg, free_string) Mint_Loggo_Log2((name), MINT_LOGGO_LEVEL_INFO, (msg), (free_string))
184 #define LOG2_WARN(name, msg, free_string) Mint_Loggo_Log2((name), MINT_LOGGO_LEVEL_WARN, (msg), (free_string))
185 #define LOG2_ERROR(name, msg, free_string) Mint_Loggo_Log2((name), MINT_LOGGO_LEVEL_ERROR, (msg), (free_string))
186 #define LOG2_FATAL(name, msg, free_string) Mint_Loggo_Log2((name), MINT_LOGGO_LEVEL_FATAL, (msg), (free_string))
187
188 #define STDOUT_STREAM_HANDLER (Mint_Loggo_LogHandler) { \
189 .handle=stdout, \
190 .write_handler=Mint_Loggo_StreamWrite, \
191 .close_handler=Mint_Loggo_StreamClose, \
192 .flush_handler=Mint_Loggo_StreamFlush \
193 }
194
195 #define STDERR_STREAM_HANDLER (Mint_Loggo_LogHandler) { \
196 .handle=stderr, \
197 .write_handler=Mint_Loggo_StreamWrite, \
198 .close_handler=Mint_Loggo_StreamClose, \
199 .flush_handler=Mint_Loggo_StreamFlush \
200 }
201
202 #define STDOUT_DESC_HANDLER (Mint_Loggo_LogHandler) { \
203 .handle=(&STDOUT_FILENO), \
204 .write_handler=Mint_Loggo_DescriptorWrite, \
205 .close_handler=Mint_Loggo_DescriptorClose, \
206 .flush_handler=Mint_Loggo_DescriptroFlush \
207 }
208
209 #define STDERR_DESC_HANDLER (Mint_Loggo_LogHandler) { \
210 .handle=(&STDERR_FILENO), \
211 .write_handler=Mint_Loggo_DescriptorWrite, \
212 .close_handler=Mint_Loggo_DescriptorClose, \
213 .flush_handler=Mint_Loggo_DescriptroFlush \
214 }
215#endif
216
217
218
219
220// Do implementation here if you do this twice when MINT_LOGGO_DEF_STATIC is not set you will get linker
221// errors from multiple definitions
222#ifdef MINT_LOGGO_IMPLEMENTATION
223
224
225////////////////////////////////////
226// Platform and Helpers
227////////////////////////////////////
228
229
230#if defined(__unix__) || defined(linux) || defined(__APPLE__) || defined(MINT_USE_POSIX)
231 #define MINT_LOGGO_RED "\033[31m"
232 #define MINT_LOGGO_GREEN "\033[32m"
233 #define MINT_LOGGO_YELLOW "\033[33m"
234 #define MINT_LOGGO_BLUE "\033[34m"
235 #define MINT_LOGGO_MAGENTA "\033[35m"
236 #define MINT_LOGGO_CYAN "\033[36m"
237 #define MINT_LOGGO_WHITE "\033[37m"
238 #define MINT_LOGGO_RESET "\033[0m"
239
240 MINT_LOGGO_DEF int Mint_Loggo_DescriptorWrite(char* text, void* arg) {
241 return write(*(int*)arg, text, strlen(text));
242 }
243
244
245 MINT_LOGGO_DEF int Mint_Loggo_DescriptorClose(void* arg) {
246 return close(*(int*)arg);
247 }
248#elif defined(_WIN32) || defined(MINT_USE_WINDOWS)
249 // TODO Fix this
250 #define MINT_LOGGO_RED ""
251 #define MINT_LOGGO_GREEN ""
252 #define MINT_LOGGO_YELLOW ""
253 #define MINT_LOGGO_BLUE ""
254 #define MINT_LOGGO_MAGENTA ""
255 #define MINT_LOGGO_CYAN ""
256 #define MINT_LOGGO_WHITE ""
257 #define MINT_LOGGO_RESET ""
258
259 MINT_LOGGO_DEF int Mint_Loggo_DescriptorWrite(char* text, void* arg) {
260 return _write(*(int*)arg, text, strlen(text));
261 }
262
263
264 MINT_LOGGO_DEF int Mint_Loggo_DescriptorClose(void* arg) {
265 return _close(*(int*)arg);
266 }
267#endif
268
269// Common
270
271MINT_LOGGO_DEF int Mint_Loggo_DescriptorFlush(void* arg) {
272 MINT_LOGGO_UNUSED(arg);
273 return 0;
274}
275
276
277MINT_LOGGO_DEF int Mint_Loggo_StreamWrite(char* text, void* arg) {
278 return fputs(text, (FILE*)arg);
279}
280
281
282MINT_LOGGO_DEF int Mint_Loggo_StreamClose(void* arg) {
283 return fclose((FILE*)arg);
284}
285
286
287MINT_LOGGO_DEF int Mint_Loggo_StreamFlush(void* arg) {
288 return fflush((FILE*)arg);
289}
290
291
292MINT_LOGGO_DEF int Mint_Loggo_NullWrite(char* text, void* arg) {
293 MINT_LOGGO_UNUSED(arg);
294 MINT_LOGGO_UNUSED(text);
295 return 0;
296}
297
298
299MINT_LOGGO_DEF int Mint_Loggo_NullClose(void* arg) {
300 MINT_LOGGO_UNUSED(arg);
301 return 0;
302}
303
304
305MINT_LOGGO_DEF int Mint_Loggo_NullFlush(void* arg) {
306 MINT_LOGGO_UNUSED(arg);
307 return 0;
308}
309
310
311
312static void* Mint_Loggo_ErrorCheckedMalloc(size_t size) {
313 void* ptr = malloc(size);
314 if (!ptr) {
315 fprintf(stderr, "[ERROR] Exiting because malloc failed...");
316 exit(EXIT_FAILURE);
317 }
318 return ptr;
319}
320
321
322static void* Mint_Loggo_ErrorCheckedRealloc(void* original, size_t size) {
323 void* ptr = realloc(original, size);
324 if (!ptr) {
325 fprintf(stderr, "[ERROR] Exiting because realloc failed...");
326 exit(EXIT_FAILURE);
327 }
328 return ptr;
329}
330
331
332////////////////////////////////////
333// Defaults
334////////////////////////////////////
335
336#define MINT_LOGGO_DEFAULT_LINE_SEP "\n"
337#define MINT_LOGGO_DEFAULT_LINE_BEG ""
338#define MINT_LOGGO_DEFAULT_QUEUE_SIZE 1024U
339#define MINT_LOGGO_DEFAULT_TIME_FORMAT "%Y-%m-%d %H:%M:%S"
340#define MINT_LOGGO_DEFAULT_HT_INITIAL_CAPACITY 128
341#define MINT_LOGGO_DEFAULT_HT_INITIAL_LOAD_FACTOR 0.7f
342
343// Can be overriden by user
344#define MINT_LOGGO_MALLOC Mint_Loggo_ErrorCheckedMalloc
345#define MINT_LOGGO_REALLOC Mint_Loggo_ErrorCheckedRealloc
346#define MINT_LOGGO_FREE free
347
348
349////////////////////////////////////
350// Types
351////////////////////////////////////
352
353// Messages are always created and must be freed
354typedef struct {
355 Mint_Loggo_LogLevel level;
356 bool done;
357 char* msg;
358} Mint_Loggo_LogMessage;
359
360
361
362// Circular dynamic array implementation
363typedef struct {
364 uint32_t head;
365 uint32_t tail;
366 uint32_t capacity;
367 uint32_t size;
368 Mint_Loggo_LogMessage** messages;
369 MINT_LOGGO_MUTEX_TYPE queue_lock;
370 MINT_LOGGO_COND_TYPE queue_not_full;
371 MINT_LOGGO_COND_TYPE queue_not_empty;
372} Mint_Loggo_LogQueue;
373
374
375// Contains everything a logger will need
376typedef struct {
377 Mint_Loggo_LogFormat* format;
378 Mint_Loggo_LogHandler* handler;
379 Mint_Loggo_LogQueue* queue;
380 int32_t id;
381 MINT_LOGGO_THREAD_TYPE thread_id;
382 const char* name;
383 bool done;
384
385} Mint_Loggo_Logger;
386
387typedef struct {
388 Mint_Loggo_Logger** loggers;
389 int32_t size;
390 int32_t capacity;
391 double load_factor;
392} Mint_Loggo_HashTable;
393
394
395////////////////////////////////////
396// Constants
397////////////////////////////////////
398static const int32_t MINT_LOGGO_PRIME_1 = 71U;
399static const int32_t MINT_LOGGO_PRIME_2 = 197U;
400
401
402////////////////////////////////////
403// Global values
404////////////////////////////////////
405static Mint_Loggo_LogMessage MINT_LOGGO_LOGGER_TERMINATE = {.done = true};
406static Mint_Loggo_Logger MINT_LOGGO_LOGGER_DELETED = {0};
407static Mint_Loggo_HashTable MINT_LOGGO_LOGGER_HASH_TABLE = {0};
408
409
410////////////////////////////////////
411// Declarations up front (not including api)
412////////////////////////////////////
413
414
415// Queue
416static Mint_Loggo_LogQueue* Mint_Loggo_CreateQueue(uint32_t capacity);
417static void Mint_Loggo_DestroyQueue(Mint_Loggo_LogQueue* queue);
418static bool Mint_Loggo_IsQueueFull(Mint_Loggo_LogQueue* queue);
419static bool Mint_Loggo_IsQueueEmpty(Mint_Loggo_LogQueue* queue);
420static void Mint_Loggo_Enqueue(Mint_Loggo_LogQueue* queue, Mint_Loggo_LogMessage* message);
421static Mint_Loggo_LogMessage* Mint_Loggo_Dequeue(Mint_Loggo_LogQueue* queue);
422
423// Logging
424static void* Mint_Loggo_RunLogger(void* arg);
425static char* Mint_Loggo_StringFromLevel(Mint_Loggo_LogLevel level);
426static char* Mint_Loggo_ColorFromLevel(Mint_Loggo_LogLevel level);
427static Mint_Loggo_LogMessage* Mint_Loggo_CreateLogMessage(Mint_Loggo_Logger* logger, Mint_Loggo_LogLevel level, const char* msg);
428static Mint_Loggo_LogFormat* Mint_Loggo_CreateLogFormat(Mint_Loggo_LogFormat* user_format);
429static Mint_Loggo_LogHandler* Mint_Loggo_CreateLogHandler(Mint_Loggo_LogHandler* user_handler);
430static void Mint_Loggo_DestroyLogHandler(Mint_Loggo_LogHandler* handler);
431static void Mint_Loggo_DestroyLogFormat(Mint_Loggo_LogFormat* format);
432static void Mint_Loggo_CleanUpLogger(Mint_Loggo_Logger* logger);
433static void Mint_Loggo_HandleLogMessage(Mint_Loggo_LogMessage* message, Mint_Loggo_LogFormat* format, Mint_Loggo_LogHandler* handler);
434
435// Hash Table
436static Mint_Loggo_Logger* Mint_Loggo_HTFindItem(const char* name);
437static int32_t Mint_Loggo_HTInsertItem(const char* name, Mint_Loggo_Logger* logger);
438static void Mint_Loggo_HTDeleteItem(const char* name);
439static void Mint_Loggo_HTResizeTable();
440static void Mint_Loggo_HTInitTable();
441static void Mint_Loggo_HTDeleteTable();
442static int32_t Mint_Loggo_StringHash(const char* name, const int32_t prime, const int32_t buckets);
443static int32_t Mint_Loggo_LoggerStringHash(const char* name, const int32_t buckets, const int32_t attempt);
444
445
446
447////////////////////////////////////
448// Api
449////////////////////////////////////
450
451
452// Create logger components, start thread with the created queue
453// Return -1 on error dont allocate anything, clean slate
454MINT_LOGGO_DEF int32_t Mint_Loggo_CreateLogger(const char* name, Mint_Loggo_LogFormat* user_format, Mint_Loggo_LogHandler* user_handler) {
455 #ifdef MINT__DEBUG
456 assert(name);
457 #endif
458
459 if (!name) {
460 return -1;
461 }
462
463 Mint_Loggo_HTInitTable();
464
465 Mint_Loggo_Logger* logger = MINT_LOGGO_MALLOC(sizeof(Mint_Loggo_Logger));
466 memset(logger, 0U, sizeof(*logger));
467
468 // Fill up with info
469 logger->handler = Mint_Loggo_CreateLogHandler(user_handler);
470
471 // Clean up and return -1
472 if (!logger->handler) {
473 Mint_Loggo_DestroyLogHandler(logger->handler);
474 memset(logger, 0U, sizeof(*logger));
475 MINT_LOGGO_FREE(logger);
476 logger = NULL;
477 return -1;
478 }
479
480 logger->format = Mint_Loggo_CreateLogFormat(user_format);
481 logger->name = name;
482 logger->queue = Mint_Loggo_CreateQueue(logger->format->queue_capacity);
483
484 // Handle the string allocation to a logger id
485 int32_t id = Mint_Loggo_HTInsertItem(name, logger);
486
487 // We failed
488 if (id == -1) {
489 Mint_Loggo_DestroyLogFormat(logger->format);
490 Mint_Loggo_DestroyLogHandler(logger->handler);
491 Mint_Loggo_DestroyQueue(logger->queue);
492 memset(logger, 0U, sizeof(*logger));
493 MINT_LOGGO_FREE(logger);
494 logger = NULL;
495 return id;
496 }
497
498 // Handle new stuff
499 logger->id = id;
500
501 // Spin up a thread for the loggers
502 MINT_LOGGO_THREAD_CREATE(&logger->thread_id, Mint_Loggo_RunLogger, ((void*)logger));
503
504 // Return Id to user
505 return logger->id;
506}
507
508
509// Shutdown the loggers by iterating and setting values
510MINT_LOGGO_DEF void Mint_Loggo_DeleteLoggers() {
511 for (int32_t idx = 0; idx < MINT_LOGGO_LOGGER_HASH_TABLE.capacity; idx++) {
512 // Delete non null items by grabbing there names from the table
513 if (MINT_LOGGO_LOGGER_HASH_TABLE.loggers[idx] != NULL && MINT_LOGGO_LOGGER_HASH_TABLE.loggers[idx] != &MINT_LOGGO_LOGGER_DELETED) {
514 Mint_Loggo_HTDeleteItem(MINT_LOGGO_LOGGER_HASH_TABLE.loggers[idx]->name);
515 }
516 }
517
518 Mint_Loggo_HTDeleteTable();
519 memset(&MINT_LOGGO_LOGGER_HASH_TABLE, 0U, sizeof(Mint_Loggo_HashTable));
520}
521
522
523// Shutdown the loggers by iterating and setting values
524MINT_LOGGO_DEF void Mint_Loggo_DeleteLogger(const char* name) {
525 Mint_Loggo_HTDeleteItem(name);
526
527 // Just delete the table and clear it so its inited next time
528 if (MINT_LOGGO_LOGGER_HASH_TABLE.size == 0) {
529 Mint_Loggo_HTDeleteTable();
530 memset(&MINT_LOGGO_LOGGER_HASH_TABLE, 0U, sizeof(Mint_Loggo_HashTable));
531 }
532}
533
534
535// Log message with Enqueue
536MINT_LOGGO_DEF void Mint_Loggo_Log(const char* name, Mint_Loggo_LogLevel level, const char* msg) {
537 #ifdef MINT__DEBUG
538 assert(name);
539 assert(msg);
540 assert(level >= 0U);
541 #endif
542
543 Mint_Loggo_Logger* logger = Mint_Loggo_HTFindItem(name);
544
545 if (!logger) {
546 fprintf(stderr, "Invalid Logger Name: %s\n", name);
547 Mint_Loggo_DeleteLoggers();
548 exit(EXIT_FAILURE);
549 }
550
551 Mint_Loggo_LogMessage* message = Mint_Loggo_CreateLogMessage(logger, level, msg);
552
553
554 Mint_Loggo_Enqueue(logger->queue, message);
555}
556
557
558// Log message with Enqueue, optionally free msg if free_string is true. In case the user wants to pass
559// a malloced string in
560MINT_LOGGO_DEF void Mint_Loggo_Log2(const char* name, Mint_Loggo_LogLevel level, char* msg, bool free_string) {
561 #ifdef MINT__DEBUG
562 assert(name);
563 assert(msg);
564 assert(level >= 0U);
565 #endif
566
567 Mint_Loggo_Logger* logger = Mint_Loggo_HTFindItem(name);
568
569 if (!logger) {
570 fprintf(stderr, "Invalid Logger Name: %s\n", name);
571 Mint_Loggo_DeleteLoggers();
572 exit(EXIT_FAILURE);
573 }
574
575 Mint_Loggo_LogMessage* message = Mint_Loggo_CreateLogMessage(logger, level, msg);
576
577 if (free_string) {
578 free(msg);
579 }
580
581 Mint_Loggo_Enqueue(logger->queue, message);
582}
583
584
585// Queue
586
587// Create the queue with sane defaults
588static Mint_Loggo_LogQueue* Mint_Loggo_CreateQueue(uint32_t capacity) {
589 #ifdef MINT__DEBUG
590 assert(capacity > 0U);
591 #endif
592
593 Mint_Loggo_LogQueue* queue = MINT_LOGGO_MALLOC(sizeof(Mint_Loggo_LogQueue));
594
595 // Clear out values and set actual ones
596 memset(queue, 0U, sizeof(*queue));
597 queue->capacity = capacity;
598 queue->head = 0U;
599 queue->tail = 0U;
600 queue->size = 0U;
601
602 // Init locks/cond
603 MINT_LOGGO_MUTEX_INIT(queue->queue_lock);
604 MINT_LOGGO_COND_INIT(queue->queue_not_full);
605 MINT_LOGGO_COND_INIT(queue->queue_not_empty);
606
607 // Init messsages circular buffer
608 queue->messages = MINT_LOGGO_MALLOC(sizeof(Mint_Loggo_LogMessage*) * queue->capacity);
609 memset(queue->messages, 0U, sizeof(Mint_Loggo_LogMessage*) * queue->capacity);
610 return queue;
611}
612
613
614// Free messages, queue and zero out mem
615static void Mint_Loggo_DestroyQueue(Mint_Loggo_LogQueue* queue) {
616 #ifdef MINT__DEBUG
617 assert(queue);
618 #endif
619
620 // Safer to go over all of them just in case and free shit,
621 // The terminate in the thread loop should do this
622 uint32_t start = queue->tail;
623 uint32_t end = queue->head;
624 while(start != end) {
625 if(queue->messages[start]) {
626 if (queue->messages[start]->msg) {
627 MINT_LOGGO_FREE(queue->messages[start]->msg);
628 queue->messages[start]->msg = NULL;
629 }
630 MINT_LOGGO_FREE(queue->messages[start]);
631 queue->messages[start] = NULL;
632 }
633
634 // Wraparound
635 start = (start + 1) % queue->capacity;
636 }
637
638 // Clean up threading stuff
639 MINT_LOGGO_MUTEX_DESTROY((queue->queue_lock));
640 MINT_LOGGO_COND_DESTROY((queue->queue_not_empty));
641 MINT_LOGGO_COND_DESTROY((queue->queue_not_full));
642
643 // Free messages that the queue owns
644 if (queue->messages) {
645 MINT_LOGGO_FREE(queue->messages);
646 queue->messages = NULL;
647 }
648
649 // Clean up last bits of memory
650 memset(queue, 0U, sizeof(*queue));
651 MINT_LOGGO_FREE(queue);
652}
653
654
655// If head + 1 == tail
656static bool Mint_Loggo_IsQueueFull(Mint_Loggo_LogQueue* queue) {
657 return queue->head + 1 == queue->tail;
658}
659
660
661// If head == tail
662static bool Mint_Loggo_IsQueueEmpty(Mint_Loggo_LogQueue* queue) {
663 return queue->head == queue->tail;
664}
665
666
667// Wait for the queue to not be full
668// Add message
669// Signal that its not empty anymore
670static void Mint_Loggo_Enqueue(Mint_Loggo_LogQueue* queue, Mint_Loggo_LogMessage* message) {
671 MINT_LOGGO_MUTEX_LOCK(queue->queue_lock);
672
673 #ifdef MINT__DEBUG
674 assert(queue);
675 assert(message);
676 #endif
677
678 // Just dont queue if full
679 while (Mint_Loggo_IsQueueFull(queue)) {
680 MINT_LOGGO_COND_WAIT(queue->queue_not_full, queue->queue_lock);
681 }
682
683 // Add message and advance queue
684 queue->messages[queue->head] = message;
685 queue->head = (queue->head + 1) % queue->capacity;
686 queue->size++;
687
688 // Let the thread know it has a message
689 MINT_LOGGO_COND_SIGNAL(queue->queue_not_empty);
690 MINT_LOGGO_MUTEX_UNLOCK(queue->queue_lock);
691}
692
693
694// If the queue is empty just wait until we get the okay from Enqueue
695// Also let enqueue know we are not full because we took a message
696static Mint_Loggo_LogMessage* Mint_Loggo_Dequeue(Mint_Loggo_LogQueue* queue) {
697 MINT_LOGGO_MUTEX_LOCK(queue->queue_lock);
698
699 #ifdef MINT__DEBUG
700 assert(queue);
701 #endif
702
703 while (Mint_Loggo_IsQueueEmpty(queue)) {
704 MINT_LOGGO_COND_WAIT(queue->queue_not_empty, queue->queue_lock);
705 }
706
707 Mint_Loggo_LogMessage* message = queue->messages[queue->tail];
708 queue->messages[queue->tail] = NULL;
709 queue->tail = (queue->tail + 1) % queue->capacity;
710 queue->size--;
711
712 MINT_LOGGO_COND_SIGNAL(queue->queue_not_full);
713 MINT_LOGGO_MUTEX_UNLOCK(queue->queue_lock);
714 return message;
715}
716
717
718// Logging
719
720
721// Stringify Level
722static char* Mint_Loggo_StringFromLevel(Mint_Loggo_LogLevel level) {
723 char* result = NULL;
724 switch(level) {
725 case MINT_LOGGO_LEVEL_DEBUG:
726 result = "DEBUG";
727 break;
728 case MINT_LOGGO_LEVEL_INFO:
729 result = "INFO";
730 break;
731 case MINT_LOGGO_LEVEL_WARN:
732 result = "WARN";
733 break;
734 case MINT_LOGGO_LEVEL_ERROR:
735 result = "ERROR";
736 break;
737 case MINT_LOGGO_LEVEL_FATAL:
738 result = "FATAL";
739 break;
740 default:
741 result = "UNKNOWN";
742 break;
743 }
744
745 return result;
746}
747
748
749// Colorify Level
750static char* Mint_Loggo_ColorFromLevel(Mint_Loggo_LogLevel level) {
751 char* result = NULL;
752 switch(level) {
753 case MINT_LOGGO_LEVEL_DEBUG:
754 result = MINT_LOGGO_MAGENTA;
755 break;
756 case MINT_LOGGO_LEVEL_INFO:
757 result = MINT_LOGGO_GREEN;
758 break;
759 case MINT_LOGGO_LEVEL_WARN:
760 result = MINT_LOGGO_YELLOW;
761 break;
762 case MINT_LOGGO_LEVEL_ERROR:
763 result = MINT_LOGGO_CYAN;
764 break;
765 case MINT_LOGGO_LEVEL_FATAL:
766 result = MINT_LOGGO_RED;
767 break;
768 default:
769 result = MINT_LOGGO_WHITE;
770 break;
771 }
772
773 return result;
774}
775
776
777static Mint_Loggo_LogFormat* Mint_Loggo_CreateLogFormat(Mint_Loggo_LogFormat* user_format) {
778 #ifdef MINT__DEBUG
779 assert(user_format);
780 #endif
781
782 // Create User handler
783 Mint_Loggo_LogFormat* log_format = MINT_LOGGO_MALLOC(sizeof(Mint_Loggo_LogFormat));
784 memset(log_format, 0U, sizeof(*log_format));
785
786 // Copy user format if it exists
787 if (user_format) {
788 memcpy(log_format, user_format, sizeof(*user_format));
789 }
790
791 // Set defaults
792 // Level defaults to LOG_DEBUG
793 // colors defaults to 0 (false)
794 // flush defaults to 0 (false)
795
796 if (!log_format->linesep) log_format->linesep = MINT_LOGGO_DEFAULT_LINE_SEP;
797 if (log_format->queue_capacity == 0) log_format->queue_capacity = MINT_LOGGO_DEFAULT_QUEUE_SIZE;
798 if (!log_format->time_format) log_format->time_format = MINT_LOGGO_DEFAULT_TIME_FORMAT;
799 if (!log_format->linebeg) log_format->linebeg = MINT_LOGGO_DEFAULT_LINE_BEG;
800 return log_format;
801}
802
803
804static Mint_Loggo_LogHandler* Mint_Loggo_CreateLogHandler(Mint_Loggo_LogHandler* user_handler) {
805
806 // Create User handler
807 Mint_Loggo_LogHandler* log_handler = MINT_LOGGO_MALLOC(sizeof(Mint_Loggo_LogHandler));
808 memset(log_handler, 0U, sizeof(*log_handler));
809
810 // Copy user format if it exists
811 if (user_handler) {
812 memcpy(log_handler, user_handler, sizeof(*user_handler));
813
814 // You need the handle
815 if (!log_handler->handle) {
816 Mint_Loggo_DestroyLogHandler(log_handler);
817 return NULL;
818 }
819
820 // Set NULL handlers if they are not there
821 if(!log_handler->write_handler) log_handler->write_handler = Mint_Loggo_NullWrite;
822 if(!log_handler->flush_handler) log_handler->flush_handler = Mint_Loggo_NullFlush;
823 if(!log_handler->close_handler) log_handler->close_handler = Mint_Loggo_NullClose;
824
825 } else {
826 // Defaults
827 log_handler->handle = stdout;
828 log_handler->write_handler = Mint_Loggo_StreamWrite;
829 log_handler->close_handler = Mint_Loggo_StreamClose;
830 log_handler->flush_handler = Mint_Loggo_StreamFlush;
831 }
832
833 return log_handler;
834}
835
836
837static void Mint_Loggo_DestroyLogHandler(Mint_Loggo_LogHandler* handler) {
838 #ifdef MINT__DEBUG
839 assert(handler);
840 #endif
841
842 memset(handler, 0U, sizeof(*handler));
843 MINT_LOGGO_FREE(handler);
844}
845
846
847static void Mint_Loggo_DestroyLogFormat(Mint_Loggo_LogFormat* format) {
848 #ifdef MINT__DEBUG
849 assert(format);
850 #endif
851
852 memset(format, 0U, sizeof(*format));
853 MINT_LOGGO_FREE(format);
854}
855
856
857// Actual ouptut of message and cleanup
858static void Mint_Loggo_HandleLogMessage(Mint_Loggo_LogMessage* message, Mint_Loggo_LogFormat* format, Mint_Loggo_LogHandler* handler) {
859 #ifdef MINT__DEBUG
860 assert(message);
861 assert(format);
862 assert(handler);
863 #endif
864
865 if (message->level >= format->level) {
866 if (format->colors) {
867 handler->write_handler(Mint_Loggo_ColorFromLevel(message->level), handler->handle);
868 }
869
870 // Write actual output
871 handler->write_handler(format->linebeg, handler->handle);
872 handler->write_handler(" ", handler->handle);
873 handler->write_handler(message->msg, handler->handle);
874 handler->write_handler(format->linesep, handler->handle);
875
876 // Reset colors
877 if (format->colors) {
878 handler->write_handler(MINT_LOGGO_RESET, handler->handle);
879 }
880
881 // Flush if needed
882 if (format->flush) {
883 handler->flush_handler(handler->handle);
884 }
885 }
886
887 // Clean up message
888 MINT_LOGGO_FREE(message->msg);
889 message->msg = NULL;
890
891 // Clean up message
892 MINT_LOGGO_FREE(message);
893}
894
895
896// Thread spawned handler of messages
897static void* Mint_Loggo_RunLogger(void* arg) {
898 #ifdef MINT__DEBUG
899 assert(arg);
900 #endif
901 Mint_Loggo_Logger* logger = arg;
902 #ifdef MINT__DEBUG
903 assert(logger->queue);
904 #endif
905
906 // For some reason you have to grab the read lock and read all that you can in a loop
907 // Or else the condition is never signaled and you wait
908 while (!logger->done) {
909 Mint_Loggo_LogMessage* message = Mint_Loggo_Dequeue(logger->queue);
910
911 #ifdef MINT__DEBUG
912 assert(logger);
913 assert(message);
914 #endif
915
916 if (message) {
917 // Done at this point
918 if (message->done) {
919 logger->done = true;
920 continue;
921 }
922
923 // Log the messages, then free them
924 Mint_Loggo_HandleLogMessage(message, logger->format, logger->handler);
925 }
926 }
927
928 return EXIT_SUCCESS;
929}
930
931
932// Create a nice formatted log message
933static Mint_Loggo_LogMessage* Mint_Loggo_CreateLogMessage(Mint_Loggo_Logger* logger, Mint_Loggo_LogLevel level, const char* msg) {
934 // Misc
935 char* formatted_msg = NULL;
936 uint32_t padding = 3U + 2U;
937
938 // Level String
939 const char* level_string = Mint_Loggo_StringFromLevel(level);
940 uint32_t level_size = strlen(level_string);
941
942 // Get time
943 char time_buffer[128U];
944 time_t current_time = time(NULL);
945 time_buffer[strftime(time_buffer, sizeof(time_buffer), logger->format->time_format, localtime(¤t_time))] = '\0';
946
947 // Create LogMessage
948 Mint_Loggo_LogMessage* message = MINT_LOGGO_MALLOC(sizeof(Mint_Loggo_LogMessage));
949 memset(message, 0U, sizeof(*message));
950
951 // Insert formatted message inside of LogMessage
952 formatted_msg = MINT_LOGGO_MALLOC(strlen(msg) + strlen(time_buffer) + level_size + padding + 1U);
953 sprintf(formatted_msg, "[%s] %s %s", time_buffer, level_string, msg);
954 message->level = level;
955 message->msg = formatted_msg;
956
957 return message;
958}
959
960
961// Free all the handles
962static void Mint_Loggo_CleanUpLogger(Mint_Loggo_Logger* logger) {
963
964 // Queue up final message and wait for logger to close
965 Mint_Loggo_Enqueue(logger->queue, &MINT_LOGGO_LOGGER_TERMINATE);
966 MINT_LOGGO_THREAD_JOIN(logger->thread_id);
967
968 // Free handles
969 Mint_Loggo_DestroyLogHandler(logger->handler);
970 logger->handler = NULL;
971
972 Mint_Loggo_DestroyLogFormat(logger->format);
973 logger->format = NULL;
974
975 Mint_Loggo_DestroyQueue(logger->queue);
976 logger->queue = NULL;
977
978 MINT_LOGGO_FREE(logger);
979}
980
981
982// Logger hash table
983
984
985// Try to find an item returning NULL if not found
986static Mint_Loggo_Logger* Mint_Loggo_HTFindItem(const char* name) {
987 int32_t hash = Mint_Loggo_LoggerStringHash(name, MINT_LOGGO_LOGGER_HASH_TABLE.capacity, 0);
988 int32_t attempt = 1;
989 Mint_Loggo_Logger* current_logger = MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash];
990 while(current_logger != NULL) {
991 if (current_logger != &MINT_LOGGO_LOGGER_DELETED) {
992 if (strcmp(name, current_logger->name) == 0) {
993 return current_logger;
994 }
995 }
996 hash = Mint_Loggo_LoggerStringHash(name, MINT_LOGGO_LOGGER_HASH_TABLE.capacity, attempt);
997 current_logger = MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash];
998 attempt++;
999 }
1000
1001 return NULL;
1002}
1003
1004
1005// Try to insert an item, resize if needed
1006static int32_t Mint_Loggo_HTInsertItem(const char* name, Mint_Loggo_Logger* logger) {
1007 // Try a resize
1008 Mint_Loggo_HTResizeTable();
1009 int32_t hash = Mint_Loggo_LoggerStringHash(name, MINT_LOGGO_LOGGER_HASH_TABLE.capacity, 0);
1010 int32_t attempt = 1;
1011 Mint_Loggo_Logger* current_logger = MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash];
1012 while(current_logger != NULL && current_logger != &MINT_LOGGO_LOGGER_DELETED) {
1013 hash = Mint_Loggo_LoggerStringHash(name, MINT_LOGGO_LOGGER_HASH_TABLE.capacity, attempt);
1014 current_logger = MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash];
1015 // If the item exists update it
1016 if (current_logger) {
1017 if (strcmp(name, current_logger->name) == 0) {
1018 Mint_Loggo_CleanUpLogger(current_logger);
1019 MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash] = logger;
1020 return logger->id;
1021 }
1022 }
1023
1024 attempt++;
1025
1026 }
1027
1028 MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash] = logger;
1029 MINT_LOGGO_LOGGER_HASH_TABLE.size++;
1030 logger->id = hash;
1031 return logger->id;
1032}
1033
1034
1035static void Mint_Loggo_HTDeleteItem( const char* name) {
1036 int32_t hash = Mint_Loggo_LoggerStringHash(name, MINT_LOGGO_LOGGER_HASH_TABLE.capacity, 0);
1037 Mint_Loggo_Logger* current_logger = MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash];
1038 int32_t attempt = 1;
1039 while(current_logger != NULL) {
1040 if (current_logger != &MINT_LOGGO_LOGGER_DELETED && current_logger != NULL) {
1041 if (strcmp(current_logger->name, name) == 0) {
1042 Mint_Loggo_CleanUpLogger(current_logger);
1043 MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash] = &MINT_LOGGO_LOGGER_DELETED;
1044 }
1045 }
1046 hash = Mint_Loggo_LoggerStringHash(name, MINT_LOGGO_LOGGER_HASH_TABLE.capacity, attempt);
1047 current_logger = MINT_LOGGO_LOGGER_HASH_TABLE.loggers[hash];
1048 attempt++;
1049 }
1050 MINT_LOGGO_LOGGER_HASH_TABLE.size--;
1051}
1052
1053
1054// Just realloc space up
1055static void Mint_Loggo_HTResizeTable() {
1056 int32_t load = (MINT_LOGGO_LOGGER_HASH_TABLE.size * 100) / MINT_LOGGO_LOGGER_HASH_TABLE.capacity;
1057 if (load > (MINT_LOGGO_LOGGER_HASH_TABLE.load_factor * 100)) {
1058 MINT_LOGGO_LOGGER_HASH_TABLE.capacity *= 2;
1059
1060 // No need to realloc a new table
1061 // Just realloc memory chunk
1062 MINT_LOGGO_LOGGER_HASH_TABLE.loggers = MINT_LOGGO_REALLOC(MINT_LOGGO_LOGGER_HASH_TABLE.loggers, sizeof(Mint_Loggo_Logger*) * MINT_LOGGO_LOGGER_HASH_TABLE.capacity);
1063 }
1064}
1065
1066
1067// Delete table and set defaults
1068static void Mint_Loggo_HTDeleteTable() {
1069 if (MINT_LOGGO_LOGGER_HASH_TABLE.loggers) {
1070 MINT_LOGGO_FREE(MINT_LOGGO_LOGGER_HASH_TABLE.loggers);
1071 MINT_LOGGO_LOGGER_HASH_TABLE.loggers = NULL;
1072 }
1073
1074 MINT_LOGGO_LOGGER_HASH_TABLE.capacity = MINT_LOGGO_DEFAULT_HT_INITIAL_CAPACITY;
1075 MINT_LOGGO_LOGGER_HASH_TABLE.size = 0;
1076 MINT_LOGGO_LOGGER_HASH_TABLE.load_factor = MINT_LOGGO_DEFAULT_HT_INITIAL_LOAD_FACTOR;
1077}
1078
1079
1080// Init whats needed
1081static void Mint_Loggo_HTInitTable() {
1082 if (!MINT_LOGGO_LOGGER_HASH_TABLE.loggers) {
1083 MINT_LOGGO_LOGGER_HASH_TABLE.capacity = MINT_LOGGO_DEFAULT_HT_INITIAL_CAPACITY;
1084 MINT_LOGGO_LOGGER_HASH_TABLE.size = 0;
1085 MINT_LOGGO_LOGGER_HASH_TABLE.load_factor = MINT_LOGGO_DEFAULT_HT_INITIAL_LOAD_FACTOR;
1086 MINT_LOGGO_LOGGER_HASH_TABLE.loggers = MINT_LOGGO_MALLOC(sizeof(Mint_Loggo_Logger*) * MINT_LOGGO_LOGGER_HASH_TABLE.capacity);
1087 }
1088}
1089
1090
1091// Simple hash
1092static int32_t Mint_Loggo_StringHash(const char* name, const int32_t prime, const int32_t buckets) {
1093 int32_t hash = 0;
1094 const int32_t len = strlen(name);
1095 for (int32_t i = 0; i < len; i++) {
1096 hash += (int32_t)pow(prime, len - (i+1)) * name[i];
1097 hash = hash % buckets;
1098 }
1099 return hash;
1100}
1101
1102
1103// Double hash
1104static int32_t Mint_Loggo_LoggerStringHash(const char* name, const int32_t buckets, const int32_t attempt) {
1105 const int32_t hasha = Mint_Loggo_StringHash(name, MINT_LOGGO_PRIME_1, buckets);
1106 const int32_t hashb = Mint_Loggo_StringHash(name, MINT_LOGGO_PRIME_2, buckets);
1107 return (hasha + (attempt * (hashb + 1))) % buckets;
1108}
1109
1110
1111#endif // MINT_LOGGO_IMPLEMENTATION
1112#undef MINT_LOGGO_IMPLEMENTATION
1113
1114#endif // MINT_LOGGO_H
1115