Line data Source code
1 : /***************************************************************************//**
2 :
3 : @file log.c
4 :
5 : @author Stephen Brennan
6 :
7 : @date Created Sunday, 24 May 2015
8 :
9 : @brief Logging facilities implementation for libstephen.
10 :
11 : @copyright Copyright (c) 2015-2016, Stephen Brennan. Released under the
12 : Revised BSD License. See LICENSE.txt for details.
13 :
14 : *******************************************************************************/
15 :
16 : #include <stdio.h>
17 : #include <stdarg.h>
18 :
19 : #include "libstephen/cb.h"
20 : #include "libstephen/log.h"
21 :
22 : /*
23 : This is the "permanent" default logger. Even when you set your own default
24 : logger, this one still hangs around, waiting for you to call
25 : sl_set_default_logger(NULL). It's declared here, but the default logger
26 : should go to standard out. Unfortunately, you can't statically declare an
27 : instance of a struct that uses the variable stdout, so this is declared
28 : without any handlers, and the first time the default logger is referenced, the
29 : default log handler is added.
30 : */
31 : static smb_logger default_logger = {
32 : .format = SMB_DEFAULT_LOGFORMAT,
33 : .num = 0
34 : };
35 :
36 : /*
37 : The only reason this pointer exists is so that reference_logger() knows
38 : whether or not the default logger has been set up yet. This variable remains
39 : NULL until the first time the default logger is used, at which point the
40 : default handler is added to it, and then pdefault_logger is set to
41 : &default_logger.
42 : */
43 : static smb_logger *pdefault_logger = NULL;
44 :
45 : static char *level_names[] = {"NOTSET", "DEBUG", "INFO",
46 : "WARNING", "ERROR", "CRITICAL"};
47 :
48 : /*
49 : This function is used by most sl_ functions to initialize their `obj` pointer.
50 : Essentially, if obj=NULL, it replaces obj with a pointer to the default
51 : logger. If the default logger hasn't been set up, it sets it up.
52 : */
53 37 : static void reference_logger(smb_logger **obj)
54 : {
55 37 : smb_status status = SMB_SUCCESS;
56 37 : if (*obj == NULL) {
57 35 : if (pdefault_logger == NULL) {
58 1 : sl_add_handler(&default_logger,
59 1 : (smb_loghandler){.level = SMB_DEFAULT_LOGLEVEL,
60 : .dst = SMB_DEFAULT_LOGDEST},
61 : &status);
62 1 : pdefault_logger = &default_logger;
63 : }
64 35 : *obj = pdefault_logger;
65 : }
66 37 : }
67 :
68 1 : void sl_init(smb_logger *obj)
69 : {
70 1 : obj->format = SMB_DEFAULT_LOGFORMAT;
71 1 : obj->num = 0;
72 1 : }
73 :
74 1 : smb_logger *sl_create(void)
75 : {
76 1 : smb_logger *obj = smb_new(smb_logger, 1);
77 1 : sl_init(obj);
78 1 : return obj;
79 : }
80 :
81 1 : void sl_destroy(smb_logger *obj) {
82 : (void)obj; // unused
83 : // nothing to delete
84 1 : }
85 :
86 1 : void sl_delete(smb_logger *obj) {
87 1 : sl_destroy(obj);
88 1 : smb_free(obj);
89 1 : }
90 :
91 2 : void sl_set_level(smb_logger *obj, int level)
92 : {
93 : int i;
94 2 : reference_logger(&obj);
95 8 : for (i = 0; i < obj->num; i++) {
96 6 : obj->handlers[i].level = level;
97 : }
98 2 : }
99 :
100 18 : void sl_add_handler(smb_logger *obj, smb_loghandler h, smb_status *status)
101 : {
102 18 : reference_logger(&obj);
103 18 : if (obj->num < SMB_MAX_LOGHANDLERS) {
104 17 : obj->handlers[obj->num++] = h;
105 : } else {
106 1 : *status = SMB_INDEX_ERROR;
107 : }
108 18 : }
109 :
110 3 : void sl_clear_handlers(smb_logger *obj)
111 : {
112 3 : reference_logger(&obj);
113 3 : obj->num = 0;
114 3 : }
115 :
116 2 : void sl_set_default_logger(smb_logger *obj)
117 : {
118 2 : if (obj == NULL)
119 1 : obj = &default_logger;
120 2 : pdefault_logger = obj;
121 2 : }
122 :
123 : /*
124 : Returns a string representing a log level. Very much not thread safe (but in
125 : reality, I don't think anything about this logger library is thread safe, so
126 : whatever ¯\_(ツ)_/¯ ).
127 : */
128 8 : static char *sl_level_string(int level) {
129 : static char buf[20]; // plenty of space, to be safe.
130 8 : if (level % 10 == 0 && level >= LEVEL_NOTSET && level <= LEVEL_CRITICAL) {
131 7 : return level_names[level / 10];
132 : } else {
133 1 : snprintf(buf, 20, "%d", level);
134 1 : return buf;
135 : }
136 : }
137 :
138 14 : bool sl_will_log(smb_logger *obj, int level)
139 : {
140 20 : for (int i = 0; i < obj->num; i++) {
141 14 : if (obj->handlers[i].level <= level) {
142 8 : return true;
143 : }
144 : }
145 6 : return false;
146 : }
147 :
148 14 : void sl_log(smb_logger *obj, char *file, int line, const char *function, int level, ...) {
149 : cbuf file_line_buf, message_buf;
150 : char *level_string, *format;
151 : va_list va;
152 : int i;
153 :
154 14 : reference_logger(&obj);
155 :
156 14 : if (!sl_will_log(obj, level)) {
157 20 : return; // early termination to prevent formatting if we can avoid it
158 : }
159 :
160 8 : cb_init(&file_line_buf, 256);
161 8 : cb_printf(&file_line_buf, "%s:%d", file, line);
162 :
163 8 : va_start(va, level);
164 8 : format = va_arg(va, char*);
165 8 : cb_init(&message_buf, 1024);
166 8 : cb_vprintf(&message_buf, format, va);
167 8 : va_end(va);
168 :
169 8 : level_string = sl_level_string(level);
170 44 : for (i = 0; i < obj->num; i++) {
171 36 : if (obj->handlers[i].level <= level) {
172 26 : fprintf(obj->handlers[i].dst, obj->format, file_line_buf.buf, function,
173 : level_string, message_buf.buf);
174 : }
175 : }
176 8 : cb_destroy(&file_line_buf);
177 8 : cb_destroy(&message_buf);
178 : }
|