Index

lognestmonster / bd6bf7a

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

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
19730 Mar 2020 10:45bd6bf7aReformat and fix cross-platform time functionJosh Stockin11513G

Blob @ lognestmonster / src / c / lognestmonster.h

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