Index

lognestmonster / dev

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

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
20825 Jul 2020 23:39b503441Expand tabs in headerJosh Stockin1345345G

Blob @ lognestmonster / src / lognestmonster.h

text/plain20848 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// struct 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 enum LNM_ITEM_TYPE type:1; // used internally; 0 = statement, 1 = event
215 uint8_t pushed:1; // whether or not this log item has been pushed
216
217 char * tag; // null-terminated tag string
218
219 lnm_pushable * pushable; // pushable of lnmItems
220} lnm_log_event;
221
222
223typedef struct lnm_log_statement {
224 enum LNM_ITEM_TYPE type:1; // used internally; 0 = statement, 1 = event
225 uint8_t pushed:1; // whether or not this log item has been pushed
226 uint8_t verbosity:3; // lnmVerbosityLevel
227
228 uint64_t timestamp; // 64-bit millisecond timestamp
229
230 char * log; // null-terminated message string
231} lnm_log_statement;
232
233
234// queue structure definition
235
236
237typedef struct lnm_queue {
238 char * name;
239 char * out_path;
240 uint64_t timestamp;
241 lnm_pushable * pushable;
242} lnm_queue;
243
244
245// time utilities
246
247
248#if defined(__unix__) || defined(unix) || defined(__unix) || defined(__CYGWIN__)
249#include <sys/time.h>
250uint64_t lnm_getus(void) {
251 uint64_t us;
252 struct timeval lnm_current_time;
253 gettimeofday(&lnm_current_time, NULL);
254 us = (lnm_current_time.tv_sec * 1000000ULL + lnm_current_time.tv_usec);
255 return us;
256}
257#elif defined(_WIN32) || defined(__WINDOWS__)
258#include <windows.h>
259#include <sysinfoapi.h>
260uint64_t lnm_getus(void) {
261 uint64_t us;
262 // get system time in ticks
263 FILETIME lnm_win32_filetime;
264 GetSystemTimeAsFileTime(&lnm_win32_filetime);
265 // load time from two 32-bit words into one 64-bit integer
266 us = lnm_win32_filetime.dwHighDateTime;
267 us = us << 32;
268 us |= lnm_win32_filetime.dwLowDateTime;
269 // convert to microseconds
270 us /= 10ULL; // is there a better way to do this?
271 // convert from time since Windows NT epoch to time since Unix epoch
272 us -= 11644473600000000ULL;
273 return us;
274}
275#else
276#error lognestmonster: Neither Windows NT nor a POSIX-compliant system were detected.\
277 Implement your own system time functions or compile on a compliant system.
278#endif
279
280
281// item registry utils
282
283
284void lnm_free_item(lnmItem item);
285static lnm_pushable * lnm_registered_queues;
286static lnm_pushable * lnm_registered_items;
287
288
289void lnm_registry_update(void) {
290 // iterate through registry
291 for (uint32_t iter = 0; iter < lnm_registered_items->length; iter++) {
292 lnm_log_statement * item = (lnm_log_statement *)(lnm_registered_items->frame[iter]);
293 // if the registered item has been pushed elsewhere, remove it from the top level of the registry
294 if (item->pushed) {
295 lnm_pushable_remove(lnm_registered_items, iter);
296 iter--;
297 }
298 }
299}
300
301
302void lnm_registry_push(lnmItem item) {
303 if (lnm_registered_items == NULL) {
304 lnm_registered_items = lnm_new_pushable();
305 }
306 lnm_pushable_push(lnm_registered_items, item);
307}
308
309
310void lnm_registry_free() {
311 lnm_registry_update();
312 while (lnm_registered_items->length > 0) {
313 // free last item
314 // this allows lnm_pushable_free to pop instead of shuffling indices
315 lnm_free_item(lnm_registered_items->frame[lnm_registered_items->length-1]);
316 }
317 lnm_pushable_realloc(lnm_registered_items);
318}
319
320
321void lnm_registry_flush_item(lnmItem item) {
322 lnm_log_statement * item_cast = (lnm_log_statement *)item;
323 if (!item_cast->pushed) {
324 item_cast->pushed = 1;
325 lnm_registry_update();
326 }
327}
328
329
330// core library utilities
331
332
333enum LNM_ITEM_TYPE lnm_item_type(lnmItem item) {
334 return ((lnm_log_statement *)item)->type;
335}
336
337void lnm_free_item(lnmItem item) {
338 if (lnm_item_type(item) == LNM_STATEMENT) {
339 // cast item
340 lnm_log_statement * statement = (lnm_log_statement *)item;
341 // flush item out of registry
342 lnm_registry_flush_item(item);
343 // free item and its contents
344 free(statement->log);
345 free(statement);
346 } else if (lnm_item_type(item) == LNM_EVENT) {
347 // create breadcrumb navigation array with root 'item'
348 lnm_pushable * breadcrumb = lnm_new_pushable();
349 lnm_pushable_push(breadcrumb, item);
350 // continually iterate breadcrumb until it's empty
351 while (breadcrumb->length > 0) {
352 // get current item (deepest element of the breadcrumb nav, aka 'z' in 'x > y > z')
353 lnmItem current = breadcrumb->frame[breadcrumb->length - 1];
354 // pop it from the breadcrumb nav
355 lnm_pushable_pop(breadcrumb);
356 // flush item out of registry
357 lnm_registry_flush_item(current);
358 if (lnm_item_type(current) == LNM_STATEMENT) {
359 // cast current item
360 lnm_log_statement * current_statement = (lnm_log_statement *)current;
361 // free statement and its contents
362 free(current_statement->log);
363 free(current_statement);
364 continue;
365 } else if (lnm_item_type(current) == LNM_EVENT) {
366 // cast current item
367 lnm_log_event * current_event = (lnm_log_event *)current;
368 if (current_event->pushable->length > 0) {
369 // the event has children, add them to the breadcrumb
370 for (uint32_t iter = 0; iter < current_event->pushable->length; iter++) {
371 lnmItem current_event_child = current_event->pushable->frame[iter];
372 lnm_pushable_push(breadcrumb, current_event_child);
373 }
374 }
375 // free original event
376 lnm_pushable_free(current_event->pushable);
377 free(current_event->tag);
378 free(current_event);
379 continue;
380 } else {
381 lnm_abort("lnm_free_item", "item in log tree has non-statement and non-event type");
382 }
383 }
384 lnm_pushable_free(breadcrumb);
385 } else {
386 lnm_abort("lnm_free_item", "log tree is non-statement and non-event type");
387 }
388}
389
390
391// queue utilities
392
393
394void lnm_free_queue(lnmQueue queue) {
395 lnm_queue * queue_cast = (lnm_queue *)queue;
396 while (queue_cast->pushable->length > 0) {
397 lnm_free_item(queue_cast->pushable->frame[queue_cast->pushable->length - 1]);
398 lnm_pushable_pop(queue_cast->pushable);
399 }
400}
401
402
403lnmQueue lnmQueueInit(const char * name, const char * out_path) {
404 // create queue and item registries if not created
405 if (lnm_registered_queues == NULL) {
406 lnm_registered_queues = lnm_new_pushable();
407 }
408 if (lnm_registered_items == NULL) {
409 lnm_registered_items = lnm_new_pushable();
410 }
411 // allocate and populate a new Queue object
412 lnm_queue * new_queue = calloc(1, sizeof(lnm_queue));
413 if (new_queue == NULL) {
414 lnm_abort("lnmQueueInit", "call to calloc() returned NULL");
415 }
416 new_queue->name = malloc(strlen(name) + 1);
417 new_queue->out_path = malloc(strlen(out_path) + 1);
418 if (new_queue->name == NULL || new_queue->out_path == NULL) {
419 lnm_abort("lnmQueueInit", "call to malloc() returned NULL");
420 }
421 strcpy(new_queue->name, name);
422 strcpy(new_queue->out_path, out_path);
423 new_queue->timestamp = lnm_getus();
424 new_queue->pushable = lnm_new_pushable();
425 // enter new Queue into registry
426 lnm_pushable_push(lnm_registered_queues, (lnmQueue)new_queue);
427 return (lnmQueue)new_queue;
428}
429
430
431lnmQueue lnmQueueByName(const char * name) {
432 if (lnm_registered_queues == NULL) {
433 lnm_abort("lnmQueueByName", "queue registry is nonexistent");
434 }
435 if (lnm_registered_queues->length == 0) {
436 lnm_abort("lnmQueueByName", "queue registry is empty");
437 }
438 for (uint32_t iter = 0; iter < lnm_registered_queues->length; iter++) {
439 lnm_queue * queue = (lnm_queue *)lnm_registered_queues->frame[iter];
440 if (strcmp(queue->name, name) == 0) {
441 return (lnmQueue)queue;
442 }
443 }
444 lnm_abort("lnmQueueByName", "queue not found in registry");
445}
446
447
448void lnmQueuePush(lnmQueue queue, lnmItem item) {
449 if (queue == NULL || item == NULL) {
450 lnm_abort("lnmQueuePush", "cannot perform operation on NULL arguments");
451 }
452 lnm_log_statement * item_cast = (lnm_log_statement *)item;
453 if (item_cast->pushed == 1) {
454 lnm_abort("lnmQueuePush", "attempt to push an already-pushed log item");
455 }
456 // flush out of registry
457 lnm_registry_flush_item(item);
458 // add to queue
459 lnm_pushable_push(((lnm_queue *)queue)->pushable, item);
460}
461
462
463lnmItem lnmStatement(enum lnmVerbosityLevel verbosity, const char * message) {
464 if (message == NULL) {
465 lnm_abort("lnmStatement", "cannot perform operation on NULL argument");
466 }
467 lnm_log_statement * new_statement = calloc(1, sizeof(lnm_log_statement));
468 if (new_statement == NULL) {
469 lnm_abort("lnmStatement", "call to calloc() returned NULL");
470 }
471 new_statement->type = LNM_STATEMENT;
472 new_statement->pushed = 0;
473 new_statement->verbosity = verbosity;
474 new_statement->timestamp = lnm_getus();
475 // enforce string lengths
476 int message_len = strlen(message) + 1;
477 if (message_len > UINT16_MAX) {
478 lnm_abort("lnmStatement", "length of statement message exceeds 2^16 characters");
479 }
480 // copy message to new_statement->log
481 new_statement->log = malloc(message_len);
482 if (new_statement->log == NULL) {
483 lnm_abort("lnmStatement", "call to malloc() returned NULL");
484 }
485 strcpy(new_statement->log, message);
486 // add to registry
487 lnm_registry_push((lnmItem)new_statement);
488 return (lnmItem)new_statement;
489}
490
491
492lnmItem lnmEvent(const char * tag) {
493 if (tag == NULL) {
494 lnm_abort("lnmEvent", "cannot perform operation on NULL argument");
495 }
496 lnm_log_event * new_event = calloc(1, sizeof(lnm_log_event));
497 if (new_event == NULL) {
498 lnm_abort("lnmEvent", "call to calloc() returned NULL");
499 }
500 new_event->type = LNM_EVENT;
501 new_event->pushed = 0;
502 new_event->pushable = lnm_new_pushable();
503 // enforce string lengths
504 int tag_len = strlen(tag) + 1;
505 if (tag_len > UINT8_MAX) {
506 lnm_abort("lnmEvent", "length of event tag exceeds 256 characters");
507 }
508 // copy tag to event
509 new_event->tag = malloc(tag_len);
510 if (new_event->tag == NULL) {
511 lnm_abort("lnmEvent", "call to malloc() returned NULL");
512 }
513 strcpy(new_event->tag, tag);
514 // add to registry
515 lnm_registry_push((lnmItem)new_event);
516 return (lnmItem)new_event;
517}
518
519
520void lnmEventPush(lnmItem event, lnmItem item) {
521 if (event == NULL || item == NULL) {
522 lnm_abort("lnmEventPush", "cannot perform operation on NULL arguments");
523 }
524 if (event == item) {
525 lnm_abort("lnmEventPush", "attempt to push event to self");
526 }
527 lnm_log_statement * item_cast = (lnm_log_statement *)item;
528 if (item_cast->pushed == 1) {
529 lnm_abort("lnmEventPush", "attempt to push an already-pushed log item");
530 }
531 if (lnm_item_type(event) != LNM_EVENT) {
532 lnm_abort("lnmEventPush", "cannot cast non-event to event type");
533 }
534 lnm_log_event * event_cast = (lnm_log_event *)event;
535 lnm_pushable_push(event_cast->pushable, item);
536 lnm_registry_flush_item(item);
537}
538
539
540void lnmEventPushS(lnmItem event, enum lnmVerbosityLevel verbosity, const char * message) {
541 lnmItem statement = lnmStatement(verbosity, message);
542 lnmEventPush(event, statement);
543}
544
545
546lnmItem lnmEventI(const char * tag, lnmItem item) {
547 lnmItem event = lnmEvent(tag);
548 lnmEventPush(event, item);
549 return event;
550}
551
552
553lnmItem lnmEventS(const char * tag, enum lnmVerbosityLevel verbosity, const char * message) {
554 lnmItem event = lnmEvent(tag);
555 lnmEventPushS(event, verbosity, message);
556 return event;
557}
558
559
560#ifdef LNM_DEBUG
561#include <inttypes.h>
562
563
564void lnm_debug_tabs(int tab_count) {
565 for (int i = 0; i < tab_count; i++) {
566 printf(" ");
567 }
568}
569
570
571void lnm_debug_parse_item(lnmItem item, int tab_count) {
572 if (lnm_item_type(item) == LNM_STATEMENT) {
573 lnm_log_statement * statement = (lnm_log_statement *) item;
574 lnm_debug_tabs(tab_count);
575 char * verbosity;
576 switch (statement->verbosity) {
577 case 0:
578 verbosity = "INFO";
579 break;
580 case 1:
581 verbosity = "DEBUG";
582 break;
583 case 2:
584 verbosity = "VERBOSE";
585 break;
586 case 3:
587 verbosity = "VERYVERBOSE";
588 break;
589 case 4:
590 verbosity = "WARNING";
591 break;
592 case 5:
593 verbosity = "ERROR";
594 break;
595 }
596 printf("%" PRIu64 " (%s) :: %s\n", statement->timestamp, verbosity, statement->log);
597 } else if (lnm_item_type(item) == LNM_EVENT) {
598 lnm_log_event * event = (lnm_log_event *) item;
599 lnm_debug_tabs(tab_count);
600 printf("Event (%" PRIu32 "/%" PRIu32 ") %s [\n", event->pushable->length, event->pushable->capacity, event->tag);
601 for (uint32_t iter = 0; iter < event->pushable->length; iter++) {
602 lnmItem item = event->pushable->frame[iter];
603 lnm_debug_parse_item(item, tab_count + 1);
604 }
605 lnm_debug_tabs(tab_count);
606 printf("]\n");
607 } else {
608 lnm_abort("lnm_debug_parse_item", "unknown item type");
609 }
610}
611
612
613void lnm_debug_parse_registry(void) {
614 printf("Top level registry (%" PRIu32 "/%" PRIu32 ") [\n", lnm_registered_items->length, lnm_registered_items->capacity);
615 for (uint32_t iter = 0; iter < lnm_registered_items->length; iter++) {
616 lnm_debug_parse_item(lnm_registered_items->frame[iter], 1);
617 }
618 printf("]\n");
619}
620
621
622void lnm_debug_parse_queue(lnmQueue queue) {
623 lnm_queue * queue_cast = (lnm_queue *)queue;
624 printf("Queue \"%s\" at %s (%" PRIu32 "/%" PRIu32 ") [\n", queue_cast->name, queue_cast->out_path, queue_cast->pushable->length, queue_cast->pushable->capacity);
625 for (uint32_t iter = 0; iter < queue_cast->pushable->length; iter++) {
626 lnm_debug_parse_item((lnmItem)queue_cast->pushable->frame[iter], 1);
627 }
628 printf("]\n");
629}
630
631
632#endif // LNM_DEBUG
633#endif // LNM_INIT
634
635
636#ifdef __cplusplus
637}
638#endif
639
640
641#endif // LOGNESTMONSTER_H
642