Index

lognestmonster / logstream

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

Latest Commit

{#}TimeHashSubjectAuthor#(+)(-)GPG?
20901 Dec 2020 11:43c5334b7UpdateJosh Stockin1108227G

Blob @ lognestmonster / src / lognestmonster.h

text/plain17154 bytesdownload raw
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
39extern "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
61enum lnmVerbosityLevel {
62 lnmCritical = 0x0, lnmError = 0x1, lnmWarn = 0x2, lnmInfo = 0x3, lnmDebug = 0x4, lnmTrace = 0x5
63};
64typedef void * lnmItem;
65typedef void * lnmQueue;
66
67lnmQueue lnmQueueInit(const char * name, const char * out_path);
68lnmQueue lnmQueueByName(const char * name);
69
70lnmItem lnmStatement(enum lnmVerbosityLevel verbosity, const char * message);
71
72lnmItem lnmEvent(const char * tag);
73lnmItem lnmEventI(const char * tag, lnmItem item);
74lnmItem lnmEventS(const char * tag, enum lnmVerbosityLevel verbosity, const char * message);
75void lnmEventPush(lnmItem event, lnmItem item);
76void lnmEventPushS(lnmItem event, enum lnmVerbosityLevel verbosity, const char * message);
77
78
79#ifdef LNM_ALL // expose private utilities
80
81// struct type definitions
82typedef struct lnm_pushable lnm_pushable;
83typedef struct lnm_log_event lnm_log_event;
84typedef struct lnm_log_statement lnm_log_statement;
85typedef struct lnm_queue lnm_queue;
86
87// lnm_pushable utilities
88lnm_pushable * lnm_new_pushable(void);
89void lnm_pushable_realloc(lnm_pushable * pushable);
90void lnm_pushable_push(lnm_pushable * pushable, lnmItem item);
91void lnm_pushable_pop(lnm_pushable * pushable);
92void lnm_pushable_remove(lnm_pushable * pushable, uint32_t index);
93void lnm_pushable_free(lnm_pushable * pushable);
94
95// misc utilities
96_Noreturn void lnm_abort(const char * function_traceback, const char * message);
97unsigned long lnm_getus(void);
98
99// registry utilities
100void lnm_registry_update(void);
101
102// memory management utilities
103void lnm_free_item(lnmItem item);
104void lnm_free_registry(void);
105void lnm_free_queue(lnmQueue queue);
106
107#endif // LNM_ALL
108
109
110#if defined(LNM_DEBUG) || defined(LNM_ALL)
111void lnm_debug_tabs(int tab_count);
112void lnm_debug_parse_item(lnmItem item, int tab_count);
113void lnm_debug_parse_registry(void);
114void 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
128enum LNM_ITEM_TYPE {
129 LNM_STATEMENT = 0x0,
130 LNM_EVENT = 0x1,
131 LNM_QUEUE = 0x2
132};
133
134enum 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
144typedef 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
155typedef 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
164typedef 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>
182uint64_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>
192uint64_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
212lnm_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
219typedef struct lnm_pushable {
220 uint32_t capacity;
221 uint32_t length;
222 lnmItem * frame;
223} lnm_pushable;
224
225void 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
247lnm_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
261void 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
267void lnm_pushable_pop(lnm_pushable * pushable) {
268 pushable->length--;
269 lnm_pushable_realloc(pushable);
270}
271
272void 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
288void lnm_pushable_free(lnm_pushable * pushable) {
289 free(pushable->frame);
290 free(pushable);
291}
292
293
294
295
296lnmQueue 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
323lnmQueue 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
339void 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
353lnmItem 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
381lnmItem 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
408void 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
427void lnmEventPushS(lnmItem event, enum lnmVerbosityLevel verbosity, const char * message) {
428 lnmItem statement = lnmStatement(verbosity, message);
429 lnmEventPush(event, statement);
430}
431
432lnmItem lnmEventI(const char * tag, lnmItem item) {
433 lnmItem event = lnmEvent(tag);
434 lnmEventPush(event, item);
435 return event;
436}
437
438lnmItem 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
448void lnm_debug_tabs(int tab_count) {
449 for (int i = 0; i < tab_count; i++) {
450 printf(" ");
451 }
452}
453
454void 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
495void 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
503void 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