diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c7dfc55 --- /dev/null +++ b/.clang-format @@ -0,0 +1,60 @@ +# Base style configuration +# Inherit all formatting rules from the LLVM style as a starting point. +BasedOnStyle: LLVM + +# Indentation settings +# - IndentWidth: number of spaces per indent level. +# - TabWidth: visual width of tab characters. +# - UseTab: whether to emit real tab characters. +# - NamespaceIndentation: ident namespaces +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +NamespaceIndentation: All + +# Include directives +# - SortIncludes: controls ordering of #include statements. +# - IncludeBlocks: allow empty lines for includes. +SortIncludes: true +IncludeBlocks: Preserve + +# Short constructs on single lines +# Disallow collapsing blocks, loops, cases, ifs, and functions into a single line. +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false + +# Access modifiers formatting +# Shift class access specifiers (public/protected/private) relative to class indent. +AccessModifierOffset: -2 + +# Brace placement +# Attach opening braces to the control statement or declaration line. +BreakBeforeBraces: Attach + +# Spacing around parentheses and casts +# ControlStatements: space before parens in if/for/while only. +# No extra spaces inside parentheses, angles, container literals, or square brackets. +SpaceBeforeParens: ControlStatements +SpaceAfterCStyleCast: false +SpacesInParentheses: false +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false + +# Template keyword spacing +# Always place a space after the 'template' keyword. +SpaceAfterTemplateKeyword: true + +# Line length +# ColumnLimit: 0 disables automatic wrapping; allows unlimited line length. +ColumnLimit: 0 + +# Pointer alignment +# DerivePointerAlignment: ignore inference, use PointerAlignment setting. +# PointerAlignment: align the '*' or '&' to the left with the type. +DerivePointerAlignment: false +PointerAlignment: Left diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..0cbf6d5 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,54 @@ +# .clang-tidy + +# Enable broad sets, disable one modernize check explicitly. +Checks: > + clang-diagnostic-*, + clang-analyzer-*, + cppcoreguidelines-*, + modernize-*, + -modernize-use-trailing-return-type + -cppcoreguidelines-avoid-magic-numbers + +# Lint everything. Set a regex (e.g. ^src/|^include/) to scope. +HeaderFilterRegex: '' + +# Use clang-format's "google" style when auto-fixing. +FormatStyle: google + +# Per-check options, grouped for readability. +CheckOptions: + # --- CERT --- + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: '0' + + # --- CppCoreGuidelines --- + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: '1' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + + # --- Google Readability --- + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + + # --- Modernize --- + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: 'reasonable' + - key: modernize-loop-convert.NamingStyle + value: 'CamelCase' + - key: modernize-pass-by-value.IncludeStyle + value: 'llvm' + - key: modernize-replace-auto-ptr.IncludeStyle + value: 'llvm' + - key: modernize-use-nullptr.NullMacros + value: 'NULL' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3143a6f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CClarify CI + +on: + push: + pull_request: + branches: [master] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + std: [99, 11, 17, 23] + compiler: [gcc, clang] + + runs-on: ${{ matrix.os }} + + steps: + - name: Install conan + uses: conan-io/setup-conan@v1 + + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Build & test + run: conan create . -o std=${{ matrix.std }} -s compiler=${{ matrix.compiler }} -s compiler.version=14 -s compiler.libcxx=libstdc++ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2711953 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dist/munit"] + path = dist/munit + url = https://github.com/nemequ/munit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..87a9706 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.15) +project(hyper-cclarify C) + +message(STATUS "Using C standard: ${CMAKE_C_STANDARD}") + +add_library(cclarify src/cclarify.c) +target_include_directories(cclarify PUBLIC include) + +set_target_properties(cclarify PROPERTIES PUBLIC_HEADER "include/cclarify/cclarify.h") +install(TARGETS cclarify) diff --git a/Makefile b/Makefile deleted file mode 100644 index ba1d632..0000000 --- a/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -all: - @gcc test.c -o test -std=c17 -g - @./test - @rm test diff --git a/README.md b/README.md index 9d12895..3be3d18 100644 --- a/README.md +++ b/README.md @@ -25,72 +25,101 @@ - [About project](#about-cclarify) - [Installation](#installation) - [Usage](#usage) - - [Assign](#assign) - - [Exec](#exec) - - [Msg](#msg) - - [Display](#display) + - [Quickstart](#quickstart) + - [Loglevels](#loglevels) + - [Formatting rules](#formatting-rules) + - [Global formatter API](#global-formatter-api) + - [Using custom loggers](#using-custom-loggers) + # About CClarify -CClarify is a small framework for creating advanced logging system. It can write to your custom descriptor, pointing to buffer, and can save everything to file, if you set it to do so. +CClarify is a small logging library, written in pure C, that doesn't use any heap memory. ## Installation -1. Download main header [cclarify.h](cclarify.h) -2. Include it to your source file, where you want to add logging -3. Now you should set up everything. -```c -GLOBAL_INIT(); // Init logger variables -Clarifier clar; // Create logger object -// If you have int descriptor like stdout -init_loggerd(clar, descriptor); - -// If you want it to write logs to file -init_loggerf(clar, fd); - -// If you want it to write logs to both destinations -init_loggerfd(clar, descriptor, fd); -``` -4. You are good to go now! -## Usage -### Assign -If you want to assign some value to variable (custom types are not supported) you should do this: -```c -ASSIGN(clar, variable, value); -``` -It will output something like: +If you use **Conan**, add the the package: ``` -==> Assigning value "value" to "variable", old value: "old_var_val" -``` -### Exec -If you wanna execute some function, and signal if function started or stopped, do this: -```c -EXEC(clar, function()); -``` -It will notify you about started execution: -``` -==> Starting execution of function "function()" -``` -And about end of function execution. -``` -==> Execution of function "function()" finished +hyper-cclarify/ ``` +You can choose a version in **Releases** tab. -### Msg -To display some king of message, you need just to use MSG() function. +## Usage +### Quickstart +If you don't need all these fancy things, you can just start logging - it will work! ```c -// To display green info message, do this -MSG(clar, "Hello World!", INFO); -// Yellow - warning -MSG(clar, "Warning", WARNING); -// Red - error -MSG(clar, "Error", ERROR); +#include "cclarify.h" + +int main() { + clar_info("Program is running"); +} ``` -### Display -Sometimes you want just to display variable value. Do this: -```c -DISPLAY(clar, variable); +### Loglevels +CClarify has the following loglevels: +- **CLAR_LOG_DEBUG** +- **CLAR_LOG_INFO** +- **CLAR_LOG_WARNING** +- **CLAR_LOG_ERROR** +- **CLAR_LOG_FATAL** + +**Example**: +If you will set loglevel to **CLAR_LOG_WARNING**, all log calls with priority lower than this (i. e. **CLAR_LOG_INFO** and **CLAR_LOG_DEBUG**) won't print anything at all. + +### Formatting rules +| %Y | Insert current year (for example, **2025**) | +|----|-----------------------------------------------------------------------------------------| +| %M | Insert abbreviated month name (for example, **Sep**) | +| %d | Insert abbreviated day of week name (for example, **Mon**) | +| %D | Insert day of month as a decimal (for example, **09**) | +| %H | Insert hour as a decimal (for example, **13**) | +| %m | Insert minute as a decimal (for example, **32**) | +| %s | Insert second as a decifuncleuncal (for example, **47**) | +| %l | Insert formatted string from log() call | +| %x | Inserts message, specific to current loglevel (applying colors, if writing to terminal) | +| %% | Inserts a single percent | +| % | Inserts a single percent too - if the next character is not a valid format specifier | + +### Global formatter API +`void clar_set_global_rotation(const char* filename, uint16_t max_files, uint32_t max_file_size)` - Enables log rotation when you enabled global output to file. Max file size is set in bytes. + +`void clar_set_global_format(const char* fmt)` - Sets global format string. Notice, that this is NOT a format string, that is used by libc's *printf functions! See [formatting rules](#formatting-rules) for more. + +`void clar_set_global_loglevel(__clar_loglevel loglevel)` - Sets global loglevel. + +`void clar_log(__clar_loglevel loglevel, const char* fmt, ...)` - Uses global logger and provided loglevel to log. + +`void clar_debug(const char* fmt, ...)` - Uses global logger and **CLAR_LOG_DEBUG** loglevel to log. + +`void clar_info(const char* fmt, ...)` - Uses global logger and **CLAR_LOG_INFO** loglevel to log. + +`void clar_warn(const char* fmt, ...)` - Uses global logger and **CLAR_LOG_WARNING** loglevel to log. + +`void clar_error(const char* fmt, ...)` - Uses global logger and **CLAR_LOG_ERROR** loglevel to log. + +`void clar_fatal(const char* fmt, ...)` - Uses global logger and **CLAR_LOG_FATAL** loglevel to log. + +### Using custom loggers + +#### Create custom logger: ``` -and you will get something like: +struct clarifier clar = clar_create_logger(CLAR_LOG_DEBUG, clar_create_logtarget("test.log", CLAR_OUT_STDOUT | CLAR_OUT_FILE), "[%x] %l"); ``` -==> Variable "variable" value: "420" -``` \ No newline at end of file +What we are doing here: +- Create logger with **CLAR_LOG_DEBUG** loglevel +- With logtarget: + - Write to file test.log + - Write to both file and stdout, i. e. terminal +- With formatting string "[%x] %l" + +#### How to use: +`void clar_log_with(struct clarifier* clar, __clar_loglevel loglevel, const char* fmt, ...)` - Uses custom logger and provided loglevel to log. + +`void clar_debug(struct clarifier* clar, const char* fmt, ...)` - Uses custom logger and **CLAR_LOG_DEBUG** loglevel to log. + +`void clar_info(struct clarifier* clar, const char* fmt, ...)` - Uses custom logger and **CLAR_LOG_INFO** loglevel to log. + +`void clar_warn(struct clarifier* clar, const char* fmt, ...)` - Uses custom logger and **CLAR_LOG_WARNING** loglevel to log. + +`void clar_error(struct clarifier* clar, const char* fmt, ...)` - Uses custom logger and **CLAR_LOG_ERROR** loglevel to log. + +`void clar_fatal(struct clarifier* clar, const char* fmt, ...)` - Uses custom logger and **CLAR_LOG_FATAL** loglevel to log. + diff --git a/cclarify.h b/cclarify.h deleted file mode 100644 index 40ce6f0..0000000 --- a/cclarify.h +++ /dev/null @@ -1,192 +0,0 @@ -#include -#include -#include -#include -#include -#include - -// Some useful macroses -#define CRED "\x1b[31m" -#define CGREEN "\x1b[32m" -#define CYELLOW "\x1b[33m" -#define CRESET "\x1b[0m" - -//Structs and enums definitions -typedef enum Output{ - FILEO, - STDOUTO, - ALL -} Output; - -typedef enum MsgType{ - INFO, - WARNING, - ERROR -} MsgType; - -typedef struct Clarifier{ - Output outmode; - int cclarify_stdout; - FILE* cclarify_fd; - char* prompt; - void (*write)(struct Clarifier*,char*,MsgType); -} Clarifier; - -// Variables -int cclarify_stdout = 0; -int cclarify_depth = 0; -FILE* cclarify_fd; -Output outmode; -char* prompt = "==>"; -char* assign_strings[] = { - "%s Assigning value \"%u\" to %s, old value: %u", - "%s Assigning value \"%lu\" to %s, old value: %lu\n", - "%s Assigning value \"%d\" to %s, old value: %d\n", - "%s Assigning value \"%ld\" to %s, old value: %ld\n", - "%s Assigning value \"%f\" to %s, old value: %f\n", - "%s Assigning value \"%lf\" to %s, old value: %lf\n", - "%s Assigning value \"%s\" to %s, old value: %s\n", - "%s Assigning value \"%p\" to %s, old value: %p\n" -}; - -char* display_strings[] = { - "%s Variable \"%s\" value: \"%u\"\n", - "%s Variable \"%s\" value: \"%lu\"\n", - "%s Variable \"%s\" value: \"%d\"\n", - "%s Variable \"%s\" value: \"%ld\"\n", - "%s Variable \"%s\" value: \"%f\"\n", - "%s Variable \"%s\" value: \"%lf\"\n", - "%s Variable \"%s\" value: \"%s\"\n", - "%s Variable \"%s\" value: \"%p\"\n" -}; - -void log_write(char* msg, MsgType type); - -// Functions -// Dummies -char* u_dummy_assign(){return assign_strings[0];} -char* lu_dummy_assign(){return assign_strings[1];} -char* d_dummy_assign(){return assign_strings[2];} -char* ld_dummy_assign(){return assign_strings[3];} -char* f_dummy_assign(){return assign_strings[4];} -char* lf_dummy_assign(){return assign_strings[5];} -char* s_dummy_assign(){return assign_strings[6];} -char* vp_dummy_assign(){return assign_strings[7];} - -char* u_dummy_display(){return display_strings[0];} -char* lu_dummy_display(){return display_strings[1];} -char* d_dummy_display(){return display_strings[2];} -char* ld_dummy_display(){return display_strings[3];} -char* f_dummy_display(){return display_strings[4];} -char* lf_dummy_display(){return display_strings[5];} -char* s_dummy_display(){return display_strings[6];} -char* vp_dummy_display(){return display_strings[7];} - -char* getcol(MsgType type){ - switch (type){ - case INFO: - return CGREEN; - case WARNING: - return CYELLOW; - case ERROR: - return CRED; - } -} -void write_buf(Clarifier* obj, char* msg, MsgType type){ - write(obj->cclarify_stdout, getcol(type), 5); - write(obj->cclarify_stdout, msg, strlen(msg)); - write(obj->cclarify_stdout, CRESET, 1); -} -void write_fd(Clarifier* obj, char* msg, MsgType type){ - fprintf(obj->cclarify_fd, "%s", msg); -} - -void write_all(Clarifier* obj, char* msg, MsgType type){ - write(obj->cclarify_stdout, getcol(type), 5); - write(obj->cclarify_stdout, msg, strlen(msg)); - write(obj->cclarify_stdout, CRESET, 1); - fprintf(obj->cclarify_fd, "%s", msg); -} - -void write_data(Clarifier* restrict clar, char* restrict buffer, size_t size, MsgType type, char* restrict format, ...){ - va_list args; - va_start(args, format); - vsnprintf(buffer, size, format, args); - clar->write(clar, buffer, type); - va_end(args); -} - -#define GLOBAL_INIT() \ - char cclarify_buffer[512] = {0}; \ - char cclarify_col = 0; \ - char* cclarify_str = 0; -#define CLEARBUF() memset(cclarify_buffer, 0x00, strlen(cclarify_buffer)) -#define ASSIGN(obj, var, val) \ - cclarify_str = _Generic(var, \ - uint8_t: u_dummy_assign, \ - uint16_t: u_dummy_assign, \ - uint32_t: u_dummy_assign, \ - uint64_t: lu_dummy_assign, \ - int8_t: d_dummy_assign, \ - int16_t: d_dummy_assign, \ - int32_t: d_dummy_assign, \ - int64_t: ld_dummy_assign, \ - float: f_dummy_assign, \ - double: lf_dummy_assign, \ - char*: s_dummy_assign, \ - void*: vp_dummy_assign)(); \ - write_data(&obj, cclarify_buffer, sizeof(cclarify_buffer), INFO, cclarify_str, prompt, val, #var, var); \ - var = val; \ - CLEARBUF(); -#define EXEC(obj, func) \ - snprintf(cclarify_buffer, sizeof(cclarify_buffer), "%s Starting execution of function \"%s\"\n", prompt, #func); \ - obj.write(&obj, cclarify_buffer, INFO); \ - cclarify_depth++; \ - CLEARBUF(); \ - func; \ - snprintf(cclarify_buffer, sizeof(cclarify_buffer), "%s Execution of function \"%s\" finished\n", prompt, #func); \ - obj.write(&obj, cclarify_buffer, INFO); \ - cclarify_depth--; \ - CLEARBUF(); -#define MSG(obj, msg, type) \ - snprintf(cclarify_buffer, sizeof(cclarify_buffer), "%s %s\n", prompt, msg); \ - obj.write(&obj, cclarify_buffer, type); \ - CLEARBUF(); -#define DISPLAY(obj, var) \ - cclarify_str = _Generic(var, \ - uint8_t: u_dummy_display, \ - uint16_t: u_dummy_display, \ - uint32_t: u_dummy_display, \ - uint64_t: lu_dummy_display, \ - int8_t: d_dummy_display, \ - int16_t: d_dummy_display, \ - int32_t: d_dummy_display, \ - int64_t: ld_dummy_display, \ - float: f_dummy_display, \ - double: lf_dummy_display, \ - char*: s_dummy_display, \ - void*: vp_dummy_display)(); \ - write_data(&obj, cclarify_buffer, sizeof(cclarify_buffer), INFO, cclarify_str, prompt, #var, var); \ - CLEARBUF(); - -#define init_loggerd(obj, desc) _init_loggerd(&obj, desc); -void _init_loggerd(Clarifier* obj, int descriptor){ - obj->outmode = STDOUTO; - obj->cclarify_stdout = descriptor; - obj->write = &write_buf; -} - -#define init_loggerf(obj, fd) _init_loggerd(&obj, fd); -void _init_loggerf(Clarifier* obj, FILE* fd){ - obj->outmode = FILEO; - obj->cclarify_fd = fd; - obj->write = &write_fd; -} - -#define init_loggerfd(obj, desc, fd) _init_loggerfd(&obj, desc, fd); -void _init_loggerfd(Clarifier* obj, int descriptor, FILE* fd){ - obj->outmode = ALL; - obj->cclarify_stdout = descriptor; - obj->cclarify_fd = fd; - obj->write = &write_all; -} \ No newline at end of file diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..ce3a34d --- /dev/null +++ b/conanfile.py @@ -0,0 +1,52 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps + + +class CCLarifyRecipe(ConanFile): + name = "hyper-cclarify" + version = "1.0" + package_type = "library" + + license = "GPLv3" + author = "HyperWinX" + url = "https://github.com/HyperWinX/CClarify.git" + description = "Small and powerful logging library for C applications" + topics = ("c", "logging") + + settings = "os", "compiler", "build_type", "arch" + options = {"shared": [True, False], "fPIC": [True, False], "std": [99, 11, 17, 23]} + default_options = {"shared": False, "fPIC": True, "std": 17} + + exports_sources = "CMakeLists.txt", "src/*", "include/*" + + def config_options(self): + if self.settings.os == "Windows": + self.options.rm_safe("fPIC") + + def configure(self): + if self.options.shared: + self.options.rm_safe("fPIC") + + def layout(self): + cmake_layout(self) + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.variables["CMAKE_C_STANDARD"] = str(self.options.std.value) + tc.variables["CMAKE_C_STANDARD_REQUIRED"] = "ON" + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.libs = ["cclarify"] + diff --git a/dist/munit b/dist/munit new file mode 160000 index 0000000..fbbdf14 --- /dev/null +++ b/dist/munit @@ -0,0 +1 @@ +Subproject commit fbbdf1467eb0d04a6ee465def2e529e4c87f2118 diff --git a/include/cclarify/cclarify.h b/include/cclarify/cclarify.h new file mode 100644 index 0000000..f0b92d7 --- /dev/null +++ b/include/cclarify/cclarify.h @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include +#include + +//Structs and enums definitions +enum __clar_loglevel : uint8_t { + CLAR_LOG_FATAL = 0, + CLAR_LOG_ERROR = 1, + CLAR_LOG_WARNING = 2, + CLAR_LOG_INFO = 3, + CLAR_LOG_DEBUG = 4 +}; + +enum __clar_outputmask : uint8_t { + CLAR_OUT_STDOUT = (1 << 0), + CLAR_OUT_FILE = (1 << 1) +}; + +struct __clar_logtarget { + FILE* file; + const char* filename; + uint8_t output_mask; + uint16_t max_files; + uint32_t max_size; + uint32_t cur_size; +}; + +struct clarifier { + enum __clar_loglevel loglevel; + struct __clar_logtarget logtarget; + const char* format; +}; +// Internals +extern struct clarifier __clar_default_fmt; +extern void __clar_log( + struct clarifier* clar, + enum __clar_loglevel loglevel, + const char* fmt, + va_list* args +); +// API +static inline struct clarifier clar_create_logger( + enum __clar_loglevel level, + struct __clar_logtarget target, + const char* fmt + ) { + return (struct clarifier){ level, target, fmt }; +} + +static inline struct __clar_logtarget clar_create_logtarget( + const char* filename, + uint8_t outmask + ) { + struct __clar_logtarget target; + target.file = fopen(filename, "w"); + target.filename = filename; + if (!target.file) { + printf("[[cclarify]] Failed to open file %s to log", filename); + } + target.output_mask = outmask; + return target; +} + +static inline void clar_destroy_logger(struct clarifier* clar) { + if (clar->logtarget.file) { + fclose(clar->logtarget.file); + clar->logtarget.file = NULL; + } + clar->logtarget.output_mask = 0; + clar->format = NULL; +} + +static inline void clar_destroy_logtarget(struct clarifier* clar) { + if (clar->logtarget.file) { + fclose(clar->logtarget.file); + } + memset(&clar->logtarget, '\0', sizeof(struct __clar_logtarget)); +} + +static inline void clar_set_global_rotation(const char* filename, uint16_t max_file_count, uint32_t max_size) { + __clar_default_fmt.logtarget.filename = filename; + __clar_default_fmt.logtarget.max_files = max_file_count; + __clar_default_fmt.logtarget.max_size = max_size; +} + +static inline void clar_set_global_format(const char* restrict fmt) { + __clar_default_fmt.format = fmt; +} + +static inline void clar_set_global_loglevel(enum __clar_loglevel loglevel) { + __clar_default_fmt.loglevel = loglevel; +} + +static inline void clar_set_global_logger(struct clarifier* clar) { + memcpy(&__clar_default_fmt, clar, sizeof(struct clarifier)); +} + +static inline void clar_set_rotation(struct clarifier* clar, const char* filename, uint16_t max_file_count, uint32_t max_size) { + clar->logtarget.filename = filename; + clar->logtarget.max_files = max_file_count; + clar->logtarget.max_size = max_size; +} + + +#define __CLAR_EXPAND(x) x +#define __CLAR_GET_MACRO(_1, name, ...) name +#define CLAR_INIT(...) __CLAR_EXPAND(__CLAR_GET_MACRO(__VA_ARGS__, __CLAR_INIT2, __CLAR_INIT1)(__VA_ARGS__)) + +#define __CLAR_INIT1() + +#define __CLAR_INIT2(arg) \ + __clar_default_fmt.format = arg; + +static inline void clar_log( + enum __clar_loglevel loglevel, + const char* restrict fmt, + ...) { + va_list args; + va_start(args, fmt); + + __clar_log(&__clar_default_fmt, loglevel, fmt, &args); + + va_end(args); +} +static inline void clar_log_with( + struct clarifier* clar, + enum __clar_loglevel loglevel, + const char* restrict fmt, + ... + ) { + va_list args; + va_start(args, fmt); + + __clar_log(clar, loglevel, fmt, &args); + + va_end(args); +} + +static inline void clar_debug( + const char* restrict fmt, + ...) { + va_list args; + va_start(args, fmt); + + __clar_log(&__clar_default_fmt, CLAR_LOG_DEBUG, fmt, &args); + + va_end(args); +} + +static inline void clar_debug_with( + struct clarifier* clar, + const char* restrict fmt, + ... + ) { + va_list args; + va_start(args, fmt); + + __clar_log(clar, CLAR_LOG_DEBUG, fmt, &args); + + va_end(args); +} + +static inline void clar_info( + const char* restrict fmt, + ...) { + va_list args; + va_start(args, fmt); + + __clar_log(&__clar_default_fmt, CLAR_LOG_INFO, fmt, &args); + + va_end(args); +} +static inline void clar_info_with( + struct clarifier* clar, + const char* restrict fmt, + ... + ) { + va_list args; + va_start(args, fmt); + + __clar_log(clar, CLAR_LOG_DEBUG, fmt, &args); + + va_end(args); +} + +static inline void clar_warn( + const char* restrict fmt, + ...) { + va_list args; + va_start(args, fmt); + + __clar_log(&__clar_default_fmt, CLAR_LOG_WARNING, fmt, &args); + + va_end(args); +} + +static inline void clar_warn_with( + struct clarifier* clar, + const char* restrict fmt, + ... + ) { + va_list args; + va_start(args, fmt); + + __clar_log(clar, CLAR_LOG_WARNING, fmt, &args); + + va_end(args); +} + +static inline void clar_err( + const char* restrict fmt, + ...) { + va_list args; + va_start(args, fmt); + + __clar_log(&__clar_default_fmt, CLAR_LOG_ERROR, fmt, &args); + + va_end(args); +} + +static inline void clar_err_with( + struct clarifier* clar, + const char* restrict fmt, + ... + ) { + va_list args; + va_start(args, fmt); + + __clar_log(clar, CLAR_LOG_ERROR, fmt, &args); + + va_end(args); +} + +static inline void clar_fatal( + const char* restrict fmt, + ...) { + va_list args; + va_start(args, fmt); + + __clar_log(&__clar_default_fmt, CLAR_LOG_FATAL, fmt, &args); + + va_end(args); +} + +static inline void clar_fatal_with( + struct clarifier* clar, + const char* restrict fmt, + ... + ) { + va_list args; + va_start(args, fmt); + + __clar_log(clar, CLAR_LOG_FATAL, fmt, &args); + + va_end(args); +} diff --git a/src/cclarify.c b/src/cclarify.c new file mode 100644 index 0000000..5b58f0d --- /dev/null +++ b/src/cclarify.c @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include + +#include "cclarify/cclarify.h" + +#define CRED "\x1b[31m" +#define CYELLOW "\x1b[33m" +#define CRESET "\x1b[0m" + + +const char* __clar_descs[] = { + "fatal", + "error", + "warn", + "info", + "debug" +}; + +const char* __clar_colors[] = { + CRED, + CRED, + CYELLOW, + CRESET, + CRESET +}; + +struct clarifier __clar_default_fmt; + +char* __clar_fmt = NULL; +char* __clar_buf = NULL; +bool __clar_logging_enabled = true; + +uint32_t __clar_get_fmt_size( + const char* restrict fmt, + va_list* args + ) { + return vsnprintf(NULL, 0, fmt, *args); +} + +void sputs( + char* buf, + const char* src, + uint32_t* offset + ) { + uint32_t len = strlen(src); + memcpy(buf, src, len); + *offset += len; +} + +uint32_t __clar_format( + struct clarifier* clar, + enum __clar_loglevel loglevel, + char* buf, + const char* fmt, + va_list* args, + bool colors + ) { + if (!clar) { + clar = &__clar_default_fmt; + } + + uint32_t offset = 0; + time_t cur_time = time(NULL); + struct tm* timeinfo = localtime(&cur_time); + + for (uint32_t i = 0; i < strlen(clar->format); ++i) { + switch (clar->format[i]) { + case '%': + switch (clar->format[i + 1]) { + case 'Y': + offset += strftime(buf + offset, 0xFFFF, "%Y", timeinfo); + break; + case 'M': + offset += strftime(buf + offset, 0xFFFF, "%b", timeinfo); + break; + case 'd': + offset += strftime(buf + offset, 0xFFFF, "%a", timeinfo); + break; + case 'D': + offset += strftime(buf + offset, 0xFFFF, "%d", timeinfo); + break; + case 'H': + offset += strftime(buf + offset, 0xFFFF, "%H", timeinfo); + break; + case 'm': + offset += strftime(buf + offset, 0xFFFF, "%M", timeinfo); + break; + case 's': + offset += strftime(buf + offset, 0xFFFF, "%S", timeinfo); + break; + case 'l': + offset += vsnprintf(buf + offset, 0xFFFF, fmt, *args); + break; + case 'x': + if (colors) { + sputs(buf + offset, __clar_colors[(uint8_t)loglevel], &offset); + sputs(buf + offset, __clar_descs[(uint8_t)loglevel], &offset); + sputs(buf + offset, CRESET, &offset); + } else { + sputs(buf + offset, __clar_descs[(uint8_t)loglevel], &offset); + } + break; + case '%': + default: + buf[offset++] = '%'; + break; + } + ++i; + break; + default: + buf[offset++] = clar->format[i]; + break; + } + } + //buf[offset++] = '\n'; + return offset; +} + +void __clar_log( + struct clarifier* clar, + enum __clar_loglevel loglevel, + const char* fmt, + va_list* args + ) { + if (loglevel > clar->loglevel) { + return; + } + uint32_t sz = __clar_get_fmt_size(fmt, args) + 2048; + char* buf = alloca(sz); + if (!buf) { + return; + } + memset(buf, 0x00, sz); + + if (clar->logtarget.output_mask & CLAR_OUT_STDOUT) { + __clar_format(clar, loglevel, buf, fmt, args, true); + fputs(buf, stdout); + fputc('\n', stdout); + } + if (clar->logtarget.output_mask & CLAR_OUT_FILE && + clar->logtarget.file) { + uint32_t size = __clar_format(clar, loglevel, buf, fmt, args, false) + 1; + if (clar->logtarget.max_size != 0 && clar->logtarget.cur_size + size > clar->logtarget.max_size) { + uint16_t len = strlen(clar->logtarget.filename); + char* buf1 = alloca(len + 8); // Enough for suffix + char* buf2 = alloca(len + 8); + fclose(clar->logtarget.file); + clar->logtarget.file = NULL; + clar->logtarget.cur_size = 0; + for (int32_t i = clar->logtarget.max_files - 1; i >= 0; --i) { + switch (i) { + case 0: + clar->logtarget.file = fopen(clar->logtarget.filename, "w"); + if (!clar->logtarget.file && clar->logtarget.output_mask & CLAR_OUT_STDOUT) { + __clar_format(clar, CLAR_LOG_ERROR, buf, "[[cclarify]] Failed to open log file!", NULL, true); + fputs(buf, stdout); + fputc('\n', stdout); + return; + } + break; + case 1: + snprintf(buf1, len + 8, "%s.%d", clar->logtarget.filename, i); + if (rename(clar->logtarget.filename, buf1) && clar->logtarget.output_mask & CLAR_OUT_STDOUT) { + __clar_format(clar, CLAR_LOG_ERROR, buf, "[[cclarify]] Failed to rotate logs!", NULL, true); + fputs(buf, stdout); + fputc('\n', stdout); + return; + } + break; + default: + snprintf(buf1, len + 8, "%s.%d", clar->logtarget.filename, i); + snprintf(buf2, len + 8, "%s.%d", clar->logtarget.filename, i - 1); + if (rename(buf2, buf1) && clar->logtarget.output_mask & CLAR_OUT_STDOUT) { + __clar_format(clar, CLAR_LOG_ERROR, buf, "[[cclarify]] Failed to rotate logs!", NULL, true); + fputs(buf, stdout); + fputc('\n', stdout); + return; + } + break; + } + } + } + clar->logtarget.cur_size += size; + fwrite(buf, 1, size, clar->logtarget.file); + fputc('\n', clar->logtarget.file); + } +} + +void clar_log( + enum __clar_loglevel loglevel, + const char* restrict fmt, + ... +); + +[[gnu::constructor]] +void __clar_construct() { + __clar_default_fmt.format = "%Y %M %d %D %H:%m:%s [%x] %l"; + __clar_default_fmt.loglevel = CLAR_LOG_DEBUG; + __clar_default_fmt.logtarget.output_mask = CLAR_OUT_STDOUT; +} + +[[gnu::destructor]] +void __clar_destroy() { + clar_destroy_logger(&__clar_default_fmt); +} diff --git a/test.c b/test.c index 37051d0..7e67135 100644 --- a/test.c +++ b/test.c @@ -1,20 +1,31 @@ #include "cclarify.h" -void func(){} -int func_assign(){return 1;} - int main(){ - GLOBAL_INIT(); - Clarifier clar; - int dupped = dup(fileno(stdout)); - init_loggerd(clar, dupped); - uint64_t t = 666; - ASSIGN(clar, t, 5); - MSG(clar, "Bruh", INFO); - MSG(clar, "Bruh", WARNING); - MSG(clar, "Bruh", ERROR); - EXEC(clar, func()); - MSG(clar, "Lets assign function return value to variable!", INFO); - ASSIGN(clar, t, func_assign()); - DISPLAY(clar, t); + CLAR_INIT("%Y %M %d %D %H:%m:%s [%x] %l"); + struct clarifier clar = clar_create_logger(CLAR_LOG_DEBUG, clar_create_logtarget("test.log", CLAR_OUT_FILE), "[%x] %l"); + clar_debug("test"); + clar_info("test"); + clar_warn("test"); + clar_err("test"); + clar_fatal("test"); + clar_fatal_with(&clar, "test"); + clar_log(CLAR_LOG_DEBUG, "Test %s %d", "HyperWin", 5); + + clar_debug("I'll try print a message with loglevel lower than the current global loglevel."); + clar_set_global_loglevel(CLAR_LOG_WARNING); + clar_info("Message that should not be seen"); + clar_set_global_loglevel(CLAR_LOG_DEBUG); + clar_debug("See? It works. Same with custom loggers."); + clar.loglevel = CLAR_LOG_WARNING; + clar_info_with(&clar, "You shouldn't see this."); + + struct clarifier clar2 = clar_create_logger(CLAR_LOG_DEBUG, clar_create_logtarget("test2.log", CLAR_OUT_FILE), "[%x] %l"); + clar_set_global_logger(&clar2); + clar_set_global_rotation("test2.log", 50, 65536); + + for (uint64_t i = 0; i < 10000000; ++i) { + clar_debug("Test, it: %d", i); + } + + puts("Completed 10.000.000 prints with max file count 50 and max size 64KB"); } diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 0000000..ed3b566 --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.15) +project(cclarify_test C) + +find_package(hyper-cclarify CONFIG REQUIRED) + +set(CMAKE_C_FLAGS "-O0 -g") +set(CMAKE_CXX_FLAGS "-O0 -g") + +add_executable(cclarify-tests + ${CMAKE_CURRENT_SOURCE_DIR}/../dist/munit/munit.c + ${PROJECT_SOURCE_DIR}/src/tests.c) +target_include_directories(cclarify-tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../dist/munit) +target_link_libraries(cclarify-tests hyper-cclarify::hyper-cclarify) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 0000000..aed6f6e --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,26 @@ +import os + +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout +from conan.tools.build import can_run + + +class pkgTestConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + + def requirements(self): + self.requires(self.tested_reference_str) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def layout(self): + cmake_layout(self) + + def test(self): + if can_run(self): + cmd = os.path.join(self.cpp.build.bindir, "cclarify-tests") + self.run(cmd, env="conanrun") diff --git a/test_package/src/tests.c b/test_package/src/tests.c new file mode 100644 index 0000000..cff5012 --- /dev/null +++ b/test_package/src/tests.c @@ -0,0 +1,213 @@ +#include +#include +#include + +#include "munit.h" +#include "cclarify.h" + +extern uint32_t __clar_format( + struct clarifier* clar, + enum __clar_loglevel loglevel, + char* buf, + const char* fmt, + va_list* args, + bool colors + ); + +MunitResult clar_year_test(const MunitParameter params[], void* ptr) { + char buf1[256], buf2[256]; + time_t tm; + struct tm* tm_; + + tm = time(NULL); + tm_ = localtime(&tm); + + clar_set_global_format("%Y"); + strftime(buf1, 255, "%Y", tm_); + __clar_format(&__clar_default_fmt, CLAR_LOG_DEBUG, buf2, "", NULL, true); + + munit_assert_string_equal(buf1, buf2); + + return MUNIT_OK; +} + +MunitResult clar_month_test(const MunitParameter params[], void* ptr) { + char buf1[256], buf2[256]; + time_t tm; + struct tm* tm_; + + tm = time(NULL); + tm_ = localtime(&tm); + + clar_set_global_format("%M"); + strftime(buf1, 255, "%b", tm_); + __clar_format(&__clar_default_fmt, CLAR_LOG_DEBUG, buf2, "", NULL, true); + + munit_assert_string_equal(buf1, buf2); + + return MUNIT_OK; +} + +MunitResult clar_day_test(const MunitParameter params[], void* ptr) { + char buf1[256], buf2[256]; + time_t tm; + struct tm* tm_; + + tm = time(NULL); + tm_ = localtime(&tm); + + clar_set_global_format("%d"); + strftime(buf1, 255, "%a", tm_); + __clar_format(&__clar_default_fmt, CLAR_LOG_DEBUG, buf2, "", NULL, true); + + munit_assert_string_equal(buf1, buf2); + + return MUNIT_OK; +} + +MunitResult clar_dof_test(const MunitParameter params[], void* ptr) { + char buf1[256], buf2[256]; + time_t tm; + struct tm* tm_; + + tm = time(NULL); + tm_ = localtime(&tm); + + clar_set_global_format("%D"); + strftime(buf1, 255, "%d", tm_); + __clar_format(&__clar_default_fmt, CLAR_LOG_DEBUG, buf2, "", NULL, true); + + munit_assert_string_equal(buf1, buf2); + + return MUNIT_OK; +} + +MunitResult clar_hour_test(const MunitParameter params[], void* ptr) { + char buf1[256], buf2[256]; + time_t tm; + struct tm* tm_; + + tm = time(NULL); + tm_ = localtime(&tm); + + clar_set_global_format("%H"); + strftime(buf1, 255, "%H", tm_); + __clar_format(&__clar_default_fmt, CLAR_LOG_DEBUG, buf2, "", NULL, true); + + munit_assert_string_equal(buf1, buf2); + + return MUNIT_OK; +} + +MunitResult clar_minute_test(const MunitParameter params[], void* ptr) { + char buf1[256], buf2[256]; + time_t tm; + struct tm* tm_; + + tm = time(NULL); + tm_ = localtime(&tm); + + clar_set_global_format("%m"); + strftime(buf1, 255, "%M", tm_); + __clar_format(&__clar_default_fmt, CLAR_LOG_DEBUG, buf2, "", NULL, true); + + munit_assert_string_equal(buf1, buf2); + + return MUNIT_OK; +} + +MunitResult clar_second_test(const MunitParameter params[], void* ptr) { + char buf1[256], buf2[256]; + time_t tm; + struct tm* tm_; + + tm = time(NULL); + tm_ = localtime(&tm); + + clar_set_global_format("%s"); + strftime(buf1, 255, "%S", tm_); + __clar_format(&__clar_default_fmt, CLAR_LOG_DEBUG, buf2, "", NULL, true); + + munit_assert_string_equal(buf1, buf2); + + return MUNIT_OK; +} + + +MunitTest tests[] = { + { + "cclarify-year-test", + clar_year_test, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL + }, + { + "cclarify-month-test", + clar_month_test, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL + }, + { + "cclarify-day-test", + clar_day_test, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL + }, + { + "cclarify-dof-test", + clar_dof_test, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL + }, + { + "cclarify-hour-test", + clar_hour_test, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL + }, + { + "cclarify-minute-test", + clar_minute_test, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL + }, + { + "cclarify-second-test", + clar_second_test, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, + NULL + }, + { + NULL, + NULL, + NULL, + NULL, + MUNIT_TEST_OPTION_NONE, NULL + } +}; + +static const MunitSuite test_suite = { + (char*)"", + tests, + NULL, + 128, + MUNIT_SUITE_OPTION_NONE +}; + +int main(int argc, char** argv) { + return munit_suite_main(&test_suite, (void*)"cclarify", argc, argv); +}