Index

lognestmonster / 64e29ab

A general-purpose single-header C logging library and parser for event-based logs. (Incomplete)

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
20322 Jul 2020 20:2864e29abBring lognestmonster.h up to dateJosh Stockin13225G

Blob @ lognestmonster / src / c / lognestmonster.h

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