├── .clang_complete ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── api.md ├── changelog.md ├── dlg_android.c ├── examples │ ├── chain.c │ ├── example.c │ ├── example.cpp │ └── example.png ├── notes.md ├── tests │ ├── core.c │ ├── cpp.cpp │ ├── disabled.c │ ├── disabled.cpp │ ├── level.c │ ├── meson.build │ ├── outputf.cpp │ └── threads.cpp └── todo.md ├── include └── dlg │ ├── dlg.h │ ├── dlg.hpp │ └── output.h ├── meson.build ├── meson_options.txt └── src └── dlg └── dlg.c /.clang_complete: -------------------------------------------------------------------------------- 1 | -I./include 2 | -Wall 3 | -Wextra 4 | -Wpedantic -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths: 7 | - "**.c" 8 | - "**.cpp" 9 | - "**.h" 10 | - "**.hpp" 11 | - "**.inl" 12 | - "**meson.build" 13 | - "**ci.yml" 14 | pull_request: 15 | branches: [ master ] 16 | paths: 17 | - "**.c" 18 | - "**.cpp" 19 | - "**.h" 20 | - "**.hpp" 21 | - "**.inl" 22 | - "*meson.build" 23 | 24 | jobs: 25 | linux-gcc: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v1 29 | - uses: actions/setup-python@v1 30 | with: 31 | python-version: '3.x' 32 | - run: pip install meson ninja 33 | - run: sudo apt-get -y update 34 | - name: Install deps 35 | run: sudo apt-get -y -f install valgrind 36 | - run: meson setup build/ --backend=ninja -Dsample=true -Dtests=true 37 | env: 38 | CC: gcc 39 | - run: meson test -C build --wrapper 'valgrind --leak-check=full --error-exitcode=1' --print-errorlogs 40 | 41 | linux-gcc-release: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v1 45 | - uses: actions/setup-python@v1 46 | with: 47 | python-version: '3.x' 48 | - run: pip install meson ninja 49 | - run: sudo apt-get -y update 50 | - name: Install deps 51 | run: sudo apt-get -y -f install valgrind 52 | - run: meson setup build/ --backend=ninja -Dsample=true -Dtests=true -Dbuildtype=release 53 | env: 54 | CC: gcc 55 | - run: meson test -C build --wrapper 'valgrind --leak-check=full --error-exitcode=1' --print-errorlogs 56 | 57 | linux-clang: 58 | runs-on: ubuntu-latest 59 | steps: 60 | - uses: actions/checkout@v1 61 | - uses: actions/setup-python@v1 62 | with: 63 | python-version: '3.x' 64 | - name: Set up Clang 65 | uses: egor-tensin/setup-clang@v1 66 | with: 67 | version: latest 68 | - run: pip install meson ninja 69 | - run: sudo apt-get -y update 70 | - name: Install deps 71 | run: sudo apt-get -y -f install valgrind 72 | - run: meson setup build/ --backend=ninja -Dsample=true -Dtests=true 73 | - run: meson test -C build --wrapper 'valgrind --leak-check=full --error-exitcode=1' --print-errorlogs 74 | 75 | linux-clang-release: 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v1 79 | - uses: actions/setup-python@v1 80 | with: 81 | python-version: '3.x' 82 | - name: Set up Clang 83 | uses: egor-tensin/setup-clang@v1 84 | with: 85 | version: latest 86 | - run: pip install meson ninja 87 | - run: sudo apt-get -y update 88 | - name: Install deps 89 | run: sudo apt-get -y -f install valgrind 90 | - run: meson setup build/ --backend=ninja -Dsample=true -Dtests=true -Dbuildtype=release 91 | - run: meson test -C build --wrapper 'valgrind --leak-check=full --error-exitcode=1' --print-errorlogs 92 | 93 | # See https://dvdhrm.github.io/2021/04/21/meson-msvc-github-actions/ 94 | windows-ninja: 95 | runs-on: windows-latest 96 | steps: 97 | - uses: actions/checkout@v1 98 | - uses: actions/setup-python@v1 99 | with: 100 | python-version: '3.x' 101 | - run: pip install ninja 102 | # TODO, tmp workaround: See https://github.com/mesonbuild/meson/pull/9918 103 | - run: pip install git+https://github.com/frida/meson.git@f7f25b19a8d71cebf8e2934733eb041eb6862eee 104 | - name: Prepare MSVC 105 | uses: bus1/cabuild/action/msdevshell@v1 106 | with: 107 | architecture: x64 108 | - run: meson setup build/ --backend=ninja -Dsample=true -Dtests=true 109 | - run: meson test -C build/ -v 110 | 111 | windows: 112 | runs-on: windows-latest 113 | steps: 114 | - uses: actions/checkout@v1 115 | - uses: actions/setup-python@v1 116 | with: 117 | python-version: '3.x' 118 | - run: pip install ninja 119 | # TODO, tmp workaround: See https://github.com/mesonbuild/meson/pull/9918 120 | - run: pip install git+https://github.com/frida/meson.git@f7f25b19a8d71cebf8e2934733eb041eb6862eee 121 | - name: Prepare MSVC 122 | uses: bus1/cabuild/action/msdevshell@v1 123 | with: 124 | architecture: x64 125 | - run: meson setup build/ -Dsample=true -Dtests=true 126 | - run: meson test -C build/ -v 127 | 128 | windows-release: 129 | runs-on: windows-latest 130 | steps: 131 | - uses: actions/checkout@v1 132 | - uses: actions/setup-python@v1 133 | with: 134 | python-version: '3.x' 135 | - run: pip install ninja 136 | # TODO, tmp workaround: See https://github.com/mesonbuild/meson/pull/9918 137 | - run: pip install git+https://github.com/frida/meson.git@f7f25b19a8d71cebf8e2934733eb041eb6862eee 138 | - name: Prepare MSVC 139 | uses: bus1/cabuild/action/msdevshell@v1 140 | with: 141 | architecture: x64 142 | - run: meson setup build/ -Dsample=true -Dtests=true -Dbuildtype=release 143 | - run: meson test -C build/ -v 144 | - name: Upload binaries 145 | uses: actions/upload-artifact@v3 146 | with: 147 | name: Windows Binaries 148 | path: | 149 | build/dlg.dll 150 | build/dlg.lib 151 | build/dlg.pdb -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | a.exe 3 | build 4 | old 5 | *.sublime* 6 | android_cross.txt 7 | compile_commands.json 8 | .vscode 9 | .clangd 10 | .cache 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dlg 2 | === 3 | 4 | Just another lightweight logging library with C and C++ api. 5 | Pretty much everything can be configured (kinda like bring your own outputting 6 | and formatting and filtering system and handle all the additional 7 | fancy stuff yourself, but only if you want to, otherwise we got some 8 | pretty nice defaults for you). 9 | 10 | I tried hard to keep the amount of unneeded bullshit to a minimum (*adding loc 11 | amount to convince you here, pretty hard to write much bullshit besides a 12 | well-documented logging interface in a few hundred lines*): 13 | 14 | There are 3 headers: 15 | 16 | - [](include/dlg/dlg.h) (around 280 loc): Everything you need, no dependencies 17 | - [](include/dlg/output.h) (around 150 loc): Utilities for implementing custom output handlers 18 | - [](include/dlg/dlg.hpp) (around 330 loc): Modern C++11 utilities, typesafe formatter 19 | 20 | You can either build dlg.c as library or include it directly into your project 21 | (nothing else needed). 22 | The name stands for some kind of super clever word mixture of the words 'debug' 23 | and 'log' (think of something yourself, duh). Uses meson as build system, but 24 | you don't really need a build system for this after all. 25 | 26 | ## Show me something fancy already 27 | 28 | Besides a simple look into the [headers](include/dlg), have a look into the 29 | __[synopsis](docs/api.md)__ and additional documentation for the latest release. 30 | 31 | Thousands words, explanations and pictures don't say as much as a single __[code example](docs/examples/example.cpp)__ though. 32 | Here a simple preview of its core functionality (there is more fancy stuff, look into the linked example): 33 | 34 | ```c 35 | dlg_warn("This is a warning. If on a console, it will be printed yellow"); 36 | dlg_error("Errors are red. Colors work even on windows consoles"); 37 | dlg_assertm(1 == 2, "Well, this assertion will probably %s...", "fail"); 38 | dlg_infot(("tag1", "tag2"), "We can tag our stuff. Can be used to filter/redirect messages"); 39 | dlg_asserttm(("tag3"), 3 == 2, "The same goes for asserts"); 40 | dlg_info("Another feature: Utf-8 printing works automatically, even for שׁǐʼnďốẅś consoles"); 41 | dlg_fatal("This one is printed bold. For more information, read the linked example above already"); 42 | ``` 43 | 44 | Nontheless a rather beautiful picture of dlg in action for you. It is probably rather nonsensical without 45 | having read the example though: 46 | 47 | ![Here should a beautiful picture of dlg in action be erected. What a shame!](docs/examples/example.png) 48 | 49 | Note though that dlg can be used without weird dummy messages as well. 50 | Building the sample can be enabled by passing the 'sample' argument as true to meson (meson -Dsample=true). 51 | 52 | __Contributions of all kind are welcome, this is nothing too serious though__ 53 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | Up-to-date api 2 | ============== 3 | 4 | # Synopsis of dlg.h 5 | 6 | ## Config macros with default and semantics: 7 | 8 | ```c 9 | // Define this macro to make all dlg macros have no effect at all 10 | // #define DLG_DISABLE 11 | 12 | // the log/assertion levels below which logs/assertions are ignored 13 | #define DLG_LOG_LEVEL dlg_level_trace 14 | #define DLG_ASSERT_LEVEL dlg_level_trace 15 | 16 | // the assert level of dlg_assert 17 | #define DLG_DEFAULT_ASSERT dlg_level_error 18 | 19 | // evaluated to the 'file' member in dlg_origin 20 | #define DLG_FILE dlg__strip_root_path(__FILE__, DLG_BASE_PATH) 21 | 22 | // the base path stripped from __FILE__. If you don't override DLG_FILE set this to 23 | // the project root to make 'main.c' from '/some/bullshit/main.c' 24 | #define DLG_BASE_PATH "" 25 | 26 | // Default tags applied to all logs/assertions (in the defining file). 27 | // Must be in format ```#define DLG_DEFAULT_TAGS "tag1", "tag2"``` 28 | // or just nothing (as defaulted here) 29 | #define DLG_DEFAULT_TAGS 30 | 31 | // The function used for formatting. Can have any signature, but must be callable with 32 | // the arguments the log/assertions macros are called with. Must return a const char* 33 | // that will not be freed by dlg, the formatting function must keep track of it. 34 | // The formatting function might use dlg_thread_buffer or a custom owned buffer. 35 | // The returned const char* has to be valid until the dlg log/assertion ends. 36 | // Usually a c function with ... (i.e. using va_list) or a variadic c++ template do 37 | // allow formatting. 38 | #define DLG_FMT_FUNC 39 | ``` 40 | 41 | ## Core macros: 42 | 43 | ```c 44 | // Tagged/Untagged logging with variable level 45 | // Tags must always be in the format `("tag1", "tag2")` (including brackets) 46 | #define dlg_log(level, ...) 47 | #define dlg_logt(level, tags, ...) 48 | 49 | // Dynamic level assert macros in various versions for additional arguments 50 | #define dlg_assertl(level, expr) // assert without tags/message 51 | #define dlg_assertlt(level, tags, expr) // assert with tags 52 | #define dlg_assertlm(level, expr, ...) // assert with message 53 | #define dlg_assertltm(level, tags, expr, ...) // assert with tags & message 54 | 55 | // Static leveled logging 56 | #define dlg_trace(...) 57 | #define dlg_debug(...) 58 | #define dlg_info(...) 59 | #define dlg_warn(...) 60 | #define dlg_error(...) 61 | #define dlg_fatal(...) 62 | 63 | // Tagged leveled logging 64 | #define dlg_tracet(tags, ...) 65 | #define dlg_debugt(tags, ...) 66 | #define dlg_infot(tags, ...) 67 | #define dlg_warnt(tags, ...) 68 | #define dlg_errort(tags, ...) 69 | #define dlg_fatalt(tags, ...) 70 | 71 | // Assert macros useing DLG_DEFAULT_ASSERT as level 72 | #define dlg_assert(expr) 73 | #define dlg_assertt(tags, expr) 74 | #define dlg_assertm(expr, ...) 75 | #define dlg_asserttm(tags, expr, ...) 76 | 77 | // If conditions that execute code and potentially log on failure 78 | #define dlg_assertltm_or(level, tags, expr, code, ...) 79 | #define dlg_assertlm_or(level, expr, code, ...) 80 | #define dlg_assertl_or(level, expr, code) 81 | 82 | #define dlg_assert_or(expr, code) 83 | #define dlg_assertm_or(expr, code, ...) 84 | ``` 85 | 86 | ## Other 87 | 88 | The tag order in which they are stored in origin.tags (all of them preserved in order): 89 | 90 | - DLG_DEFAULT_TAGS 91 | - current tags set via dlg_add_tag 92 | - tags used in the specific call 93 | 94 | ```c 95 | // Represents the importance of a log/assertion call. 96 | enum dlg_level { 97 | dlg_level_trace = 0, // temporary used debug, e.g. to check if control reaches function 98 | dlg_level_debug, // general debugging, prints e.g. all major events 99 | dlg_level_info, // general useful information 100 | dlg_level_warn, // warning, something went wrong but might have no (really bad) side effect 101 | dlg_level_error, // something really went wrong; expect serious issues 102 | dlg_level_fatal // critical error; application is likely to crash/exit 103 | }; 104 | 105 | // Holds various information associated with a log/assertion call. 106 | // Forwarded to the output handler. 107 | struct dlg_origin { 108 | const char* file; 109 | unsigned int line; 110 | const char* func; 111 | enum dlg_level level; 112 | const char** tags; // null-terminated 113 | const char* expr; // assertion expression, otherwise null 114 | }; 115 | 116 | // Type of the output handler, see dlg_set_handler. 117 | typedef void(*dlg_handler)(const struct dlg_origin* origin, const char* string, void* data); 118 | 119 | // Sets the handler that is responsible for formatting and outputting log calls. 120 | // This function is not thread safe and the handler is set globally. 121 | // The handler itself must not change dlg tags or call a dlg macro. 122 | // The handler can also be used for various other things such as dealing 123 | // with failed assertions or filtering calls based on the passed tags. 124 | // The default handler is dlg_default_output (see its doc for more info). 125 | // If using c++ make sure the registered handler cannot throw e.g. by 126 | // wrapping everything into a try-catch blog. 127 | void dlg_set_handler(dlg_handler handler, void* data); 128 | 129 | // Returns the currently active dlg handler and sets `data` to 130 | // its user data pointer. `data` must not be NULL. 131 | // Useful to create handler chains. 132 | // This function is not threadsafe, i.e. retrieving the handler while 133 | // changing it from another thread is unsafe. 134 | // See `dlg_set_handler`. 135 | dlg_handler dlg_get_handler(void** data); 136 | 137 | // The default output handler. Pass a valid FILE* as stream or NULL to use stderr/stdout. 138 | // Simply calls dlg_generic_output from dlg/output.h with the file_line feature enabled, 139 | // the style feature enabled if the stream is a console (and if on windows ansi mode could 140 | // be set) and dlg_default_output_styles as styles. 141 | // It also flushes the stream used. 142 | void dlg_default_output(const struct dlg_origin* origin, const char* string, void* stream); 143 | 144 | // Adds the given tag associated with the given function to the thread specific list. 145 | // If func is not NULL the tag will only applied to calls from the same function. 146 | // Remove the tag again calling dlg_remove_tag (with exactly the same pointers!). 147 | // Does not check if the tag is already present. 148 | void dlg_add_tag(const char* tag, const char* func); 149 | 150 | // Removes a tag added with dlg_add_tag (has no effect for tags no present). 151 | // The pointers must be exactly the same pointers that were supplied to dlg_add_tag, 152 | // this function will not check using strcmp. When the same tag/func combination 153 | // is added multiple times, this function remove exactly one candidate, it is 154 | // undefined which. Returns whether a tag was found (and removed). 155 | bool dlg_remove_tag(const char* tag, const char* func); 156 | 157 | // Returns the thread-specific buffer and its size for dlg. 158 | // The buffer should only be used by formatting functions. 159 | // The buffer can be reallocated and the size changed, just make sure 160 | // to update both values correctly. 161 | char** dlg_thread_buffer(size_t** size); 162 | ``` 163 | 164 | # Synopsis of output.h 165 | 166 | 167 | ```c 168 | // Text style 169 | enum dlg_text_style { 170 | dlg_text_style_reset = 0, 171 | dlg_text_style_bold = 1, 172 | dlg_text_style_dim = 2, 173 | dlg_text_style_italic = 3, 174 | dlg_text_style_underline = 4, 175 | dlg_text_style_blink = 5, 176 | dlg_text_style_rblink = 6, 177 | dlg_text_style_reversed = 7, 178 | dlg_text_style_conceal = 8, 179 | dlg_text_style_crossed = 9, 180 | dlg_text_style_none, 181 | }; 182 | 183 | // Text color 184 | enum dlg_color { 185 | dlg_color_black = 0, 186 | dlg_color_red, 187 | dlg_color_green, 188 | dlg_color_yellow, 189 | dlg_color_blue, 190 | dlg_color_magenta, 191 | dlg_color_cyan, 192 | dlg_color_gray, 193 | dlg_color_reset = 9, 194 | 195 | dlg_color_black2 = 60, 196 | dlg_color_red2, 197 | dlg_color_green2, 198 | dlg_color_yellow2, 199 | dlg_color_blue2, 200 | dlg_color_magenta2, 201 | dlg_color_cyan2, 202 | dlg_color_gray2, 203 | 204 | dlg_color_none = 69, 205 | }; 206 | 207 | struct dlg_style { 208 | enum dlg_text_style style; 209 | enum dlg_color fg; 210 | enum dlg_color bg; 211 | }; 212 | 213 | // Like fprintf but fixes utf-8 output to console on windows. 214 | // On non-windows sytems just uses the corresponding standard library 215 | // functions. On windows, if dlg was compiled with the win_console option, 216 | // will first try to output it in a way that allows the default console 217 | // to display utf-8. If that fails, will fall back to the standard 218 | // library functions. 219 | int dlg_fprintf(FILE* stream, const char* format, ...); 220 | int dlg_vfprintf(FILE* stream, const char* format, va_list list); 221 | 222 | // Like dlg_printf, but also applies the given style to this output. 223 | // The style will always be applied (using escape sequences), independent of the given stream. 224 | // On windows escape sequences don't work out of the box, see dlg_win_init_ansi(). 225 | int dlg_styled_fprintf(FILE* stream, const struct dlg_style style, 226 | const char* format, ...); 227 | 228 | // Features to output from the generic output handler. 229 | // Some features might have only an effect in the specializations. 230 | enum dlg_output_feature { 231 | dlg_output_tags = 1, // output tags list 232 | dlg_output_time = 2, // output time of log call (hour:minute:second) 233 | dlg_output_style = 4, // whether to use the supplied styles 234 | dlg_output_func = 8, // output function 235 | dlg_output_file_line = 16, // output file:line, 236 | dlg_output_newline = 32, // output a newline at the end 237 | dlg_output_threadsafe = 64, // locks stream before printing 238 | dlg_output_time_msecs = 128 // output micro seconds (ms on windows) 239 | }; 240 | 241 | // The default level-dependent output styles. The array values represent the styles 242 | // to be used for the associated level (i.e. [0] for trace level). 243 | const struct dlg_style dlg_default_output_styles[6]; 244 | 245 | // Generic output function. Used by the default output handler and might be useful 246 | // for custom output handlers (that don't want to manually format the output). 247 | // Will call the given output func with the given data (and format + args to print) 248 | // for everything it has to print in printf format. 249 | // See also the *_stream and *_buf specializations for common usage. 250 | // The given output function must not be NULL. 251 | typedef void(*dlg_generic_output_handler)(void* data, const char* format, ...); 252 | void dlg_generic_output(dlg_generic_output_handler output, void* data, 253 | unsigned int features, const struct dlg_origin* origin, const char* string, 254 | const struct dlg_style styles[6]); 255 | 256 | // Generic output function. Used by the default output handler and might be useful 257 | // for custom output handlers (that don't want to manually format the output). 258 | // If stream is NULL uses stdout. 259 | // Automatically uses dlg_fprintf to assure correct utf-8 even on windows consoles. 260 | // Locks the stream (i.e. assures threadsafe access) when the associated feature 261 | // is passed (note that stdout/stderr might still mix from multiple threads). 262 | void dlg_generic_output_stream(FILE* stream, unsigned int features, 263 | const struct dlg_origin* origin, const char* string, 264 | const struct dlg_style styles[6]); 265 | 266 | // Generic output function (see dlg_generic_output) that uses a buffer instead of 267 | // a stream. buf must at least point to *size bytes. Will set *size to the number 268 | // of bytes written (capped to the given size), if buf == NULL will set *size 269 | // to the needed size. The size parameter must not be NULL. 270 | void dlg_generic_output_buf(char* buf, size_t* size, unsigned int features, 271 | const struct dlg_origin* origin, const char* string, 272 | const struct dlg_style styles[6]); 273 | 274 | // Returns if the given stream is a tty. Useful for custom output handlers 275 | // e.g. to determine whether to use color. 276 | // NOTE: Due to windows limitations currently returns false for wsl ttys. 277 | bool dlg_is_tty(FILE* stream); 278 | 279 | // Returns the null-terminated escape sequence for the given style into buf. 280 | // Undefined behvaiour if any member of style has a value outside its enum range (will 281 | // probably result in a buffer overflow or garbage being printed). 282 | // If all member of style are 'none' will simply nullterminate the first buf char. 283 | void dlg_escape_sequence(const struct dlg_style style, char buf[12]); 284 | 285 | // The reset style escape sequence. 286 | const char* dlg_reset_sequence; 287 | 288 | // Just returns true without other effect on non-windows systems or if dlg 289 | // was compiled without the win_console option. 290 | // On windows tries to set the console mode to ansi to make escape sequences work. 291 | // This works only on newer windows 10 versions. Returns false on error. 292 | // Only the first call to it will have an effect, following calls just return the result. 293 | // The function is threadsafe. Automatically called by the default output handler. 294 | // This will only be able to set the mode for the stdout and stderr consoles, so 295 | // other streams to consoles will still not work. 296 | bool dlg_win_init_ansi(void); 297 | ``` 298 | 299 | # Synopsis of dlg.hpp 300 | 301 | ```cpp 302 | // By default this header automatically uses a different, typesafe formatting 303 | // function. Make sure to never include dlg.h in your translation unit before 304 | // including dlg.hpp to make this work. 305 | // The new formatting function works like a type-safe version of printf, see dlg::format. 306 | // It can also be called with only an object (e.g. dlg_info(42)) which will 307 | // then simply output the object. 308 | #ifndef DLG_FMT_FUNC 309 | #define DLG_FMT_FUNC 310 | #endif 311 | 312 | // The default string to replace by the dlg::*format functions. 313 | // Used as default by tlformat (set as new DLG_FMT_FUNC) or dlg::format. 314 | // If a custom replace string is required in certain situations without 315 | // overriding this macro, use dlg::rformat or dlg::gformat. 316 | #ifndef DLG_FORMAT_DEFAULT_REPLACE 317 | #define DLG_FORMAT_DEFAULT_REPLACE "{}" 318 | #endif 319 | 320 | namespace dlg { 321 | 322 | // Sets dlg tags on its construction and removes them on its destruction. 323 | // Instead of explicitly constructing an object, just use the dlg_tags and 324 | // dlg_tags_global macros which will construct one in the current scope. 325 | // Just forwards the arguments on construction to dlg_add_tag, so if func 326 | // is nullptr the tags will be applied even to called functions from the current 327 | // scope, otherwise only to calls coming directly from the current function. 328 | class TagsGuard { 329 | public: 330 | TagsGuard(const char** tags, const char* func); 331 | ~TagsGuard(); 332 | 333 | protected: 334 | const char** tags_; 335 | const char* func_; 336 | }; 337 | 338 | // Constructs a dlg::TagsGuard in the current scope, passing correctly the 339 | // current function, i.e. only dlg calls made from other functions 340 | // that are called in the current scope will not use the given tags. 341 | // Expects the tags to be set as parameters like this: 342 | // ```dlg_tags("tag1", "tag2")```. 343 | #define dlg_tags(...) 344 | 345 | // Constructs a dlg::TagsGuard in the current scope, passing nullptr as func. 346 | // This means that even dlg calls made from other functions called in the current 347 | // scope will use those tags. 348 | // Expects the tags to be set as parameters like this: 349 | // ```dlg_tags_global("tag1", "tag2")```. 350 | #define dlg_tags_global(...) 351 | 352 | // Executes the given block only if dlg checking is enabled 353 | #define dlg_check(code) 354 | 355 | // Executes the given blocks with the given tags only if dlg checking is enabled 356 | // The tags must have the default `("tag1", "tag2")` format. 357 | #define dlg_checkt(tags, code) 358 | 359 | /// Alternative output handler that allows to e.g. set lambdas or member functions. 360 | using Handler = std::function; 361 | 362 | // Allows to set a std::function as dlg handler. 363 | // The handler should not throw, all exceptions (and non-exceptions) are caught 364 | // in a wrapper since they must not be passed through dlg (since it's c and dlg 365 | // might be called from c code). 366 | void set_handler(Handler handler); 367 | 368 | /// Generic version of dlg::format, allows to set the special string sequence 369 | /// to be replaced with arguments instead of using DLG_FORMAT_DEFAULT_REPLACE. 370 | /// Simply replaces all occurrences of 'replace' in 'fmt' with the given 371 | /// arguments (as printed using operator<< with an ostream) in order and 372 | /// prints everything to the given ostream. 373 | /// Throws std::invalid_argument if there are too few or too many arguments. 374 | /// If you want to print the replace string without being replaced, wrap 375 | /// it into backslashes (\\). If you want to print your own types, simply 376 | /// overload operator<< for ostream correctly. The replace string itself 377 | /// must not be a backslash character. 378 | /// - gformat("%", "number: '%', string: '%'", 42, "aye"); -> "number: '42', string: 'aye'" 379 | /// - gformat("{}", "{} replaced: \\{}\\", "not"); -> "not replaced: {}" 380 | /// - gformat("@", "@ @", 1); -> std::invalid_argument, too few arguments 381 | /// - gformat("#", "#", 1, 2); -> std::invalid_argument, too many arguments 382 | /// - gformat("$", "$ $", std::setw(5), 2); -> " 2" 383 | template 384 | void gformat(std::ostream& os, const char* replace, const char* fmt, Args&&... args); 385 | 386 | /// Simply calls gformat with a local stringstream and returns the stringstreams 387 | /// contents. 388 | template 389 | std::string rformat(const char* replace, const char* fmt, Args&&... args); 390 | 391 | /// Simply calls rformat with DLG_FORMAT_DEFAULT_REPLACE (defaulted to '{}') as 392 | /// replace string. 393 | template 394 | std::string format(const char* fmt, Args&&... args); 395 | 396 | /// Specialization of dlg_generic_output that returns a std::string. 397 | std::string generic_output(unsigned int features, 398 | const struct dlg_origin& origin, const char* string, 399 | const struct dlg_style styles[6] = dlg_default_output_styles); 400 | 401 | } // namespace dlg 402 | ``` 403 | 404 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | 2023-04-16 (cumulative) 2 | - Add `dlg_assert_or` macro that will execute code in case the 3 | assertion fails. 4 | Note that this will even happen when dlg is disabled, so suited 5 | for early-outs and defensive coding. 6 | - Add `DLG_FAILED_ASSERTION_TEXT(x)`, a wrapper around the failed 7 | expression of an assert. Only ever called when the assertion 8 | has failed, so can be used to add additional handling (e.g. 9 | throwing) in that case for certain build configs. Found to be 10 | useful for static analysis, to silence warnings asserted upon. 11 | - Rework how DLG_DISABLE works for api non-macro functions. 12 | The functions are now independent from DLG_DISABLE to prevent 13 | linking issues when DLG_DISABLE is defined 14 | 15 | === Release of v0.3 === 16 | 17 | 2019-12-07 18 | - Add dlg_get_handler to allow handler chaining for debugging 19 | [api addition] 20 | - dlg.h previously required DLG_DISABLE to be defined to 1, this 21 | was already documented differently in api.md and examples. 22 | Now it's enough if DLG_DISABLE is defined at all 23 | [breaking change] 24 | - rework dlg__strip_root_path to actually check for prefix. 25 | If the base path isn't a prefix of the file, we won't strip it. 26 | - change naming of header guard to not start with underscore 27 | 28 | === Release of v0.2.2 === 29 | 30 | 2018-5-20 31 | Rework the tags array building macro due to new errors in gcc8. 32 | Seems like the C++ way was undefined behavior before, now uses 33 | initializer_list. 34 | 35 | 2018-3-20 36 | Fix default_output_handler output on wsl, add option to disable special 37 | windows console handling. Also add options to always use color in 38 | default output handler (to work around wsl color limitation). 39 | 40 | 2018-3-19 41 | dlg.hpp: Allow tlformat (default dlg.hpp formatter) to be called with just a 42 | non-string object (which will then just be printed) 43 | 44 | 2018-01-17 45 | output.h: add dlg_output_msecs for more time output precision 46 | 47 | === Release of v0.2.1 === 48 | -------------------------------------------------------------------------------- /docs/dlg_android.c: -------------------------------------------------------------------------------- 1 | // Simple dlg output handler that uses the android ndk logging library. 2 | // Include and and link its library. 3 | // You probably want to rename the "" with the name 4 | // of your application/library. 5 | // 6 | // To use this in C simply use dlg_generic_output with the 7 | // default compute-size-then-malloc pattern. 8 | 9 | void output_handler(const dlg_origin& origin, const char* str) { 10 | auto features = dlg_output_file_line; 11 | auto output = dlg::generic_output(features, origin, str); 12 | 13 | auto prio = ANDROID_LOG_DEFAULT; 14 | switch(origin.level) { 15 | case dlg_level_trace: [[fallthrough]] 16 | case dlg_level_debug: 17 | prio = ANDROID_LOG_DEBUG; 18 | break; 19 | case dlg_level_info: 20 | prio = ANDROID_LOG_INFO; 21 | break; 22 | case dlg_level_warn: 23 | prio = ANDROID_LOG_WARN; 24 | break; 25 | case dlg_level_error: 26 | prio = ANDROID_LOG_ERROR; 27 | break; 28 | case dlg_level_fatal: 29 | prio = ANDROID_LOG_FATAL; 30 | break; 31 | default: 32 | break; 33 | } 34 | 35 | __android_log_write(prio, "", output.c_str()); 36 | } 37 | -------------------------------------------------------------------------------- /docs/examples/chain.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static dlg_handler old_handler; 5 | static void* old_data; 6 | 7 | void pre_handler(const struct dlg_origin* origin, const char* string, void* data) { 8 | (void) data; 9 | if(origin->expr && strcmp(origin->expr, "10 + 10 == 100") == 0) { 10 | return; 11 | } 12 | 13 | old_handler(origin, string, old_data); 14 | } 15 | 16 | int main() { 17 | old_handler = dlg_get_handler(&old_data); 18 | dlg_set_handler(pre_handler, NULL); 19 | dlg_assertm(false, "Obviously false"); 20 | dlg_assertm(10 + 10 == 100, "This won't be printed due to pre_handler"); 21 | } 22 | -------------------------------------------------------------------------------- /docs/examples/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // NOTE: could really be extended. 4 | // Maybe make the c++ example only showing the c++ specific features 5 | int main() { 6 | dlg_warn("This is a warning. If on a console, it will be printed yellow"); 7 | dlg_error("Errors are red. Colors work even on windows consoles"); 8 | dlg_assertm(1 == 2, "Well, this assertion will probably %s...", "fail"); 9 | dlg_infot(("tag1", "tag2"), "We can tag our stuff. Can be used to filter/redirect messages"); 10 | dlg_asserttm(("tag3"), 3 == 2, "The same goes for asserts"); 11 | dlg_info("Another feature: Utf-8 printing works automatically, even for שׁǐʼnďốẅś consoles"); 12 | dlg_fatal("This one is printed bold. For more information, read the example already"); 13 | } 14 | -------------------------------------------------------------------------------- /docs/examples/example.cpp: -------------------------------------------------------------------------------- 1 | // this is the probably most important config macro (for more see config.hpp): 2 | // if this is defined all dlg macros will do nothing (literally nothing, they 3 | // will not produce a single instruction). 4 | // #define DLG_DISABLE 5 | 6 | // for other macros see the header of dlg.h 7 | // dlg.hpp has some additional settings 8 | // we could e.g. set a custom file name, shorter than its in-project path 9 | #define DLG_FILE "example.cpp" 10 | 11 | #include // dlg macros and basic stuff 12 | #include // needed for custom output handler set later 13 | 14 | #include // since we manually use std::cout 15 | #include // since we might write to a file later on 16 | #include // std::find 17 | 18 | int main() 19 | { 20 | // dlg offers assert and log calls mainly 21 | // asserts work with or without message 22 | dlg_assert(true); 23 | 24 | // assertions with message have the 'm' prefix 25 | dlg_assertm(true, "This message should never be seen"); 26 | 27 | // there are various log levels, 28 | // realized via static named macros 29 | dlg_trace("A trace log"); 30 | dlg_warn("A warn log"); 31 | dlg_debug("A debug log"); 32 | dlg_info("An info log"); 33 | dlg_error("An error log"); 34 | dlg_fatal("A fatal log"); 35 | 36 | // you can also attach tags to log and assert calls 37 | // they can (as later shown) be used to e.g. filter certain 38 | // messages or to decide where messages should go 39 | // the tagged macros have a 't' suffix 40 | dlg_infot(("main"), "Use dlg tags to show where a log call came from"); 41 | dlg_infot(("main"), "We are in the main function"); 42 | dlg_infot(("main"), "That can also be done using a scoped tag guard"); 43 | 44 | { 45 | // This set those tags in the current scope 46 | // Use dlg_tags_global to also appply it to other functions 47 | // called from the given scope 48 | dlg_tags("dlg", "example", "main_sub"); 49 | 50 | dlg_info("Now we are using the tags specified above"); 51 | dlg_info("Btw, if this output is confusing for you, look at example.cpp"); 52 | dlg_info("The tags applied are not printed by default"); 53 | dlg_info("But we could output and use them (e.g. as filter) in a custom handler"); 54 | } 55 | 56 | // we can also use formatting 57 | // the default c api uses printf semantics but by including dlg.hpp we override 58 | // them to a small typesafe replacement that simply replaces a given 59 | // sequence (see dlg.hpp for config) with the printed object. 60 | // Uses std::ostream with operator<< so should work for everything and is extendable. 61 | // You could also use your custom formatting function (like libfmt) here if you wish. 62 | // It is not included (anymore) to reduce bloat. 63 | dlg_debug("Let's switch to debug output for some variation!"); 64 | dlg_debug("We can also {} strings", "format"); 65 | dlg_debug("You can use [{}], [{}] or [{}] formatter", "printf", 66 | "our own fmtlib-like, typesafe", "your own custom"); 67 | 68 | std::cout << "\n"; 69 | std::cout << "This is a message sent to std::cout\n"; 70 | std::cerr << "And this one sent to std::cerr\n"; 71 | std::cout << "They should not muddle with dlg in any way, i hope!\n"; 72 | std::cout << "(The empty lines above/below are done intentionally)\n"; 73 | std::cout << "\n"; 74 | 75 | // we can also filter out certain message, switch the outputs 76 | // we set the function that is called everytime somethin is to be outputted 77 | dlg::set_handler([](const struct dlg_origin& origin, const char* msg){ 78 | // don't print anything below warning messages 79 | // note that if this is done for performance reasons or statically project-wide 80 | // prefer to use the config macros since they will result in zero 81 | // compile and runtime overhead, this way will not 82 | if(origin.level < dlg_level_warn) 83 | return; 84 | 85 | // we can e.g. also filter out certain tags 86 | auto tend = origin.tags; 87 | while(*tend) ++tend; 88 | 89 | if(std::find(origin.tags, tend, std::string("filtered")) != tend) 90 | return; 91 | 92 | // depending on tags/type/level or even based on the messages content (probably 93 | // a bad idea) we an print the output to different streams/files 94 | // in this case we check if the origin has an expression associated, i.e. if 95 | // it came from an assert call and then write it without color to a file stream. 96 | // everything else is dumped with color to cout 97 | // NOTE: the simple color switch here will lead to troubles when cout 98 | // is e.g. redirected to a file (but could be useful again when using unix's 'less -R') 99 | std::ostream* os = &std::cout; 100 | bool use_color = true; 101 | if(origin.expr) { 102 | static std::ofstream log_file ("example_assert_log.txt"); 103 | if(log_file.is_open()) { 104 | os = &log_file; 105 | use_color = false; 106 | } 107 | } 108 | 109 | // we could add additional switches for e.g. file/line, tags or msg content 110 | 111 | // we call the generic output handler that will take care 112 | // of formatting the origin (tags/type/expression/level/file/line) and color. 113 | // We could print more stuff (like tags/time, see dlg/output.h) but stick 114 | // with some clean defaults here 115 | unsigned int features = dlg_output_file_line | dlg_output_newline; 116 | features |= use_color * dlg_output_style; 117 | (*os) << dlg::generic_output(features, origin, msg); 118 | }); 119 | 120 | // test out our custom output handler 121 | // assertions go into a file now 122 | // there are also custom assertion levels 123 | // we can also choose the level dynamically by using the macros with the 'l' suffix 124 | dlg_assertl(dlg_level_debug, 42 * 42 == -42); // should not be printed at all, level too low 125 | dlg_assertl(dlg_level_fatal, "dlg"[0] == 42); // should be printed into file 126 | dlg_assertm(false, "Error assert message"); // default assert level is error, printed to file 127 | 128 | // test the tag filtering 129 | dlg_trace("ayyy, this is never shown"); // level too low, not printed 130 | dlg_warn("Anyone for some beautiful yellow color?"); // printed to cout 131 | 132 | // this is filtered in our handler due to its tag; not printed at all 133 | dlg_fatalt(("filtered"), "I am {}: feelsbadman", "useless"); 134 | 135 | // example of a dynamic leveled, tagged log 136 | // dynamic level logs just use the 'log' macro 137 | dlg_logt(dlg_level_error, ("tag1", "tag2"), "error!"); 138 | 139 | // reset the output handler 140 | dlg_set_handler(dlg_default_output, NULL); 141 | 142 | dlg_assertm(true == false, "Assertions are printed to cout again"); // printed to cout 143 | dlg_tracet(("filtered"), "I am printed again, yeay"); // printed to cout 144 | 145 | dlg_info("If you don't like the colors here, don't despair; they may be changed!"); 146 | dlg_info("Also make sure to check out the example_assert_log file i printed to\n"); 147 | 148 | dlg_fatal("What now follows are the important bottom lines"); 149 | dlg_info("You must be really confused right now if you don't know what this program is"); 150 | dlg_info("Just read the dlg example file which uses all this to show off"); 151 | dlg_info("Now, it is time. I'm a busy program after all. A good day m'lady or my dear sir!"); 152 | 153 | // some of the utility functions for outputting custom stuff 154 | auto style = dlg_default_output_styles[dlg_level_trace]; 155 | dlg_styled_fprintf(stdout, style, "*tips fedora and flies away*\n"); 156 | } 157 | -------------------------------------------------------------------------------- /docs/examples/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyorain/dlg/72dfcc858c040c54a6a0b88fcb7e70ee186d3167/docs/examples/example.png -------------------------------------------------------------------------------- /docs/notes.md: -------------------------------------------------------------------------------- 1 | Notes 2 | ===== 3 | 4 | # General 5 | 6 | - The default of DLG_FILE (stripping DLG_BASE_PATH (if existent) from the current file) 7 | will not really compare DLG_BASE_PATH with the file name but just skip its length 8 | - Adding your project name as tag to all calls coming from your library is probably 9 | a good idea. It can either be done defining DLG_DEFAULT_TAGS e.g. from the 10 | build system (prefer this method). 11 | Or add macros that add the tags like this (usually not a good idea): 12 | 13 | ```c 14 | #define MY_ADD_TAGS(tags, ...) (MY_EVAL tags, __VA_ARGS__) 15 | #define MY_EVAL(...) __VA_ARGS__ 16 | 17 | #define my_warn(...) dlg_warnt(("my"), __VA_ARGS__) 18 | #deinfe my_warnt(tags, ...) dlg_warnt(MY_ADD_TAGS(tags, "my"), __VA_ARGS__) 19 | ``` 20 | 21 | __TODO__: Add some examples on how tags could be used 22 | 23 | # Windows/msvc troubleshooting 24 | 25 | Windows and msvc are fully supported (and tested) by dlg. 26 | We try to work around most of the... issues on windows by there are a few 27 | things you have to keep in mind for dlg to work. 28 | 29 | - msvc (at the state of 2017, still) does not allow/correctly handle utf-8 string literals 30 | - this means ```dlg_fprintf(stdout, u8"äü")``` will not work by default since the 31 | string passed there is not utf-8 encoded 32 | - The above example can be made to work using the ```/utf-8``` switch on msvc 33 | - note that dlg always passes filepaths in their native representation so with msvc 34 | the filepath will have backslashes (important if you e.g. want to handle logging 35 | calls from different files differently) 36 | - meson on windows: if you want to define DLG_BASE_PATH using meson you will have 37 | to work around the backslashes (e.g. in the path returned from ```meson.source_root()```) 38 | sine those would be interpreted as invalid escape characters and there is not meson 39 | function to escape backslashes correctly (as of 2017). 40 | 41 | DLG itself handles it this way: 42 | 43 | ```meson 44 | source_root = meson.source_root().split('\\') 45 | add_project_arguments('-DDLG_BASE_PATH="' + '/'.join(source_root) + '/"', language: 'c') 46 | ``` 47 | 48 | ## C-api tag guards 49 | 50 | ```c 51 | #define dlg_tag_base(global, tags, code) { \ 52 | const char* _dlg_tag_tags[] = {DLG_EVAL tags}; \ 53 | const char** _dlg_tag_ptr = _dlg_tag_tags;; \ 54 | const char* _dlg_tag_func = global ? NULL : __FUNCTION__; \ 55 | while(_dlg_tag_ptr) \ 56 | dlg_add_tag(_dlg_tag_ptr++, _dlg_tag_func); \ 57 | code \ 58 | _dlg_tag_ptr = _dlg_tag_tags; \ 59 | while(_dlg_tag_ptr) \ 60 | dlg_remove_tag(_dlg_tag_ptr++, _dlg_tag_func); \ 61 | } 62 | 63 | #if DLG_CHECK 64 | #define dlg_check(code) { code } 65 | #define dlg_checkt(tags, code) dlg_tag(tags, code) 66 | #else 67 | #define dlg_check(code) 68 | #define dlg_checkt(tags, code) 69 | #endif // DLG_CHECK 70 | 71 | #define dlg_tag(tags, code) dlg_tag_base(false, tags, code) 72 | #define dlg_tag_global(tags, code) dlg_tag_base(true, tags, code) 73 | ``` 74 | 75 | ## Idea: Custom assert macro handler 76 | 77 | ```c 78 | // Can be specified to a custom assert handler/checker. 79 | // Will be called for every failed assertion. If it returns true, 80 | // the default failed assertion log will be done by dlg, otherwise 81 | // no further action will be taken. 82 | // Must have the signature 'bool (const char* expr)'. 83 | // The default (dlg__assertion_failed) just always returns false. 84 | // Useful as breakpoint. 85 | #ifndef DLG_ASSERT_FILTER 86 | #define DLG_ASSERT_FILTER dlg__assertion_failed 87 | #endif 88 | 89 | bool dlg_assertion_failed(const char* expr); 90 | 91 | // 92 | #define DLG__CHECK_ASSERT(lvl, expr) \ 93 | (level > DLG_ASSERT_LEVEL && !(expr) && DLG_ASSERT_FILER(#expr)) 94 | 95 | #define dlg_assertl(level, expr) if(DLG__CHECK_ASSERT(level, expr)) \ 96 | dlg__do_log(level, DLG_CREATE_TAGS(NULL), DLG_FILE, __LINE__, __func__, NULL, #expr) 97 | 98 | // ... 99 | ``` 100 | -------------------------------------------------------------------------------- /docs/tests/core.c: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #define DLG_DEFAULT_ASSERT dlg_level_fatal 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // TODO: custom format functions (using dlg_thread_buffer) 12 | // other custom output handlers 13 | // some more output.h testing 14 | 15 | enum check { 16 | check_line = 1, 17 | check_tags = 2, 18 | check_expr = 4, 19 | check_string = 8, 20 | check_level = 16, 21 | check_fire = 32 22 | }; 23 | 24 | struct { 25 | enum check check; 26 | unsigned int line; 27 | const char** tags; 28 | const char* expr; 29 | const char* string; 30 | enum dlg_level level; 31 | bool fire; 32 | bool fired; 33 | } gdata = { 34 | .check = check_line, 35 | .fired = false 36 | }; 37 | 38 | unsigned int gerror = 0; 39 | FILE* check_file; 40 | FILE* check_formatted_file; 41 | 42 | void custom_handler(const struct dlg_origin* origin, const char* string, void* data); 43 | 44 | #define EXPECT(a) if(!(a)) { \ 45 | printf("$$$ Expect '" #a "' failed [%d]\n", __LINE__); \ 46 | ++gerror; \ 47 | } 48 | 49 | void foo_log(); 50 | void foo_assert(); 51 | 52 | int main() { 53 | printf("__FILE__: %s\n", __FILE__); 54 | 55 | dlg_log(dlg_level_trace, "trace %d", 1); 56 | dlg_log(dlg_level_debug, "debug %d", 1); 57 | dlg_log(dlg_level_info, "info %d", 1); 58 | dlg_log(dlg_level_warn, "warn %d", 1); 59 | dlg_log(dlg_level_error, "error %d", 1); 60 | dlg_log(dlg_level_fatal, "fatal %d", 1); 61 | printf("----\n"); // should have no color/style 62 | 63 | dlg_trace("%s %d", "trace", 2); 64 | dlg_debug("%s %d", "debug", 2); 65 | dlg_info("%s %d", "info", 2); 66 | dlg_warn("%s %d", "warn", 2); 67 | dlg_error("%s %d", "error", 2); 68 | dlg_fatal("%s %d", "fatal", 2); 69 | printf("----\n\n"); 70 | 71 | dlg_assertl(dlg_level_trace, false); 72 | dlg_assertl(dlg_level_debug, false); 73 | dlg_assertl(dlg_level_info, false); 74 | dlg_assertl(dlg_level_warn, false); 75 | dlg_assertl(dlg_level_error, false); 76 | dlg_assertl(dlg_level_fatal, false); 77 | printf("----\n\n"); 78 | 79 | dlg_assertlm(dlg_level_trace, false, "Should %s, %s", "fire", "trace"); 80 | dlg_assertlm(dlg_level_debug, false, "Should %s, %s", "fire", "debug"); 81 | dlg_assertlm(dlg_level_info, false, "Should %s, %s", "fire", "info"); 82 | dlg_assertlm(dlg_level_warn, false, "Should %s, %s", "fire", "warn"); 83 | dlg_assertlm(dlg_level_error, false, "Should %s, %s", "fire", "error"); 84 | dlg_assertlm(dlg_level_fatal, false, "Should %s, %s", "fire", "fatal"); 85 | printf("----\n\n"); 86 | 87 | dlg_assertlm(dlg_level_trace, true, "Should %s, %s", "not fire", "trace"); 88 | dlg_assertlm(dlg_level_debug, true, "Should %s, %s", "not fire", "debug"); 89 | dlg_assertlm(dlg_level_info, true, "Should %s, %s", "not fire", "info"); 90 | dlg_assertlm(dlg_level_warn, true, "Should %s, %s", "not fire", "warn"); 91 | dlg_assertlm(dlg_level_error, true, "Should %s, %s", "not fire", "error"); 92 | dlg_assertlm(dlg_level_fatal, true, "Should %s, %s", "not fire", "fatal"); 93 | printf("----\n\n"); 94 | 95 | dlg_set_handler(&custom_handler, &gdata); 96 | check_file = fopen("dlg_test_output.txt", "w"); 97 | check_formatted_file = fopen( "dlg_test_formatted_output.txt","w" ); 98 | EXPECT(!dlg_is_tty(check_file)); 99 | 100 | dlg_fprintf(check_file, u8"beginning of (some utf-8: äüß) %s", "test output file with default layout of dlg_features\n"); 101 | dlg_fprintf(check_formatted_file, u8"beginning of (some utf-8: äüß) %s", "test output file with custom layout of dlg_features\n"); 102 | 103 | // checks 104 | // logging 105 | const char* t1[] = {NULL}; 106 | gdata.check = check_level | check_line | check_string | check_expr | check_tags | check_fire; 107 | gdata.expr = NULL; 108 | gdata.fire = true; 109 | gdata.tags = t1; 110 | 111 | gdata.fired = false; 112 | gdata.string = "trace 3"; 113 | gdata.level = dlg_level_trace; 114 | gdata.line = __LINE__ + 1; 115 | dlg_trace("%s %d", "trace", 3); 116 | EXPECT(gdata.fired); 117 | 118 | gdata.fired = false; 119 | gdata.string = "debug 3"; 120 | gdata.level = dlg_level_debug; 121 | gdata.line = __LINE__ + 1; 122 | dlg_debug("%s %d", "debug", 3); 123 | EXPECT(gdata.fired); 124 | 125 | gdata.fired = false; 126 | gdata.string = "info 3"; 127 | gdata.level = dlg_level_info; 128 | gdata.line = __LINE__ + 1; 129 | dlg_info("%s %d", "info", 3); 130 | EXPECT(gdata.fired); 131 | 132 | gdata.fired = false; 133 | gdata.string = "warn 3"; 134 | gdata.level = dlg_level_warn; 135 | gdata.line = __LINE__ + 1; 136 | dlg_warn("%s %d", "warn", 3); 137 | EXPECT(gdata.fired); 138 | 139 | gdata.fired = false; 140 | gdata.string = "error 3"; 141 | gdata.level = dlg_level_error; 142 | gdata.line = __LINE__ + 1; 143 | dlg_error("%s %d", "error", 3); 144 | EXPECT(gdata.fired); 145 | 146 | gdata.fired = false; 147 | gdata.string = "fatal 3"; 148 | gdata.level = dlg_level_fatal; 149 | gdata.line = __LINE__ + 1; 150 | dlg_fatal("%s %d", "fatal", 3); 151 | EXPECT(gdata.fired); 152 | printf("----\n\n"); 153 | 154 | 155 | // assertion 156 | gdata.check &= ~check_line; 157 | gdata.string = NULL; 158 | gdata.level = DLG_DEFAULT_ASSERT; 159 | gdata.fire = true; 160 | gdata.fired = false; 161 | gdata.expr = "0 && 1"; 162 | dlg_assert(0 && 1); 163 | EXPECT(gdata.fired); 164 | 165 | gdata.string = "assertion 1"; 166 | gdata.expr = "1 && 0"; 167 | dlg_assertm(1 && 0, "%s %d", "assertion", 1); 168 | 169 | gdata.fire = false; 170 | dlg_assert(true || false); 171 | 172 | gdata.string = "assertion failed"; 173 | gdata.fire = true; 174 | gdata.level = dlg_level_warn; 175 | gdata.expr = "false && true"; 176 | gdata.fired = false; 177 | dlg_assertlm(dlg_level_warn, false && true, "%s%s %s", "assert", "ion", "failed"); 178 | EXPECT(gdata.fired); 179 | 180 | // tags 181 | // local, single 182 | const char* t2[] = {"tag1", NULL}; 183 | gdata.check = check_tags; 184 | gdata.tags = t2; 185 | gdata.fired = false; 186 | dlg_infot(("tag1"), "."); 187 | EXPECT(gdata.fired); 188 | dlg_logt(dlg_level_warn, ("tag1"), "."); 189 | dlg_assertt(("tag1"), false); 190 | dlg_asserttm(("tag1"), false, "."); 191 | dlg_assertltm(dlg_level_warn, ("tag1"), false, "."); 192 | 193 | // local, multiple 194 | const char* t3[] = {"tag1", "tag2", NULL}; 195 | gdata.tags = t3; 196 | gdata.fired = false; 197 | dlg_errort(("tag1", "tag2"), "."); 198 | EXPECT(gdata.fired); 199 | dlg_logt(dlg_level_trace, ("tag1", "tag2"), "."); 200 | dlg_assertt(("tag1", "tag2"), false); 201 | dlg_asserttm(("tag1", "tag2"), false, "."); 202 | dlg_assertltm(dlg_level_fatal, ("tag1", "tag2"), false, "."); 203 | 204 | // current 205 | const char* t4[] = {"lt", "gt", NULL}; 206 | gdata.tags = t4; 207 | gdata.fired = false; 208 | const char* lt = "lt"; 209 | const char* gt = "gt"; 210 | const char* func = __func__; 211 | dlg_add_tag(lt, func); 212 | dlg_add_tag(gt, NULL); 213 | dlg_info("."); 214 | EXPECT(gdata.fired); 215 | dlg_assert(false); 216 | 217 | EXPECT(dlg_remove_tag(lt, func)); 218 | EXPECT(dlg_remove_tag(gt, NULL)); 219 | EXPECT(!dlg_remove_tag(lt, func)); 220 | EXPECT(!dlg_remove_tag(gt, NULL)); 221 | 222 | gdata.tags = t1; 223 | gdata.fired = false; 224 | dlg_warn("."); 225 | dlg_assert(false); 226 | EXPECT(gdata.fired); 227 | 228 | // correct scope of added tags 229 | const char* lt3 = "lt3"; 230 | const char* gt3 = "gt3"; 231 | const char* t7[] = {gt3, NULL}; 232 | dlg_add_tag(lt3, func); 233 | dlg_add_tag(gt3, NULL); 234 | gdata.fired = false; 235 | gdata.tags = t7; 236 | foo_log(); 237 | EXPECT(gdata.fired); 238 | foo_assert(); 239 | EXPECT(dlg_remove_tag(lt3, func)); 240 | EXPECT(dlg_remove_tag(gt3, NULL)); 241 | EXPECT(!dlg_remove_tag(lt3, func)); 242 | EXPECT(!dlg_remove_tag("non-existent", NULL)); 243 | 244 | // reset handler 245 | fclose(check_file); 246 | fclose(check_formatted_file); 247 | 248 | dlg_set_handler(dlg_default_output, NULL); 249 | gdata.fired = false; 250 | dlg_info("Hai!"); 251 | EXPECT(!gdata.fired); 252 | 253 | // fprintf 254 | if(!dlg_win_init_ansi()) 255 | printf("$$$ dlg init ansi console, the following might get weird\n"); 256 | 257 | printf(" - There should follow some utf-8 chars\n"); 258 | 259 | struct dlg_style mstyle = { .style = dlg_text_style_bold, .fg = dlg_color_red, .bg = dlg_color_none }; 260 | dlg_fprintf(stdout, u8"Ŝǿмẽ śạოрłё ẶŠČÌĬ-ŧēם (%s, אָǒť %s ãşçĩị...): %d\n", "ẃέłĺ", "all", 42); 261 | 262 | printf(" - The following line should be bold red, using utf-8 chars\n"); 263 | 264 | dlg_styled_fprintf(stdout, mstyle, u8"ầŝƒđĵšҝďƒĵqשׂęрốґμĝĺ (<%s> in dingus-evlish)\n", "it's some kind of evlish"); 265 | 266 | // return count of total errors 267 | return gerror; 268 | } 269 | 270 | void foo_log() { 271 | dlg_info("log call from foo"); 272 | } 273 | 274 | void foo_assert() { 275 | dlg_assert(false); 276 | } 277 | 278 | void custom_handler(const struct dlg_origin* origin, const char* string, void* data) { 279 | gdata.fired = true; 280 | if(data != &gdata) { 281 | printf("$$$ handler: invalid data %p [%d]\n", data, origin->line); 282 | ++gerror; 283 | } 284 | 285 | if(strcmp(origin->file, "docs/tests/core.c") != 0) { 286 | printf("$$$ handler: invalid file %s [%d]\n", origin->file, origin->line); 287 | ++gerror; 288 | } 289 | 290 | if(gdata.check & check_string) { 291 | if((string == NULL) != (gdata.string == NULL)) { 292 | printf("$$$ handler: Invalid string (validness) %d, expected %d [%d]\n", 293 | (string != NULL), (gdata.string != NULL), origin->line); 294 | } else if(string && strcmp(string, gdata.string)) { 295 | printf("$$$ handler: invalid string '%s' [%d]\n", string, origin->line); 296 | } 297 | } 298 | 299 | if(gdata.check & check_expr) { 300 | if((origin->expr == NULL) != (gdata.expr == NULL)) { 301 | printf("$$$ handler: Invalid expr (ptr) %d, expected %d [%d]\n", 302 | (origin->expr != NULL), (gdata.expr != NULL), origin->line); 303 | } else if(origin->expr && strcmp(origin->expr, gdata.expr)) { 304 | printf("$$$ handler: invalid expr '%s' [%d]\n", origin->expr, origin->line); 305 | } 306 | } 307 | 308 | if(gdata.check & check_line && origin->line != gdata.line) { 309 | printf("$$$ handler: invalid line: %d, expected %d\n", origin->line, gdata.line); 310 | ++gerror; 311 | } 312 | 313 | if(gdata.check & check_level && origin->level != gdata.level) { 314 | printf("$$$ handler: invalid level: %d, expected %d [%d]\n", origin->level, gdata.level, origin->line); 315 | ++gerror; 316 | } 317 | 318 | if(gdata.check & check_fire && !gdata.fire) { 319 | printf("$$$ handler: fired although it should not [%d]\n", origin->line); 320 | ++gerror; 321 | } 322 | 323 | if(gdata.check & check_tags) { 324 | const char** tags = origin->tags; 325 | const char** etags = gdata.tags; 326 | while(*tags && *etags) { 327 | if(strcmp(*tags, *etags) != 0) { 328 | printf("$$$ handler: invalid tag %s, expected %s [%d]\n", *tags, *etags, origin->line); 329 | ++gerror; 330 | } 331 | ++tags; 332 | ++etags; 333 | } 334 | 335 | if(*tags && !*etags) { 336 | printf("$$$ handler: more tags than expected [%d]\n", origin->line); 337 | ++gerror; 338 | } 339 | 340 | if(!*tags && *etags) { 341 | printf("$$$ handler: fewer tags than expected [%d]\n", origin->line); 342 | ++gerror; 343 | } 344 | } 345 | 346 | unsigned int features = dlg_output_tags | dlg_output_time | dlg_output_file_line | dlg_output_newline ; 347 | 348 | 349 | dlg_generic_output_stream(check_file, features, origin, string, dlg_default_output_styles); 350 | 351 | // unsigned int features_o = dlg_output_file_line | dlg_output_func | dlg_output_tags | dlg_output_newline | dlg_output_time | dlg_output_time_msecs | dlg_output_threadsafe; 352 | // dlg_set_layout( "[ $ tags: {%t} $ time: %s%ms file: %l func: %F] " ); 353 | // dlg_generic_output_stream(check_formatted_file, features_o, origin, string, dlg_default_output_styles); 354 | // dlg_set_default_layout(); 355 | } 356 | -------------------------------------------------------------------------------- /docs/tests/cpp.cpp: -------------------------------------------------------------------------------- 1 | #define DLG_DEFAULT_TAGS "dlg" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | unsigned int gerror = 0; 10 | 11 | // TODO: pretty much todo... test all of the header correctly 12 | 13 | #define EXPECT(a) if(!(a)) { \ 14 | printf("$$$ Expect '" #a "' failed [%d]\n", __LINE__); \ 15 | ++gerror; \ 16 | } 17 | 18 | int main() { 19 | // utility functions 20 | EXPECT(dlg::rformat("$", "\\$\\") == "$"); 21 | EXPECT(dlg::rformat("$", "$\\", 0) == "0\\"); 22 | EXPECT(dlg::rformat("$", "\\$", 1) == "\\1"); 23 | EXPECT(dlg::rformat("$", "\\$\\$\\", 2) == "$2\\"); 24 | EXPECT(dlg::rformat("@", "@", "ayyy") == "ayyy"); 25 | EXPECT(dlg::format("{{}}", 2) == "{2}"); 26 | EXPECT(dlg::format("\\{}\\") == "{}"); 27 | EXPECT(dlg::format("\\{{}}\\", 2) == "\\{2}\\"); 28 | 29 | std::string a; 30 | for(auto i = 0; i < 1000; ++i) { 31 | a += std::to_string(i); 32 | } 33 | 34 | EXPECT(dlg::detail::tlformat("{}", a) == a); 35 | EXPECT(dlg::detail::tlformat(42) == std::string("42")); 36 | EXPECT(dlg::detail::tlformat("ỦŤ₣8 ťéŝť ŝťяïאָğ") 37 | == std::string("ỦŤ₣8 ťéŝť ŝťяïאָğ")); 38 | 39 | // TODO: more output.h testing 40 | { 41 | dlg_origin origin {}; 42 | 43 | size_t size; 44 | dlg_generic_output_buf(nullptr, &size, 0, &origin, 45 | "test", dlg_default_output_styles); 46 | EXPECT(size == 4u); 47 | 48 | auto str = dlg::generic_output(0, origin, "test string"); 49 | EXPECT(str == "test string"); 50 | } 51 | 52 | std::string str; 53 | str += 'a'; 54 | str += 'b'; 55 | str += 'c'; 56 | str += '\0'; 57 | str += 'd'; 58 | str += 'e'; 59 | str += 'f'; 60 | dlg_info("{} xyz", str); 61 | 62 | // check output 63 | enum check { 64 | check_line = 1, 65 | check_tags = 2, 66 | check_expr = 4, 67 | check_string = 8, 68 | check_level = 16, 69 | check_fire = 32 70 | }; 71 | 72 | struct { 73 | unsigned int check; 74 | const char* str; 75 | bool fired; 76 | } expected {}; 77 | 78 | dlg::set_handler([&](const struct dlg_origin& origin, const char* str){ 79 | expected.fired = true; 80 | if(expected.check & check_string) { 81 | if((str == nullptr) != (expected.str == nullptr) || 82 | (str && std::strcmp(str, expected.str) != 0)) { 83 | std::printf("$$$ handler: invalid string [%d]\n", origin.line); 84 | ++gerror; 85 | } 86 | } 87 | 88 | // output 89 | dlg_win_init_ansi(); 90 | dlg_generic_output_stream(nullptr, ~0u, &origin, str, dlg_default_output_styles); 91 | }); 92 | 93 | { 94 | dlg_tags("a", "b"); 95 | expected.check &= check_string; 96 | expected.str = "Just some formatted info"; 97 | dlg_infot(("tag1", "tag2"), "Just some {} info", "formatted"); 98 | } 99 | 100 | expected = {}; 101 | dlg_warnt(("tag2", "tag3"), "Just some {} warning: {} {}", "sick", std::setw(10), 69); 102 | dlg_assertm(true, "eeeehhh... {}", "wtf"); 103 | 104 | dlg_info("We can also just log objects"); 105 | dlg_info(42); 106 | 107 | str = "should fire... {} {}"; 108 | dlg_assertm(false, str, "!", 24); 109 | 110 | auto entered = false; 111 | dlg_checkt(("checked"), { 112 | entered = true; 113 | dlg_info("from inside the check block"); 114 | EXPECT(expected.fired); 115 | }); 116 | EXPECT(entered); 117 | 118 | return gerror; 119 | } 120 | -------------------------------------------------------------------------------- /docs/tests/disabled.c: -------------------------------------------------------------------------------- 1 | #define DLG_DISABLE 1 2 | #include 3 | 4 | struct { 5 | bool fired; 6 | } gdata = { 7 | .fired = false 8 | }; 9 | 10 | #define EXPECT(a) if(!(a)) { \ 11 | printf("$$$ Expect '" #a "' failed [%d]\n", __LINE__); \ 12 | ++gerror; \ 13 | } 14 | 15 | unsigned int gerror = 0; 16 | 17 | void custom_handler(const struct dlg_origin* origin, const char* string, void* data) { 18 | (void) origin; 19 | (void) string; 20 | (void) data; 21 | gdata.fired = true; 22 | } 23 | 24 | int main() 25 | { 26 | dlg_set_handler(custom_handler, NULL); 27 | dlg_info("well, this is not printed"); 28 | EXPECT(!gdata.fired); 29 | return gerror; 30 | } 31 | -------------------------------------------------------------------------------- /docs/tests/disabled.cpp: -------------------------------------------------------------------------------- 1 | #define DLG_DISABLE 1 2 | #include 3 | 4 | int main() 5 | { 6 | // TODO: test all usual features, make sure it is NOT printed 7 | dlg_info("well, this is not printed"); 8 | dlg_check(dlg_info("this is not printed as well")); 9 | } 10 | -------------------------------------------------------------------------------- /docs/tests/level.c: -------------------------------------------------------------------------------- 1 | #define DLG_LOG_LEVEL dlg_level_warn 2 | #define DLG_ASSERT_LEVEL dlg_level_debug 3 | #include 4 | 5 | struct { 6 | bool fired; 7 | } gdata = { 8 | .fired = false 9 | }; 10 | 11 | #define EXPECT(a) if(!(a)) { \ 12 | printf("$$$ Expect '" #a "' failed [%d]\n", __LINE__); \ 13 | ++gerror; \ 14 | } 15 | 16 | unsigned int gerror = 0; 17 | 18 | void custom_handler(const struct dlg_origin* origin, const char* string, void* data) { 19 | (void) origin; 20 | (void) string; 21 | (void) data; 22 | gdata.fired = true; 23 | } 24 | 25 | int main() 26 | { 27 | dlg_set_handler(custom_handler, NULL); 28 | 29 | // log 30 | dlg_trace("well, this is not printed"); 31 | dlg_debug("well, this is not printed"); 32 | dlg_info("well, this is not printed"); 33 | EXPECT(!gdata.fired); 34 | 35 | dlg_warn("well, this is printed"); 36 | EXPECT(gdata.fired); 37 | gdata.fired = false; 38 | 39 | dlg_error("well, this is printed"); 40 | EXPECT(gdata.fired); 41 | gdata.fired = false; 42 | 43 | dlg_fatal("well, this is printed"); 44 | EXPECT(gdata.fired); 45 | gdata.fired = false; 46 | 47 | // assert 48 | dlg_assertlm(dlg_level_trace, false, "well, this is not printed"); 49 | EXPECT(!gdata.fired); 50 | 51 | dlg_assertlm(dlg_level_debug, false, "well, this is printed"); 52 | EXPECT(gdata.fired); 53 | gdata.fired = false; 54 | 55 | dlg_assertlm(dlg_level_warn, false, "well, this is printed"); 56 | EXPECT(gdata.fired); 57 | gdata.fired = false; 58 | 59 | dlg_assertlm(dlg_level_error, false, "well, this is printed"); 60 | EXPECT(gdata.fired); 61 | gdata.fired = false; 62 | 63 | dlg_assertlm(dlg_level_fatal, false, "well, this is printed"); 64 | EXPECT(gdata.fired); 65 | gdata.fired = false; 66 | 67 | return gerror; 68 | } 69 | -------------------------------------------------------------------------------- /docs/tests/meson.build: -------------------------------------------------------------------------------- 1 | tests = [ 2 | ['core', 'core.c', []], 3 | ['level', 'level.c', []], 4 | ['cpp', 'cpp.cpp', []], 5 | ['disabledc', 'disabled.c', []], 6 | ['disabledcpp', 'disabled.cpp', []], 7 | ['threads', 'threads.cpp', [dep_threads]], 8 | ['outputf', 'outputf.cpp', []], 9 | ] 10 | 11 | foreach test : tests 12 | test(test[0], 13 | executable(test[0], test[1], 14 | c_args: common_args, 15 | cpp_args: common_args, 16 | dependencies: test[2] + [dlg_dep])) 17 | endforeach 18 | -------------------------------------------------------------------------------- /docs/tests/outputf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void sample() { 4 | dlg_trace("This is a trace"); 5 | dlg_debug("This is a debug info"); 6 | dlg_info("This is an info"); 7 | dlg_warn("This is a warning"); 8 | dlg_error("Errors are red"); 9 | dlg_assertm(1 == 2, "Well, this assertion will probably {}...", "fail"); 10 | dlg_infot(("tag1", "tag2"), "We can tag our stuff. Can be used to filter/redirect messages"); 11 | dlg_asserttm(("tag3"), 3 == 2, "The same goes for asserts"); 12 | dlg_info("Another feature: Utf-8 printing works automatically, even for שׁǐʼnďốẅś consoles"); 13 | dlg_fatal("This one is printed bold. For more information, read the example already"); 14 | } 15 | 16 | int main() { 17 | dlg::set_handler([&](const struct dlg_origin& origin, const char* str){ 18 | dlg_generic_outputf_stream(stdout, "%s%c\n", &origin, str, dlg_default_output_styles, false); 19 | }); 20 | 21 | dlg_info("Using output handler 1, pretty simple"); 22 | dlg_info("It should just output the messages in the appropriate styles"); 23 | sample(); 24 | 25 | std::printf("-------------------------------------------\n"); 26 | 27 | dlg::set_handler([&](const struct dlg_origin& origin, const char* str){ 28 | dlg_generic_outputf_stream(stdout, "[%o %h:%m {%t} %f] %s%c\n", &origin, str, dlg_default_output_styles, false); 29 | }); 30 | 31 | dlg_info("outputting pretty much all information now. Only message is styled"); 32 | sample(); 33 | std::fprintf(stdout, "Normal, non-dlg printf message. Should not be effected by style\n"); 34 | 35 | std::printf("-------------------------------------------\n"); 36 | 37 | 38 | dlg::set_handler([&](const struct dlg_origin& origin, const char* str){ 39 | dlg_generic_outputf_stream(stdout, "%s[%o %h:%m %f]%r %c %s[%t]%r\n", &origin, str, dlg_default_output_styles, false); 40 | }); 41 | 42 | dlg_info("This time, only the meta information is styled"); 43 | sample(); 44 | std::fprintf(stdout, "Normal, non-dlg printf message. Should not be effected by style\n"); 45 | } 46 | -------------------------------------------------------------------------------- /docs/tests/threads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // mainly used to test that everything works multithreaded and that 7 | // there will be no leaks 8 | void test() 9 | { 10 | dlg_info("main"); 11 | dlg_info("initing thread 2"); 12 | 13 | std::thread t([&](){ 14 | dlg_info("hello world from thread 2"); 15 | dlg_warn("Just a small {}", "warning"); 16 | dlg_info("Goodbye from thread 2"); 17 | dlg_info("thread 2 id: {}", std::this_thread::get_id()); 18 | 19 | for(auto i = 0u; i < 10; ++i) { 20 | dlg_warn("Some nice yellow warning (hopefully)"); 21 | std::this_thread::sleep_for(std::chrono::nanoseconds(10)); 22 | } 23 | }); 24 | 25 | dlg_error("Just some messages from the main thread"); 26 | dlg_info("thread 1 id: {}", std::this_thread::get_id()); 27 | dlg_info("Waiting on thread 2 to finally spawn..."); 28 | dlg_info("Hurry ffs!"); 29 | dlg_info("Well i guess i go to sleep for now..."); 30 | dlg_info("Wake me up then!"); 31 | 32 | for(auto i = 0u; i < 10; ++i) { 33 | std::this_thread::sleep_for(std::chrono::nanoseconds(i * 10)); 34 | dlg_info("Did he say something already?"); 35 | } 36 | 37 | dlg_info("Hopefully he said something by now..."); 38 | dlg_info("Goodbye from main thread"); 39 | 40 | t.join(); 41 | } 42 | 43 | int main() { 44 | // default handler (threadsafe) 45 | std::cout << " =================== 1 (threadsafe) ==================== \n"; 46 | test(); 47 | 48 | // custom handler (threadsafe) 49 | std::cout << " =================== 2 (threadsafe) ==================== \n"; 50 | auto tid1 = std::this_thread::get_id(); 51 | dlg::set_handler([&](const struct dlg_origin& origin, const char* str){ 52 | auto t = (std::this_thread::get_id() == tid1) ? "thread-1: " : "thread-2: "; 53 | dlg_win_init_ansi(); 54 | dlg_fprintf(stdout, "%s %s", t, dlg::generic_output(~0u, origin, str).c_str()); 55 | }); 56 | test(); 57 | 58 | // custom handler (not threadsafe) 59 | std::cout << " =================== 3 (not threasafe) ==================== \n"; 60 | dlg::set_handler([&](const struct dlg_origin& origin, const char* str){ 61 | unsigned int features = dlg_output_file_line | dlg_output_newline | 62 | dlg_output_style; 63 | dlg_generic_output_stream(stdout, features, &origin, str, dlg_default_output_styles); 64 | }); 65 | test(); 66 | } 67 | -------------------------------------------------------------------------------- /docs/todo.md: -------------------------------------------------------------------------------- 1 | # Ideas and todo 2 | 3 | - [x] return int from dlg printf wrappers 4 | - [x] windows utf-8 output (see ny) 5 | - [x] windows text style support 6 | - [x] assert without error message 7 | - [x] add real example and screenshot 8 | - [x] custom (changeable) base paths (for nested projects/header calls) __[DLG_FILE]__ 9 | - [x] possibility to get current scope (or more general: exception support) 10 | - [x] make default scope signs customizable by macro 11 | - [x] unit tests (at least some basic stuff) + ci (travis) 12 | - [x] c example 13 | - [ ] Could be extended. Examples can generally be improved/reworked 14 | - [x] extend testing (mainly tag setting scope) 15 | - test all macros (also disable and stuff) 16 | - [x] fix c++ example 17 | - [x] release version 0.2.0 18 | - [x] add appveyor testing (for mingw as well as visual studio) 19 | - [x] decide on whether to catch exceptions from assert expressions. Config variable? 20 | - Yeah, don't do it. We are c now 21 | - [x] make dlg.c valid c++ (mainly casting issues atm) 22 | - [x] update README picture 23 | - [ ] Fix all todos in dlg.c (mainly error handling questions) 24 | - [ ] Check GetLastError with winapi functions? 25 | - [x] contrib file that implements android log handler (using android liblog) 26 | - [ ] make wsl output faster. It currently triggers a WriteConsole error on 27 | every output and then falls back to default, performing the formatting 28 | twice 29 | - [ ] get stable (or make a list of what to do for 1.0) 30 | 31 | ### Kinda trashed ideas 32 | 33 | Just because you can something, doesn't mean you should, right? 34 | 35 | - [ ] Default dummy platform (fallback if neither unix nor windows detected) 36 | - [ ] rework/further strip fmt.hpp 37 | - [ ] since it is parsed to some type-erased list anyways, don't include the whole header 38 | - [ ] ~~constexpr string parsing~~ __[Not really worth it/fully possible i guess]__ 39 | - [ ] ~~warn about format issues~~ 40 | - [ ] ~~warn about unused but passed variables~~ 41 | - [ ] add field to Origin that determines whether the origin is inside a dlg::check block? 42 | - [ ] add at least really simply pattern matching utility function for tags? 43 | - [ ] make dlg_assert return false on failure 44 | - not that easy to accomplish actually since we use an if, if we would use ? : we would get 45 | an unused expression warning if it is not used. Not worth it 46 | - [ ] compile time format specifier validation (c++) instead of exceptions 47 | - really no way to implement it that is at least somewhat safe 48 | - [ ] assert_failed function (maybe as c symbol) that can be easily used as breakpoint 49 | - [ ] Also custom assertion handler? that is called inline (macro) and might throw? 50 | - [ ] example for custom failed assertion handle, i.e. print backtrace/exception/abort 51 | - Decided not worth it since all of this can already be achieved in the log 52 | handler. If you want an assert failed breakpoint, just check for expr 53 | in the log handler and depending on level call a function to 54 | set a breakpoint on. 55 | See notes.md for some first concepts though 56 | -------------------------------------------------------------------------------- /include/dlg/dlg.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #ifndef INC_DLG_DLG_H_ 6 | #define INC_DLG_DLG_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // Hosted at https://github.com/nyorain/dlg. 15 | // There are examples and documentation. 16 | // Issue reports and contributions appreciated. 17 | 18 | // - CONFIG - 19 | // Define this macro to make all dlg macros have no effect at all 20 | // #define DLG_DISABLE 21 | 22 | // the log/assertion levels below which logs/assertions are ignored 23 | // defaulted depending on the NDEBUG macro 24 | #ifndef DLG_LOG_LEVEL 25 | #ifdef NDEBUG 26 | #define DLG_LOG_LEVEL dlg_level_warn 27 | #else 28 | #define DLG_LOG_LEVEL dlg_level_trace 29 | #endif 30 | #endif 31 | 32 | #ifndef DLG_ASSERT_LEVEL 33 | #ifdef NDEBUG 34 | #define DLG_ASSERT_LEVEL dlg_level_warn 35 | #else 36 | #define DLG_ASSERT_LEVEL dlg_level_trace 37 | #endif 38 | #endif 39 | 40 | // the assert level of dlg_assert 41 | #ifndef DLG_DEFAULT_ASSERT 42 | #define DLG_DEFAULT_ASSERT dlg_level_error 43 | #endif 44 | 45 | // evaluated to the 'file' member in dlg_origin 46 | #ifndef DLG_FILE 47 | #define DLG_FILE dlg__strip_root_path(__FILE__, DLG_BASE_PATH) 48 | 49 | // the base path stripped from __FILE__. If you don't override DLG_FILE set this to 50 | // the project root to make 'main.c' from '/some/bullshit/main.c' 51 | #ifndef DLG_BASE_PATH 52 | #define DLG_BASE_PATH "" 53 | #endif 54 | #endif 55 | 56 | // Default tags applied to all logs/assertions (in the defining file). 57 | // Must be in format ```#define DLG_DEFAULT_TAGS "tag1", "tag2"``` 58 | // or just nothing (as defaulted here) 59 | #ifndef DLG_DEFAULT_TAGS 60 | #define DLG_DEFAULT_TAGS_TERM NULL 61 | #else 62 | #define DLG_DEFAULT_TAGS_TERM DLG_DEFAULT_TAGS, NULL 63 | #endif 64 | 65 | // The function used for formatting. Can have any signature, but must be callable with 66 | // the arguments the log/assertions macros are called with. Must return a const char* 67 | // that will not be freed by dlg, the formatting function must keep track of it. 68 | // The formatting function might use dlg_thread_buffer or a custom owned buffer. 69 | // The returned const char* has to be valid until the dlg log/assertion ends. 70 | // Usually a c function with ... (i.e. using va_list) or a variadic c++ template do 71 | // allow formatting. 72 | #ifndef DLG_FMT_FUNC 73 | #define DLG_FMT_FUNC dlg__printf_format 74 | #endif 75 | 76 | // Only overwrite (i.e. predefine) this if you know what you are doing. 77 | // On windows this is used to add the dllimport specified. 78 | // If you are using the static version of dlg (on windows) define 79 | // DLG_STATIC before including dlg.h 80 | #ifndef DLG_API 81 | #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(DLG_STATIC) 82 | #define DLG_API __declspec(dllimport) 83 | #else 84 | #define DLG_API 85 | #endif 86 | #endif 87 | 88 | // This macro is used when an assertion fails. It gets the source expression 89 | // and can return an alternative (that must stay alive). 90 | // Mainly useful to execute something on failed assertion. 91 | #ifndef DLG_FAILED_ASSERTION_TEXT 92 | #define DLG_FAILED_ASSERTION_TEXT(x) x 93 | #endif 94 | 95 | // - utility - 96 | // two methods needed since cplusplus does not support compound literals 97 | // and c does not support uniform initialization/initializer lists 98 | #ifdef __cplusplus 99 | #include 100 | #define DLG_CREATE_TAGS(...) std::initializer_list \ 101 | {DLG_DEFAULT_TAGS_TERM, __VA_ARGS__, NULL}.begin() 102 | #else 103 | #define DLG_CREATE_TAGS(...) (const char* const[]) {DLG_DEFAULT_TAGS_TERM, __VA_ARGS__, NULL} 104 | #endif 105 | 106 | #ifdef __GNUC__ 107 | #define DLG_PRINTF_ATTRIB(a, b) __attribute__ ((format (printf, a, b))) 108 | #else 109 | #define DLG_PRINTF_ATTRIB(a, b) 110 | #endif 111 | 112 | #ifdef __cplusplus 113 | extern "C" { 114 | #endif 115 | 116 | 117 | // Represents the importance of a log/assertion call. 118 | enum dlg_level { 119 | dlg_level_trace = 0, // temporary used debug, e.g. to check if control reaches function 120 | dlg_level_debug, // general debugging, prints e.g. all major events 121 | dlg_level_info, // general useful information 122 | dlg_level_warn, // warning, something went wrong but might have no (really bad) side effect 123 | dlg_level_error, // something really went wrong; expect serious issues 124 | dlg_level_fatal // critical error; application is likely to crash/exit 125 | }; 126 | 127 | // Holds various information associated with a log/assertion call. 128 | // Forwarded to the output handler. 129 | struct dlg_origin { 130 | const char* file; 131 | unsigned int line; 132 | const char* func; 133 | enum dlg_level level; 134 | const char** tags; // null-terminated 135 | const char* expr; // assertion expression, otherwise null 136 | }; 137 | 138 | // Type of the output handler, see dlg_set_handler. 139 | typedef void(*dlg_handler)(const struct dlg_origin* origin, const char* string, void* data); 140 | 141 | #ifndef DLG_DISABLE 142 | // Tagged/Untagged logging with variable level 143 | // Tags must always be in the format `("tag1", "tag2")` (including brackets) 144 | // Example usages: 145 | // dlg_log(dlg_level_warning, "test 1") 146 | // dlg_logt(("tag1, "tag2"), dlg_level_debug, "test %d", 2) 147 | #define dlg_log(level, ...) if(level >= DLG_LOG_LEVEL) \ 148 | dlg__do_log(level, DLG_CREATE_TAGS(NULL), DLG_FILE, __LINE__, __func__, \ 149 | DLG_FMT_FUNC(__VA_ARGS__), NULL) 150 | #define dlg_logt(level, tags, ...) if(level >= DLG_LOG_LEVEL) \ 151 | dlg__do_log(level, DLG_CREATE_TAGS tags, DLG_FILE, __LINE__, __func__, \ 152 | DLG_FMT_FUNC(__VA_ARGS__), NULL) 153 | 154 | // Dynamic level assert macros in various versions for additional arguments 155 | // Example usages: 156 | // dlg_assertl(dlg_level_warning, data != nullptr); 157 | // dlg_assertlt(("tag1, "tag2"), dlg_level_trace, data != nullptr); 158 | // dlg_asserttlm(("tag1), dlg_level_warning, data != nullptr, "Data must not be null"); 159 | // dlg_assertlm(dlg_level_error, data != nullptr, "Data must not be null"); 160 | #define dlg_assertl(level, expr) if(level >= DLG_ASSERT_LEVEL && !(expr)) \ 161 | dlg__do_log(level, DLG_CREATE_TAGS(NULL), DLG_FILE, __LINE__, __func__, NULL, \ 162 | DLG_FAILED_ASSERTION_TEXT(#expr)) 163 | #define dlg_assertlt(level, tags, expr) if(level >= DLG_ASSERT_LEVEL && !(expr)) \ 164 | dlg__do_log(level, DLG_CREATE_TAGS tags, DLG_FILE, __LINE__, __func__, NULL, \ 165 | DLG_FAILED_ASSERTION_TEXT(#expr)) 166 | #define dlg_assertlm(level, expr, ...) if(level >= DLG_ASSERT_LEVEL && !(expr)) \ 167 | dlg__do_log(level, DLG_CREATE_TAGS(NULL), DLG_FILE, __LINE__, __func__, \ 168 | DLG_FMT_FUNC(__VA_ARGS__), DLG_FAILED_ASSERTION_TEXT(#expr)) 169 | #define dlg_assertltm(level, tags, expr, ...) if(level >= DLG_ASSERT_LEVEL && !(expr)) \ 170 | dlg__do_log(level, DLG_CREATE_TAGS tags, DLG_FILE, __LINE__, \ 171 | __func__, DLG_FMT_FUNC(__VA_ARGS__), DLG_FAILED_ASSERTION_TEXT(#expr)) 172 | 173 | #define dlg__assert_or(level, tags, expr, code, msg) if(!(expr)) {\ 174 | if(level >= DLG_ASSERT_LEVEL) \ 175 | dlg__do_log(level, tags, DLG_FILE, __LINE__, __func__, msg, \ 176 | DLG_FAILED_ASSERTION_TEXT(#expr)); \ 177 | code; \ 178 | } (void) NULL 179 | 180 | // - Private interface: not part of the abi/api but needed in macros - 181 | // Formats the given format string and arguments as printf would, uses the thread buffer. 182 | DLG_API const char* dlg__printf_format(const char* format, ...) DLG_PRINTF_ATTRIB(1, 2); 183 | DLG_API void dlg__do_log(enum dlg_level lvl, const char* const*, const char*, int, 184 | const char*, const char*, const char*); 185 | DLG_API const char* dlg__strip_root_path(const char* file, const char* base); 186 | 187 | #else // DLG_DISABLE 188 | 189 | #define dlg_log(level, ...) 190 | #define dlg_logt(level, tags, ...) 191 | 192 | #define dlg_assertl(level, expr) // assert without tags/message 193 | #define dlg_assertlt(level, tags, expr) // assert with tags 194 | #define dlg_assertlm(level, expr, ...) // assert with message 195 | #define dlg_assertltm(level, tags, expr, ...) // assert with tags & message 196 | 197 | #define dlg__assert_or(level, tags, expr, code, msg) if(!(expr)) { code; } (void) NULL 198 | #endif // DLG_DISABLE 199 | 200 | // The API below is independent from DLG_DISABLE 201 | 202 | // Sets the handler that is responsible for formatting and outputting log calls. 203 | // This function is not thread safe and the handler is set globally. 204 | // The handler itself must not change dlg tags or call a dlg macro (if it 205 | // does so, the provided string or tags array in 'origin' might get invalid). 206 | // The handler can also be used for various other things such as dealing 207 | // with failed assertions or filtering calls based on the passed tags. 208 | // The default handler is dlg_default_output (see its doc for more info). 209 | // If using c++ make sure the registered handler cannot throw e.g. by 210 | // wrapping everything into a try-catch blog. 211 | DLG_API void dlg_set_handler(dlg_handler handler, void* data); 212 | 213 | // The default output handler. 214 | // Only use this to reset the output handler, prefer to use 215 | // dlg_generic_output (from output.h) which this function simply calls. 216 | // It also flushes the stream used and correctly outputs even from multiple threads. 217 | DLG_API void dlg_default_output(const struct dlg_origin*, const char* string, void*); 218 | 219 | // Returns the currently active dlg handler and sets `data` to 220 | // its user data pointer. `data` must not be NULL. 221 | // Useful to create handler chains. 222 | // This function is not threadsafe, i.e. retrieving the handler while 223 | // changing it from another thread is unsafe. 224 | // See `dlg_set_handler`. 225 | DLG_API dlg_handler dlg_get_handler(void** data); 226 | 227 | // Adds the given tag associated with the given function to the thread specific list. 228 | // If func is not NULL the tag will only applied to calls from the same function. 229 | // Remove the tag again calling dlg_remove_tag (with exactly the same pointers!). 230 | // Does not check if the tag is already present. 231 | DLG_API void dlg_add_tag(const char* tag, const char* func); 232 | 233 | // Removes a tag added with dlg_add_tag (has no effect for tags no present). 234 | // The pointers must be exactly the same pointers that were supplied to dlg_add_tag, 235 | // this function will not check using strcmp. When the same tag/func combination 236 | // is added multiple times, this function remove exactly one candidate, it is 237 | // undefined which. Returns whether a tag was found (and removed). 238 | DLG_API bool dlg_remove_tag(const char* tag, const char* func); 239 | 240 | // Returns the thread-specific buffer and its size for dlg. 241 | // The buffer should only be used by formatting functions. 242 | // The buffer can be reallocated and the size changed, just make sure 243 | // to update both values correctly. 244 | DLG_API char** dlg_thread_buffer(size_t** size); 245 | 246 | // Untagged leveled logging 247 | #define dlg_trace(...) dlg_log(dlg_level_trace, __VA_ARGS__) 248 | #define dlg_debug(...) dlg_log(dlg_level_debug, __VA_ARGS__) 249 | #define dlg_info(...) dlg_log(dlg_level_info, __VA_ARGS__) 250 | #define dlg_warn(...) dlg_log(dlg_level_warn, __VA_ARGS__) 251 | #define dlg_error(...) dlg_log(dlg_level_error, __VA_ARGS__) 252 | #define dlg_fatal(...) dlg_log(dlg_level_fatal, __VA_ARGS__) 253 | 254 | // Tagged leveled logging 255 | #define dlg_tracet(tags, ...) dlg_logt(dlg_level_trace, tags, __VA_ARGS__) 256 | #define dlg_debugt(tags, ...) dlg_logt(dlg_level_debug, tags, __VA_ARGS__) 257 | #define dlg_infot(tags, ...) dlg_logt(dlg_level_info, tags, __VA_ARGS__) 258 | #define dlg_warnt(tags, ...) dlg_logt(dlg_level_warn, tags, __VA_ARGS__) 259 | #define dlg_errort(tags, ...) dlg_logt(dlg_level_error, tags, __VA_ARGS__) 260 | #define dlg_fatalt(tags, ...) dlg_logt(dlg_level_fatal, tags, __VA_ARGS__) 261 | 262 | // Assert macros useing DLG_DEFAULT_ASSERT as level 263 | #define dlg_assert(expr) dlg_assertl(DLG_DEFAULT_ASSERT, expr) 264 | #define dlg_assertt(tags, expr) dlg_assertlt(DLG_DEFAULT_ASSERT, tags, expr) 265 | #define dlg_assertm(expr, ...) dlg_assertlm(DLG_DEFAULT_ASSERT, expr, __VA_ARGS__) 266 | #define dlg_asserttm(tags, expr, ...) dlg_assertltm(DLG_DEFAULT_ASSERT, tags, expr, __VA_ARGS__) 267 | 268 | // If (expr) does not evaluate to true, always executes 'code' (no matter what 269 | // DLG_ASSERT_LEVEL is or if dlg is disabled or not). 270 | // When dlg is enabled and the level is greater or equal to DLG_ASSERT_LEVEL, 271 | // logs the failed assertion. 272 | // Example usages: 273 | // dlg_assertl_or(dlg_level_warn, data != nullptr, return); 274 | // dlg_assertlm_or(dlg_level_fatal, data != nullptr, return, "Data must not be null"); 275 | // dlg_assert_or(data != nullptr, logError(); return false); 276 | #define dlg_assertltm_or(level, tags, expr, code, ...) dlg__assert_or(level, \ 277 | DLG_CREATE_TAGS tags, expr, code, DLG_FMT_FUNC(__VA_ARGS__)) 278 | #define dlg_assertlm_or(level, expr, code, ...) dlg__assert_or(level, \ 279 | DLG_CREATE_TAGS(NULL), expr, code, DLG_FMT_FUNC(__VA_ARGS__)) 280 | #define dlg_assertl_or(level, expr, code) dlg__assert_or(level, \ 281 | DLG_CREATE_TAGS(NULL), expr, code, NULL) 282 | 283 | #define dlg_assert_or(expr, code) dlg_assertl_or(DLG_DEFAULT_ASSERT, expr, code) 284 | #define dlg_assertm_or(expr, code, ...) dlg_assertlm_or(DLG_DEFAULT_ASSERT, expr, code, __VA_ARGS__) 285 | 286 | #ifdef __cplusplus 287 | } 288 | #endif 289 | 290 | #endif // header guard 291 | -------------------------------------------------------------------------------- /include/dlg/dlg.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #ifndef INC_DLG_DLG_HPP_ 6 | #define INC_DLG_DLG_HPP_ 7 | 8 | 9 | // By default this header automatically uses a different, typesafe formatting 10 | // function. Make sure to never include dlg.h in your translation unit before 11 | // including dlg.hpp to make this work. 12 | // The new formatting function works like a type-safe version of printf, see dlg::format. 13 | // TODO: override the default (via undef) if a dlg.h was included before this file? 14 | #ifndef DLG_FMT_FUNC 15 | #define DLG_FMT_FUNC ::dlg::detail::tlformat 16 | #elif defined(INC_DLG_DLG_H_) 17 | #warning "dlg.h was included before dlg.hpp, not overriding DLG_FMT_FUNC" 18 | #endif 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | // Define this macro as 1 to disable all dlg_check* blocks. 34 | // Note that checking is defined separately from DLG_DISABLE. 35 | // By default this is defined to 1 if NDEBUG is defined OR DLG_DISABLe is 1. 36 | #ifndef DLG_DISABLE_CHECK 37 | #if defined(NDEBUG) || defined(DLG_DISABLE) 38 | #define DLG_DISABLE_CHECK 1 39 | #else 40 | #define DLG_DISABLE_CHECK 0 41 | #endif 42 | #endif 43 | 44 | // The default string to replace by the dlg::*format functions. 45 | // Used as default by tlformat (set as new DLG_FMT_FUNC) or dlg::format. 46 | // If a custom replace string is required in certain situations without 47 | // overriding this macro, use dlg::rformat or dlg::gformat. 48 | #ifndef DLG_FORMAT_DEFAULT_REPLACE 49 | #define DLG_FORMAT_DEFAULT_REPLACE "{}" 50 | #endif 51 | 52 | namespace dlg { 53 | 54 | // Sets dlg tags on its construction and removes them on its destruction. 55 | // Instead of explicitly constructing an object, just use the dlg_tags and 56 | // dlg_tags_global macros which will construct one in the current scope. 57 | // Just forwards the arguments on construction to dlg_add_tag, so if func 58 | // is nullptr the tags will be applied even to called functions from the current 59 | // scope, otherwise only to calls coming directly from the current function. 60 | class TagsGuard { 61 | public: 62 | TagsGuard(const char** tags, const char* func) : tags_(tags), func_(func) { 63 | while(*tags) { 64 | dlg_add_tag(*tags, func); 65 | ++tags; 66 | } 67 | } 68 | 69 | ~TagsGuard() { 70 | while(*tags_) { 71 | dlg_remove_tag(*tags_, func_); 72 | ++tags_; 73 | } 74 | } 75 | 76 | protected: 77 | const char** tags_; 78 | const char* func_; 79 | }; 80 | 81 | #ifdef DLG_DISABLE 82 | // Constructs a dlg::TagsGuard in the current scope, passing correctly the 83 | // current function, i.e. only dlg calls made from other functions 84 | // that are called in the current scope will not use the given tags. 85 | // Expects the tags to be set as parameters like this: 86 | // ```dlg_tags("tag1", "tag2")```. 87 | #define dlg_tags(...) 88 | 89 | // Constructs a dlg::TagsGuard in the current scope, passing nullptr as func. 90 | // This means that even dlg calls made from other functions called in the current 91 | // scope will use those tags. 92 | // Expects the tags to be set as parameters like this: 93 | // ```dlg_tags_global("tag1", "tag2")```. 94 | #define dlg_tags_global(...) 95 | #else 96 | #define dlg_tags(...) \ 97 | const char* _dlgtags_[] = {__VA_ARGS__, nullptr}; \ 98 | ::dlg::TagsGuard _dlgltg_(_dlgtags_, __func__) 99 | #define dlg_tags_global(...) \ 100 | const char* _dlgtags_[] = {__VA_ARGS__, nullptr}; \ 101 | ::dlg::TagsGuard _dlggtg_(_dlgtags_.begin(), nullptr) 102 | #endif 103 | 104 | // TODO: move this to the c api? together with (a scoped version of) dlg_tags? 105 | #if DLG_DISABLE_CHECK 106 | // Executes the given block only if dlg checking is enabled 107 | #define dlg_check(code) 108 | 109 | // Executes the given blocks with the given tags only if dlg checking is enabled 110 | // The tags must have the default `("tag1", "tag2")` format. 111 | #define dlg_checkt(tags, code) 112 | #else 113 | #define dlg_check(code) do code while(0) 114 | #define dlg_checkt(tags, code) do { dlg_tags(tags); code } while(0) 115 | #endif 116 | 117 | /// Alternative output handler that allows to e.g. set lambdas or member functions. 118 | using Handler = std::function; 119 | 120 | /// Small std::string_view replacement that allows taking std::string 121 | /// and const char* arguments 122 | struct StringParam { 123 | StringParam(const std::string& s) : str(s.c_str()) {} 124 | StringParam(const char* s) : str(s) {} 125 | 126 | const char* str; 127 | }; 128 | 129 | /// Allows to set a std::function as dlg handler. 130 | /// The handler should not throw, all exceptions (and non-exceptions) are caught 131 | /// in a wrapper since they must not be passed through dlg (since it's c and dlg 132 | /// might be called from c code). 133 | inline void set_handler(Handler handler); 134 | 135 | // TODO: maybe don't use exceptions for wrong formats? 136 | 137 | /// Generic version of dlg::format, allows to set the special string sequence 138 | /// to be replaced with arguments instead of using DLG_FORMAT_DEFAULT_REPLACE. 139 | /// Simply replaces all occurrences of 'replace' in 'fmt' with the given 140 | /// arguments (as printed using operator<< with an ostream) in order and 141 | /// prints everything to the given ostream. 142 | /// Throws std::invalid_argument if there are too few or too many arguments. 143 | /// If you want to print the replace string without being replaced, wrap 144 | /// it into backslashes (\\). If you want to print your own types, simply 145 | /// overload operator<< for ostream correctly. The replace string itself 146 | /// must not be a backslash character. 147 | /// - gformat("%", "number: '%', string: '%'", 42, "aye"); -> "number: '42', string: 'aye'" 148 | /// - gformat("{}", "not replaced: \\{}\\"); -> "not replaced: {}" 149 | /// - gformat("$", "{} {}", 1); -> std::invalid_argument, too few arguments 150 | /// - gformat("$", "{}", 1, 2); -> std::invalid_argument, too many arguments 151 | /// - gformat("$", "{} {}", std::setw(5), 2); -> " 2" 152 | template 153 | void gformat(std::ostream& os, StringParam replace, StringParam fmt, Arg&& arg, Args&&... args); 154 | inline void gformat(std::ostream& os, StringParam replace, StringParam fmt); 155 | 156 | /// Simply calls gformat with a local stringstream and returns the stringstreams 157 | /// contents. 158 | template 159 | std::string rformat(StringParam replace, StringParam fmt, Args&&... args) { 160 | std::stringstream sstream; 161 | gformat(sstream, replace, fmt, std::forward(args)...); 162 | return sstream.str(); 163 | } 164 | 165 | /// Simply calls rformat with DLG_FORMAT_DEFAULT_REPLACE (defaulted to '{}') as 166 | /// replace string. 167 | template 168 | std::string format(StringParam fmt, Args&&... args) { 169 | return rformat(DLG_FORMAT_DEFAULT_REPLACE, fmt, std::forward(args)...); 170 | } 171 | 172 | /// Specialization of dlg_generic_output that returns a std::string. 173 | inline std::string generic_output(unsigned int features, 174 | const struct dlg_origin& origin, StringParam string, 175 | const struct dlg_style styles[6] = dlg_default_output_styles); 176 | 177 | 178 | // - Private interface & implementation - 179 | namespace detail { 180 | 181 | inline void handler_wrapper(const struct dlg_origin* origin, const char* str, void* data) { 182 | auto& handler = *static_cast(data); 183 | try { 184 | handler(*origin, str); 185 | } catch(const std::exception& err) { 186 | fprintf(stderr, "dlg.hpp: handler has thrown exception: '%s'\n", err.what()); 187 | } catch(...) { 188 | fprintf(stderr, "dlg.hpp: handler has thrown something else than std::exception"); 189 | } 190 | } 191 | 192 | // Implements std::basic_streambuf into the dlg_thread_buffer. 193 | class StreamBuffer : public std::basic_streambuf { 194 | public: 195 | StreamBuffer(char*& buf, std::size_t& size) : buf_(buf), size_(size) { 196 | setp(buf, buf + size); // we will only read from it 197 | } 198 | 199 | ~StreamBuffer() { 200 | // make sure only the end has a null terminator 201 | setp(std::remove(buf_, pptr(), '\0'), buf_ + size_); 202 | sputc('\0'); 203 | } 204 | 205 | int_type overflow(int_type ch = traits_type::eof()) override { 206 | if(pptr() >= epptr()) { 207 | auto off = pptr() - buf_; 208 | size_ = size_ * 2 + 1; 209 | buf_ = static_cast(std::realloc(buf_, size_)); 210 | if(!buf_) { 211 | size_ = 0; 212 | setp(nullptr, nullptr); 213 | return traits_type::eof(); 214 | } 215 | 216 | setp(buf_ + off, buf_ + size_); 217 | } 218 | 219 | if(!traits_type::eq_int_type(ch, traits_type::eof())) { 220 | *pptr() = (char_type) ch; 221 | pbump(1); 222 | } 223 | 224 | return 0; 225 | } 226 | 227 | protected: 228 | char*& buf_; 229 | size_t& size_; 230 | }; 231 | 232 | // Like std::strstr but only matches if target is not wrapped in backslashes, 233 | // otherwise prints the target. 234 | inline const char* find_next(std::ostream& os, const char*& src, const char* target) { 235 | auto len = std::strlen(target); 236 | const char* next = std::strstr(src, target); 237 | while(next && next > src && *(next - 1) == '\\' && *(next + len) == '\\') { 238 | os.write(src, next - src - 1); 239 | os.write(target, len); 240 | src = next + len + 1; 241 | next = std::strstr(next + 1, target); 242 | } 243 | 244 | return next; 245 | } 246 | 247 | // Used as DLG_FMT_FUNC, uses a threadlocal stringstream to not allocate 248 | // a new buffer on every call 249 | template 250 | const char* tlformat(StringParam fmt, Args&&... args) { 251 | { 252 | std::size_t* size; 253 | char** dbuf = dlg_thread_buffer(&size); 254 | detail::StreamBuffer buf(*dbuf, *size); 255 | std::ostream output(&buf); 256 | gformat(output, DLG_FORMAT_DEFAULT_REPLACE, fmt, std::forward(args)...); 257 | } 258 | 259 | return *dlg_thread_buffer(nullptr); 260 | } 261 | 262 | template::value>::type> 264 | const char* tlformat(Arg arg) { 265 | { 266 | std::size_t* size; 267 | char** dbuf = dlg_thread_buffer(&size); 268 | detail::StreamBuffer buf(*dbuf, *size); 269 | std::ostream output(&buf); 270 | output << arg; 271 | } 272 | 273 | return *dlg_thread_buffer(nullptr); 274 | } 275 | 276 | } // namespace detail 277 | 278 | void gformat(std::ostream& os, StringParam replace, StringParam fmt) { 279 | if(detail::find_next(os, fmt.str, replace.str)) { 280 | throw std::invalid_argument("Too few arguments given to format"); 281 | } 282 | 283 | os << fmt.str; 284 | } 285 | 286 | template 287 | void gformat(std::ostream& os, StringParam replace, StringParam fmt, Arg&& arg, Args&&... args) { 288 | const char* next = detail::find_next(os, fmt.str, replace.str); 289 | if(!next) { 290 | throw std::invalid_argument("Too many arguments to format supplied"); 291 | } 292 | 293 | // XXX: any drawback compared to formatted? 294 | // this allows to use temporary ostream modifiers only to real arguments 295 | os.write(fmt.str, next - fmt.str); 296 | os << std::forward(arg); 297 | auto len = std::strlen(replace.str); 298 | return gformat(os, replace, next + len, std::forward(args)...); 299 | } 300 | 301 | void set_handler(Handler handler) { 302 | static Handler handler_; 303 | handler_ = std::move(handler); 304 | dlg_set_handler(&detail::handler_wrapper, &handler_); 305 | } 306 | 307 | std::string generic_output(unsigned int features, 308 | const struct dlg_origin& origin, StringParam string, 309 | const struct dlg_style styles[6]) { 310 | std::size_t size; 311 | dlg_generic_output_buf(nullptr, &size, features, &origin, 312 | string.str, styles); 313 | std::string ret(++size, ' '); 314 | 315 | // NOTE: this might (theoretically) cause problems before C++17 316 | // but this function should return a string and all compilers 317 | // make it work. If you want to go sure, just use C++17 318 | // where it is guaranteed that this works (everybody is 319 | // using it anyways so shit will blow up if this fails) 320 | dlg_generic_output_buf(&ret[0], &size, features, &origin, 321 | string.str, styles); 322 | ret.pop_back(); // terminating null-char 323 | return ret; 324 | } 325 | 326 | } // namespace dlg 327 | 328 | #endif // header guard 329 | -------------------------------------------------------------------------------- /include/dlg/output.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #ifndef INC_DLG_OUTPUT_H_ 6 | #define INC_DLG_OUTPUT_H_ 7 | 8 | #include 9 | #include 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | // Text style 16 | enum dlg_text_style { 17 | dlg_text_style_reset = 0, 18 | dlg_text_style_bold = 1, 19 | dlg_text_style_dim = 2, 20 | dlg_text_style_italic = 3, 21 | dlg_text_style_underline = 4, 22 | dlg_text_style_blink = 5, 23 | dlg_text_style_rblink = 6, 24 | dlg_text_style_reversed = 7, 25 | dlg_text_style_conceal = 8, 26 | dlg_text_style_crossed = 9, 27 | dlg_text_style_none, 28 | }; 29 | 30 | // Text color 31 | enum dlg_color { 32 | dlg_color_black = 0, 33 | dlg_color_red, 34 | dlg_color_green, 35 | dlg_color_yellow, 36 | dlg_color_blue, 37 | dlg_color_magenta, 38 | dlg_color_cyan, 39 | dlg_color_gray, 40 | dlg_color_reset = 9, 41 | 42 | dlg_color_black2 = 60, 43 | dlg_color_red2, 44 | dlg_color_green2, 45 | dlg_color_yellow2, 46 | dlg_color_blue2, 47 | dlg_color_magenta2, 48 | dlg_color_cyan2, 49 | dlg_color_gray2, 50 | 51 | dlg_color_none = 69, 52 | }; 53 | 54 | struct dlg_style { 55 | enum dlg_text_style style; 56 | enum dlg_color fg; 57 | enum dlg_color bg; 58 | }; 59 | 60 | // Like fprintf but fixes utf-8 output to console on windows. 61 | // On non-windows sytems just uses the corresponding standard library 62 | // functions. On windows, if dlg was compiled with the win_console option, 63 | // will first try to output it in a way that allows the default console 64 | // to display utf-8. If that fails, will fall back to the standard 65 | // library functions. 66 | DLG_API int dlg_fprintf(FILE* stream, const char* format, ...) DLG_PRINTF_ATTRIB(2, 3); 67 | DLG_API int dlg_vfprintf(FILE* stream, const char* format, va_list list); 68 | 69 | // Like dlg_printf, but also applies the given style to this output. 70 | // The style will always be applied (using escape sequences), independent of the given stream. 71 | // On windows escape sequences don't work out of the box, see dlg_win_init_ansi(). 72 | DLG_API int dlg_styled_fprintf(FILE* stream, struct dlg_style style, 73 | const char* format, ...) DLG_PRINTF_ATTRIB(3, 4); 74 | 75 | // Features to output from the generic output handler. 76 | // Some features might have only an effect in the specializations. 77 | enum dlg_output_feature { 78 | dlg_output_tags = 1, // output tags list 79 | dlg_output_time = 2, // output time of log call (hour:minute:second) 80 | dlg_output_style = 4, // whether to use the supplied styles 81 | dlg_output_func = 8, // output function 82 | dlg_output_file_line = 16, // output file:line, 83 | dlg_output_newline = 32, // output a newline at the end 84 | dlg_output_threadsafe = 64, // locks stream before printing 85 | dlg_output_time_msecs = 128 // output micro seconds (ms on windows) 86 | }; 87 | 88 | // The default level-dependent output styles. The array values represent the styles 89 | // to be used for the associated level (i.e. [0] for trace level). 90 | DLG_API extern const struct dlg_style dlg_default_output_styles[6]; 91 | 92 | // Generic output function. Used by the default output handler and might be useful 93 | // for custom output handlers (that don't want to manually format the output). 94 | // Will call the given output func with the given data (and format + args to print) 95 | // for everything it has to print in printf format. 96 | // See also the *_stream and *_buf specializations for common usage. 97 | // The given output function must not be NULL. 98 | typedef void(*dlg_generic_output_handler)(void* data, const char* format, ...); 99 | DLG_API void dlg_generic_output(dlg_generic_output_handler output, void* data, 100 | unsigned int features, const struct dlg_origin* origin, const char* string, 101 | const struct dlg_style styles[6]); 102 | 103 | // Generic output function, using a format string instead of feature flags. 104 | // Use following conversion characters: 105 | // %h - output the time in H:M:S format 106 | // %m - output the time in milliseconds 107 | // %t - output the full list of tags, comma separated 108 | // %f - output the function name noted in the origin 109 | // %o - output the file:line of the origin 110 | // %s - print the appropriate style escape sequence. 111 | // %r - print the escape sequence to reset the style. 112 | // %c - The content of the log/assert 113 | // %% - print the '%' character 114 | // Only the above specified conversion characters are valid, the rest are 115 | // written as it is. 116 | DLG_API void dlg_generic_outputf(dlg_generic_output_handler output, void* data, 117 | const char* format_string, const struct dlg_origin* origin, 118 | const char* string, const struct dlg_style styles[6]); 119 | 120 | // Generic output function. Used by the default output handler and might be useful 121 | // for custom output handlers (that don't want to manually format the output). 122 | // If stream is NULL uses stdout. 123 | // Automatically uses dlg_fprintf to assure correct utf-8 even on windows consoles. 124 | // Locks the stream (i.e. assures threadsafe access) when the associated feature 125 | // is passed (note that stdout/stderr might still mix from multiple threads). 126 | DLG_API void dlg_generic_output_stream(FILE* stream, unsigned int features, 127 | const struct dlg_origin* origin, const char* string, 128 | const struct dlg_style styles[6]); 129 | DLG_API void dlg_generic_outputf_stream(FILE* stream, const char* format_string, 130 | const struct dlg_origin* origin, const char* string, 131 | const struct dlg_style styles[6], bool lock_stream); 132 | 133 | // Generic output function (see dlg_generic_output) that uses a buffer instead of 134 | // a stream. buf must at least point to *size bytes. Will set *size to the number 135 | // of bytes written (capped to the given size), if buf == NULL will set *size 136 | // to the needed size. The size parameter must not be NULL. 137 | DLG_API void dlg_generic_output_buf(char* buf, size_t* size, unsigned int features, 138 | const struct dlg_origin* origin, const char* string, 139 | const struct dlg_style styles[6]); 140 | DLG_API void dlg_generic_outputf_buf(char* buf, size_t* size, const char* format_string, 141 | const struct dlg_origin* origin, const char* string, 142 | const struct dlg_style styles[6]); 143 | 144 | // Returns if the given stream is a tty. Useful for custom output handlers 145 | // e.g. to determine whether to use color. 146 | // NOTE: Due to windows limitations currently returns false for wsl ttys. 147 | DLG_API bool dlg_is_tty(FILE* stream); 148 | 149 | // Returns the null-terminated escape sequence for the given style into buf. 150 | // Undefined behvaiour if any member of style has a value outside its enum range (will 151 | // probably result in a buffer overflow or garbage being printed). 152 | // If all member of style are 'none' will simply nullterminate the first buf char. 153 | DLG_API void dlg_escape_sequence(struct dlg_style style, char buf[12]); 154 | 155 | // The reset style escape sequence. 156 | DLG_API extern const char* const dlg_reset_sequence; 157 | 158 | // Just returns true without other effect on non-windows systems or if dlg 159 | // was compiled without the win_console option. 160 | // On windows tries to set the console mode to ansi to make escape sequences work. 161 | // This works only on newer windows 10 versions. Returns false on error. 162 | // Only the first call to it will have an effect, following calls just return the result. 163 | // The function is threadsafe. Automatically called by the default output handler. 164 | // This will only be able to set the mode for the stdout and stderr consoles, so 165 | // other streams to consoles will still not work. 166 | DLG_API bool dlg_win_init_ansi(void); 167 | 168 | #ifdef __cplusplus 169 | } // extern "C" 170 | #endif 171 | 172 | #endif // header guard 173 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('dlg', ['c', 'cpp'], 2 | version: '0.2.2', 3 | meson_version: '>=0.46.0', 4 | default_options: [ 5 | 'c_std=c11', 6 | 'cpp_std=c++11', 7 | 'warning_level=3', 8 | 'werror=true']) 9 | 10 | buildlib = get_option('buildlib') 11 | build_sample = get_option('sample') 12 | tests = get_option('tests') 13 | win_console = get_option('win_console') 14 | default_output_always_color = get_option('default_output_always_color') 15 | 16 | # dep variables depending on build type 17 | inc = include_directories('include') 18 | sources = [] 19 | libs = [] 20 | 21 | # base path, correctly escaped on windows 22 | base_path = join_paths([meson.source_root(), '']) 23 | add_project_arguments('-DDLG_BASE_PATH="' + base_path + '"', language: ['c', 'cpp']) 24 | message('DLG_BASE_PATH: ' + base_path) 25 | 26 | headers = files([ 27 | 'include/dlg/output.h', 28 | 'include/dlg/dlg.h', 29 | 'include/dlg/dlg.hpp', 30 | ]) 31 | 32 | # TODO: import args (dllimport)? 33 | # default warn settings 34 | dlg_args = [] 35 | common_args = [] 36 | deps = [] 37 | if meson.get_compiler('c').get_id() == 'msvc' 38 | # make utf-8 examples work; ignore unused parameter and 39 | # constant conditional expression warnings and a plainly wrong warning 40 | common_args += ['/utf-8', '/wd4100', '/wd4127', '/wd4090', '/wd5105'] 41 | endif 42 | 43 | if win_console 44 | dlg_args += '-DDLG_WIN_CONSOLE=1' 45 | endif 46 | 47 | if default_output_always_color 48 | dlg_args += '-DDLG_DEFAULT_OUTPUT_ALWAYS_COLOR=1' 49 | endif 50 | 51 | # build library 52 | dep_threads = dependency('threads') 53 | dep_args = [] 54 | 55 | if buildlib 56 | libtype = get_option('default_library') 57 | 58 | # TODO: temporary workaround, see https://github.com/mesonbuild/meson/issues/3304 59 | # The problem is that on windows, function symbols must be declared 60 | # differently when building a static library. 61 | if libtype == 'both' 62 | static_args = dlg_args + ['-DDLG_STATIC'] 63 | shared_args = dlg_args 64 | if host_machine.system() == 'windows' 65 | shared_args += ['-DDLG_API=__declspec(dllexport)'] 66 | endif 67 | 68 | shared_lib = shared_library('dlg', 69 | 'src/dlg/dlg.c', 70 | c_args: shared_args + common_args + dep_args, 71 | dependencies: dep_threads, 72 | install: true, 73 | include_directories: inc) 74 | 75 | static_lib = static_library('dlg', 76 | 'src/dlg/dlg.c', 77 | c_args: static_args + common_args + dep_args, 78 | dependencies: dep_threads, 79 | install: true, 80 | include_directories: inc) 81 | 82 | # when both library types are built, the declared dependency 83 | # and generate pkgconfig file will contain the shared library, 84 | # since that's the default way of linking in c. 85 | # That's also why we don't add 'DLG_STATIC' to dep_args 86 | message('When building both libraries, the shared one will be ' + 87 | 'used by default for dependency and pkgconfig file') 88 | libs += [shared_lib] 89 | else 90 | if libtype == 'shared' 91 | if host_machine.system() == 'windows' 92 | dlg_args += '-DDLG_API=__declspec(dllexport)' 93 | endif 94 | elif libtype == 'static' 95 | dep_args += '-DDLG_STATIC' 96 | else 97 | error('Unknown default_library type') 98 | endif 99 | 100 | libs += library('dlg', 101 | 'src/dlg/dlg.c', 102 | c_args: dlg_args + common_args + dep_args, 103 | dependencies: dep_threads, 104 | install: true, 105 | include_directories: inc) 106 | endif 107 | 108 | pkg = import('pkgconfig') 109 | pkg.generate( 110 | libs, 111 | name: 'dlg', 112 | filebase: 'dlg', 113 | subdirs: ['.', 'dlg'], 114 | version: meson.project_version(), 115 | description: 'C/C++ logging and debug library') 116 | else 117 | sources = ['src/dlg/dlg.c'] 118 | deps = [dep_threads] 119 | endif 120 | 121 | # dependency 122 | dlg_dep = declare_dependency( 123 | include_directories: inc, 124 | compile_args: dep_args, 125 | link_with: libs, 126 | dependencies: deps, 127 | sources: sources) 128 | 129 | # sample 130 | if build_sample 131 | sample = executable('sample', 132 | 'docs/examples/example.cpp', 133 | cpp_args: common_args, 134 | dependencies: dlg_dep) 135 | test('sample', sample) 136 | 137 | sample_c = executable('sample-c', 138 | 'docs/examples/example.c', 139 | c_args: common_args, 140 | dependencies: dlg_dep) 141 | test('sample_c', sample_c) 142 | 143 | sample_chain = executable('sample-chain', 144 | 'docs/examples/chain.c', 145 | c_args: common_args, 146 | dependencies: dlg_dep) 147 | test('sample_chain', sample_chain) 148 | endif 149 | 150 | # tests 151 | if tests 152 | subdir('docs/tests') 153 | endif 154 | 155 | # install 156 | install_headers(headers, subdir: 'dlg') 157 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | # Whether to build a library 2 | # If this is false, will just include dlg.c as source in the 3 | # meson dependency (making this still usable as suproject) 4 | option('buildlib', type: 'boolean', value: true) 5 | 6 | option('sample', type: 'boolean', value: false) # build the sample? 7 | option('tests', type: 'boolean', value: false) # build the tests? 8 | 9 | # If set to true, the default output handler will always use color, 10 | # and not only when stdout is a tty and dlg_win_init_ansi returns true. 11 | option('default_output_always_color', type: 'boolean', value: false) 12 | 13 | # - Only relevant on windows - 14 | # Whether to handle the windows console correctly (in terms of color/style). 15 | # Allows apps to print utf-8 and use color on the windows command line. 16 | # When setting this to true, on wsl dlg_win_init_ansi will return false 17 | # since then the default handles will be files. 18 | option('win_console', type: 'boolean', value: true) 19 | -------------------------------------------------------------------------------- /src/dlg/dlg.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 nyorain 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | #define _XOPEN_SOURCE 600 6 | #define _POSIX_C_SOURCE 200809L 7 | #define _WIN32_WINNT 0x0600 8 | 9 | // Needed on windows so that we can use sprintf without warning. 10 | #define _CRT_SECURE_NO_WARNINGS 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | const char* const dlg_reset_sequence = "\033[0m"; 21 | const struct dlg_style dlg_default_output_styles[] = { 22 | {dlg_text_style_italic, dlg_color_green, dlg_color_none}, 23 | {dlg_text_style_dim, dlg_color_gray, dlg_color_none}, 24 | {dlg_text_style_none, dlg_color_cyan, dlg_color_none}, 25 | {dlg_text_style_none, dlg_color_yellow, dlg_color_none}, 26 | {dlg_text_style_none, dlg_color_red, dlg_color_none}, 27 | {dlg_text_style_bold, dlg_color_red, dlg_color_none} 28 | }; 29 | 30 | static void* xalloc(size_t size) { 31 | void* ret = calloc(size, 1); 32 | if(!ret) fprintf(stderr, "dlg: calloc returned NULL, probably crashing (size: %zu)\n", size); 33 | return ret; 34 | } 35 | 36 | static void* xrealloc(void* ptr, size_t size) { 37 | void* ret = realloc(ptr, size); 38 | if(!ret) fprintf(stderr, "dlg: realloc returned NULL, probably crashing (size: %zu)\n", size); 39 | return ret; 40 | } 41 | 42 | struct dlg_tag_func_pair { 43 | const char* tag; 44 | const char* func; 45 | }; 46 | 47 | struct dlg_data { 48 | const char** tags; // vec 49 | struct dlg_tag_func_pair* pairs; // vec 50 | char* buffer; 51 | size_t buffer_size; 52 | }; 53 | 54 | static dlg_handler g_handler = dlg_default_output; 55 | static void* g_data = NULL; 56 | 57 | static void dlg_free_data(void* data); 58 | static struct dlg_data* dlg_create_data(void); 59 | 60 | // platform-specific 61 | #if defined(__unix__) || defined(__unix) || defined(__linux__) || defined(__APPLE__) || defined(__MACH__) 62 | #define DLG_OS_UNIX 63 | #include 64 | #include 65 | #include 66 | 67 | static pthread_key_t dlg_data_key; 68 | 69 | static void dlg_main_cleanup(void) { 70 | void* data = pthread_getspecific(dlg_data_key); 71 | if(data) { 72 | dlg_free_data(data); 73 | pthread_setspecific(dlg_data_key, NULL); 74 | } 75 | } 76 | 77 | static void init_data_key(void) { 78 | pthread_key_create(&dlg_data_key, dlg_free_data); 79 | atexit(dlg_main_cleanup); 80 | } 81 | 82 | static struct dlg_data* dlg_data(void) { 83 | static pthread_once_t key_once = PTHREAD_ONCE_INIT; 84 | pthread_once(&key_once, init_data_key); 85 | 86 | void* data = pthread_getspecific(dlg_data_key); 87 | if(!data) { 88 | data = dlg_create_data(); 89 | pthread_setspecific(dlg_data_key, data); 90 | } 91 | 92 | return (struct dlg_data*) data; 93 | } 94 | 95 | static void lock_file(FILE* file) { 96 | flockfile(file); 97 | } 98 | 99 | static void unlock_file(FILE* file) { 100 | funlockfile(file); 101 | } 102 | 103 | bool dlg_is_tty(FILE* stream) { 104 | return isatty(fileno(stream)); 105 | } 106 | 107 | static unsigned get_msecs(void) { 108 | struct timeval tv; 109 | gettimeofday(&tv, NULL); 110 | return tv.tv_usec; 111 | } 112 | 113 | // platform switch -- end unix 114 | #elif defined(WIN32) || defined(_WIN32) || defined(_WIN64) 115 | #define DLG_OS_WIN 116 | #define WIN32_LEAN_AND_MEAN 117 | #define DEFINE_CONSOLEV2_PROPERTIES 118 | #include 119 | #include 120 | 121 | // thanks for nothing, microsoft 122 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 123 | #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 124 | #endif 125 | 126 | // the max buffer size we will convert on the stack 127 | #define DLG_MAX_STACK_BUF_SIZE 1024 128 | 129 | static void WINAPI dlg_fls_destructor(void* data) { 130 | dlg_free_data(data); 131 | } 132 | 133 | // TODO: error handling 134 | static BOOL CALLBACK dlg_init_fls(PINIT_ONCE io, void* param, void** lpContext) { 135 | (void) io; 136 | (void) param; 137 | **((DWORD**) lpContext) = FlsAlloc(dlg_fls_destructor); 138 | return true; 139 | } 140 | 141 | static struct dlg_data* dlg_data(void) { 142 | static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; 143 | static DWORD fls = 0; 144 | void* flsp = (void*) &fls; 145 | InitOnceExecuteOnce(&init_once, dlg_init_fls, NULL, &flsp); 146 | void* data = FlsGetValue(fls); 147 | if(!data) { 148 | data = dlg_create_data(); 149 | FlsSetValue(fls, data); 150 | } 151 | 152 | return (struct dlg_data*) data; 153 | } 154 | 155 | static void lock_file(FILE* file) { 156 | _lock_file(file); 157 | } 158 | 159 | static void unlock_file(FILE* file) { 160 | _unlock_file(file); 161 | } 162 | 163 | bool dlg_is_tty(FILE* stream) { 164 | return _isatty(_fileno(stream)); 165 | } 166 | 167 | #ifdef DLG_WIN_CONSOLE 168 | static bool init_ansi_console(void) { 169 | HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 170 | HANDLE err = GetStdHandle(STD_ERROR_HANDLE); 171 | if(out == INVALID_HANDLE_VALUE || err == INVALID_HANDLE_VALUE) 172 | return false; 173 | 174 | DWORD outMode, errMode; 175 | if(!GetConsoleMode(out, &outMode) || !GetConsoleMode(err, &errMode)) 176 | return false; 177 | 178 | outMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 179 | errMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 180 | if(!SetConsoleMode(out, outMode) || !SetConsoleMode(out, errMode)) 181 | return false; 182 | 183 | return true; 184 | } 185 | 186 | static bool win_write_heap(void* handle, int needed, const char* format, va_list args) { 187 | char* buf1 = xalloc(3 * needed + 3 + (needed % 2)); 188 | wchar_t* buf2 = (wchar_t*) (buf1 + needed + 1 + (needed % 2)); 189 | vsnprintf(buf1, needed + 1, format, args); 190 | needed = MultiByteToWideChar(CP_UTF8, 0, buf1, needed, buf2, needed + 1); 191 | bool ret = (needed != 0 && WriteConsoleW(handle, buf2, needed, NULL, NULL) != 0); 192 | free(buf1); 193 | return ret; 194 | } 195 | 196 | static bool win_write_stack(void* handle, int needed, const char* format, va_list args) { 197 | char buf1[DLG_MAX_STACK_BUF_SIZE]; 198 | wchar_t buf2[DLG_MAX_STACK_BUF_SIZE]; 199 | vsnprintf(buf1, needed + 1, format, args); 200 | needed = MultiByteToWideChar(CP_UTF8, 0, buf1, needed, buf2, needed + 1); 201 | return (needed != 0 && WriteConsoleW(handle, buf2, needed, NULL, NULL) != 0); 202 | } 203 | #endif // DLG_WIN_CONSOLE 204 | 205 | static unsigned get_msecs() { 206 | SYSTEMTIME st; 207 | GetSystemTime(&st); 208 | return st.wMilliseconds; 209 | } 210 | 211 | #else // platform switch -- end windows 212 | #error Cannot determine platform (needed for color and utf-8 and stuff) 213 | #endif 214 | 215 | // general 216 | void dlg_escape_sequence(struct dlg_style style, char buf[12]) { 217 | int nums[3]; 218 | unsigned int count = 0; 219 | 220 | if(style.fg != dlg_color_none) { 221 | nums[count++] = style.fg + 30; 222 | } 223 | 224 | if(style.bg != dlg_color_none) { 225 | nums[count++] = style.fg + 40; 226 | } 227 | 228 | if(style.style != dlg_text_style_none) { 229 | nums[count++] = style.style; 230 | } 231 | 232 | switch(count) { 233 | case 1: snprintf(buf, 12, "\033[%dm", nums[0]); break; 234 | case 2: snprintf(buf, 12, "\033[%d;%dm", nums[0], nums[1]); break; 235 | case 3: snprintf(buf, 12, "\033[%d;%d;%dm", nums[0], nums[1], nums[2]); break; 236 | default: buf[0] = '\0'; break; 237 | } 238 | } 239 | 240 | int dlg_vfprintf(FILE* stream, const char* format, va_list args) { 241 | #if defined(DLG_OS_WIN) && defined(DLG_WIN_CONSOLE) 242 | void* handle = NULL; 243 | if(stream == stdout) { 244 | handle = GetStdHandle(STD_OUTPUT_HANDLE); 245 | } else if(stream == stderr) { 246 | handle = GetStdHandle(STD_ERROR_HANDLE); 247 | } 248 | 249 | if(handle) { 250 | va_list args_copy; 251 | va_copy(args_copy, args); 252 | int needed = vsnprintf(NULL, 0, format, args_copy); 253 | va_end(args_copy); 254 | 255 | if(needed < 0) { 256 | return needed; 257 | } 258 | 259 | // We don't allocate too much on the stack 260 | // but we also don't want to call alloc every logging call 261 | // or use another cached buffer 262 | if(needed >= DLG_MAX_STACK_BUF_SIZE) { 263 | if(win_write_heap(handle, needed, format, args)) { 264 | return needed; 265 | } 266 | } else { 267 | if(win_write_stack(handle, needed, format, args)) { 268 | return needed; 269 | } 270 | } 271 | } 272 | #endif 273 | 274 | return vfprintf(stream, format, args); 275 | } 276 | 277 | int dlg_fprintf(FILE* stream, const char* format, ...) { 278 | va_list args; 279 | va_start(args, format); 280 | int ret = dlg_vfprintf(stream, format, args); 281 | va_end(args); 282 | return ret; 283 | } 284 | 285 | int dlg_styled_fprintf(FILE* stream, struct dlg_style style, const char* format, ...) { 286 | char buf[12]; 287 | dlg_escape_sequence(style, buf); 288 | 289 | fprintf(stream, "%s", buf); 290 | va_list args; 291 | va_start(args, format); 292 | int ret = dlg_vfprintf(stream, format, args); 293 | va_end(args); 294 | fprintf(stream, "%s", dlg_reset_sequence); 295 | return ret; 296 | } 297 | 298 | void dlg_generic_output(dlg_generic_output_handler output, void* data, 299 | unsigned int features, const struct dlg_origin* origin, const char* string, 300 | const struct dlg_style styles[6]) { 301 | // We never print any dynamic content below so we can be sure at compile 302 | // time that a buffer of size 64 is large enough. 303 | char format_buf[64]; 304 | char* format = format_buf; 305 | 306 | if(features & dlg_output_style) { 307 | format += sprintf(format, "%%s"); 308 | } 309 | 310 | if(features & (dlg_output_time | dlg_output_file_line | dlg_output_tags | dlg_output_func)) { 311 | format += sprintf(format, "["); 312 | } 313 | 314 | bool first_meta = true; 315 | if(features & dlg_output_time) { 316 | format += sprintf(format, "%%h"); 317 | first_meta = false; 318 | } 319 | 320 | if(features & dlg_output_time_msecs) { 321 | if(!first_meta) { 322 | format += sprintf(format, ":"); 323 | } 324 | 325 | format += sprintf(format, "%%m"); 326 | first_meta = false; 327 | } 328 | 329 | if(features & dlg_output_file_line) { 330 | if(!first_meta) { 331 | format += sprintf(format, " "); 332 | } 333 | 334 | format += sprintf(format, "%%o"); 335 | first_meta = false; 336 | } 337 | 338 | if(features & dlg_output_func) { 339 | if(!first_meta) { 340 | format += sprintf(format, " "); 341 | } 342 | 343 | format += sprintf(format, "%%f"); 344 | first_meta = false; 345 | } 346 | 347 | if(features & dlg_output_tags) { 348 | if(!first_meta) { 349 | format += sprintf(format, " "); 350 | } 351 | 352 | format += sprintf(format, "{%%t}"); 353 | first_meta = false; 354 | } 355 | 356 | if(features & (dlg_output_time | dlg_output_file_line | dlg_output_tags | dlg_output_func)) { 357 | format += sprintf(format, "] "); 358 | } 359 | 360 | format += sprintf(format, "%%c"); 361 | 362 | if(features & dlg_output_newline) { 363 | format += sprintf(format, "\n"); 364 | } 365 | 366 | *format = '\0'; 367 | dlg_generic_outputf(output, data, format_buf, origin, string, styles); 368 | } 369 | 370 | void dlg_generic_outputf(dlg_generic_output_handler output, void* data, 371 | const char* format_string, const struct dlg_origin* origin, const char* string, 372 | const struct dlg_style styles[6]) { 373 | bool reset_style = false; 374 | for(const char* it = format_string; *it; it++) { 375 | if(*it != '%') { 376 | output(data, "%c", *it); 377 | continue; 378 | } 379 | 380 | char next = *(it + 1); // must be valid since *it is not '\0' 381 | if(next == 'h') { 382 | time_t t = time(NULL); 383 | struct tm tm_info; 384 | 385 | #ifdef DLG_OS_WIN 386 | if(localtime_s(&tm_info, &t)) { 387 | #else 388 | if(!localtime_r(&t, &tm_info)) { 389 | #endif 390 | output(data, ""); 391 | } else { 392 | char timebuf[32]; 393 | strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm_info); 394 | output(data, "%s", timebuf); 395 | } 396 | it++; 397 | } else if(next == 'm') { 398 | output(data, "%06d", get_msecs()); 399 | it++; 400 | } else if(next == 't') { 401 | bool first_tag = true; 402 | for(const char** tags = origin->tags; *tags; ++tags) { 403 | if(!first_tag) { 404 | output(data, ", "); 405 | } 406 | 407 | output(data, "%s", *tags); 408 | first_tag = false; 409 | } 410 | ++it; 411 | } else if(next == 'f') { 412 | output(data, "%s", origin->func); 413 | ++it; 414 | } else if(next == 'o') { 415 | output(data, "%s:%u", origin->file, origin->line); 416 | ++it; 417 | } else if(next == 's') { 418 | char buf[12]; 419 | dlg_escape_sequence(styles[origin->level], buf); 420 | output(data, "%s", buf); 421 | reset_style = true; 422 | ++it; 423 | } else if(next == 'r') { 424 | output(data, "%s", dlg_reset_sequence); 425 | reset_style = false; 426 | ++it; 427 | } else if(next == 'c') { 428 | if(origin->expr && string) { 429 | output(data, "assertion '%s' failed: '%s'", origin->expr, string); 430 | } else if(origin->expr) { 431 | output(data, "assertion '%s' failed", origin->expr); 432 | } else if(string) { 433 | output(data, "%s", string); 434 | } 435 | ++it; 436 | } else if(next == '%') { 437 | output(data, "%s", "%"); 438 | ++it; 439 | } else { 440 | // in this case it's a '%' without known format specifier following 441 | output(data, "%s", "%"); 442 | } 443 | } 444 | 445 | if(reset_style) { 446 | output(data, "%s", dlg_reset_sequence); 447 | } 448 | } 449 | 450 | struct buf { 451 | char* buf; 452 | size_t* size; 453 | }; 454 | 455 | static void print_size(void* size, const char* format, ...) { 456 | va_list args; 457 | va_start(args, format); 458 | 459 | int ret = vsnprintf(NULL, 0, format, args); 460 | va_end(args); 461 | 462 | if(ret > 0) { 463 | *((size_t*) size) += ret; 464 | } 465 | } 466 | 467 | static void print_buf(void* dbuf, const char* format, ...) { 468 | struct buf* buf = (struct buf*) dbuf; 469 | va_list args; 470 | va_start(args, format); 471 | 472 | int printed = vsnprintf(buf->buf, *buf->size, format, args); 473 | va_end(args); 474 | 475 | if(printed > 0) { 476 | *buf->size -= printed; 477 | buf->buf += printed; 478 | } 479 | } 480 | 481 | void dlg_generic_output_buf(char* buf, size_t* size, unsigned int features, 482 | const struct dlg_origin* origin, const char* string, 483 | const struct dlg_style styles[6]) { 484 | if(buf) { 485 | struct buf mbuf; 486 | mbuf.buf = buf; 487 | mbuf.size = size; 488 | dlg_generic_output(print_buf, &mbuf, features, origin, string, styles); 489 | } else { 490 | *size = 0; 491 | dlg_generic_output(print_size, size, features, origin, string, styles); 492 | } 493 | } 494 | 495 | void dlg_generic_outputf_buf(char* buf, size_t* size, const char* format_string, 496 | const struct dlg_origin* origin, const char* string, 497 | const struct dlg_style styles[6]) { 498 | if(buf) { 499 | struct buf mbuf; 500 | mbuf.buf = buf; 501 | mbuf.size = size; 502 | dlg_generic_outputf(print_buf, &mbuf, format_string, origin, string, styles); 503 | } else { 504 | *size = 0; 505 | dlg_generic_outputf(print_size, size, format_string, origin, string, styles); 506 | } 507 | } 508 | 509 | static void print_stream(void* stream, const char* format, ...) { 510 | va_list args; 511 | va_start(args, format); 512 | dlg_vfprintf((FILE*) stream, format, args); 513 | va_end(args); 514 | } 515 | 516 | void dlg_generic_output_stream(FILE* stream, unsigned int features, 517 | const struct dlg_origin* origin, const char* string, 518 | const struct dlg_style styles[6]) { 519 | stream = stream ? stream : stdout; 520 | if(features & dlg_output_threadsafe) { 521 | lock_file(stream); 522 | } 523 | 524 | dlg_generic_output(print_stream, stream, features, origin, string, styles); 525 | if(features & dlg_output_threadsafe) { 526 | unlock_file(stream); 527 | } 528 | } 529 | 530 | void dlg_generic_outputf_stream(FILE* stream, const char* format_string, 531 | const struct dlg_origin* origin, const char* string, 532 | const struct dlg_style styles[6], bool lock_stream) { 533 | stream = stream ? stream : stdout; 534 | if(lock_stream) { 535 | lock_file(stream); 536 | } 537 | 538 | dlg_generic_outputf(print_stream, stream, format_string, origin, string, styles); 539 | if(lock_stream) { 540 | unlock_file(stream); 541 | } 542 | } 543 | 544 | void dlg_default_output(const struct dlg_origin* origin, const char* string, void* data) { 545 | FILE* stream = data ? (FILE*) data : stdout; 546 | unsigned int features = dlg_output_file_line | 547 | dlg_output_newline | 548 | dlg_output_threadsafe; 549 | 550 | #ifdef DLG_DEFAULT_OUTPUT_ALWAYS_COLOR 551 | dlg_win_init_ansi(); 552 | features |= dlg_output_style; 553 | #else 554 | if(dlg_is_tty(stream) && dlg_win_init_ansi()) { 555 | features |= dlg_output_style; 556 | } 557 | #endif 558 | 559 | dlg_generic_output_stream(stream, features, origin, string, dlg_default_output_styles); 560 | fflush(stream); 561 | } 562 | 563 | bool dlg_win_init_ansi(void) { 564 | #if defined(DLG_OS_WIN) && defined(DLG_WIN_CONSOLE) 565 | // TODO: use init once 566 | static volatile LONG status = 0; 567 | LONG res = InterlockedCompareExchange(&status, 1, 0); 568 | if(res == 0) { // not initialized 569 | InterlockedExchange(&status, 3 + init_ansi_console()); 570 | } 571 | 572 | while(status == 1); // currently initialized in another thread, spinlock 573 | return (status == 4); 574 | #else 575 | return true; 576 | #endif 577 | } 578 | 579 | // small dynamic vec/array implementation 580 | // Since the macros vec_init and vec_add[c]/vec_push might 581 | // change the pointers value it must not be referenced somewhere else. 582 | #define vec__raw(vec) (((unsigned int*) vec) - 2) 583 | 584 | static void* vec_do_create(unsigned int typesize, unsigned int cap, unsigned int size) { 585 | unsigned long a = (size > cap) ? size : cap; 586 | void* ptr = xalloc(2 * sizeof(unsigned int) + a * typesize); 587 | unsigned int* begin = (unsigned int*) ptr; 588 | begin[0] = size * typesize; 589 | begin[1] = a * typesize; 590 | return begin + 2; 591 | } 592 | 593 | // NOTE: can be more efficient if we are allowed to reorder vector 594 | static void vec_do_erase(void* vec, unsigned int pos, unsigned int size) { 595 | unsigned int* begin = vec__raw(vec); 596 | begin[0] -= size; 597 | char* buf = (char*) vec; 598 | memcpy(buf + pos, buf + pos + size, size); 599 | } 600 | 601 | static void* vec_do_add(void** vec, unsigned int size) { 602 | unsigned int* begin = vec__raw(*vec); 603 | unsigned int needed = begin[0] + size; 604 | if(needed >= begin[1]) { 605 | void* ptr = xrealloc(begin, sizeof(unsigned int) * 2 + needed * 2); 606 | begin = (unsigned int*) ptr; 607 | begin[1] = needed * 2; 608 | (*vec) = begin + 2; 609 | } 610 | 611 | void* ptr = ((char*) (*vec)) + begin[0]; 612 | begin[0] += size; 613 | return ptr; 614 | } 615 | 616 | #define vec_create(type, size) (type*) vec_do_create(sizeof(type), size * 2, size) 617 | #define vec_create_reserve(type, size, capacity) (type*) vec_do_create(sizeof(type), capcity, size) 618 | #define vec_init(array, size) array = vec_do_create(sizeof(*array), size * 2, size) 619 | #define vec_init_reserve(array, size, capacity) *((void**) &array) = vec_do_create(sizeof(*array), capacity, size) 620 | #define vec_free(vec) (free((vec) ? vec__raw(vec) : NULL), vec = NULL) 621 | #define vec_erase_range(vec, pos, count) vec_do_erase(vec, pos * sizeof(*vec), count * sizeof(*vec)) 622 | #define vec_erase(vec, pos) vec_do_erase(vec, pos * sizeof(*vec), sizeof(*vec)) 623 | #define vec_size(vec) (vec__raw(vec)[0] / sizeof(*vec)) 624 | #define vec_capacity(vec) (vec_raw(vec)[1] / sizeof(*vec)) 625 | #define vec_add(vec) vec_do_add((void**) &vec, sizeof(*vec)) 626 | #define vec_addc(vec, count) (vec_do_add((void**) &vec, sizeof(*vec) * count)) 627 | #define vec_push(vec, value) (vec_do_add((void**) &vec, sizeof(*vec)), vec_last(vec) = (value)) 628 | #define vec_pop(vec) (vec__raw(vec)[0] -= sizeof(*vec)) 629 | #define vec_popc(vec, count) (vec__raw(vec)[0] -= sizeof(*vec) * count) 630 | #define vec_clear(vec) (vec__raw(vec)[0] = 0) 631 | #define vec_last(vec) (vec[vec_size(vec) - 1]) 632 | 633 | static struct dlg_data* dlg_create_data(void) { 634 | struct dlg_data* data = (struct dlg_data*) xalloc(sizeof(struct dlg_data)); 635 | vec_init_reserve(data->tags, 0, 20); 636 | vec_init_reserve(data->pairs, 0, 20); 637 | data->buffer_size = 100; 638 | data->buffer = (char*) xalloc(data->buffer_size); 639 | return data; 640 | } 641 | 642 | static void dlg_free_data(void* ddata) { 643 | struct dlg_data* data = (struct dlg_data*) ddata; 644 | if(data) { 645 | vec_free(data->pairs); 646 | vec_free(data->tags); 647 | free(data->buffer); 648 | free(data); 649 | } 650 | } 651 | 652 | void dlg_add_tag(const char* tag, const char* func) { 653 | struct dlg_data* data = dlg_data(); 654 | struct dlg_tag_func_pair* pair = 655 | (struct dlg_tag_func_pair*) vec_add(data->pairs); 656 | pair->tag = tag; 657 | pair->func = func; 658 | } 659 | 660 | bool dlg_remove_tag(const char* tag, const char* func) { 661 | struct dlg_data* data = dlg_data(); 662 | for(unsigned int i = 0; i < vec_size(data->pairs); ++i) { 663 | if(data->pairs[i].func == func && data->pairs[i].tag == tag) { 664 | vec_erase(data->pairs, i); 665 | return true; 666 | } 667 | } 668 | 669 | return false; 670 | } 671 | 672 | char** dlg_thread_buffer(size_t** size) { 673 | struct dlg_data* data = dlg_data(); 674 | if(size) { 675 | *size = &data->buffer_size; 676 | } 677 | return &data->buffer; 678 | } 679 | 680 | void dlg_set_handler(dlg_handler handler, void* data) { 681 | g_handler = handler; 682 | g_data = data; 683 | } 684 | 685 | dlg_handler dlg_get_handler(void** data) { 686 | *data = g_data; 687 | return g_handler; 688 | } 689 | 690 | const char* dlg__printf_format(const char* str, ...) { 691 | va_list vlist; 692 | va_start(vlist, str); 693 | 694 | va_list vlistcopy; 695 | va_copy(vlistcopy, vlist); 696 | int needed = vsnprintf(NULL, 0, str, vlist); 697 | if(needed < 0) { 698 | printf("dlg__printf_format: invalid format given\n"); 699 | va_end(vlist); 700 | va_end(vlistcopy); 701 | return NULL; 702 | } 703 | 704 | va_end(vlist); 705 | 706 | size_t* buf_size; 707 | char** buf = dlg_thread_buffer(&buf_size); 708 | if(*buf_size <= (unsigned int) needed) { 709 | *buf_size = (needed + 1) * 2; 710 | *buf = (char*) xrealloc(*buf, *buf_size); 711 | } 712 | 713 | vsnprintf(*buf, *buf_size, str, vlistcopy); 714 | va_end(vlistcopy); 715 | 716 | return *buf; 717 | } 718 | 719 | void dlg__do_log(enum dlg_level lvl, const char* const* tags, const char* file, int line, 720 | const char* func, const char* string, const char* expr) { 721 | struct dlg_data* data = dlg_data(); 722 | unsigned int tag_count = 0; 723 | 724 | // push default tags 725 | while(tags[tag_count]) { 726 | vec_push(data->tags, tags[tag_count++]); 727 | } 728 | 729 | // push current global tags 730 | for(size_t i = 0; i < vec_size(data->pairs); ++i) { 731 | const struct dlg_tag_func_pair pair = data->pairs[i]; 732 | if(pair.func == NULL || !strcmp(pair.func, func)) { 733 | vec_push(data->tags, pair.tag); 734 | } 735 | } 736 | 737 | // push call-specific tags, skip first terminating NULL 738 | ++tag_count; 739 | while(tags[tag_count]) { 740 | vec_push(data->tags, tags[tag_count++]); 741 | } 742 | 743 | vec_push(data->tags, NULL); // terminating NULL 744 | struct dlg_origin origin; 745 | origin.level = lvl; 746 | origin.file = file; 747 | origin.line = line; 748 | origin.func = func; 749 | origin.expr = expr; 750 | origin.tags = data->tags; 751 | 752 | g_handler(&origin, string, g_data); 753 | vec_clear(data->tags); 754 | } 755 | 756 | #ifdef _MSC_VER 757 | // shitty msvc compatbility 758 | // meson gives us sane paths (separated by '/') while on MSVC, 759 | // __FILE__ contains a '\\' separator. 760 | static bool path_same(char a, char b) { 761 | return (a == b) || 762 | (a == '/' && b == '\\') || 763 | (a == '\\' && b == '/'); 764 | } 765 | #else 766 | 767 | static inline bool path_same(char a, char b) { 768 | return a == b; 769 | } 770 | 771 | #endif 772 | 773 | const char* dlg__strip_root_path(const char* file, const char* base) { 774 | if(!file) { 775 | return NULL; 776 | } 777 | 778 | const char* saved = file; 779 | if(*file == '.') { // relative path detected 780 | while(*(++file) == '.' || *file == '/' || *file == '\\'); 781 | if(*file == '\0') { // weird case: purely relative path without file 782 | return saved; 783 | } 784 | 785 | return file; 786 | } 787 | 788 | // strip base from file if it is given 789 | if(base) { 790 | char fn = *file; 791 | char bn = *base; 792 | while(bn != '\0' && path_same(fn, bn)) { 793 | fn = *(++file); 794 | bn = *(++base); 795 | } 796 | 797 | if(fn == '\0' || bn != '\0') { // weird case: base isn't prefix of file 798 | return saved; 799 | } 800 | } 801 | 802 | return file; 803 | } 804 | --------------------------------------------------------------------------------