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