Index

lognestmonster / 3ce6207

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

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
19424 Mar 2020 23:273ef85c1Change all abort() calls to lnm_abort()Josh Stockin13556G

Blob @ lognestmonster / src / c / lognestmonster.h

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