Pico Headers
Loading...
Searching...
No Matches
pico_log.h
Go to the documentation of this file.
1
69#ifndef PICO_LOG_H
70#define PICO_LOG_H
71
72#include <stdarg.h> // ...
73#include <stdbool.h> // bool, true, false
74#include <stddef.h> // NULL
75#include <stdio.h> // FILE
76
77#ifdef __cplusplus
78extern "C" {
79#endif
80
95
100typedef void (*log_appender_fn)(const char* entry, void* udata);
101
108typedef void (*log_appender_lock_fn)(bool lock, void *udata);
109
113typedef int log_appender_t;
114
118bool log_str_to_level(const char* str, log_level_t* level);
119
123void log_enable(void);
124
128void log_disable(void);
129
151 log_level_t level,
152 void* udata);
153
164
171
179
186
193void log_set_lock(log_appender_t id, log_appender_lock_fn lock_fp, void* udata);
194
205
215void log_set_time_fmt(log_appender_t id, const char* fmt);
216
224void log_display_colors(log_appender_t id, bool enabled);
225
234
242void log_display_level(log_appender_t id, bool enabled);
243
252void log_display_file(log_appender_t id, bool enabled);
253
262
269#define log_trace(...) \
270 log_write(LOG_LEVEL_TRACE, __FILE__, __LINE__, __func__, __VA_ARGS__)
271
278#define log_debug(...) \
279 log_write(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__)
280
287#define log_info(...) \
288 log_write(LOG_LEVEL_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__)
289
296#define log_warn(...) \
297 log_write(LOG_LEVEL_WARN, __FILE__, __LINE__, __func__, __VA_ARGS__)
298
305#define log_error(...) \
306 log_write(LOG_LEVEL_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__)
307
314#define log_fatal(...) \
315 log_write(LOG_LEVEL_FATAL, __FILE__, __LINE__, __func__, __VA_ARGS__)
316
317
322#if defined(__GNUC__) || defined(__clang__)
323__attribute__((format(printf, 5, 6)))
324#endif
326 const char* file,
327 unsigned line,
328 const char* func,
329 const char* fmt, ...);
330
331
332#ifdef __cplusplus
333}
334#endif
335
336#endif // PICO_LOG_H
337
338#ifdef PICO_LOG_IMPLEMENTATION
339
340#include <time.h>
341#include <string.h>
342
343/*
344 * Configuration constants/macros.
345 */
346#ifndef PICO_LOG_MAX_APPENDERS
347#define PICO_LOG_MAX_APPENDERS 16
348#endif
349
350#ifndef PICO_LOG_MAX_MSG_LENGTH
351#define PICO_LOG_MAX_MSG_LENGTH 1024
352#endif
353
354#ifdef NDEBUG
355 #define PICO_LOG_ASSERT(expr) ((void)0)
356#else
357 #ifndef PICO_LOG_ASSERT
358 #include <assert.h>
359 #define PICO_LOG_ASSERT(expr) assert(expr)
360 #endif
361#endif
362
363/*
364 * Internal aliases
365 */
366
367#define LOG_MAX_APPENDERS PICO_LOG_MAX_APPENDERS
368#define LOG_MAX_MSG_LENGTH PICO_LOG_MAX_MSG_LENGTH
369#define LOG_ASSERT PICO_LOG_ASSERT
370
371/*
372 * Log entry component maximum sizes. These have been chosen to be overly
373 * generous powers of 2 for the sake of safety and simplicity.
374 */
375
376#define LOG_TIMESTAMP_LEN 64
377#define LOG_LEVEL_LEN 32
378#define LOG_FILE_LEN 512
379#define LOG_FUNC_LEN 32
380#define LOG_MSG_LEN LOG_MAX_MSG_LENGTH
381#define LOG_BREAK_LEN 1
382
383#define LOG_ENTRY_LEN (LOG_TIMESTAMP_LEN + \
384 LOG_LEVEL_LEN + \
385 LOG_FILE_LEN + \
386 LOG_FUNC_LEN + \
387 LOG_MSG_LEN + \
388 LOG_BREAK_LEN)
389
390#define LOG_TIME_FMT_LEN 32
391#define LOG_TIME_FMT "%d/%m/%Y %H:%M:%S"
392
393#define LOG_TERM_CODE 0x1B
394#define LOG_TERM_RESET "[0m"
395#define LOG_TERM_GRAY "[90m"
396
397static bool log_initialized = false; // True if logger is initialized
398static bool log_enabled = true; // True if logger is enabled
399static int log_appender_count = 0; // Number of appenders
400
401/*
402 * Logger level strings indexed by level ID (log_level_t).
403 */
404static const char* const log_level_str[] =
405{
406 "TRACE",
407 "DEBUG",
408 "INFO",
409 "WARN",
410 "ERROR",
411 "FATAL",
412 0
413};
414
415/*
416 * Logger level strings indexed by level ID (log_level_t).
417 */
418static const char* const log_level_str_formatted[] =
419{
420 "TRACE",
421 "DEBUG",
422 "INFO ",
423 "WARN ",
424 "ERROR",
425 "FATAL",
426 0
427};
428
429// Appropriated from https://github.com/rxi/log.c (MIT licensed)
430static const char* log_level_color[] =
431{
432 "[94m", "[36m", "[32m", "[33m", "[31m", "[35m", NULL
433};
434
435/*
436 * Appender pointer and metadata.
437 */
438typedef struct
439{
440 log_appender_fn appender_fp;
441 void* udata;
442 log_appender_lock_fn lock_fp;
443 void* lock_udata;
444 bool enabled;
445 log_level_t log_level;
446 char time_fmt[LOG_TIME_FMT_LEN];
447 bool colors;
448 bool timestamp;
449 bool level;
450 bool file;
451 bool func;
452} log_appender_data_t;
453
454/*
455 * Array of appenders.
456 */
457static log_appender_data_t log_appenders[LOG_MAX_APPENDERS];
458
459/*
460 * Initializes the logger provided it has not been initialized.
461 */
462static void
463log_try_init (void)
464{
465 if (log_initialized)
466 {
467 return;
468 }
469
470 for (int i = 0; i < LOG_MAX_APPENDERS; i++)
471 {
472 log_appenders[i].appender_fp = NULL;
473 }
474
475 log_initialized = true;
476}
477
478static bool log_appender_exists(log_appender_t id)
479{
480 log_try_init();
481
482 return (id < LOG_MAX_APPENDERS && NULL != log_appenders[id].appender_fp);
483}
484
485static bool log_appender_enabled(log_appender_t id)
486{
487 log_try_init();
488
489 return log_appender_exists(id) && log_appenders[id].enabled;
490}
491
492bool log_str_to_level(const char* str, log_level_t* level)
493{
494 if (!level)
495 return false;
496
497 for (int i = 0; log_level_str[i]; i++)
498 {
499 if (0 == strcmp(str, log_level_str[i]))
500 {
501 *level = (log_level_t)i;
502 return true;
503 }
504 }
505
506 return false;
507}
508
509void
510log_enable (void)
511{
512 log_enabled = true;
513}
514
515void
516log_disable (void)
517{
518 log_enabled = false;
519}
520
522log_add_appender (log_appender_fn appender_fp, log_level_t level, void* udata)
523{
524 // Initialize logger if neccesary
525 log_try_init();
526
527 // Check if there is space for a new appender.
528 LOG_ASSERT(log_appender_count < LOG_MAX_APPENDERS);
529
530 // Ensure level is valid
531 LOG_ASSERT(level >= 0 && level < LOG_LEVEL_COUNT);
532
533 // Iterate through appender array and find an empty slot.
534 for (int i = 0; i < LOG_MAX_APPENDERS; i++)
535 {
536 if (NULL == log_appenders[i].appender_fp)
537 {
538 log_appender_data_t* appender = &log_appenders[i];
539
540 // Store and enable appender
541 appender->appender_fp = appender_fp;
542 appender->log_level = level;
543 appender->udata = udata;
544 appender->level = LOG_LEVEL_INFO;
545 appender->enabled = true;
546 appender->colors = false;
547 appender->level = true;
548 appender->timestamp = false;
549 appender->file = false;
550 appender->func = false;
551 appender->lock_fp = NULL;
552 appender->lock_udata = NULL;
553
554 strncpy(appender->time_fmt, LOG_TIME_FMT, LOG_TIME_FMT_LEN);
555
556 log_appender_count++;
557
558 return (log_appender_t)i;
559 }
560 }
561
562 // This should never happen
563 LOG_ASSERT(false);
564 return 0;
565}
566
567static void
568log_stream_appender (const char* entry, void* udata)
569{
570 FILE* stream = (FILE*)udata;
571 fprintf(stream, "%s", entry);
572 fflush(stream);
573}
574
576log_add_stream (FILE* stream, log_level_t level)
577{
578 // Stream must not be NULL
579 LOG_ASSERT(NULL != stream);
580
581 return log_add_appender(log_stream_appender, level, stream);
582}
583
584void
586{
587 // Initialize logger if neccesary
588 log_try_init();
589
590 // Ensure appender is registered
591 LOG_ASSERT(log_appender_exists(id));
592
593 // Reset appender with given ID
594 log_appenders[id].appender_fp = NULL;
595
596 log_appender_count--;
597}
598
599void
601{
602 // Initialize logger if neccesary
603 log_try_init();
604
605 // Ensure appender is registered
606 LOG_ASSERT(log_appender_exists(id));
607
608 // Enable appender
609 log_appenders[id].enabled = true;
610}
611
612void
614{
615 // Initialize logger if neccesary
616 log_try_init();
617
618 // Ensure appender is registered
619 LOG_ASSERT(log_appender_exists(id));
620
621 // Disable appender
622 log_appenders[id].enabled = false;
623}
624
625void
626log_set_lock (log_appender_t id, log_appender_lock_fn lock_fp, void* udata)
627{
628 // Ensure lock function is initialized
629 LOG_ASSERT(NULL != lock_fp);
630
631 // Ensure appender is registered
632 log_try_init();
633
634 // Ensure appender is registered
635 LOG_ASSERT(log_appender_exists(id));
636
637 log_appenders[id].lock_fp = lock_fp;
638 log_appenders[id].lock_udata = udata;
639}
640
641void
643{
644 // Initialize logger if neccesary
645 log_try_init();
646
647 // Ensure appender is registered
648 LOG_ASSERT(log_appender_exists(id));
649
650 // Ensure level is valid
651 LOG_ASSERT(level >= 0 && level < LOG_LEVEL_COUNT);
652
653 // Set the level
654 log_appenders[id].log_level = level;
655}
656
657void
658log_set_time_fmt (log_appender_t id, const char* fmt)
659{
660 // Initialize logger if neccesary
661 log_try_init();
662
663 // Ensure appender is registered
664 LOG_ASSERT(log_appender_exists(id));
665
666 // Copy the time string
667 strncpy(log_appenders[id].time_fmt, fmt, LOG_TIME_FMT_LEN);
668}
669
670void
671log_display_colors (log_appender_t id, bool enabled)
672{
673 // Initialize logger if neccesary
674 log_try_init();
675
676 // Ensure appender is registered
677 LOG_ASSERT(log_appender_exists(id));
678
679 // Disable appender
680 log_appenders[id].colors = enabled;
681}
682
683void
684log_display_timestamp (log_appender_t id, bool enabled)
685{
686 // Initialize logger if neccesary
687 log_try_init();
688
689 // Ensure appender is registered
690 LOG_ASSERT(log_appender_exists(id));
691
692 // Turn timestamp on
693 log_appenders[id].timestamp = enabled;
694}
695
696void
697log_display_level (log_appender_t id, bool enabled)
698{
699 // Initialize logger if neccesary
700 log_try_init();
701
702 // Ensure appender is registered
703 LOG_ASSERT(log_appender_exists(id));
704
705 // Turn level reporting on
706 log_appenders[id].level = enabled;
707}
708
709void
710log_display_file (log_appender_t id, bool enabled)
711{
712 // Initialize logger if neccesary
713 log_try_init();
714
715 // Ensure appender is registered
716 LOG_ASSERT(log_appender_exists(id));
717
718 // Turn file reporting on
719 log_appenders[id].file = enabled;
720}
721
722void
723log_display_function (log_appender_t id, bool enabled)
724{
725 // Initialize logger if neccesary
726 log_try_init();
727
728 // Ensure appender is registered
729 LOG_ASSERT(log_appender_exists(id));
730
731 // Turn file reporting on
732 log_appenders[id].func = enabled;
733}
734
735/*
736 * Formats the current time as as string.
737 */
738static char*
739log_time_str (const char* time_fmt, char* str, size_t len)
740{
741 time_t now = time(0);
742 size_t ret = strftime(str, len, time_fmt, localtime(&now));
743
744 LOG_ASSERT(ret > 0);
745
746 return str;
747}
748
749static void
750log_append_timestamp (char* entry_str, const char* time_fmt)
751{
752 char time_str[LOG_TIMESTAMP_LEN + 1];
753 char tmp_str[LOG_TIMESTAMP_LEN];
754
755 snprintf(time_str, sizeof(time_str), "%s ",
756 log_time_str(time_fmt, tmp_str, sizeof(tmp_str)));
757
758 strncat(entry_str, time_str, LOG_TIMESTAMP_LEN);
759}
760
761static void
762log_append_level (char* entry_str, log_level_t level, bool colors)
763{
764 char level_str[LOG_LEVEL_LEN];
765
766 if (colors)
767 {
768 snprintf(level_str, sizeof(level_str), "%c%s%s %c%s",
769 LOG_TERM_CODE, log_level_color[level],
770 log_level_str_formatted[level],
771 LOG_TERM_CODE, LOG_TERM_RESET);
772 }
773 else
774 {
775 snprintf(level_str, sizeof(level_str), "%s ", log_level_str_formatted[level]);
776 }
777
778 strncat(entry_str, level_str, LOG_LEVEL_LEN);
779}
780
781static void
782log_append_file (char* entry_str, const char* file, unsigned line)
783{
784 char file_str[LOG_FILE_LEN];
785 snprintf(file_str, sizeof(file_str), "[%s:%u] ", file, line);
786 strncat(entry_str, file_str, LOG_FILE_LEN);
787}
788
789static void
790log_append_func (char* entry_str, const char* func, bool colors)
791{
792 char func_str[LOG_FUNC_LEN];
793
794 if (colors)
795 {
796 snprintf(func_str, sizeof(func_str), "%c%s[%s] %c%s",
797 LOG_TERM_CODE, LOG_TERM_GRAY,
798 func,
799 LOG_TERM_CODE, LOG_TERM_RESET);
800 }
801 else
802 {
803 snprintf(func_str, sizeof(func_str), "[%s] ", func);
804 }
805
806 strncat(entry_str, func_str, LOG_FUNC_LEN);
807}
808
809void
810log_write (log_level_t level, const char* file, unsigned line,
811 const char* func, const char* fmt, ...)
812{
813 // Ensure logger is initialized
814 log_try_init();
815
816 // Only write entry if there are registered appenders and the logger is
817 // enabled
818 if (0 == log_appender_count || !log_enabled)
819 {
820 return;
821 }
822
823 // Ensure valid log level
824 LOG_ASSERT(level < LOG_LEVEL_COUNT);
825
826 for (log_appender_t i = 0; i < LOG_MAX_APPENDERS; i++)
827 {
828 log_appender_data_t* appender = &log_appenders[i];
829
830 if (!log_appender_enabled(i))
831 continue;
832
833 if (log_appenders[i].log_level <= level)
834 {
835 char entry_str[LOG_ENTRY_LEN + 1]; // Ensure there is space for
836 // null char
837
838 entry_str[0] = '\0'; // Ensure the entry is null terminated
839
840 // Append a timestamp
841 if (appender->timestamp)
842 {
843 log_append_timestamp(entry_str, appender->time_fmt);
844 }
845
846 // Append the logger level
847 if (appender->level)
848 {
849 log_append_level(entry_str, level, appender->colors);
850 }
851
852 // Append the filename/line number
853 if (appender->file)
854 {
855 log_append_file(entry_str, file, line);
856 }
857
858 // Append the function name
859 if (appender->func)
860 {
861 log_append_func(entry_str, func, appender->colors);
862 }
863
864 // Append the log message
865 char msg_str[LOG_MSG_LEN];
866
867 va_list args;
868 va_start(args, fmt);
869 vsnprintf(msg_str, sizeof(msg_str), fmt, args);
870 va_end(args);
871
872 strncat(entry_str, msg_str, LOG_MSG_LEN);
873 strcat(entry_str, "\n");
874
875 // Locks the appender
876 if (NULL != appender->lock_fp)
877 {
878 appender->lock_fp(true, appender->lock_udata);
879 }
880
881 appender->appender_fp(entry_str, appender->udata);
882
883 // Unlocks the appender
884 if (NULL != appender->lock_fp)
885 {
886 appender->lock_fp(false, appender->lock_udata);
887 }
888 }
889 }
890}
891
892#endif // PICO_LOG_IMPLEMENTATION
893
894
895/*
896 ----------------------------------------------------------------------------
897 This software is available under two licenses (A) or (B). You may choose
898 either one as you wish:
899 ----------------------------------------------------------------------------
900
901 (A) The zlib License
902
903 Copyright (c) 2021 James McLean
904
905 This software is provided 'as-is', without any express or implied warranty.
906 In no event will the authors be held liable for any damages arising from the
907 use of this software.
908
909 Permission is granted to anyone to use this software for any purpose,
910 including commercial applications, and to alter it and redistribute it
911 freely, subject to the following restrictions:
912
913 1. The origin of this software must not be misrepresented; you must not
914 claim that you wrote the original software. If you use this software in a
915 product, an acknowledgment in the product documentation would be appreciated
916 but is not required.
917
918 2. Altered source versions must be plainly marked as such, and must not be
919 misrepresented as being the original software.
920
921 3. This notice may not be removed or altered from any source distribution.
922
923 ----------------------------------------------------------------------------
924
925 (B) Public Domain (www.unlicense.org)
926
927 This is free and unencumbered software released into the public domain.
928
929 Anyone is free to copy, modify, publish, use, compile, sell, or distribute
930 this software, either in source code form or as a compiled binary, for any
931 purpose, commercial or non-commercial, and by any means.
932
933 In jurisdictions that recognize copyright laws, the author or authors of
934 this software dedicate any and all copyright interest in the software to the
935 public domain. We make this dedication for the benefit of the public at
936 large and to the detriment of our heirs and successors. We intend this
937 dedication to be an overt act of relinquishment in perpetuity of all present
938 and future rights to this software under copyright law.
939
940 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
941 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
942 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
943 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
944 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
945 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
946*/
947
948// EoF
void log_display_timestamp(log_appender_t id, bool enabled)
Turns timestamp reporting on/off for the specified appender. NOTE: Off by default.
void log_write(log_level_t level, const char *file, unsigned line, const char *func, const char *fmt,...)
void log_disable(void)
Disables logging.
void log_display_file(log_appender_t id, bool enabled)
Turns filename and line number reporting on/off for the specified appender. NOTE: Off by default.
void log_enable(void)
Enables logging. NOTE: Logging is enabled by default.
void(* log_appender_fn)(const char *entry, void *udata)
Appender function definition. An appender writes a log entry to an output stream. This could be the c...
Definition pico_log.h:100
bool log_str_to_level(const char *str, log_level_t *level)
Converts a string to the corresponding log level.
void log_display_colors(log_appender_t id, bool enabled)
Turns colors ouput on or off for the specified appender. NOTE: Off by default.
void log_display_level(log_appender_t id, bool enabled)
Turns log level reporting on/off for the specified appender. NOTE: On by default.
log_appender_t log_add_stream(FILE *stream, log_level_t level)
Registers an output stream appender.
log_level_t
These codes allow different layers of granularity when logging. See the documentation of the log_set_...
Definition pico_log.h:86
@ LOG_LEVEL_DEBUG
Definition pico_log.h:88
@ LOG_LEVEL_ERROR
Definition pico_log.h:91
@ LOG_LEVEL_FATAL
Definition pico_log.h:92
@ LOG_LEVEL_TRACE
Definition pico_log.h:87
@ LOG_LEVEL_WARN
Definition pico_log.h:90
@ LOG_LEVEL_COUNT
Definition pico_log.h:93
@ LOG_LEVEL_INFO
Definition pico_log.h:89
void log_set_level(log_appender_t id, log_level_t level)
Sets the logging level.
void log_set_lock(log_appender_t id, log_appender_lock_fn lock_fp, void *udata)
Sets the lock function for a given appender.
void log_remove_appender(log_appender_t id)
Unregisters appender (removes the appender from the logger).
void log_display_function(log_appender_t id, bool enabled)
Turns function reporting on/off for the specified appender. NOTE: Off by default.
void log_enable_appender(log_appender_t id)
Enables the specified appender. NOTE: Appenders are enabled by default after registration.
log_appender_t log_add_appender(log_appender_fn appender_fp, log_level_t level, void *udata)
Registers an appender.
void log_set_time_fmt(log_appender_t id, const char *fmt)
Set the appender timestamp.
int log_appender_t
Identifies a registered appender.
Definition pico_log.h:113
void log_disable_appender(log_appender_t id)
Disables the specified appender.
void(* log_appender_lock_fn)(bool lock, void *udata)
Lock function definition. This is called during log_write.
Definition pico_log.h:108