1 | // lognestmonster.h |
2 | // C header file for implementation of the lognestmonster library |
3 |
|
4 | /* lognestmonster Copyright (c) 2020 Joshua 'joshuas3' Stockin |
5 | * <https://joshstock.in> |
6 | * <https://github.com/JoshuaS3/lognestmonster> |
7 | * |
8 | * This software is licensed and distributed under the terms of the MIT License: |
9 | * ----- BEGIN LICENSE ----- |
10 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
11 | * of this software and associated documentation files (the "Software"), to deal |
12 | * in the Software without restriction, including without limitation the rights |
13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
14 | * copies of the Software, and to permit persons to whom the Software is |
15 | * furnished to do so, subject to the following conditions: |
16 | * |
17 | * The above copyright notice and this permission notice shall be included in |
18 | * all copies or substantial portions of the Software. |
19 | * |
20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
26 | * SOFTWARE. |
27 | * ----- END LICENSE ----- |
28 | * |
29 | * This comment block and its contents, including this disclaimer, MUST be |
30 | * preserved in all copies or distributions of this software's source. |
31 | */ |
32 |
|
33 |
|
34 | #ifndef LOGNESTMONSTER_H |
35 | #define LOGNESTMONSTER_H 1 |
36 |
|
37 |
|
38 | #ifdef __cplusplus |
39 | extern "C" { |
40 | #endif |
41 |
|
42 |
|
43 | #ifdef __GNUC__ |
44 | #define lnm_inline inline __attribute__((__always_inline__)) |
45 | #elif defined(__CLANG__) |
46 | #if __has_attribute(__always_inline__) |
47 | #define lnm_inline inline __attribute__((__always_inline__)) |
48 | #else |
49 | #define lnm_inline inline |
50 | #endif |
51 | #elif defined(_MSC_VER) |
52 | #define lnm_inline __forceinline |
53 | #else |
54 | #define lnm_inline inline |
55 | #endif |
56 |
|
57 |
|
58 | // declare public functions |
59 | #include <stdint.h> |
60 |
|
61 | enum lnmVerbosityLevel { |
62 | lnmCritical = 0x0, lnmError = 0x1, lnmWarn = 0x2, lnmInfo = 0x3, lnmDebug = 0x4, lnmTrace = 0x5 |
63 | }; |
64 | typedef void * lnmItem; |
65 | typedef void * lnmQueue; |
66 |
|
67 | lnmQueue lnmQueueInit(const char * name, const char * out_path); |
68 | lnmQueue lnmQueueByName(const char * name); |
69 |
|
70 | lnmItem lnmStatement(enum lnmVerbosityLevel verbosity, const char * message); |
71 |
|
72 | lnmItem lnmEvent(const char * tag); |
73 | lnmItem lnmEventI(const char * tag, lnmItem item); |
74 | lnmItem lnmEventS(const char * tag, enum lnmVerbosityLevel verbosity, const char * message); |
75 | void lnmEventPush(lnmItem event, lnmItem item); |
76 | void lnmEventPushS(lnmItem event, enum lnmVerbosityLevel verbosity, const char * message); |
77 |
|
78 |
|
79 | #ifdef LNM_ALL // expose private utilities |
80 | |
81 | // struct type definitions |
82 | typedef struct lnm_pushable lnm_pushable; |
83 | typedef struct lnm_log_event lnm_log_event; |
84 | typedef struct lnm_log_statement lnm_log_statement; |
85 | typedef struct lnm_queue lnm_queue; |
86 |
|
87 | // lnm_pushable utilities |
88 | lnm_pushable * lnm_new_pushable(void); |
89 | void lnm_pushable_realloc(lnm_pushable * pushable); |
90 | void lnm_pushable_push(lnm_pushable * pushable, lnmItem item); |
91 | void lnm_pushable_pop(lnm_pushable * pushable); |
92 | void lnm_pushable_remove(lnm_pushable * pushable, uint32_t index); |
93 | void lnm_pushable_free(lnm_pushable * pushable); |
94 |
|
95 | // misc utilities |
96 | _Noreturn void lnm_abort(const char * function_traceback, const char * message); |
97 | unsigned long lnm_getus(void); |
98 |
|
99 | // registry utilities |
100 | void lnm_registry_update(void); |
101 |
|
102 | // memory management utilities |
103 | void lnm_free_item(lnmItem item); |
104 | void lnm_free_registry(void); |
105 | void lnm_free_queue(lnmQueue queue); |
106 |
|
107 | #endif // LNM_ALL |
108 | |
109 |
|
110 | #if defined(LNM_DEBUG) || defined(LNM_ALL) |
111 | void lnm_debug_tabs(int tab_count); |
112 | void lnm_debug_parse_item(lnmItem item, int tab_count); |
113 | void lnm_debug_parse_registry(void); |
114 | void lnm_debug_parse_queue(lnmQueue queue); |
115 | #endif // LNM_DEBUG || LNM_ALL |
116 | |
117 |
|
118 | #ifdef LNM_INIT // define the library |
119 | |
120 | #include <stdint.h> |
121 | #include <stdio.h> |
122 | #include <stdlib.h> |
123 | #include <string.h> |
124 |
|
125 |
|
126 | // enum constants |
127 |
|
128 | enum LNM_ITEM_TYPE { |
129 | LNM_STATEMENT = 0x0, |
130 | LNM_EVENT = 0x1, |
131 | LNM_QUEUE = 0x2 |
132 | }; |
133 |
|
134 | enum LNM_CONTROL_BYTES { |
135 | LNM_STATEMENT_BYTE = 0xF8, |
136 | LNM_EVENT_OPEN_BYTE = 0xF9, |
137 | LNM_EVENT_CLOSE_BYTE = 0xFA, |
138 | LNM_CHECKSUM_BYTE = 0xFF |
139 | }; |
140 |
|
141 |
|
142 | // structs |
143 |
|
144 | typedef struct lnm_log_event { |
145 | enum LNM_ITEM_TYPE type:2; |
146 | uint8_t pushed:1; // whether or not this log item has been pushed |
147 | uint8_t parent_id; // ID of this event's parent |
148 | uint8_t event_id; // ID of this event |
149 |
|
150 | char * tag; // null-terminated tag string |
151 |
|
152 | lnm_queue * queue; |
153 | } lnm_log_event; |
154 |
|
155 | typedef struct lnm_log_statement { |
156 | enum LNM_ITEM_TYPE type:2; |
157 | enum lnmVerbosityLevel verbosity:3; |
158 |
|
159 | uint64_t timestamp; // 64-bit millisecond timestamp |
160 |
|
161 | char * log; // null-terminated message string |
162 | } lnm_log_statement; |
163 |
|
164 | typedef struct lnm_queue { |
165 | enum LNM_ITEM_TYPE type:2; |
166 | char * name; |
167 | char * out_path; |
168 | uint64_t timestamp; |
169 | uint8_t open_events[32]; |
170 | } lnm_queue; |
171 |
|
172 |
|
173 | // base utilities |
174 |
|
175 | _Noreturn void lnm_abort(const char * function_traceback, const char * message) { |
176 | printf("lognestmonster (%s): %s. aborting...\n", function_traceback, message); |
177 | abort(); |
178 | } |
179 |
|
180 | #if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__) || defined(__APPLE__) && defined(__MACH__) |
181 | #include <sys/time.h> |
182 | uint64_t lnm_getus(void) { |
183 | uint64_t us; |
184 | struct timeval lnm_current_time; |
185 | gettimeofday(&lnm_current_time, NULL); |
186 | us = (lnm_current_time.tv_sec * 1000000ULL + lnm_current_time.tv_usec); |
187 | return us; |
188 | } |
189 | #elif defined(_WIN32) || defined(__WINDOWS__) |
190 | #include <windows.h> |
191 | #include <sysinfoapi.h> |
192 | uint64_t lnm_getus(void) { |
193 | uint64_t us; |
194 | // get system time in ticks |
195 | FILETIME lnm_win32_filetime; |
196 | GetSystemTimeAsFileTime(&lnm_win32_filetime); |
197 | // load time from two 32-bit words into one 64-bit integer |
198 | us = lnm_win32_filetime.dwHighDateTime; |
199 | us = us << 32; |
200 | us |= lnm_win32_filetime.dwLowDateTime; |
201 | // convert to microseconds |
202 | us /= 10ULL; // is there a better way to do this? |
203 | // convert from time since Windows NT epoch to time since Unix epoch |
204 | us -= 11644473600000000ULL; |
205 | return us; |
206 | } |
207 | #else |
208 | #error lognestmonster: Neither Windows NT nor a POSIX-compliant system were detected.\ |
209 | Implement your own system time functions or compile on a compliant system. |
210 | #endif |
211 |
|
212 | lnm_inline enum LNM_ITEM_TYPE lnm_item_type(lnmItem item) { |
213 | return ((lnm_log_statement *)item)->type; |
214 | } |
215 |
|
216 |
|
217 | // lnm_pushable utilities |
218 |
|
219 | typedef struct lnm_pushable { |
220 | uint32_t capacity; |
221 | uint32_t length; |
222 | lnmItem * frame; |
223 | } lnm_pushable; |
224 |
|
225 | void lnm_pushable_realloc(lnm_pushable * pushable) { |
226 | if (pushable->length > pushable->capacity) { |
227 | if (pushable->capacity == 0x80000000) { |
228 | lnm_abort("lnm_pushable_realloc", "pushable can't surpass max capacity of 2^31"); |
229 | } |
230 | pushable->capacity <<= 1; |
231 | pushable->frame = realloc(pushable->frame, sizeof(lnmItem) * pushable->capacity); |
232 | if (pushable->frame == NULL) { |
233 | lnm_abort("lnm_pushable_realloc", "call to realloc() returned NULL"); |
234 | } |
235 | } else if (pushable->capacity > 8 && pushable->length <= (pushable->capacity >> 1)) { |
236 | while (pushable->capacity > 8 && pushable->length <= (pushable->capacity >> 1)) { |
237 | // decrease array's capacity |
238 | pushable->capacity >>= 1; // divide by 2 |
239 | } |
240 | pushable->frame = realloc(pushable->frame, sizeof(lnmItem) * (pushable->capacity)); |
241 | if (pushable->frame == NULL) { |
242 | lnm_abort("lnm_pushable_realloc", "call to realloc() returned NULL"); |
243 | } |
244 | } |
245 | } |
246 |
|
247 | lnm_pushable * lnm_new_pushable(void) { |
248 | lnm_pushable * new_pushable = calloc(1, sizeof(lnm_pushable)); |
249 | if (new_pushable == NULL) { |
250 | lnm_abort("lnm_new_pushable", "call to calloc() returned NULL"); |
251 | } |
252 | new_pushable->capacity = 8; |
253 | new_pushable->length = 0; |
254 | new_pushable->frame = calloc(8, sizeof(lnmItem)); |
255 | if (new_pushable->frame == NULL) { |
256 | lnm_abort("lnm_new_pushable", "call to calloc() returned NULL"); |
257 | } |
258 | return new_pushable; |
259 | } |
260 |
|
261 | void lnm_pushable_push(lnm_pushable * pushable, lnmItem item) { |
262 | pushable->length++; |
263 | lnm_pushable_realloc(pushable); |
264 | pushable->frame[pushable->length - 1] = item; |
265 | } |
266 |
|
267 | void lnm_pushable_pop(lnm_pushable * pushable) { |
268 | pushable->length--; |
269 | lnm_pushable_realloc(pushable); |
270 | } |
271 |
|
272 | void lnm_pushable_remove(lnm_pushable * pushable, uint32_t index) { |
273 | if (index >= pushable->length) { |
274 | lnm_abort("lnm_pushable_remove", "attempt to remove out of bounds index from pushable"); |
275 | } |
276 | if (index == pushable->length - 1) { |
277 | lnm_pushable_pop(pushable); |
278 | } else { |
279 | // shift entire array from index until end |
280 | for (uint32_t iter = index; iter < pushable->length - 1; iter++) { |
281 | pushable->frame[iter] = pushable->frame[iter + 1]; |
282 | } |
283 | pushable->length--; |
284 | lnm_pushable_realloc(pushable); |
285 | } |
286 | } |
287 |
|
288 | void lnm_pushable_free(lnm_pushable * pushable) { |
289 | free(pushable->frame); |
290 | free(pushable); |
291 | } |
292 |
|
293 |
|
294 |
|
295 |
|
296 | lnmQueue lnmQueueInit(const char * name, const char * out_path) { |
297 | // create queue and item registries if not created |
298 | if (lnm_registered_queues == NULL) { |
299 | lnm_registered_queues = lnm_new_pushable(); |
300 | } |
301 | if (lnm_registered_items == NULL) { |
302 | lnm_registered_items = lnm_new_pushable(); |
303 | } |
304 | // allocate and populate a new Queue object |
305 | lnm_queue * new_queue = calloc(1, sizeof(lnm_queue)); |
306 | if (new_queue == NULL) { |
307 | lnm_abort("lnmQueueInit", "call to calloc() returned NULL"); |
308 | } |
309 | new_queue->name = malloc(strlen(name) + 1); |
310 | new_queue->out_path = malloc(strlen(out_path) + 1); |
311 | if (new_queue->name == NULL || new_queue->out_path == NULL) { |
312 | lnm_abort("lnmQueueInit", "call to malloc() returned NULL"); |
313 | } |
314 | strcpy(new_queue->name, name); |
315 | strcpy(new_queue->out_path, out_path); |
316 | new_queue->timestamp = lnm_getus(); |
317 | new_queue->pushable = lnm_new_pushable(); |
318 | // enter new Queue into registry |
319 | lnm_pushable_push(lnm_registered_queues, (lnmQueue)new_queue); |
320 | return (lnmQueue)new_queue; |
321 | } |
322 |
|
323 | lnmQueue lnmQueueByName(const char * name) { |
324 | if (lnm_registered_queues == NULL) { |
325 | lnm_abort("lnmQueueByName", "queue registry is nonexistent"); |
326 | } |
327 | if (lnm_registered_queues->length == 0) { |
328 | lnm_abort("lnmQueueByName", "queue registry is empty"); |
329 | } |
330 | for (uint32_t iter = 0; iter < lnm_registered_queues->length; iter++) { |
331 | lnm_queue * queue = (lnm_queue *)lnm_registered_queues->frame[iter]; |
332 | if (strcmp(queue->name, name) == 0) { |
333 | return (lnmQueue)queue; |
334 | } |
335 | } |
336 | lnm_abort("lnmQueueByName", "queue not found in registry"); |
337 | } |
338 |
|
339 | void lnmQueuePush(lnmQueue queue, lnmItem item) { |
340 | if (queue == NULL || item == NULL) { |
341 | lnm_abort("lnmQueuePush", "cannot perform operation on NULL arguments"); |
342 | } |
343 | lnm_log_statement * item_cast = (lnm_log_statement *)item; |
344 | if (item_cast->pushed == 1) { |
345 | lnm_abort("lnmQueuePush", "attempt to push an already-pushed log item"); |
346 | } |
347 | // flush out of registry |
348 | lnm_registry_flush_item(item); |
349 | // add to queue |
350 | lnm_pushable_push(((lnm_queue *)queue)->pushable, item); |
351 | } |
352 |
|
353 | lnmItem lnmStatement(enum lnmVerbosityLevel verbosity, const char * message) { |
354 | if (message == NULL) { |
355 | lnm_abort("lnmStatement", "cannot perform operation on NULL argument"); |
356 | } |
357 | lnm_log_statement * new_statement = calloc(1, sizeof(lnm_log_statement)); |
358 | if (new_statement == NULL) { |
359 | lnm_abort("lnmStatement", "call to calloc() returned NULL"); |
360 | } |
361 | new_statement->type = LNM_STATEMENT; |
362 | new_statement->pushed = 0; |
363 | new_statement->verbosity = verbosity; |
364 | new_statement->timestamp = lnm_getus(); |
365 | // enforce string lengths |
366 | size_t message_len = strlen(message) + 1; |
367 | if (message_len > UINT16_MAX) { |
368 | lnm_abort("lnmStatement", "length of statement message exceeds 2^16 characters"); |
369 | } |
370 | // copy message to new_statement->log |
371 | new_statement->log = malloc(message_len); |
372 | if (new_statement->log == NULL) { |
373 | lnm_abort("lnmStatement", "call to malloc() returned NULL"); |
374 | } |
375 | strcpy(new_statement->log, message); |
376 | // add to registry |
377 | lnm_registry_push((lnmItem)new_statement); |
378 | return (lnmItem)new_statement; |
379 | } |
380 |
|
381 | lnmItem lnmEvent(const char * tag) { |
382 | if (tag == NULL) { |
383 | lnm_abort("lnmEvent", "cannot perform operation on NULL argument"); |
384 | } |
385 | lnm_log_event * new_event = calloc(1, sizeof(lnm_log_event)); |
386 | if (new_event == NULL) { |
387 | lnm_abort("lnmEvent", "call to calloc() returned NULL"); |
388 | } |
389 | new_event->type = LNM_EVENT; |
390 | new_event->pushed = 0; |
391 | new_event->pushable = lnm_new_pushable(); |
392 | // enforce string lengths |
393 | size_t tag_len = strlen(tag) + 1; |
394 | if (tag_len > UINT8_MAX) { |
395 | lnm_abort("lnmEvent", "length of event tag exceeds 256 characters"); |
396 | } |
397 | // copy tag to event |
398 | new_event->tag = malloc(tag_len); |
399 | if (new_event->tag == NULL) { |
400 | lnm_abort("lnmEvent", "call to malloc() returned NULL"); |
401 | } |
402 | strcpy(new_event->tag, tag); |
403 | // add to registry |
404 | lnm_registry_push((lnmItem)new_event); |
405 | return (lnmItem)new_event; |
406 | } |
407 |
|
408 | void lnmEventPush(lnmItem event, lnmItem item) { |
409 | if (event == NULL || item == NULL) { |
410 | lnm_abort("lnmEventPush", "cannot perform operation on NULL arguments"); |
411 | } |
412 | if (event == item) { |
413 | lnm_abort("lnmEventPush", "attempt to push event to self"); |
414 | } |
415 | lnm_log_statement * item_cast = (lnm_log_statement *)item; |
416 | if (item_cast->pushed == 1) { |
417 | lnm_abort("lnmEventPush", "attempt to push an already-pushed log item"); |
418 | } |
419 | if (lnm_item_type(event) != LNM_EVENT) { |
420 | lnm_abort("lnmEventPush", "cannot cast non-event to event type"); |
421 | } |
422 | lnm_log_event * event_cast = (lnm_log_event *)event; |
423 | lnm_pushable_push(event_cast->pushable, item); |
424 | lnm_registry_flush_item(item); |
425 | } |
426 |
|
427 | void lnmEventPushS(lnmItem event, enum lnmVerbosityLevel verbosity, const char * message) { |
428 | lnmItem statement = lnmStatement(verbosity, message); |
429 | lnmEventPush(event, statement); |
430 | } |
431 |
|
432 | lnmItem lnmEventI(const char * tag, lnmItem item) { |
433 | lnmItem event = lnmEvent(tag); |
434 | lnmEventPush(event, item); |
435 | return event; |
436 | } |
437 |
|
438 | lnmItem lnmEventS(const char * tag, enum lnmVerbosityLevel verbosity, const char * message) { |
439 | lnmItem event = lnmEvent(tag); |
440 | lnmEventPushS(event, verbosity, message); |
441 | return event; |
442 | } |
443 |
|
444 |
|
445 | #ifdef LNM_DEBUG |
446 | #include <inttypes.h> |
447 |
|
448 | void lnm_debug_tabs(int tab_count) { |
449 | for (int i = 0; i < tab_count; i++) { |
450 | printf(" "); |
451 | } |
452 | } |
453 |
|
454 | void lnm_debug_parse_item(lnmItem item, int tab_count) { |
455 | if (lnm_item_type(item) == LNM_STATEMENT) { |
456 | lnm_log_statement * statement = (lnm_log_statement *) item; |
457 | lnm_debug_tabs(tab_count); |
458 | char * verbosity; |
459 | switch (statement->verbosity) { |
460 | case 0: |
461 | verbosity = "INFO"; |
462 | break; |
463 | case 1: |
464 | verbosity = "DEBUG"; |
465 | break; |
466 | case 2: |
467 | verbosity = "VERBOSE"; |
468 | break; |
469 | case 3: |
470 | verbosity = "VERYVERBOSE"; |
471 | break; |
472 | case 4: |
473 | verbosity = "WARNING"; |
474 | break; |
475 | case 5: |
476 | verbosity = "ERROR"; |
477 | break; |
478 | } |
479 | printf("%" PRIu64 " (%s) :: %s\n", statement->timestamp, verbosity, statement->log); |
480 | } else if (lnm_item_type(item) == LNM_EVENT) { |
481 | lnm_log_event * event = (lnm_log_event *) item; |
482 | lnm_debug_tabs(tab_count); |
483 | printf("Event (%" PRIu32 "/%" PRIu32 ") %s [\n", event->pushable->length, event->pushable->capacity, event->tag); |
484 | for (uint32_t iter = 0; iter < event->pushable->length; iter++) { |
485 | lnmItem item = event->pushable->frame[iter]; |
486 | lnm_debug_parse_item(item, tab_count + 1); |
487 | } |
488 | lnm_debug_tabs(tab_count); |
489 | printf("]\n"); |
490 | } else { |
491 | lnm_abort("lnm_debug_parse_item", "unknown item type"); |
492 | } |
493 | } |
494 |
|
495 | void lnm_debug_parse_registry(void) { |
496 | printf("Top level registry (%" PRIu32 "/%" PRIu32 ") [\n", lnm_registered_items->length, lnm_registered_items->capacity); |
497 | for (uint32_t iter = 0; iter < lnm_registered_items->length; iter++) { |
498 | lnm_debug_parse_item(lnm_registered_items->frame[iter], 1); |
499 | } |
500 | printf("]\n"); |
501 | } |
502 |
|
503 | void lnm_debug_parse_queue(lnmQueue queue) { |
504 | lnm_queue * queue_cast = (lnm_queue *)queue; |
505 | printf("Queue \"%s\" at %s (%" PRIu32 "/%" PRIu32 ") [\n", queue_cast->name, queue_cast->out_path, queue_cast->pushable->length, queue_cast->pushable->capacity); |
506 | for (uint32_t iter = 0; iter < queue_cast->pushable->length; iter++) { |
507 | lnm_debug_parse_item((lnmItem)queue_cast->pushable->frame[iter], 1); |
508 | } |
509 | printf("]\n"); |
510 | } |
511 |
|
512 |
|
513 | #endif // LNM_DEBUG |
514 | #endif // LNM_INIT |
515 | |
516 |
|
517 | #ifdef __cplusplus |
518 | } |
519 | #endif |
520 |
|
521 |
|
522 | #endif // LOGNESTMONSTER_H |
523 |
|