Dead simple C logging library contained in a single header (.h) file

Overview

Seethe

demonstration

Logging so simple, you only need to include a single header file. seethe supports 6 different log levels (DEBUG, INFO, NOTICE, WARNING, ERROR, and CRITICAL), a SILENT mode as well as printf-style variadic printing.

demonstration

Installation and Usage

Just #include "seethe.h" in the file you're using and use any of the 6 macros. It's that simple. Look at the below sample code or example.c for more boilerplate.

#include "seethe.h"
#include <stdio.h>

int main(int argc, char *argv[]) {

    // all the available macros
    debug("debug log");
    info("informational log");
    notice("notice log");
    warning("warning log");
    error("error log");
    critical("critical log");

    // sample usage
    info("starting program");
    char *my_string;
    if (!(my_string = malloc(BUFSIZ * sizeof(char)))) {
        critical("failed to make size-%zu buffer. exiting\n", BUFSIZ);
        abort();
    }
    debug("getting input from user");
    printf("Enter some stuff: ");
    while (fgets(my_string, BUFSIZ, stdin) != NULL) {
        if (!strcmp(my_string, "\n")) {
            info("user entered blank input. exiting");
            return 0;
        }
    }
    error("fgets failed to return a proper result");
    return 1;

}

Locally Changing the Log Level

To change the warning level for a particular point in a particular file, re-#define the pre-processor macro at the point you'd like:

...
#define LOG_LEVEL   WARNING
warning("first warning");
#define LOG_LEVEL   ERROR     // now only ERROR and CRITICAL are visible
warning("second warning");
error("some error");
...
21:59:28 [WARNING] runner.c:37 - first warning
21:59:28 [ERROR] runner.c:40 - some error

(If you want to suppress the 'redefinition' error from gcc, put #undef LOG_LEVEL before your #define).

To set the logging level for an entire file, define the LOG_LEVEL before #include'ing seeth.

#define LOG_LEVEL   DEBUG
#include "seethe.h"
...

Silencing Log Output

The SILENT log level is provided for complete suppression of all warnings.

#define LOG_LEVEL   SILENT
#include "seethe.h"
...

Globally Changing the Log Level

If you'd like to set a global LOG_LEVEL for your entire project, compile the LOG_LEVEL as a gcc flag:

gcc *.c -D LOG_LEVEL=ERROR

Customization

I tried making seethe as configurable as possible, though there's still room for improvement.

Colour

It is possible to configure the different level colours using VT100 ANSI escape codes. The defaults are:

#define DEBUG_COLOUR    ""
#define INFO_COLOUR     "\x1B[36m"
#define NOTICE_COLOUR   "\x1B[32;1m"
#define WARNING_COLOUR  "\x1B[33m"
#define ERROR_COLOUR    "\x1B[31m"
#define CRITICAL_COLOUR "\x1B[41;1m"

Message Endings

By default, every log message has a newline (\n) appended to it in order to give puts-like syntax (i.e. for those who hate typing \n for every print statement). To remove the trailing newline and restore full printf functionality, change "\n" to "". The default is:

#define MSG_ENDING      "\n"

Time Format

Each log message has a timestamp attached to it, defined from the strftime function. It can be changed (though if you are making a particularly long message, you should probably up the buffer from 80 chars). The default is:

#define TIME_FORMAT     "%T "

Message Border

You can configure the border that shows up before the actual message (by default it's -). The default is:

#define BORDER          "-"

Logger Format

There are two ways to customize this: there are flags that can toggle existing components and I tried my best to make it easy(-ish) to make new ones.

  • Enabler flags — toggles for the logger message features. By default, everything is enabled (=1). To turn something off, change it to 0. Do not remove the macro. The built-in flags are:

    • DISPLAY_COLOUR — enable colour-coding the log messages based on level
    • DISPLAY_TIME — output the time for each log event (date should be configurable)
    • DISPLAY_LEVEL — display the logging level of each message
    • DISPLAY_FUNC — display the calling function for each log message
    • DISPLAY_FILE — print the file from which the log message originates from
    • DISPLAY_LINE — print the line from which the log message originates from
    • DISPLAY_BORDER — print the border that comes before the messages
    • DISPLAY_MESSAGE — print the log message
    • DISPLAY_ENDING — print the message ending (by default it's \n)
    • DISPLAY_RESET — reset the message colour-coding at the end of each log

    It is not recommended to disable DISPLAY_ENDING or DISPLAY_RESET.

    In the library, the flags are arranged like so:

    #define DISPLAY_COLOUR  1
    #define DISPLAY_TIME    1
    #define DISPLAY_LEVEL   1
    #define DISPLAY_FUNC    1
    #define DISPLAY_FILE    1
    #define DISPLAY_LINE    1
    #define DISPLAY_BORDER  1
    #define DISPLAY_MESSAGE 1
    #define DISPLAY_ENDING  1
    #define DISPLAY_RESET   1
  • Message components — the actual components that make up the log messages. Do note that although you can rearrange them freely, it is important to follow the same formatting structure as the existing components when creating new ones. This means if a component is removed, so will its spacing (hence why a lot of the existing components have a second %s in the printf—the extra space needs to be able to seamlessly appear and disappear for modularity and toggling).

    For a lot of components, it's a very simple check:

    printf("%s%s", DISPLAY_FUNC ? func : "", DISPLAY_FUNC ? " " : "");

    Do you need to display the calling function? If so, print a space along with it. Otherwise, don't print anything (not even the space).

    But for some others, it is a lot more complex. Take a look at this monstrosity here:

    printf(                                                                         \
        "%s%s%s%.d%s%s",                                                            \
        DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? "(" : "",                  \
        DISPLAY_FILE ? file : "",                                                   \
        DISPLAY_FILE && DISPLAY_LINE ? ":" : "",                                    \
        DISPLAY_LINE ? line : 0,                                                    \
        DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? ") " : "",                 \
        !DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? " " : ""                  \
    );

    This part of the emit_log macro is responsible for the part of the logger output that emits the calling function, source file, and line number (for example main (example.c:24)).

    The left and right brackets ((/)) are only printed when the actual function itself is printed and either source file and/or line are also printed (otherwise you'd get lines like 04:44:32 [WARNING] (example.c:27) - warning log where don't need the brackets, or 04:44:32 [WARNING] main () - warning log where neither file nor line are printed, resulting in empty brackets being printed next to the function).

    The second last print specifier is responsible for printing a space after the right bracket ()) in the case that the logger is configured to print the calling function. However, in the case that brackets aren't printed at all, we still need a space to trail the entire component, hence the !DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? " " : "" construct.

    As a result of this over-engineered mess, seethe can dynamically output:

    • main (example.c:24)

    • example.c:24

    • main (24)

    • main

    • 24

    Also, feel free to play around with the spacing on the printf print specifiers (I personally prefer %10s%s for level display).

TODO?

  • Allow for configuring print specifiers easily?

License

ISC License. Do whatever the fuck you want.

Issues
  • fix level toggle

    fix level toggle

    Problem

    #define DISPLAY_LEVEL 0
    printf("%10s%s", DISPLAY_LEVEL ? level : "", DISPLAY_LEVEL ? " " : "");
    

    Even if you #define DISPLAY_LEVEL as 0, the %10s stipulation of the printf call still goes through.

    Expected Behaviour

       [ERROR] main (src/test_sagittarius.c:27) hello // #define DISPLAY_LEVEL 1
    
    main (src/test_sagittarius.c:27) hello // #define DISPLAY_LEVEL 0
    

    Actual Behaviour

       [ERROR] main (src/test_sagittarius.c:27) hello // #define DISPLAY_LEVEL 1
    
               main (src/test_sagittarius.c:27) hello // #define DISPLAY_LEVEL 0
    

    Proposed Fix

    Make the %10s stipulation only occur when DISPLAY_LEVEL is 1. This may mean splitting it up into multiple lines.

    Change

    printf("%10s%s", DISPLAY_LEVEL ? level : "", DISPLAY_LEVEL ? " " : "");
    

    to

    printf(DISPLAY_LEVEL ? "%10s%s" : "%s%s" , DISPLAY_LEVEL ? level : "", DISPLAY_LEVEL ? " " : "");
    
    opened by jnguyen1098 1
Owner
Jason Nguyen
ganyu's biggest fan ヾ(•ω•`)o
Jason Nguyen
Dead by Daylight utilities created while researching

DeadByDaylight Research material and PoC for bugs found during the reversal of the game Dead by Daylight. All information provided is for educational

Layle | Luca 13 Jul 27, 2022
convert elf file to single c/c++ header file

elf-to-c-header Split ELF to single C/C++ header file

Musa Ünal 2 Nov 4, 2021
A small self-contained alternative to readline and libedit

Linenoise A minimal, zero-config, BSD licensed, readline replacement used in Redis, MongoDB, and Android. Single and multi line editing mode with the

Salvatore Sanfilippo 3k Jul 28, 2022
Block unauthorized users from logging in via RDP.

RDPBlocker is a tool to prevent brute force password cracking against RDP (Remote Desktop Protocol) services. This tool is developed by cpp

null 41 May 19, 2022
RapidObj is an easy-to-use, single-header C++17 library that loads and parses Wavefront .obj files.

RapidObj About Integration Prerequisites Manual Integration CMake Integration API RapidObj Result Next Steps OS Support Third Party Tools and Resource

Slobodan Pavlic 77 Aug 4, 2022
curl4cpp - single header cURL wrapper for C++ around libcURL.

curl4cpp - single header cURL wrapper for C++ around libcURL.

Ferhat Geçdoğan 14 Jun 12, 2022
Simple .INI file parser in C, good for embedded systems

inih (INI Not Invented Here) inih (INI Not Invented Here) is a simple .INI file parser written in C. It's only a couple of pages of code, and it was d

Ben Hoyt 1.8k Aug 6, 2022
Simple and lightweight pathname parser for C. This module helps to parse dirname, basename, filename and file extension .

Path Module For C File name and extension parsing functionality are removed because it's difficult to distinguish between a hidden dir (ex: .git) and

Prajwal Chapagain 3 Feb 25, 2022
A linux library to get the file path of the currently running shared library. Emulates use of Win32 GetModuleHandleEx/GetModuleFilename.

whereami A linux library to get the file path of the currently running shared library. Emulates use of Win32 GetModuleHandleEx/GetModuleFilename. usag

Blackle Morisanchetto 1 Nov 5, 2021
Locate the current executable and the current module/library on the file system

Where Am I? A drop-in two files library to locate the current executable and the current module on the file system. Supported platforms: Windows Linux

Gregory Pakosz 363 Jul 28, 2022
A small and portable INI file library with read/write support

minIni minIni is a portable and configurable library for reading and writing ".INI" files. At just below 900 lines of commented source code, minIni tr

Thiadmer Riemersma 268 Jul 22, 2022
Small configuration file parser library for C.

libConfuse Introduction Documentation Examples Build & Install Origin & References Introduction libConfuse is a configuration file parser library writ

null 410 Jul 24, 2022
C++ NIF library for the Gamebryo/NetImmerse File Format

nifly C++ NIF library for the Gamebryo/NetImmerse File Format. Created with a clean-room design. Features Reading and writing NIF files (Gamebryo/NetI

null 29 Jul 26, 2022
Small header-only C++ library that helps to initialize Vulkan instance and device object

Vulkan Extensions & Features Help, or VkExtensionsFeaturesHelp, is a small, header-only, C++ library for developers who use Vulkan API.

Adam Sawicki 10 Apr 13, 2022
match(it): A lightweight header-only pattern-matching library for C++17 with macro-free APIs.

match(it): A lightweight header-only pattern-matching library for C++17 with macro-free APIs. Features Easy to get started. Single header library. Mac

Bowen Fu 229 Jul 30, 2022
C++ header-only library for generic data validation.

C++ header-only library for generic data validation.

Evgeny Sidorov 26 Jul 13, 2022
a compile-time, header-only, dimensional analysis and unit conversion library built on c++14 with no dependencies.

UNITS A compile-time, header-only, dimensional analysis library built on c++14 with no dependencies. Get in touch If you are using units.h in producti

Nic Holthaus 768 Jul 27, 2022
A header only C++ library that provides type safety and user defined literals for physical units

SI - Type safety for physical units A header only c++ library that provides type safety and user defined literals for handling pyhsical values defined

Dominik Berner 379 Jul 21, 2022
Small Header only library to parse argv for flags

Small Header only library to parse argv for flags

Ben Wernicke 2 Dec 8, 2021