├── .clang-format ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── CPM.cmake └── target_if_include.cmake ├── config.json ├── dependencies ├── any_lite.h ├── ejson4cpp │ └── fetch_ejson4cpp.cmake ├── fetch.cmake ├── fmt │ └── fetch_fmt.cmake └── string_vew_lite.h ├── doc ├── README_zh.md ├── logger.puml └── logger_class_uml.png ├── examples ├── CMakeLists.txt ├── simple1.cc ├── simple2.cc ├── simple3.cc └── simple4.cc ├── include └── elog │ ├── any.h │ ├── async_logging.h │ ├── common.h │ ├── config.h │ ├── context.h │ ├── count_down_latch.h │ ├── file_appender.h │ ├── formatter.h │ ├── log_file.h │ ├── logger.h │ ├── logger_util.h │ ├── micros.h │ ├── noncopyable.h │ ├── output_buffer.h │ ├── processinfo.h │ ├── source_location.h │ ├── string_view.h │ ├── switch_helper.h │ ├── systemcall_wrapper.h │ └── trace.impl.h ├── src ├── async_logging.cc ├── config.cc ├── count_down_latch.cc ├── file_appender.cc ├── formatter.cc ├── log_file.cc ├── logger.cc ├── logger_util.cc └── processinfo.cc └── tests ├── CMakeLists.txt ├── bench_elog.cc ├── bench_interface.h ├── bench_start.cc ├── common_func.h ├── perf.cc ├── test_common.cc └── test_config_micros.cc /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | Language: Cpp 3 | AccessModifierOffset: -3 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveMacros: AcrossComments 6 | AlignConsecutiveAssignments: AcrossComments 7 | AlignConsecutiveDeclarations: AcrossComments 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllArgumentsOnNextLine: true 12 | AllowAllParametersOfDeclarationOnNextLine: true 13 | AllowShortBlocksOnASingleLine: Always 14 | AllowShortCaseLabelsOnASingleLine: true 15 | AllowShortFunctionsOnASingleLine: All 16 | AllowShortLambdasOnASingleLine: All 17 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse 18 | AllowShortLoopsOnASingleLine: true 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: false 21 | AlwaysBreakTemplateDeclarations: Yes 22 | BinPackArguments: true 23 | BinPackParameters: true 24 | BraceWrapping: 25 | AfterCaseLabel: false 26 | AfterClass: true 27 | AfterControlStatement: Always 28 | AfterEnum: false 29 | AfterFunction: true 30 | AfterNamespace: false 31 | AfterObjCDeclaration: false 32 | AfterStruct: true 33 | AfterUnion: true 34 | AfterExternBlock: true 35 | BeforeCatch: true 36 | BeforeElse: true 37 | 38 | BreakBeforeBinaryOperators: None 39 | BreakBeforeBraces: Custom 40 | BreakBeforeInheritanceComma: false 41 | BreakInheritanceList: BeforeColon 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 80 48 | CommentPragmas: "^ NOLINT:" 49 | CompactNamespaces: false 50 | ConstructorInitializerIndentWidth: 2 51 | ContinuationIndentWidth: 2 52 | Cpp11BracedListStyle: true 53 | DeriveLineEnding: true 54 | DerivePointerAlignment: true 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Regroup 63 | IncludeCategories: 64 | - Regex: '^' 65 | Priority: 2 66 | SortPriority: 0 67 | - Regex: '^<.*\.h>' 68 | Priority: 1 69 | SortPriority: 0 70 | - Regex: "^<.*" 71 | Priority: 2 72 | SortPriority: 0 73 | - Regex: ".*" 74 | Priority: 3 75 | SortPriority: 0 76 | IncludeIsMainRegex: "([-_](test|unittest))?$" 77 | IncludeIsMainSourceRegex: "" 78 | IndentCaseLabels: true 79 | IndentGotoLabels: true 80 | IndentPPDirectives: None 81 | IndentWidth: 3 82 | IndentWrappedFunctionNames: false 83 | JavaScriptQuotes: Leave 84 | JavaScriptWrapImports: true 85 | KeepEmptyLinesAtTheStartOfBlocks: false 86 | MacroBlockBegin: "" 87 | MacroBlockEnd: "" 88 | MaxEmptyLinesToKeep: 1 89 | NamespaceIndentation: None 90 | ObjCBinPackProtocolList: Never 91 | ObjCBlockIndentWidth: 3 92 | ObjCSpaceAfterProperty: false 93 | ObjCSpaceBeforeProtocolList: true 94 | PenaltyBreakAssignment: 2 95 | PenaltyBreakBeforeFirstCallParameter: 1 96 | PenaltyBreakComment: 300 97 | PenaltyBreakFirstLessLess: 120 98 | PenaltyBreakString: 1000 99 | PenaltyBreakTemplateDeclaration: 10 100 | PenaltyExcessCharacter: 1000000 101 | PenaltyReturnTypeOnItsOwnLine: 200 102 | PointerAlignment: Right 103 | RawStringFormats: 104 | - Language: Cpp 105 | Delimiters: 106 | - cc 107 | - CC 108 | - cpp 109 | - Cpp 110 | - CPP 111 | - "c++" 112 | - "C++" 113 | CanonicalDelimiter: "" 114 | BasedOnStyle: google 115 | - Language: TextProto 116 | Delimiters: 117 | - pb 118 | - PB 119 | - proto 120 | - PROTO 121 | EnclosingFunctions: 122 | - EqualsProto 123 | - EquivToProto 124 | - PARSE_PARTIAL_TEXT_PROTO 125 | - PARSE_TEST_PROTO 126 | - PARSE_TEXT_PROTO 127 | - ParseTextOrDie 128 | - ParseTextProtoOrDie 129 | CanonicalDelimiter: "" 130 | BasedOnStyle: google 131 | ReflowComments: true 132 | SortIncludes: CaseInsensitive 133 | SortUsingDeclarations: false 134 | SpaceAfterCStyleCast: false 135 | SpaceAfterLogicalNot: false 136 | SpaceAfterTemplateKeyword: true 137 | SpaceBeforeAssignmentOperators: true 138 | SpaceBeforeCpp11BracedList: false 139 | SpaceBeforeCtorInitializerColon: true 140 | SpaceBeforeInheritanceColon: true 141 | SpaceBeforeParens: ControlStatements 142 | SpaceBeforeRangeBasedForLoopColon: true 143 | SpaceInEmptyBlock: false 144 | SpaceInEmptyParentheses: false 145 | SpacesBeforeTrailingComments: 3 146 | SpacesInAngles: false 147 | SpacesInConditionalStatement: false 148 | SpacesInContainerLiterals: false 149 | SpacesInCStyleCastParentheses: false 150 | SpacesInParentheses: false 151 | SpacesInSquareBrackets: false 152 | SpaceBeforeSquareBrackets: false 153 | Standard: Auto 154 | StatementMacros: 155 | - Q_UNUSED 156 | - QT_REQUIRE_VERSION 157 | TabWidth: 4 -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake Build Matrix 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | BUILD_DIR: ${{ github.workspace }}/build 12 | 13 | jobs: 14 | build: 15 | name: ${{ matrix.config.name }} 16 | runs-on: ${{ matrix.config.os }} 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | - { 23 | name: "Windows Latest", 24 | os: "windows-latest", 25 | cc: "cl", 26 | cxx: "cl", 27 | } 28 | - { 29 | name: "Ubuntu 22.04 GCC", 30 | os: "ubuntu-22.04", 31 | cc: "gcc", 32 | cxx: "g++", 33 | } 34 | - { 35 | name: "Ubuntu 22.04 Clang", 36 | os: "ubuntu-22.04", 37 | cc: "clang-14", 38 | cxx: "clang++-14", 39 | } 40 | - { 41 | name: "MacOS Latest", 42 | os: "macos-latest", 43 | cc: "clang", 44 | cxx: "clang++", 45 | } 46 | steps: 47 | - uses: actions/checkout@v3 48 | 49 | - name: Print env 50 | run: | 51 | echo github.event.action: ${{ github.event.action }} 52 | echo github.event_name: ${{ github.event_name }} 53 | - name: Configure CMake 54 | run: > 55 | cmake -B ${{ env.BUILD_DIR }} 56 | -DCMAKE_CXX_COMPILER=${{ matrix.config.cxx }} 57 | -DCMAKE_C_COMPILER=${{ matrix.config.cc }} 58 | -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} 59 | -DENABLE_ELG_TEST=ON 60 | -S ${{ github.workspace }} 61 | 62 | - name: Build 63 | run: cmake --build ${{ env.BUILD_DIR }} --config ${{env.BUILD_TYPE}} 64 | 65 | - name: Check Tests 66 | working-directory: ${{ env.BUILD_DIR }} 67 | run: ctest --build-config ${{ env.BUILD_TYPE }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-* 2 | .DS_Store 3 | build 4 | build-* 5 | log 6 | *.log 7 | .idea 8 | log.txt 9 | .vscode 10 | .vs 11 | gh-md-toc.exe 12 | gh-md-toc 13 | auto_clean.sh -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(elog) 3 | 4 | #防止依赖源码的情况下产生重复的目标 5 | if (TARGET elog) 6 | message(WARNING "Target elog already exists!") 7 | return() 8 | endif () 9 | 10 | # 检测C++版本,并给出提示 11 | if (CMAKE_CXX_STANDARD LESS 11) 12 | message(FATAL_ERROR "CMAKE_CXX_STANDARD must be at least 11") 13 | elseif (CMAKE_CXX_STANDARD EQUAL 11) 14 | message(STATUS "Using C++11") 15 | elseif (CMAKE_CXX_STANDARD EQUAL 14) 16 | message(STATUS "Using C++14") 17 | else () 18 | message(STATUS "Using C++17") 19 | set(CMAKE_CXX_STANDARD 17) 20 | endif () 21 | 22 | include(${PROJECT_SOURCE_DIR}/dependencies/fetch.cmake) 23 | 24 | option(ENABLE_ELG_TEST "Build unittest." OFF) 25 | option(ENABLE_ELG_EXAMPLE "Build examples." OFF) 26 | 27 | file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.cc) 28 | 29 | add_library(${PROJECT_NAME} ${SRC}) 30 | target_include_directories( 31 | ${PROJECT_NAME} 32 | PUBLIC 33 | ${PROJECT_SOURCE_DIR} 34 | ${PROJECT_SOURCE_DIR}/include 35 | ) 36 | 37 | if (UNIX AND NOT APPLE) 38 | target_link_libraries(${PROJECT_NAME} PUBLIC ejson fmt pthread) 39 | MESSAGE(STATUS "Now is Linux") 40 | elseif (APPLE) 41 | target_link_libraries(${PROJECT_NAME} PUBLIC ejson fmt pthread) 42 | MESSAGE(STATUS "Now is MacOS") 43 | elseif (WIN32) 44 | target_link_libraries(${PROJECT_NAME} PUBLIC ejson fmt ws2_32) 45 | MESSAGE(STATUS "Now is windows") 46 | endif () 47 | 48 | if (ENABLE_ELG_TEST) 49 | enable_testing() 50 | message("BUILD_UNITTEST") 51 | add_subdirectory(tests) 52 | endif () 53 | 54 | if (ENABLE_ELG_EXAMPLE) 55 | message("BUILD_EXAMPLE") 56 | add_subdirectory(examples) 57 | endif () -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 L_B__ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [简中](./doc/README_zh.md) | English 2 | 3 | * [elog4cpp](#elog4cpp) 4 | * [Quick Start](#quick-start) 5 | * [Requirements](#requirements) 6 | * [Installation and Introduction](#installation-and-introduction) 7 | * [Getting Started](#getting-started) 8 | * [How to configure](#how-to-configure) 9 | * [Global configuration](#global-configuration) 10 | * [Configuration methods](#configuration-methods) 11 | * [Example of using](#example-of-using-) 12 | * [Local configuration](#local-configuration) 13 | * [Detailed interface description](#detailed-interface-description) 14 | * [Formatter](#formatter) 15 | * [Built-in formatter](#built-in-formatter) 16 | * [Custom formatter](#custom-formatter) 17 | * [Micros](#micros) 18 | * [ENABLE_ELG_LOG](#enableelglog) 19 | * [ENABLE_ELG_CHECK](#enableelgcheck) 20 | * [Other util](#other-util) 21 | 22 | 23 | 24 | # elog4cpp 25 | [![License](https://img.shields.io/badge/License-MIT-green)](https://github.com/ACking-you/elog4cpp/blob/master/LICENSE) 26 | [![Platform](https://img.shields.io/badge/Platform-Cross--platformable-blue)](https://img.shields.io/badge/Platform-Cross--platformable-blue) 27 | [![Language](https://img.shields.io/badge/Language-C%2B%2B11%20or%20above-red)](https://en.cppreference.com/w/cpp/compiler_support/11) 28 | 29 | `elog4cpp` : means that this is a very `easy` to use and very `efficient` in performance c++ logging library. It supports c++11 and above, and is fully cross-platform. 30 | 31 | The use of `easy` is reflected in: 32 | * Simple api, you only need to focus on an `elog::Log` class, or static method `Log::`, or macro definition `ELG_`. 33 | * Formatting output is simple because the [fmt](https://github.com/fmtlib/fmt) library is used for formatting output. 34 | * Custom formatting is simple because custom `formatter` is supported, and four `formatters` are preconfigured, including defaultFormatter, colorfulFormatter, jsonFormatter, and customFormatter. 35 | * Easy to configure, supports reading configuration items via `json` file with one click. 36 | * Easy to introduce, support `cmake` command to introduce and use the project with one click. 37 | 38 | Performance `efficiency` is reflected in: 39 | 40 | * The latency of outputting a log is only `180ns` synchronously and `120ns` asynchronously, which is at least 4 times the performance of spdlog. 41 | 42 | For benchmark, see [tests/bench_start.cc](https://github.com/ACking-you/elog4cpp/blob/master/tests/bench_start.cc) 43 | 44 | ## Quick Start 45 | 46 | ### Requirements 47 | 48 | * C++11 and above, which is cross-platform 49 | 50 | ### Installation and Introduction 51 | 52 | The following two methods are recommended for introduction. 53 | 54 | * Method 1: Introduce via the `FetchContent` module in cmake. 55 | 1. add the following code to the project cmake to introduce, domestic if the network problems can not use this gitee image source: https://gitee.com/acking-you/elog4cpp.git 56 | ```cmake 57 | include(FetchContent) 58 | 59 | FetchContent_Declare( 60 | elog4cpp 61 | GIT_REPOSITORY https://github.com/ACking-you/elog4cpp.git 62 | GIT_TAG origin/fetch 63 | GIT_SHALLOW TRUE) 64 | FetchContent_MakeAvailable(elog4cpp) 65 | ```` 66 | 2. Link `elog` in the target that needs to use the library. 67 | ```cmake 68 | target_link_libraries(target elog) 69 | ``` 70 | * Method 2: Download the source code manually and then introduce it via the cmake command. 71 | 72 | 1. download the project source code via the git command 73 | 74 | ```shell 75 | git clone https://github.com/ACking-you/elog4cpp.git --recursive 76 | ```` 77 | 78 | 2. Add the project to a subproject. 79 | 80 | ```cmake 81 | add_subdirectory(elog4cpp) 82 | ``` 83 | 84 | 3. Link `elog` in the target that needs to use the library. 85 | 86 | ```cmake 87 | target_link_libraries(target elog) 88 | ``` 89 | 90 | ### Getting Started 91 | 92 | * Without any configuration, we can directly call the static method output to the terminal with the following code. 93 | 94 | ```cpp 95 | #include 96 | using namespace elog; 97 | 98 | int main() 99 | { 100 | Log::trace("hello elog4cpp"); 101 | Log::debug("hello elog4cpp"); 102 | Log::info("hello elog4cpp"); 103 | Log::warn("hello elog4cpp"); 104 | Log::error("hello elog4cpp"); 105 | Log::fatal("hello elog4cpp"); 106 | } 107 | ``` 108 | The output is as follows. 109 | ![output](https://img-blog.csdnimg.cn/15ddda1c5a184e61b54afa83b33dcaa2.png) 110 | Through the above example, we need to understand the following three points. 111 | 1. The output levels of this log library are `trace`, `debug`, `info`, `warn`, `error`, `fatal`. 112 | 2. The default output level is `debug`, which means that the `trace` level is not output. 113 | 3. The `fatal` level of output will throw an exception. 114 | 115 | > In fact, if there is an error in `errno` at the `error` or `fatal` level of output, then the corresponding error will be output, which is useful some time. 116 | --- 117 | 118 | * In the previous example, we were unable to output the log at `trace` level, now we try to change its minimum output level and then add more information to the output (such as file name, line number, function name, etc.) and output the output in color highlighting. 119 | 120 | The code is as follows: 121 | 122 | ```cpp 123 | #include 124 | using namespace elog; 125 | 126 | int main() 127 | { 128 | GlobalConfig::Get() 129 | .setLevel(Levels::kTrace) 130 | .setFormatter(formatter::colorfulFormatter); 131 | Log::trace(loc::current(), "hello elog4cpp"); 132 | Log::debug(loc::current(), "hello elog4cpp"); 133 | Log::info(loc::current(), "hello elog4cpp"); 134 | Log::warn(loc::current(), "hello elog4cpp"); 135 | Log::error(loc::current(), "hello elog4cpp"); 136 | } 137 | ``` 138 | The output looks like this: 139 | ![img2](https://img-blog.csdnimg.cn/ba02536932f54116a73c74006b6587bf.png) 140 | From the above example code, we found that if we need to get information such as file name, we need to pass `loc::current()` in the first parameter, obviously most of the time we will find it troublesome to use this, so we can solve this problem through macros, you can do like the following, before introducing `` define ` ENABLE_ELG_LOG` macro before introducing `` to use the shorter macro definition `ELG_`. 141 | ```cpp 142 | #define ENABLE_ELG_LOG 143 | #include 144 | using namespace elog; 145 | 146 | int main() 147 | { 148 | GlobalConfig::Get() 149 | .setLevel(Levels::kTrace) 150 | .setFormatter(formatter::colorfulFormatter); 151 | ELG_TRACE("hello elog4cpp"); 152 | ELG_DEBUG("hello elog4cpp"); 153 | ELG_INFO("hello elog4cpp"); 154 | ELG_WARN("hello elog4cpp"); 155 | ELG_ERROR("hello elog4cpp"); 156 | } 157 | ``` 158 | The code generated by this macro definition is equivalent to the code in the previous example. 159 | --- 160 | 161 | * The previous examples all just output to the console, we now output the content to a file. 162 | The code is as follows: 163 | ```cpp 164 | #define ENABLE_ELG_LOG 165 | #include 166 | using namespace elog; 167 | 168 | int main() 169 | { 170 | GlobalConfig::Get() 171 | .setFilepath(". /log/") 172 | .setLevel(Levels::kTrace) 173 | .setFormatter(formatter::colorfulFormatter); 174 | ELG_TRACE("hello elog4cpp"); 175 | ELG_DEBUG("hello elog4cpp"); 176 | ELG_INFO("hello elog4cpp"); 177 | ELG_WARN("hello elog4cpp"); 178 | ELG_ERROR("hello elog4cpp"); 179 | } 180 | ``` 181 | 182 | The output of the above code is output both to the file and to the console, `setFilepath(". /log/")` specifies that the output file is in the `log` folder at the next level. Note that the file path passed here can only be the output file's folder path, meaning that file output only supports scrolling logs. If the parameter is changed to `". /log"` then the output folder path is the parent directory, and the output file names are prefixed with `log`. The output folder is named in the following format: `....log`.If you want to disable output to the console, just add the following configuration:`GlobalConfig::Get().enableConsole(false)`. Similarly, if you don't need to output to a file, you need to keep `log_filepath` at the default value of `nullptr`. 183 | 184 | After the above three practices, you should have understood the basic use of this library, and if you need to understand the corresponding use in detail, you can continue to learn more about the following contents. 185 | 186 | 1. [How to configure](#how-to-configure) 187 | 2. [Detailed interface description](#detailed-interface-description) 188 | 189 | 190 | ## How to configure 191 | 192 | All configurations are based on the `Config` class or the `GlobalConfig` class. Note the relationship between these two classes, the `Config` class acts as a base class for `GlobalConfig`, `Config` contains some output generic configuration, which is generally used for local configuration, and `GlobalConfig` contains some special one-time configuration, which is generally used as a global singleton for global configuration. 193 | 194 | ### Global configuration 195 | 196 | If no local configuration is set, the global configuration is used by default. If you are using static methods or macros for log printing, then you can only use configuration via global configuration. 197 | 198 | #### Configuration methods 199 | 200 | All global configurations are configured through a global singleton `GlobalConfig`, which can be obtained by calling `GlobalConfig::Get`. 201 | 202 | 1. call the methods of `GlobalConfig` to configure it, as follows. 203 | 204 | The special methods are as follows: 205 | * `GlobalConfig::setRollSize(int size)`: Sets the maximum size over which a single file will create a new file for log printing, with mb as the base unit. 206 | * `GlobalConfig::setFlushInterval(int flushinterval)`: Sets how often to flush the log to disk, in seconds. 207 | * `GlobalConfig::setFilepath(const char* basedir)`: set the output path of the scrolling log, note that the path passed in is not a single file path, but a folder path, this log library only supports scrolling log. 208 | * `GlobalConfig::enableConsole(bool s)`: set whether to output to console. 209 | 210 | The methods inherited from `config` are as follows: 211 | * `Config::setFlag(Flags flag)`: Sets flags, which can be used to control the content of the log output in a more fine-grained way, for flags there are the following enumerations. 212 | * `kDate`: whether to output the date. 213 | * `kTime`: whether to output the time. 214 | * `kLongname`: whether to output long file names. 215 | * `kShortname`: whether to output short file names. 216 | * `kLine`: whether output the line number. 217 | * `kFuncName`: whether output the function name. 218 | * `kThreadId`: whether output the thread id. 219 | * `kStdFlags`: represents `kDate | kTime | kShortname | kLine | kFuncName`, the or operation inside means the corresponding function is enabled. 220 | * `Config::setLevel(Levels level)`: set the minimum log output level. 221 | * `Config::setName(const char* name)`: Set the name of the logger, which will be added to the output. 222 | * `Config::setBefore(callback_t const& cb)`: Sets the callback function that occurs before formatting. 223 | * `Config::setAfter(callback_t const& cb)`: Sets the callback function that occurs after formatting. 224 | * `Config::setFormatter(formatter_t const& formatter)`: Sets the formatter, which is already written by default as follows. 225 | * `defaultFormatter` : The default formatter. 226 | * `colorfulFormatter`: bring color to the output of the console desk, based on the default formatter. 227 | * `jsonFormatter` : Output in json format. 228 | * `customFromString(str) -> formatter_t`: This is a custom formatter that you can get from the string you pass in, please see the subsequent description for details on how to use it. 229 | 2. configure by passing in the json configuration file, the details are as follows. 230 | The key methods are `loadFromJSON` and `loadToJSON`, which are used to read information from the json file to set the variable value of `GlobalConfig` and invert the value of `GlobalConfig` according to the value of the variable. to generate the corresponding json file. 231 | The specific `json` configuration file is as follows, and all usage is described in the `comments`. 232 | 233 | ```json 234 | { 235 | "comments": [ 236 | "The following values are all comments generated by default to explain the considerations for filling in the parameters", 237 | "name:optional parameter, if not filled by default, the log output will have no name", 238 | "roll_size: the threshold value for rolling the log, in mb", 239 | "flush_interval: the time in seconds to flush the log background", 240 | "out_console: whether to turn on the output console, is bool value", 241 | "out_file: if or not to turn on the output log file, use null value if not, use a folder directory if you turn on", 242 | "flag: used to turn on the log corresponding to the output data content, there are date, time, line, file, short_file, tid, func seven, can be turned on at the same time through the + sign, of course, you can also directly use default, which indicates that all options other than tid", 243 | "level:used to specify the global minimum output level, there are trace,debug,info,warn,error,fatal, the default use debug", 244 | "format: used to specify the global log formatting, there are default, colorful, custom three, the default take default, if you use custom, you need to add fmt_string", 245 | "fmt_string: only when the formatter select custom used to set the custom formatter, the corresponding data is expressed as follows: %T:time,%t:tid,%F:filepath,%f:func,%e:error info,%L:long levelText,%l: short levelText,%v:message ,%c color start %C color end" 246 | ], 247 | "elog": { 248 | "flag": "default", 249 | "flush_interval": 3, 250 | "formatter": "default", 251 | "level": "debug", 252 | "out_console": true, 253 | "out_file": "null", 254 | "roll_size": 20 255 | } 256 | } 257 | ``` 258 | 259 | #### Example of using 260 | 261 | Two simple and complete usage examples are as follows. 262 | 263 | 1. configuration via the `GlobalConfig` method. 264 | 265 | ```cpp 266 | #define ENABLE_ELG_LOG 267 | #include 268 | using namespace elog; 269 | 270 | int main() 271 | { 272 | GlobalConfig::Get() 273 | .setRollSize(4) 274 | .setFlushInterval(3) 275 | .setFilepath(". /log/") 276 | .enableConsole(true) 277 | .setFlag(kStdFlags + kThreadId) 278 | .setLevel(kTrace) 279 | .setName("elog") 280 | .setBefore([](output_buf_t& buf) { 281 | buf.append("before"); 282 | }) 283 | .setAfter([](output_buf_t& buf) { 284 | buf.append("after"); 285 | }) 286 | .setFormatter(formatter::customFromString("%c[%L][%T][tid:%t][name:%n][file:%F][func:%f]:%v%C")); 287 | ELG_TRACE("hello elog4cpp"); 288 | ELG_DEBUG("hello elog4cpp"); 289 | ELG_INFO("hello elog4cpp"); 290 | ELG_WARN("hello elog4cpp"); 291 | ELG_ERROR("hello elog4cpp"); 292 | } 293 | ``` 294 | The printed result is as follows: 295 | ![img2](https://img-blog.csdnimg.cn/39e4711a3dd0428a903a4120710d31e2.png) 296 | 297 | 2. Similarly, you can directly load the corresponding configuration items using the equivalent `json` configuration file. 298 | 299 | The configuration items are as follows: 300 | ```cpp 301 | { 302 | "elog": { 303 | "flag": "default+tid", 304 | "flush_interval": 3, 305 | "name": "elog", 306 | "formatter": "custom", 307 | "fmt_string": "%c[%L][%T][tid:%t][name:%n][file:%F][func:%f]:%v%C", 308 | "level": "trace", 309 | "out_console": true, 310 | "out_file": ". /log/", 311 | "roll_size": 4 312 | } 313 | } 314 | ``` 315 | 316 | The code is as follows: 317 | ```cpp 318 | #define ENABLE_ELG_LOG 319 | #include 320 | using namespace elog; 321 | 322 | int main() 323 | { 324 | GlobalConfig::Get() 325 | .loadFromJSON(". /config.json") 326 | .setBefore([](output_buf_t& buf) { 327 | buf.append("before"); 328 | }) 329 | .setAfter([](output_buf_t& buf) { 330 | buf.append("after"); 331 | }); 332 | ELG_TRACE("hello elog4cpp"); 333 | ELG_DEBUG("hello elog4cpp"); 334 | ELG_INFO("hello elog4cpp"); 335 | ELG_WARN("hello elog4cpp"); 336 | ELG_ERROR("hello elog4cpp"); 337 | } 338 | ``` 339 | 340 | ### Local configuration 341 | 342 | A local configuration means that a separate `Config` configuration can be used by creating a separate class. Some configurations are only available globally, specifically `roolSize`, `flushInterval`, `outConsole`, `outFile`, and because the thread responsible for output on the backend can only be a single instance, these configurations can also only be configured once. 343 | 344 | The steps on how to use the local configuration are as follows. 345 | 346 | 1. Through `Log::RegisterConfig` into your custom `Config`. 347 | 2. Create a `Log` using the configuration with the `Config` name you inject. 348 | 3. Using the `Log` object, call its corresponding `println` and `printf` methods to print. 349 | 350 | The sample code is as follows. 351 | 352 | ```cpp 353 | #include 354 | #include 355 | #include 356 | using namespace elog; 357 | 358 | void config_global() 359 | { 360 | GlobalConfig::Get() 361 | .loadFromJSON(". /config.json") 362 | .setBefore([](output_buf_t& buf) { 363 | buf.append("before"); 364 | }) 365 | .setAfter([](output_buf_t& buf) { 366 | buf.append("after"); 367 | }); 368 | } 369 | 370 | void register_local_config() 371 | { 372 | auto config = make_unique(); 373 | config->log_formatter = formatter::colorfulFormatter; 374 | config->log_name = "local_config"; 375 | config->log_level = kTrace; 376 | config->log_flag = kStdFlags + kThreadId; 377 | config->log_before = [](output_buf_t& buf) { 378 | buf.append("before"); 379 | }; 380 | config->log_after = [](output_buf_t& buf) { 381 | buf.append("after"); 382 | }; 383 | // Register Config 384 | Log::RegisterConfig("local_config",std::move(config)); 385 | } 386 | 387 | int main() 388 | { 389 | config_global(); 390 | register_local_config(); 391 | // Create Log object and set the corresponding Config and level 392 | auto trace = Log(kTrace, "local_config"); 393 | trace.printf("hello {}", "world"); 394 | trace.println("hello", std::vector{1, 2, 32}); 395 | //Change the log output level 396 | trace.set_level(kDebug); 397 | trace.printf("hello {}", "world"); 398 | trace.println("hello", std::vector{1, 2, 32}); 399 | //Can be safely copied to continue to use 400 | auto info = trace; 401 | info.set_level(kInfo); 402 | info.printf("hello {}", "world"); 403 | info.println("hello", std::vector{1, 2, 32}); 404 | } 405 | ``` 406 | > Note: Registering Config is not thread-safe, make sure you complete the registration of Config before logging out. 407 | 408 | ## Detailed interface description 409 | 410 | ### Formatter 411 | 412 | #### Built-in formatter 413 | 414 | If you have read the previous section, then you should have some idea about the role of `formatter`, which is an interface implementation for controlling formatted output. 415 | 416 | * `defaultFormatter`: This is the default formatter, with a fixed format. 417 | * `jsonFormatter` : This is the default formatter with a fixed format. 418 | * `colorfulFormatter`: The output is in the same format as the formatter, but with color highlighting when output to the console. 419 | * `customFromString`: can customize the output format according to the string passed in by the user. 420 | Specific description. 421 | * %T: indicates the whole date and time, also includes the time zone. 422 | * %t: indicates the thread id. 423 | * %F: indicates which file the log output comes from. 424 | * %f: indicates which function the log output came from. 425 | * %e: indicates the error message if there is an error in `errno`, otherwise it is empty. 426 | * %n: indicates the name of the current logger, or null if it does not exist. 427 | * %L: indicates a long string representing the logging level, such as `TRACE`. 428 | * %l: indicates a short string representing the log level, such as `TRC`. 429 | * %v: indicates the content of the log output. 430 | * %c and %C: indicates the start and end of the color, valid only in terminals that support `\033`. 431 | 432 | #### Custom formatter 433 | 434 | Since you want `formatter`, it is important to be clear about what exactly `formatter` is designed to do in this log library. In fact, `formatter` is simply a callback function in this log library, with the following function signature. 435 | 436 | ```cpp 437 | using formatter_t = std::function; 438 | ``` 439 | 440 | The meaning of each parameter is as follows. 441 | 442 | * ``config``: The current configuration used for log output. 443 | * ``ctx``: the content of the current log output, including the contents of the log to be output, the log level and line number, and other information. 444 | * `buf`: the `buffer` that the current log will be output to after final formatting. 445 | * `appder_type`: this is an enumeration representing the destination of the current log formatted output, specifically either file or console. 446 | 447 | Based on the above explanation, if you want to implement a `formatter` of your own, you can customize the formatted output to `buffer` by using the configuration information of `config` and the output information of `ctx` and the destination information of `appender_type`. 448 | 449 | With the above understanding, we can implement our own `formatter` by looking at the already implemented `formatter`, for example, the source code link for `defaultFormatter` is as follows: [src/formatter.cc](https://github.com/ACking-you/elog4cpp/blob/master/src/formatter.cc) 450 | 451 | ### Micros 452 | 453 | #### ENABLE_ELG_LOG 454 | 455 | In order to make the output of file location information without manually adding the `loc::current()` parameter, the log library provides `ELG_` to simplify this process, so if file location information is needed you can replace `Log::` with the following macro. 456 | 457 | * ELG_TRACE 458 | * ELG_DEBUG 459 | * ELG_INFO 460 | * ELG_WARN 461 | * ELG_ERROR 462 | * ELG_FATAL 463 | 464 | > Note: Before using the above macro, you need to define the `ENABLE_ELG_LOG` macro before `#include`. 465 | 466 | #### ENABLE_ELG_CHECK 467 | 468 | By defining the `ENABLE_ELG_CHECK` macro, we can use the following macro definitions to more easily check the relationship between values. 469 | * Assertion macro that throws an exception if the condition is not met. 470 | * `ELG_CHECK_EQ(a,b)` is equivalent to `ELG_ASSERT_IF(a == b)`. 471 | * `ELG_CHECK_NQ(a,b)` is equivalent to `ELG_ASSERT_IF(a != b)`. 472 | * `ELG_CHECK_GE(a,b)` is equivalent to `ELG_ASSERT_IF(a > b)`. 473 | * `ELG_CHECK_GT(a,b)` is equivalent to `ELG_ASSERT_IF(a >= b)`. 474 | * `ELG_CHECK_LE(a,b)` is equivalent to `ELG_ASSERT_IF(a < b)`. 475 | * `ELG_CHECK_LT(a,b)` is equivalent to `ELG_ASSERT_IF(a <= b)`. 476 | * `ELG_CHECK_NOTNULL(a)` is equivalent to `ELG_ASSERT_IF(a != nullptr)`. 477 | * Judgment assertion, custom printing. 478 | After passing in the judgment condition, an object will be returned for you to print the prompt message, and you can choose different levels to print, such as the following example to print the `trace` level. 479 | ```cpp 480 | ELG_CHECK(1 == 2).trace("1 != 2"); 481 | ``` 482 | 483 | ### Other util 484 | 485 | There are also the following functions that provide convenience. 486 | * `elog::Ptr`: Used to force arbitrary pointers to `void*`, this is to facilitate printing the value of the pointer directly. 487 | * `elog::WaitForDone`: waits for a background thread to flush log information to disk, which is useful at times. 488 | -------------------------------------------------------------------------------- /cmake/target_if_include.cmake: -------------------------------------------------------------------------------- 1 | function(target_if_include target path) 2 | if (TARGET ${target}) 3 | message(WARNING "Target ${target} already exists!") 4 | else () 5 | message(STATUS "path:${path}") 6 | include(${path}) 7 | endif () 8 | endfunction() -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | "下面的数值都是默认生成的注释,用于说明参数填写的注意事项", 4 | "roll_size:滚动日志的阈值,以mb为单位", 5 | "flush_interval:日志后台刷盘的时间,以秒为单位", 6 | "out_console:是否开启输出控制台,是bool值", 7 | "out_file:是否开启输出日志文件,不开启请使用null值,开启请用一个文件夹目录", 8 | "flag:用于开启日志对应输出的数据内容,有date,time,line,file,short_file,tid,func七种,可以通过+号来同时开启,当然也可直接使用default,它表示除tid以外的所有选项", 9 | "level:用于规定全局的最低输出等级,有trace,debug,info,warn,error,fatal,默认使用debug", 10 | "formatter:用于规定全局的日志格式化方式,有default,colorful,custom这三种,默认采取default,如果使用custom,则需要添加fmt_string", 11 | "fmt_string:仅当formatter选择custom后用于设定自定义的formatter,对应的数据表示如下:%n:name %T:time,%t:tid,%F:filepath,%f:func,%e:error info,%L:long levelText,%l:short levelText,%v:message ,%c color start %C color end" 12 | ], 13 | "elog": { 14 | "name": "elog4cpp", 15 | "flag": "default+tid+short_file+func", 16 | "flush_interval": 3, 17 | "formatter": "default", 18 | "level": "debug", 19 | "out_console": true, 20 | "out_file": "null", 21 | "roll_size": 20 22 | } 23 | } -------------------------------------------------------------------------------- /dependencies/any_lite.h: -------------------------------------------------------------------------------- 1 | // 2 | // Implementation of N4562 std::experimental::any (merged into C++17) for C++11 compilers. 3 | // 4 | // See also: 5 | // + http://en.cppreference.com/w/cpp/any 6 | // + http://en.cppreference.com/w/cpp/experimental/any 7 | // + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#any 8 | // + https://cplusplus.github.io/LWG/lwg-active.html#2509 9 | // 10 | // 11 | // Copyright (c) 2016 Denilson das Mercês Amorim 12 | // 13 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 14 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 15 | // 16 | #ifndef LINB_ANY_HPP 17 | #define LINB_ANY_HPP 18 | #pragma once 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | #if defined(PARTICLE) 26 | #if !defined(__cpp_exceptions) && !defined(ANY_IMPL_NO_EXCEPTIONS) && !defined(ANY_IMPL_EXCEPTIONS) 27 | # define ANY_IMPL_NO_EXCEPTIONS 28 | # endif 29 | #else 30 | // you can opt-out of exceptions by definining ANY_IMPL_NO_EXCEPTIONS, 31 | // but you must ensure not to cast badly when passing an `any' object to any_cast(any) 32 | #endif 33 | 34 | #if defined(PARTICLE) 35 | #if !defined(__cpp_rtti) && !defined(ANY_IMPL_NO_RTTI) && !defined(ANY_IMPL_RTTI) 36 | # define ANY_IMPL_NO_RTTI 37 | # endif 38 | #else 39 | // you can opt-out of RTTI by defining ANY_IMPL_NO_RTTI, 40 | // in order to disable functions working with the typeid of a type 41 | #endif 42 | 43 | 44 | namespace linb 45 | { 46 | 47 | class bad_any_cast : public std::bad_cast 48 | { 49 | public: 50 | const char* what() const noexcept override 51 | { 52 | return "bad any cast"; 53 | } 54 | }; 55 | 56 | class any final 57 | { 58 | public: 59 | /// Constructs an object of type any with an empty state. 60 | any() : 61 | vtable(nullptr) 62 | { 63 | } 64 | 65 | /// Constructs an object of type any with an equivalent state as other. 66 | any(const any& rhs) : 67 | vtable(rhs.vtable) 68 | { 69 | if(!rhs.empty()) 70 | { 71 | rhs.vtable->copy(rhs.storage, this->storage); 72 | } 73 | } 74 | 75 | /// Constructs an object of type any with a state equivalent to the original state of other. 76 | /// rhs is left in a valid but otherwise unspecified state. 77 | any(any&& rhs) noexcept : 78 | vtable(rhs.vtable) 79 | { 80 | if(!rhs.empty()) 81 | { 82 | rhs.vtable->move(rhs.storage, this->storage); 83 | rhs.vtable = nullptr; 84 | } 85 | } 86 | 87 | /// Same effect as this->clearLocation(). 88 | ~any() 89 | { 90 | this->clear(); 91 | } 92 | 93 | /// Constructs an object of type any that contains an object of type T direct-initialized with std::forward(value). 94 | /// 95 | /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. 96 | /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. 97 | template::type, any>::value>::type> 98 | any(ValueType&& value) 99 | { 100 | static_assert(std::is_copy_constructible::type>::value, 101 | "T shall satisfy the CopyConstructible requirements."); 102 | this->construct(std::forward(value)); 103 | } 104 | 105 | /// Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown. 106 | any& operator=(const any& rhs) 107 | { 108 | any(rhs).swap(*this); 109 | return *this; 110 | } 111 | 112 | /// Has the same effect as any(std::move(rhs)).swap(*this). 113 | /// 114 | /// The state of *this is equivalent to the original state of rhs and rhs is left in a valid 115 | /// but otherwise unspecified state. 116 | any& operator=(any&& rhs) noexcept 117 | { 118 | any(std::move(rhs)).swap(*this); 119 | return *this; 120 | } 121 | 122 | /// Has the same effect as any(std::forward(value)).swap(*this). No effect if a exception is thrown. 123 | /// 124 | /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. 125 | /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. 126 | template::type, any>::value>::type> 127 | any& operator=(ValueType&& value) 128 | { 129 | static_assert(std::is_copy_constructible::type>::value, 130 | "T shall satisfy the CopyConstructible requirements."); 131 | any(std::forward(value)).swap(*this); 132 | return *this; 133 | } 134 | 135 | /// If not empty, destroys the contained object. 136 | void clear() noexcept 137 | { 138 | if(!empty()) 139 | { 140 | this->vtable->destroy(storage); 141 | this->vtable = nullptr; 142 | } 143 | } 144 | 145 | /// Returns true if *this has no contained object, otherwise false. 146 | bool empty() const noexcept 147 | { 148 | return this->vtable == nullptr; 149 | } 150 | 151 | #ifndef ANY_IMPL_NO_RTTI 152 | /// If *this has a contained object of type T, typeid(T); otherwise typeid(void). 153 | const std::type_info& type() const noexcept 154 | { 155 | return empty()? typeid(void) : this->vtable->type(); 156 | } 157 | #endif 158 | 159 | /// Exchange the states of *this and rhs. 160 | void swap(any& rhs) noexcept 161 | { 162 | if(this->vtable != rhs.vtable) 163 | { 164 | any tmp(std::move(rhs)); 165 | 166 | // move from *this to rhs. 167 | rhs.vtable = this->vtable; 168 | if(this->vtable != nullptr) 169 | { 170 | this->vtable->move(this->storage, rhs.storage); 171 | //this->vtable = nullptr; -- unneeded, see below 172 | } 173 | 174 | // move from tmp (previously rhs) to *this. 175 | this->vtable = tmp.vtable; 176 | if(tmp.vtable != nullptr) 177 | { 178 | tmp.vtable->move(tmp.storage, this->storage); 179 | tmp.vtable = nullptr; 180 | } 181 | } 182 | else // same types 183 | { 184 | if(this->vtable != nullptr) 185 | this->vtable->swap(this->storage, rhs.storage); 186 | } 187 | } 188 | 189 | private: // Storage and Virtual Method Table 190 | 191 | union storage_union 192 | { 193 | using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of::value>::type; 194 | 195 | void* dynamic; 196 | stack_storage_t stack; // 2 words for e.g. shared_ptr 197 | }; 198 | 199 | /// Base VTable specification. 200 | struct vtable_type 201 | { 202 | // Note: The caller is responssible for doing .vtable = nullptr after destructful operations 203 | // such as destroy() and/or move(). 204 | 205 | #ifndef ANY_IMPL_NO_RTTI 206 | /// The type of the object this vtable is for. 207 | const std::type_info& (*type)() noexcept; 208 | #endif 209 | 210 | /// Destroys the object in the union. 211 | /// The state of the union after this call is unspecified, caller must ensure not to use src anymore. 212 | void(*destroy)(storage_union&) noexcept; 213 | 214 | /// Copies the **inner** content of the src union into the yet unitialized dest union. 215 | /// As such, both inner objects will have the same state, but on separate memory locations. 216 | void(*copy)(const storage_union& src, storage_union& dest); 217 | 218 | /// Moves the storage from src to the yet unitialized dest union. 219 | /// The state of src after this call is unspecified, caller must ensure not to use src anymore. 220 | void(*move)(storage_union& src, storage_union& dest) noexcept; 221 | 222 | /// Exchanges the storage between lhs and rhs. 223 | void(*swap)(storage_union& lhs, storage_union& rhs) noexcept; 224 | }; 225 | 226 | /// VTable for dynamically allocated storage. 227 | template 228 | struct vtable_dynamic 229 | { 230 | #ifndef ANY_IMPL_NO_RTTI 231 | static const std::type_info& type() noexcept 232 | { 233 | return typeid(T); 234 | } 235 | #endif 236 | 237 | static void destroy(storage_union& storage) noexcept 238 | { 239 | //assert(reinterpret_cast(storage.dynamic)); 240 | delete reinterpret_cast(storage.dynamic); 241 | } 242 | 243 | static void copy(const storage_union& src, storage_union& dest) 244 | { 245 | dest.dynamic = new T(*reinterpret_cast(src.dynamic)); 246 | } 247 | 248 | static void move(storage_union& src, storage_union& dest) noexcept 249 | { 250 | dest.dynamic = src.dynamic; 251 | src.dynamic = nullptr; 252 | } 253 | 254 | static void swap(storage_union& lhs, storage_union& rhs) noexcept 255 | { 256 | // just exchage the storage pointers. 257 | std::swap(lhs.dynamic, rhs.dynamic); 258 | } 259 | }; 260 | 261 | /// VTable for stack allocated storage. 262 | template 263 | struct vtable_stack 264 | { 265 | #ifndef ANY_IMPL_NO_RTTI 266 | static const std::type_info& type() noexcept 267 | { 268 | return typeid(T); 269 | } 270 | #endif 271 | 272 | static void destroy(storage_union& storage) noexcept 273 | { 274 | reinterpret_cast(&storage.stack)->~T(); 275 | } 276 | 277 | static void copy(const storage_union& src, storage_union& dest) 278 | { 279 | new (&dest.stack) T(reinterpret_cast(src.stack)); 280 | } 281 | 282 | static void move(storage_union& src, storage_union& dest) noexcept 283 | { 284 | // one of the conditions for using vtable_stack is a nothrow move constructor, 285 | // so this move constructor will never throw a exception. 286 | new (&dest.stack) T(std::move(reinterpret_cast(src.stack))); 287 | destroy(src); 288 | } 289 | 290 | static void swap(storage_union& lhs, storage_union& rhs) noexcept 291 | { 292 | storage_union tmp_storage; 293 | move(rhs, tmp_storage); 294 | move(lhs, rhs); 295 | move(tmp_storage, lhs); 296 | } 297 | }; 298 | 299 | /// Whether the type T must be dynamically allocated or can be stored on the stack. 300 | template 301 | struct requires_allocation : 302 | std::integral_constant::value // N4562 §6.3/3 [any.class] 304 | && sizeof(T) <= sizeof(storage_union::stack) 305 | && std::alignment_of::value <= std::alignment_of::value)> 306 | {}; 307 | 308 | /// Returns the pointer to the vtable of the type T. 309 | template 310 | static vtable_type* vtable_for_type() 311 | { 312 | using VTableType = typename std::conditional::value, vtable_dynamic, vtable_stack>::type; 313 | static vtable_type table = { 314 | #ifndef ANY_IMPL_NO_RTTI 315 | VTableType::type, 316 | #endif 317 | VTableType::destroy, 318 | VTableType::copy, VTableType::move, 319 | VTableType::swap, 320 | }; 321 | return &table; 322 | } 323 | 324 | protected: 325 | template 326 | friend const T* any_cast(const any* operand) noexcept; 327 | template 328 | friend T* any_cast(any* operand) noexcept; 329 | 330 | #ifndef ANY_IMPL_NO_RTTI 331 | /// Same effect as is_same(this->type(), t); 332 | bool is_typed(const std::type_info& t) const 333 | { 334 | return is_same(this->type(), t); 335 | } 336 | #endif 337 | 338 | #ifndef ANY_IMPL_NO_RTTI 339 | /// Checks if two type infos are the same. 340 | /// 341 | /// If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the 342 | /// type infos, otherwise does an actual comparision. Checking addresses is 343 | /// only a valid approach when there's no interaction with outside sources 344 | /// (other shared libraries and such). 345 | static bool is_same(const std::type_info& a, const std::type_info& b) 346 | { 347 | #ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE 348 | return &a == &b; 349 | #else 350 | return a == b; 351 | #endif 352 | } 353 | #endif 354 | 355 | /// Casts (with no type_info checks) the storage pointer as const T*. 356 | template 357 | const T* cast() const noexcept 358 | { 359 | return requires_allocation::type>::value? 360 | reinterpret_cast(storage.dynamic) : 361 | reinterpret_cast(&storage.stack); 362 | } 363 | 364 | /// Casts (with no type_info checks) the storage pointer as T*. 365 | template 366 | T* cast() noexcept 367 | { 368 | return requires_allocation::type>::value? 369 | reinterpret_cast(storage.dynamic) : 370 | reinterpret_cast(&storage.stack); 371 | } 372 | 373 | private: 374 | storage_union storage; // on offset(0) so no padding for align 375 | vtable_type* vtable; 376 | 377 | template 378 | typename std::enable_if::value>::type 379 | do_construct(ValueType&& value) 380 | { 381 | storage.dynamic = new T(std::forward(value)); 382 | } 383 | 384 | template 385 | typename std::enable_if::value>::type 386 | do_construct(ValueType&& value) 387 | { 388 | new (&storage.stack) T(std::forward(value)); 389 | } 390 | 391 | /// Chooses between stack and dynamic allocation for the type decay_t, 392 | /// assigns the correct vtable, and constructs the object on our storage. 393 | template 394 | void construct(ValueType&& value) 395 | { 396 | using T = typename std::decay::type; 397 | 398 | this->vtable = vtable_for_type(); 399 | 400 | do_construct(std::forward(value)); 401 | } 402 | }; 403 | 404 | 405 | 406 | namespace detail 407 | { 408 | template 409 | inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::true_type) 410 | { 411 | return std::move(*p); 412 | } 413 | 414 | template 415 | inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::false_type) 416 | { 417 | return *p; 418 | } 419 | } 420 | 421 | /// Performs *any_cast>>(&operand), or throws bad_any_cast on failure. 422 | template 423 | inline ValueType any_cast(const any& operand) 424 | { 425 | auto p = any_cast::type>::type>(&operand); 426 | #ifndef ANY_IMPL_NO_EXCEPTIONS 427 | if(p == nullptr) throw bad_any_cast(); 428 | #endif 429 | return *p; 430 | } 431 | 432 | /// Performs *any_cast>(&operand), or throws bad_any_cast on failure. 433 | template 434 | inline ValueType any_cast(any& operand) 435 | { 436 | auto p = any_cast::type>(&operand); 437 | #ifndef ANY_IMPL_NO_EXCEPTIONS 438 | if(p == nullptr) throw bad_any_cast(); 439 | #endif 440 | return *p; 441 | } 442 | 443 | /// 444 | /// If ValueType is MoveConstructible and isn't a lvalue reference, performs 445 | /// std::move(*any_cast>(&operand)), otherwise 446 | /// *any_cast>(&operand). Throws bad_any_cast on failure. 447 | /// 448 | template 449 | inline ValueType any_cast(any&& operand) 450 | { 451 | using can_move = std::integral_constant::value 453 | && !std::is_lvalue_reference::value>; 454 | 455 | auto p = any_cast::type>(&operand); 456 | #ifndef ANY_IMPL_NO_EXCEPTIONS 457 | if(p == nullptr) throw bad_any_cast(); 458 | #endif 459 | return detail::any_cast_move_if_true(p, can_move()); 460 | } 461 | 462 | /// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object 463 | /// contained by operand, otherwise nullptr. 464 | template 465 | inline const ValueType* any_cast(const any* operand) noexcept 466 | { 467 | using T = typename std::decay::type; 468 | 469 | #ifndef ANY_IMPL_NO_RTTI 470 | if (operand && operand->is_typed(typeid(T))) 471 | #else 472 | if (operand && operand->vtable == any::vtable_for_type()) 473 | #endif 474 | return operand->cast(); 475 | else 476 | return nullptr; 477 | } 478 | 479 | /// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object 480 | /// contained by operand, otherwise nullptr. 481 | template 482 | inline ValueType* any_cast(any* operand) noexcept 483 | { 484 | using T = typename std::decay::type; 485 | 486 | #ifndef ANY_IMPL_NO_RTTI 487 | if (operand && operand->is_typed(typeid(T))) 488 | #else 489 | if (operand && operand->vtable == any::vtable_for_type()) 490 | #endif 491 | return operand->cast(); 492 | else 493 | return nullptr; 494 | } 495 | 496 | } 497 | 498 | namespace std 499 | { 500 | inline void swap(linb::any& lhs, linb::any& rhs) noexcept 501 | { 502 | lhs.swap(rhs); 503 | } 504 | } 505 | 506 | #endif -------------------------------------------------------------------------------- /dependencies/ejson4cpp/fetch_ejson4cpp.cmake: -------------------------------------------------------------------------------- 1 | CPMAddPackage( 2 | ejson4cpp 3 | GIT_REPOSITORY https://github.com/acking-you/ejson4cpp.git 4 | GIT_TAG origin/master 5 | GIT_SHALLOW TRUE) -------------------------------------------------------------------------------- /dependencies/fetch.cmake: -------------------------------------------------------------------------------- 1 | #include(FetchContent) 2 | include(${PROJECT_SOURCE_DIR}/cmake/CPM.cmake) 3 | include(${PROJECT_SOURCE_DIR}/cmake/target_if_include.cmake) 4 | target_if_include(fmt ${CMAKE_CURRENT_LIST_DIR}/fmt/fetch_fmt.cmake) 5 | target_if_include(ejson ${CMAKE_CURRENT_LIST_DIR}/ejson4cpp/fetch_ejson4cpp.cmake) -------------------------------------------------------------------------------- /dependencies/fmt/fetch_fmt.cmake: -------------------------------------------------------------------------------- 1 | CPMAddPackage( 2 | fmt 3 | GIT_REPOSITORY https://github.com/fmtlib/fmt.git 4 | GIT_TAG origin/master 5 | GIT_SHALLOW TRUE) -------------------------------------------------------------------------------- /doc/README_zh.md: -------------------------------------------------------------------------------- 1 | 中文 | [English](../README.md) 2 | 3 | * [elog4cpp](#elog4cpp) 4 | * [快速开始](#快速开始) 5 | * [要求](#要求) 6 | * [安装与引入](#安装与引入) 7 | * [开始使用](#开始使用) 8 | * [如何配置](#如何配置) 9 | * [全局配置](#全局配置) 10 | * [配置方式](#配置方式) 11 | * [使用示例](#使用示例) 12 | * [局部配置](#局部配置) 13 | * [详细接口描述](#详细接口描述) 14 | * [Formatter](#formatter) 15 | * [内置formatter](#内置formatter) 16 | * [自定义formatter](#自定义formatter) 17 | * [Micros](#micros) 18 | * [ENABLE_ELG_LOG](#enableelglog) 19 | * [ENABLE_ELG_CHECK](#enableelgcheck) 20 | * [其他杂项](#其他杂项) 21 | 22 | 23 | # elog4cpp 24 | [![License](https://img.shields.io/badge/License-MIT-green)](https://github.com/ACking-you/elog4cpp/blob/master/LICENSE) 25 | [![Platform](https://img.shields.io/badge/Platform-Cross--platformable-blue)](https://img.shields.io/badge/Platform-Cross--platformable-blue) 26 | [![Language](https://img.shields.io/badge/Language-C%2B%2B11%20or%20above-red)](https://en.cppreference.com/w/cpp/compiler_support/11) 27 | 28 | `elog4cpp` :意味着这是一个使用上非常 `easy`,同时性能上也非常 `efficiency` c++ log 日志库。 支持c++11及以上,并且完全的跨平台。 29 | 30 | 使用 `easy` 体现在: 31 | 32 | * api简单,你只需要关注一个 `elog::Log` 类,或者静态方法 `Log::` ,又或是宏定义 `ELG_`。 33 | * 格式化输出简单,因为格式化输出使用的 [fmt](https://github.com/fmtlib/fmt) 库。 34 | * 自定义格式化方式简单,支持自定义 `formatter`,而且已经预置四种 `formatter`,包括 defaultFormatter、colorfulFormatter、jsonFormatter、customFormatter。 35 | 36 | * 配置简单,支持通过 `json` 文件一键读取配置项。 37 | * 引入简单,支持 `cmake` 命令一键引入项目并使用。 38 | 39 | 性能 `efficiency` 体现在: 40 | 41 | * 同步输出一条日志的延迟只需 `180ns` ,异步只需 `120ns`,是 spdlog 至少4倍的性能。 42 | 43 | 对于benchmark,可以参考[tests/bench_start.cc](https://github.com/ACking-you/elog4cpp/blob/master/tests/bench_start.cc) 44 | 45 | ## 快速开始 46 | 47 | ### 要求 48 | 49 | * C++11及以上,是跨全平台的 50 | 51 | ### 安装与引入 52 | 53 | 推荐用以下两种方式进行引入: 54 | 55 | * 方法一:通过cmake中的 `FetchContent` 模块引入: 56 | 57 | 1. 在项目的cmake中添加下列代码进行引入,国内如果因为网络问题无法使用可以换这个gitee的镜像源:https://gitee.com/acking-you/elog4cpp.git 58 | ```cmake 59 | include(FetchContent) 60 | 61 | FetchContent_Declare( 62 | elog4cpp 63 | GIT_REPOSITORY https://github.com/ACking-you/elog4cpp.git 64 | GIT_TAG origin/fetch 65 | GIT_SHALLOW TRUE) 66 | FetchContent_MakeAvailable(elog4cpp) 67 | ``` 68 | 2. 在需要使用该库的目标中链接 `elog` 即可。 69 | ```cmake 70 | target_link_libraries(target elog) 71 | ``` 72 | * 方法二:手动下载源代码,然后通过cmake命令引入: 73 | 74 | 1. 通过git命令下载项目源码 75 | 76 | ```shell 77 | git clone https://github.com/ACking-you/elog4cpp.git --recursive 78 | ```` 79 | 80 | 2. 将该项目添加到子项目中: 81 | 82 | ```cmake 83 | add_subdirectory(elog4cpp) 84 | ``` 85 | 86 | 3. 在需要使用该库的目标中链接 `elog` 即可。 87 | 88 | ```cmake 89 | target_link_libraries(target elog) 90 | ``` 91 | 92 | ### 开始使用 93 | 94 | * 在不经过任何配置的情况下,我们可以直接调用静态方法输出到终端,代码如下: 95 | 96 | ```cpp 97 | #include 98 | using namespace elog; 99 | 100 | int main() 101 | { 102 | Log::trace("hello elog4cpp"); 103 | Log::debug("hello elog4cpp"); 104 | Log::info("hello elog4cpp"); 105 | Log::warn("hello elog4cpp"); 106 | Log::error("hello elog4cpp"); 107 | Log::fatal("hello elog4cpp"); 108 | } 109 | ``` 110 | 111 | 输出结果如下: 112 | 113 | ![output](https://img-blog.csdnimg.cn/15ddda1c5a184e61b54afa83b33dcaa2.png) 114 | 115 | 通过上述例子,我们需要明白以下三点: 116 | 117 | 1. 本日志库的输出等级一共有 `trace` 、`debug` 、`info` 、`warn` 、`error` 、`fatal` 。 118 | 2. 默认的输出最低输出等级为 `debug` ,也就是 `trace` 等级并不输出。 119 | 3. 在进行 `fatal` 等级的输出时会抛出异常。 120 | 121 | > 实际上在 `error` 或 `fatal` 等级输出时,如果 `errno` 存在错误,那么会输出对应的错误,这在进行系统编程的时候很有用。 122 | --- 123 | 124 | * 前面的例子中,我们无法输出 `trace` 等级的日志,现在我们尝试着改变它的最低输出等级,然后将输出的内容增加更多的信息(比如文件名、行号、函数名等),并且将输出的内容以颜色高亮的形式输出。 125 | 126 | 代码如下: 127 | 128 | ```cpp 129 | #include 130 | using namespace elog; 131 | 132 | int main() 133 | { 134 | GlobalConfig::Get() 135 | .setLevel(Levels::kTrace) 136 | .setFormatter(formatter::colorfulFormatter); 137 | Log::trace(loc::current(),"hello elog4cpp"); 138 | Log::debug(loc::current(),"hello elog4cpp"); 139 | Log::info(loc::current(),"hello elog4cpp"); 140 | Log::warn(loc::current(),"hello elog4cpp"); 141 | Log::error(loc::current(),"hello elog4cpp"); 142 | } 143 | ``` 144 | 145 | 输出效果如下: 146 | 147 | ![tupian](https://img-blog.csdnimg.cn/ba02536932f54116a73c74006b6587bf.png) 148 | 从上面的示例代码中,我们发现,如果需要获得文件名等信息,需要在第一个参数中传入 `loc::current()` ,显然大多数时候我们会觉得这样使用起来会很麻烦,所以我们可以通过宏去解决这个问题,你可以像下面这样,在引入 `` 之前定义 `ENABLE_ELG_LOG` 宏来使用更简短的宏定义 `ELG_`。 149 | 150 | ```cpp 151 | #define ENABLE_ELG_LOG 152 | #include 153 | using namespace elog; 154 | 155 | int main() 156 | { 157 | GlobalConfig::Get() 158 | .setLevel(Levels::kTrace) 159 | .setFormatter(formatter::colorfulFormatter); 160 | ELG_TRACE("hello elog4cpp"); 161 | ELG_DEBUG("hello elog4cpp"); 162 | ELG_INFO("hello elog4cpp"); 163 | ELG_WARN("hello elog4cpp"); 164 | ELG_ERROR("hello elog4cpp"); 165 | } 166 | ``` 167 | 168 | 这个宏定义生成的代码与之前的示例中的代码等效。 169 | 170 | --- 171 | 172 | 173 | 174 | * 之前的例子都只是输出到控制台,我们现在把内容输出到文件中去。 175 | 176 | 代码如下: 177 | 178 | ```cpp 179 | #define ENABLE_ELG_LOG 180 | #include 181 | using namespace elog; 182 | 183 | int main() 184 | { 185 | GlobalConfig::Get() 186 | .setFilepath("../log/") 187 | .setLevel(Levels::kTrace) 188 | .setFormatter(formatter::colorfulFormatter); 189 | ELG_TRACE("hello elog4cpp"); 190 | ELG_DEBUG("hello elog4cpp"); 191 | ELG_INFO("hello elog4cpp"); 192 | ELG_WARN("hello elog4cpp"); 193 | ELG_ERROR("hello elog4cpp"); 194 | } 195 | ``` 196 | 197 | 上述代码的输出的结果既会输出到文件中也会输出到控制台中,`setFilepath("../log/")` 指定了输出文件的文件夹为上一层级的 `log` 文件夹。注意这里传递的文件路径只能是输出文件的文件夹路径,也就是说文件输出只支持滚动日志。如果参数换为 `"../log"` 则表示输出文件夹路径为上级目录,且输出文件的名字前面都带有 `log` 。输出文件夹的命名格式为:`....log` 。 198 | 199 | 如果需要禁用输出到控制台,则只需要添加下列配置:`GlobalConfig::Get().enableConsole(false)` 。同理如果不需要输出到文件,则需要保持 `log_filepath` 的值为默认值 `nullptr` 即可。 200 | 201 | 经过以上三次实践,大家应该对本库的基本使用已经有所了解,接下来如果需要详细了解对应的使用方式,则可以以继续深入了解如下内容: 202 | 203 | 1. [如何配置](#如何配置) 204 | 2. [详细接口描述](#详细接口描述) 205 | 206 | ## 如何配置 207 | 208 | 所有的配置都基于 `Config` 类或者 `GlobalConfig` 类。请注意这两个类的关系,`Config` 类作为 `GlobalConfig` 的基类,`Config` 中含有一些输出的通用配置,一般用于局部配置,`GlobalConfig` 中含有一些特殊的一次性配置,一般作为全局单例用于全局配置。 209 | 210 | ### 全局配置 211 | 212 | 如果没有设置局部配置,默认使用的都是全局配置。如果是使用静态方法或宏进行日志打印,那就只能使用通过全局配置进行配置。 213 | 214 | #### 配置方式 215 | 216 | 所有的全局配置都是通过一个全局的单例 `GlobalConfig` 来进行配置,可以调用 `GlobalConfig::Get` 来获取该单例,共有以下两种方式对该单例进行配置: 217 | 218 | 1. 调用 `GlobalConfig` 的方法来配置,具体情况如下。 219 | 特有的方法如下: 220 | * `GlobalConfig::setRollSize(int size)`:设置单个文件最大超过多大,就创建新的文件进行日志打印,以 mb 为基本单位。 221 | * `GlobalConfig::setFlushInterval(int flushinterval)`:设置每过多长时间进行一次刷新日志到磁盘,以秒为基本单位。 222 | * `GlobalConfig::setFilepath(const char* basedir)`:设置滚动日志的输出路径,注意传入的并不是单个文件路径,而是一个文件夹路径,本日志库只支持滚动日志。 223 | * `GlobalConfig::enableConsole(bool s)`:设置是否输出到控制台。 224 | 继承自 `config` 的方法如下: 225 | * `Config::setFlag(Flags flag)`:设置 flags ,该 flags 可以用于更精细的控制日志输出的内容,对于flags有以下枚举。 226 | * `kDate`:是否输出日期。 227 | * `kTime`:是否输出时间。 228 | * `kLongname`:是否输出长文件名。 229 | * `kShortname`:是否输出短文件名。 230 | * `kLine`:是否输出行号。 231 | * `kFuncName`:是否输出函数名。 232 | * `kThreadId`:是否输出线程id。 233 | * `kStdFlags`:代表 `kDate | kTime | kShortname | kLine | kFuncName` ,里面的或运算代表开启对应的功能。 234 | * `Config::setLevel(Levels level)`:设置最低的日志输出等级。 235 | * `Config::setName(const char* name)`:设置日志器的名字,会在输出的时候添加该内容。 236 | * `Config::setBefore(callback_t const& cb)`:设置发生在格式化之前的回调函数。 237 | * `Config::setAfter(callback_t const& cb)`:设置发生在格式化之后的回调函数。 238 | * `Config::setFormatter(formatter_t const& formatter)`:设置格式化器,默认已经写好了如下格式化器: 239 | * `defaultFormatter` :默认的格式化器。 240 | * `colorfulFormatter`:在默认格式化器的基础上,在控制台台的输出中带上颜色。 241 | * `jsonFormatter`:以json格式进行输出。 242 | * `customFromString(str) -> formatter_t`:这是一个可以你传入的字符串获取自定义的格式化器,具体的是使用方式请查看后续的描述。 243 | 2. 通过传入json配置文件来配置,具体情况如下。 244 | 你除了调用对应的方法来进行配置以外,还可以通过外部的json文件进行配置,关键方法在于 `loadFromJSON` 和 `loadToJSON` ,分别用于从 json 文件中读取信息设置 `GlobalConfig` 的变量值和根据 `GlobalConfig` 变量值反过来生成对应的 json 文件。 245 | 具体的 `json` 配置文件如下,所有的使用方式均在 `comments` 中有说明: 246 | 247 | ```json 248 | { 249 | "comments": [ 250 | "下面的数值都是默认生成的注释,用于说明参数填写的注意事项", 251 | "name:可选参数,默认不填则日志输出无name", 252 | "roll_size:滚动日志的阈值,以mb为单位", 253 | "flush_interval:日志后台刷盘的时间,以秒为单位", 254 | "out_console:是否开启输出控制台,是bool值", 255 | "out_file:是否开启输出日志文件,不开启请使用null值,开启请用一个文件夹目录", 256 | "flag:用于开启日志对应输出的数据内容,有date,time,line,file,short_file,tid,func七种,可以通过+号来同时开启,当然也可直接使用default,它表示除tid以外的所有选项", 257 | "level:用于规定全局的最低输出等级,有trace,debug,info,warn,error,fatal,默认使用debug", 258 | "formatter:用于规定全局的日志格式化方式,有default,colorful,custom这三种,默认采取default,如果使用custom,则需要添加fmt_string", 259 | "fmt_string:仅当formatter选择custom后用于设定自定义的formatter,对应的数据表示如下:%T:time,%t:tid,%F:filepath,%f:func,%e:error info,%L:long levelText,%l:short levelText,%v:message ,%c color start %C color end" 260 | ], 261 | "elog": { 262 | "flag": "default", 263 | "flush_interval": 3, 264 | "formatter": "default", 265 | "level": "debug", 266 | "out_console": true, 267 | "out_file": "null", 268 | "roll_size": 20 269 | } 270 | } 271 | ``` 272 | 273 | #### 使用示例 274 | 275 | 两个简单完整使用示例如下: 276 | 277 | 1. 通过 `GlobalConfig` 的方法进行配置。 278 | 279 | ```cpp 280 | #define ENABLE_ELG_LOG 281 | #include 282 | using namespace elog; 283 | 284 | int main() 285 | { 286 | GlobalConfig::Get() 287 | .setRollSize(4) 288 | .setFlushInterval(3) 289 | .setFilepath("../log/") 290 | .enableConsole(true) 291 | .setFlag(kStdFlags + kThreadId) 292 | .setLevel(kTrace) 293 | .setName("elog") 294 | .setBefore([](output_buf_t& buf) { 295 | buf.append("before"); 296 | }) 297 | .setAfter([](output_buf_t& buf) { 298 | buf.append("after"); 299 | }) 300 | .setFormatter(formatter::customFromString("%c[%L][%T][tid:%t][name:%n][file:%F][func:%f]:%v%C")); 301 | ELG_TRACE("hello elog4cpp"); 302 | ELG_DEBUG("hello elog4cpp"); 303 | ELG_INFO("hello elog4cpp"); 304 | ELG_WARN("hello elog4cpp"); 305 | ELG_ERROR("hello elog4cpp"); 306 | } 307 | ``` 308 | 309 | 打印结果如下: 310 | ![img2](https://img-blog.csdnimg.cn/39e4711a3dd0428a903a4120710d31e2.png) 311 | 312 | 2. 同理可以直接使用等效的 `json` 配置文件直接加载对应的配置项。 313 | 配置项如下: 314 | 315 | ```cpp 316 | { 317 | "elog": { 318 | "flag": "default+tid", 319 | "flush_interval": 3, 320 | "name": "elog", 321 | "formatter": "custom", 322 | "fmt_string": "%c[%L][%T][tid:%t][name:%n][file:%F][func:%f]:%v%C", 323 | "level": "trace", 324 | "out_console": true, 325 | "out_file": "../log/", 326 | "roll_size": 4 327 | } 328 | } 329 | ``` 330 | 331 | 代码如下: 332 | 333 | ```cpp 334 | #define ENABLE_ELG_LOG 335 | #include 336 | using namespace elog; 337 | 338 | int main() 339 | { 340 | GlobalConfig::Get() 341 | .loadFromJSON("../config.json") 342 | .setBefore([](output_buf_t& buf) { 343 | buf.append("before"); 344 | }) 345 | .setAfter([](output_buf_t& buf) { 346 | buf.append("after"); 347 | }); 348 | ELG_TRACE("hello elog4cpp"); 349 | ELG_DEBUG("hello elog4cpp"); 350 | ELG_INFO("hello elog4cpp"); 351 | ELG_WARN("hello elog4cpp"); 352 | ELG_ERROR("hello elog4cpp"); 353 | } 354 | ``` 355 | 356 | 357 | 358 | ### 局部配置 359 | 360 | 局部配置指的是可以通过单独创建一个类,来使用一个单独的 `Config` 配置。某些配置只提供了全局,具体有`roolSize` 、`flushInterval`、`outConsole`、`outFile`,因为后端负责输出的线程只能是一个单例,所以这些配置也只能配置一次。 361 | 362 | 关于如何使用局部配置的步骤如下: 363 | 364 | 1. 通过 `Log::RegisterConfig` 注入 `Config` 。 365 | 2. 创建 `Log` 对象并传入当前日志输出的等级以及 `Config` 的名字。 366 | 3. 使用 `Log` 对象,调用它对应的 `println` 和 `printf` 方法进行打印。 367 | 368 | 示例代码如下: 369 | 370 | ```cpp 371 | #include 372 | #include 373 | #include 374 | using namespace elog; 375 | 376 | void config_global() 377 | { 378 | GlobalConfig::Get() 379 | .loadFromJSON("../config.json") 380 | .setBefore([](output_buf_t& buf) { 381 | buf.append("before"); 382 | }) 383 | .setAfter([](output_buf_t& buf) { 384 | buf.append("after"); 385 | }); 386 | } 387 | 388 | void register_local_config() 389 | { 390 | auto config = make_unique(); 391 | config->log_formatter = formatter::colorfulFormatter; 392 | config->log_name = "local_config"; 393 | config->log_level = kTrace; 394 | config->log_flag = kStdFlags + kThreadId; 395 | config->log_before = [](output_buf_t& buf) { 396 | buf.append("before"); 397 | }; 398 | config->log_after = [](output_buf_t& buf) { 399 | buf.append("after"); 400 | }; 401 | // Register Config 402 | Log::RegisterConfig("local_config",std::move(config)); 403 | } 404 | 405 | int main() 406 | { 407 | config_global(); 408 | register_local_config(); 409 | //创建Log对象,设置level,并根据唯一的名字获取已经注册Config 410 | auto trace = Log(kTrace, "local_config"); 411 | trace.printf("hello {}", "world"); 412 | trace.println("hello ", std::vector{1, 2, 32}); 413 | //改变日志输出等级 414 | trace.set_level(kDebug); 415 | trace.printf("hello {}", "world"); 416 | trace.println("hello ", std::vector{1, 2, 32}); 417 | //移动构造到新对象 418 | auto info = std::move(trace); 419 | info.set_level(kInfo); 420 | info.printf("hello {}", "world"); 421 | info.println("hello ", std::vector{1, 2, 32}); 422 | } 423 | ``` 424 | > 注意:注册 Config 的操作不是线程安全的,请确保在进行日志输出之前完成Config的注册。 425 | 426 | ## 详细接口描述 427 | 428 | ### Formatter 429 | 430 | #### 内置formatter 431 | 432 | 如果你看过前面的内容,那么对 `formatter` 的作用应该有了一定的了解,他是一个用于控制格式化输出的接口实现,本日志库内部已经实现的 `formatter` 有如下几种: 433 | 434 | * `defaultFormatter` :这是默认的formatter,格式固定。 435 | * `jsonFormatter`:以json格式输出,格式固定。 436 | * `colorfulFormatter`:输出的格式与formatter相同,但输出到控制台的时候有颜色高亮。 437 | * `customFromString`:可以根据用户传入的字符串自定义输出格式。 438 | 具体描述: 439 | * %T:表示整个日期时间,还包括时区。 440 | * %t:表示线程id。 441 | * %F:表示该条日志输出来自哪个文件。 442 | * %f:表示该条日志输出来自哪个函数。 443 | * %e:表示如果 `errno` 存在错误则表示该错误信息,否则表示空。 444 | * %n:表示当前日志器的名字,如果不存在,则表示为空。 445 | * %L:表示长的代表日志等级的字符串,比如 `TRACE` 。 446 | * %l:表示短的代表日志等级的字符串,比如 `TRC` 。 447 | * %v:表示日志输出的内容。 448 | * %c和%C:表示颜色的开始与结束,只在支持 `\033` 的终端中有效。 449 | 450 | #### 自定义formatter 451 | 452 | 既然想要 `formatter` ,那么就必须清楚 `formatter` 在本日志库中到底被设计成了什么。实际上 `formatter` 在本日志库中只是一个简单的回调函数,函数签名如下: 453 | 454 | ```cpp 455 | using formatter_t = std::function; 456 | ``` 457 | 458 | 各个参数的含义如下: 459 | 460 | * `config`:当前日志输出使用的配置。 461 | * `ctx`:当前日志输出的相关内容,包括需要输出的日志内容、日志等级和行号等等信息。 462 | * `buf`:当前日志最终格式化后需要输出到的 `buffer` 。 463 | * `appder_type`:这是一个枚举代表当前日志格式化输出的目的地,具体有 文件 或 控制台 两种。 464 | 465 | 根据上述解释,如果想要实现一个自己的 `formatter` ,就可以通过 `config` 的配置信息和 `ctx` 的输出信息以及 `appender_type` 的目的地信息来定制化的格式化输出内容到 `buf` 中。 466 | 467 | 有了上述理解,我们就能够通过观察原本已经实现的 `formatter` 来仿照的实现自己的 `formatter` 了,例如 `defaultFormatter` 的源码链接如下:[src/formatter.cc](https://github.com/ACking-you/elog4cpp/blob/master/src/formatter.cc) 468 | 469 | ### Micros 470 | 471 | #### ENABLE_ELG_LOG 472 | 473 | 为了使得文件位置信息的输出不需要手动的添加 `loc::current()` 这一参数,本日志库提供了 `ELG_` 来简化这一过程,所以如果需要文件位置信息可以将 `Log::` 替换为下面的宏: 474 | 475 | * ELG_TRACE 476 | * ELG_DEBUG 477 | * ELG_INFO 478 | * ELG_WARN 479 | * ELG_ERROR 480 | * ELG_FATAL 481 | 482 | > 注意:使用上述宏之前,需要在 `#include` 之前定义 `ENABLE_ELG_LOG` 宏。 483 | 484 | #### ENABLE_ELG_CHECK 485 | 486 | 通过定义 `ENABLE_ELG_CHECK` 宏,我们可以使用到如下的宏定义来更方便的检查值之间的关系: 487 | * 断言宏,不满足条件,则抛出异常。 488 | * `ELG_CHECK_EQ(a,b)` 等价于 `ELG_ASSERT_IF(a == b)`. 489 | * `ELG_CHECK_NQ(a,b)` 等价于 `ELG_ASSERT_IF(a != b)`. 490 | * `ELG_CHECK_GE(a,b)` 等价于 `ELG_ASSERT_IF(a > b)`. 491 | * `ELG_CHECK_GT(a,b)` 等价于 `ELG_ASSERT_IF(a >= b)`. 492 | * `ELG_CHECK_LE(a,b)` 等价于 `ELG_ASSERT_IF(a < b)`. 493 | * `ELG_CHECK_LT(a,b)` 等价于 `ELG_ASSERT_IF(a <= b)`. 494 | * `ELG_CHECK_NOTNULL(a)` 等价于 `ELG_ASSERT_IF(a != nullptr)`. 495 | * 判断断言,自定义打印。 496 | 在传入判断条件后,会返回一个对象供你进行打印提示信息,可以选择不同的等级进行打印,如下面的例子是打印 `trace` 等级。 497 | ```cpp 498 | ELG_CHECK(1 == 2).trace("1 != 2"); 499 | ``` 500 | 501 | ### 其他杂项 502 | 503 | 还有如下提供方便的函数: 504 | * `elog::Ptr`:用于将任意指针强制转化为 `void*` ,这是为了方便直接打印指针的值。 505 | * `elog::WaitForDone`:等待后台线程将日志信息刷入磁盘,这在某些时候很有用。 -------------------------------------------------------------------------------- /doc/logger.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 'https://plantuml.com/class-diagram 3 | 4 | class noncopyable{ 5 | 6 | } 7 | 8 | class Util{ 9 | +getCurDateTime(isTime,time_t* now = nullptr); 10 | +getErrorInfo(error_code); 11 | +getLogFileName(basename,time_t& now); 12 | } 13 | 14 | class ProcessInfo{ 15 | +GetPid():pid_t 16 | +GetTid():pid_t 17 | +GetUid():pid_t 18 | +GetHostname():string 19 | } 20 | 21 | class FixedBuffer{ 22 | - m_buffer: char[SIZE] 23 | - m_cur_buf: char* 24 | 25 | + avail(): int 26 | + size(): int 27 | + data(): char* 28 | + append(char* line,int len) 29 | + bzero() 30 | + reset() 31 | } 32 | 33 | class LoggerImpl{ 34 | - unique_ptr m_logging 35 | +GetInstance(): LoggerImpl& 36 | +DoLog(context const &ctx) 37 | +LogFile(context const& ctx) 38 | +LogConsole(context const& ctx) 39 | } 40 | 41 | class CountDownLatch{ 42 | -m_cv: condition_variable 43 | -m_mtx: mutex 44 | -m_count: int 45 | +CountDownLatch(count) 46 | +wait() 47 | +countDown(): int 48 | +getCount(): int 49 | } 50 | 51 | class AsyncLogging{ 52 | -m_flushInterval: const int 53 | -m_rolSize: const off64_t 54 | -m_done: atomic 55 | -m_basename: const string 56 | -m_thread: unique_ptr 57 | -m_latch: CountDownLatch 58 | -m_mtx: mutex 59 | -m_cv: condition_variable 60 | -m_curBuffer: BufferPtr 61 | -m_nextBuffer: BufferPtr 62 | -BufferVectorPtr: m_buffers 63 | +AsyncLogging(basename,rollSize,flushInterval = 3) 64 | +append(line,len) 65 | -do_done() 66 | -thread_worker() 67 | } 68 | 69 | class LogFile{ 70 | -m_rollSize: const off64_t 71 | -m_flushInterval: const int 72 | -m_checkEveryN: const int 73 | -m_count: int 74 | -m_basename: const string 75 | -m_mtx: unique_ptr 76 | -m_lastPeriod: time_t 77 | -m_lastRoll: time_t 78 | -m_lastFlush: time_t 79 | -m_file: unique_ptr 80 | +LogFile(basename,rollSize,threadSafe = true,flushInterval = 3,checkEveryN = 1024) 81 | +append(line,len) 82 | +flush() 83 | +rollFile(now = nullptr) 84 | -append_unlocked(line,len) 85 | } 86 | 87 | 88 | class FileAppender{ 89 | - m_file: FILE* 90 | - m_buffer: char [64*1024] 91 | - m_writenBytes: off64_t 92 | + FileAppender(std::string const& filename) 93 | + append(line,len) 94 | + flush() 95 | + writtenBytes(): off64_t 96 | + resetWritten() 97 | - write(line,len) 98 | - init(basename) 99 | } 100 | LoggerImpl -.|> noncopyable 101 | LoggerImpl --o AsyncLogging 102 | 103 | AsyncLogging --|> LogFile 104 | 105 | AsyncLogging --|> FixedBuffer 106 | 107 | AsyncLogging --|> CountDownLatch 108 | 109 | AsyncLogging -.|> noncopyable 110 | LogFile --|> FileAppender 111 | 112 | LogFile -.|> noncopyable 113 | FileAppender -.|> noncopyable 114 | @enduml -------------------------------------------------------------------------------- /doc/logger_class_uml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acking-you/elog4cpp/6fb591490ef584d9a8a55b9311732ec68f5d5f86/doc/logger_class_uml.png -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB example_src ${PROJECT_SOURCE_DIR}/examples/*.cc) 2 | 3 | add_definitions(-DLOG_DIR="${PROJECT_SOURCE_DIR}/examples/log/") 4 | 5 | foreach (src_path ${example_src}) 6 | get_filename_component(target ${src_path} NAME_WLE) 7 | add_executable(${target} ${src_path}) 8 | target_link_libraries(${target} elog) 9 | endforeach () -------------------------------------------------------------------------------- /examples/simple1.cc: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace elog; 3 | 4 | int main() 5 | { 6 | Log::trace("hello elog4cpp"); 7 | Log::debug("hello elog4cpp"); 8 | Log::info("hello elog4cpp"); 9 | Log::warn("hello elog4cpp"); 10 | Log::error("hello elog4cpp"); 11 | Log::fatal("hello elog4cpp"); 12 | } -------------------------------------------------------------------------------- /examples/simple2.cc: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace elog; 3 | 4 | int main() 5 | { 6 | GlobalConfig::Get() 7 | .setLevel(Levels::kTrace) 8 | .setFormatter(formatter::colorfulFormatter); 9 | Log::trace(loc::current(), "hello elog4cpp"); 10 | Log::debug(loc::current(), "hello elog4cpp"); 11 | Log::info(loc::current(), "hello elog4cpp"); 12 | Log::warn(loc::current(), "hello elog4cpp"); 13 | Log::error(loc::current(), "hello elog4cpp"); 14 | } -------------------------------------------------------------------------------- /examples/simple3.cc: -------------------------------------------------------------------------------- 1 | #define ENABLE_ELG_LOG 2 | #include 3 | using namespace elog; 4 | 5 | int main() 6 | { 7 | GlobalConfig::Get() 8 | .setLevel(Levels::kTrace) 9 | .setFormatter(formatter::colorfulFormatter); 10 | ELG_TRACE("hello elog4cpp"); 11 | ELG_DEBUG("hello elog4cpp"); 12 | ELG_INFO("hello elog4cpp"); 13 | ELG_WARN("hello elog4cpp"); 14 | ELG_ERROR("hello elog4cpp"); 15 | } -------------------------------------------------------------------------------- /examples/simple4.cc: -------------------------------------------------------------------------------- 1 | #define ENABLE_ELG_LOG 2 | #include 3 | using namespace elog; 4 | 5 | int main() 6 | { 7 | GlobalConfig::Get() 8 | .setFilepath(LOG_DIR) 9 | .setLevel(Levels::kTrace) 10 | .setFormatter(formatter::colorfulFormatter); 11 | ELG_TRACE("hello elog4cpp"); 12 | ELG_DEBUG("hello elog4cpp"); 13 | ELG_INFO("hello elog4cpp"); 14 | ELG_WARN("hello elog4cpp"); 15 | ELG_ERROR("hello elog4cpp"); 16 | } -------------------------------------------------------------------------------- /include/elog/any.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if (__cplusplus && __cplusplus >= 201703L) || \ 4 | (_MSC_VER && _MSVC_LANG >= 201703L) 5 | #include 6 | namespace elog { 7 | using Any_t = std::any; 8 | using std::any_cast; 9 | } // namespace elog 10 | #else 11 | #include "dependencies/any_lite.h" 12 | namespace elog { 13 | using Any_t = linb::any; 14 | using linb::any_cast; 15 | } // namespace elog 16 | #endif 17 | -------------------------------------------------------------------------------- /include/elog/async_logging.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-21. 3 | // 4 | 5 | #pragma once 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "count_down_latch.h" 14 | #include "noncopyable.h" 15 | 16 | LBLOG_NAMESPACE_BEGIN 17 | 18 | struct context; 19 | struct Config; 20 | using SharedContext = std::shared_ptr; 21 | 22 | #ifdef _MSC_VER 23 | #pragma warning(push) 24 | #pragma warning(disable : 4018) 25 | #endif 26 | namespace detail { 27 | enum { kSmallBuffer = 4096, kLargeBuffer = 65536 }; 28 | 29 | struct inner_message 30 | { 31 | Config* config{}; 32 | SharedContext source; 33 | }; 34 | 35 | template 36 | class FixedBuffer : noncopyable 37 | { 38 | public: 39 | FixedBuffer() : m_data(new inner_message[SIZE]), m_current(m_data) {} 40 | 41 | ~FixedBuffer() { delete[] m_data; } 42 | 43 | FixedBuffer(FixedBuffer&& other) noexcept 44 | : m_data(other.m_data), m_current(other.m_current) 45 | { 46 | other.m_data = nullptr; 47 | other.m_current = nullptr; 48 | } 49 | 50 | FixedBuffer& operator=(FixedBuffer&& other) noexcept 51 | { 52 | m_data = other.m_data; 53 | m_current = other.m_current; 54 | other.m_data = nullptr; 55 | other.m_current = nullptr; 56 | return *this; 57 | } 58 | 59 | bool valid() { return m_data != nullptr; } 60 | 61 | int avail() { return static_cast(end_addr() - m_current); } 62 | 63 | void push(const inner_message& msg) { *(m_current++) = msg; } 64 | 65 | inner_message* begin() { return m_data; } 66 | 67 | inner_message* end() { return m_current; } 68 | 69 | void reset() { m_current = m_data; } 70 | 71 | private: 72 | inner_message* end_addr() { return m_data + SIZE; } 73 | 74 | inner_message* m_data; 75 | inner_message* m_current; 76 | }; 77 | 78 | #ifdef _MSC_VER 79 | #pragma warning(pop) 80 | #endif 81 | 82 | class AsyncLogging : noncopyable 83 | { 84 | public: 85 | explicit AsyncLogging(const char* basename, int rollSize, 86 | int flushInterval = 3); 87 | 88 | ~AsyncLogging(); 89 | 90 | void pushMsg(inner_message const& msg); 91 | 92 | void waitDone(); 93 | 94 | private: 95 | void do_done(); 96 | 97 | void thread_worker(); 98 | 99 | private: 100 | using Buffer = FixedBuffer; 101 | using BufferVector = std::vector; 102 | 103 | const int m_flushInterval; 104 | const int m_rollSize; 105 | std::atomic m_done; 106 | std::once_flag m_onceFlag; 107 | const char* m_basename; 108 | std::unique_ptr m_thread; 109 | // 用于确保刷盘的线程资源初始化完成 110 | CountDownLatch m_latch; 111 | std::mutex m_mtx; 112 | std::condition_variable m_cv; 113 | Buffer m_curBuffer; 114 | Buffer m_nextBuffer; 115 | BufferVector m_buffers; 116 | }; 117 | } // namespace detail 118 | LBLOG_NAMESPACE_END -------------------------------------------------------------------------------- /include/elog/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace elog { 5 | #if __cplusplus == 201103l 6 | template 7 | auto make_unique(Args&&... args) -> ::std::unique_ptr 8 | { 9 | return ::std::unique_ptr(new T(::std::forward(args)...)); 10 | } 11 | #else 12 | using std::make_unique; 13 | #endif 14 | 15 | enum Flags { 16 | kDate = 1 << 0, 17 | kTime = 1 << 1, 18 | kLongname = 1 << 2, 19 | kShortname = 1 << 3, 20 | kLine = 1 << 4, 21 | kFuncName = 1 << 5, 22 | kThreadId = 1 << 6, 23 | kStdFlags = kDate | kTime | kShortname | kLine | kFuncName 24 | }; 25 | 26 | enum Levels { kTrace, kDebug, kInfo, kWarn, kError, kFatal, kLevelCount }; 27 | 28 | enum Appenders { kConsole, kFile }; 29 | 30 | inline Flags operator+(Flags lhs, Flags rhs) 31 | { 32 | return static_cast(lhs | rhs); 33 | } 34 | 35 | inline bool operator<=(Levels lhs, Levels rhs) 36 | { 37 | return static_cast(lhs) <= static_cast(rhs); 38 | } 39 | 40 | inline bool operator>=(Levels lhs, Levels rhs) 41 | { 42 | return static_cast(lhs) >= static_cast(rhs); 43 | } 44 | 45 | inline bool operator<(Levels lhs, Levels rhs) 46 | { 47 | return static_cast(lhs) < static_cast(rhs); 48 | } 49 | 50 | inline bool operator>(Levels lhs, Levels rhs) 51 | { 52 | return static_cast(lhs) > static_cast(rhs); 53 | } 54 | } // namespace elog -------------------------------------------------------------------------------- /include/elog/config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-22. 3 | // 4 | #pragma once 5 | #include "formatter.h" 6 | 7 | namespace elog { 8 | // Config must have static lifetime 9 | struct Config 10 | { 11 | Flags log_flag = Flags::kStdFlags; 12 | Levels log_level = Levels::kDebug; 13 | const char* log_name = nullptr; 14 | callback_t log_before; 15 | callback_t log_after; 16 | formatter_t log_formatter = formatter::defaultFormatter; // 默认的formatter 17 | auto setFlag(Flags flag) -> Config&; 18 | 19 | auto setName(const char* name) -> Config&; 20 | 21 | auto setLevel(Levels level) -> Config&; 22 | 23 | auto setBefore(callback_t const& function) -> Config&; 24 | 25 | auto setAfter(callback_t const& function) -> Config&; 26 | 27 | auto setFormatter(formatter_t const& formatter) -> Config&; 28 | 29 | [[nodiscard]] auto level() const -> Levels { return log_level; } 30 | }; 31 | 32 | struct GlobalConfig : Config 33 | { 34 | int log_rollSize = 20 * 1024 * 1024; // 默认rollSize为20m 35 | int log_flushInterval = 3; // 默认3s刷新一次日志 36 | const char* log_filepath = nullptr; 37 | bool log_console = true; 38 | 39 | static auto Get() -> GlobalConfig&; 40 | 41 | auto setFilepath(const char* basedir) -> GlobalConfig&; 42 | 43 | auto setRollSize(int size) -> GlobalConfig&; 44 | 45 | auto setFlushInterval(int flushInterval) -> GlobalConfig&; 46 | 47 | auto enableConsole(bool s) -> GlobalConfig&; 48 | 49 | auto loadToJSON(const char* filename) -> GlobalConfig&; 50 | auto loadFromJSON(const char* filename) -> GlobalConfig&; 51 | }; 52 | 53 | } // namespace elog -------------------------------------------------------------------------------- /include/elog/context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #ifdef _MSC_VER 6 | #pragma warning(push) 7 | #pragma warning(disable : 4244) 8 | #endif 9 | #ifdef __APPLE__ 10 | #include 11 | #endif 12 | 13 | namespace elog { 14 | // The context used to pass each output 15 | struct context 16 | { 17 | // 用于传递每次输出内容的上下文 18 | int level; 19 | unsigned int tid; 20 | int line{}; 21 | int err{}; 22 | const char* short_filename{}; 23 | const char* long_filename{}; 24 | const char* func_name{}; 25 | std::string text; 26 | // 计算中间经常可变的位置信息长度,可用于通过memset优化清零 27 | static unsigned int GetNoTextAndLevelLength(context& ctx) 28 | { 29 | static const unsigned int s_ctx_len = (char*)&ctx.text - (char*)&ctx.line; 30 | return s_ctx_len; 31 | } 32 | 33 | static std::shared_ptr New() 34 | { 35 | #if __cplusplus >= 201403L || (_MSC_VER && _MSVC_LANG >= 201403L) 36 | return std::make_shared(); 37 | #else 38 | return SharedContext{new context}; 39 | #endif 40 | } 41 | }; 42 | using SharedContext = std::shared_ptr; 43 | } // namespace elog 44 | 45 | #ifdef _MSC_VER 46 | #pragma warning(pop) 47 | #endif -------------------------------------------------------------------------------- /include/elog/count_down_latch.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-23. 3 | // 4 | 5 | #pragma once 6 | #include 7 | #include 8 | 9 | #include "micros.h" 10 | 11 | LBLOG_NAMESPACE_BEGIN 12 | namespace detail { 13 | class CountDownLatch 14 | { 15 | public: 16 | explicit CountDownLatch(int count); 17 | void wait(); 18 | void countDown(); 19 | int getCount(); 20 | 21 | private: 22 | std::mutex m_mtx; 23 | std::condition_variable m_cv; 24 | int m_count; 25 | }; 26 | } // namespace detail 27 | LBLOG_NAMESPACE_END 28 | -------------------------------------------------------------------------------- /include/elog/file_appender.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-21. 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "micros.h" 9 | #include "noncopyable.h" 10 | 11 | LBLOG_NAMESPACE_BEGIN 12 | namespace detail { 13 | class FileAppender : noncopyable 14 | { 15 | public: 16 | // 'e' 代表O_CLOEXEC,可以防止多进程中文件描述符暴露给子进程 17 | explicit FileAppender(const char* filename); 18 | ~FileAppender(); 19 | void append(const char* line, size_t len); 20 | void flush(); 21 | [[nodiscard]] size_t writtenBytes() const { return m_writenBytes; } 22 | void resetWritten() { m_writenBytes = 0; } 23 | 24 | private: 25 | size_t write(const char* line, size_t len); 26 | void init(const char*); 27 | 28 | private: 29 | char m_buffer[64 * 1024]{}; 30 | FILE* m_file{}; 31 | size_t m_writenBytes{}; 32 | }; 33 | } // namespace detail 34 | LBLOG_NAMESPACE_END -------------------------------------------------------------------------------- /include/elog/formatter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-12-14. 3 | // 4 | 5 | #pragma once 6 | #include 7 | 8 | #include "common.h" 9 | #include "context.h" 10 | #include "output_buffer.h" 11 | 12 | namespace elog { 13 | struct Config; 14 | using formatter_t = std::function; 16 | class formatter 17 | { 18 | public: 19 | static void defaultFormatter(Config* config, SharedContext const& ctx, 20 | buffer_t& buf, Appenders appenderType); 21 | static void colorfulFormatter(Config* config, SharedContext const& ctx, 22 | buffer_t& buf, Appenders appenderType); 23 | static void jsonFormatter(Config* config, SharedContext const& ctx, 24 | buffer_t& buf, Appenders appenderType); 25 | // use %T:time,%t:tid,%F:filepath,%f:func, %e:error info 26 | // %L:long levelText,%l:short levelText,%v:message ,%c color start %C color 27 | // end 28 | static inline formatter_t customFromString(const char* formatString) 29 | { 30 | return [formatString](Config* config, SharedContext const& context, 31 | buffer_t& buffer, Appenders appender) { 32 | return customStringFormatter( 33 | formatString, std::forward(config), 34 | std::forward(context), 35 | std::forward(buffer), 36 | std::forward(appender)); 37 | }; 38 | } 39 | 40 | private: 41 | static void customStringFormatter(const char* format_str, Config* config, 42 | SharedContext const& ctx, buffer_t& buf, 43 | Appenders appenderType); 44 | }; // namespace formatter 45 | } // namespace elog 46 | -------------------------------------------------------------------------------- /include/elog/log_file.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-21. 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | 9 | #include "file_appender.h" 10 | 11 | LBLOG_NAMESPACE_BEGIN 12 | namespace detail { 13 | class LogFile : noncopyable 14 | { 15 | public: 16 | // 默认是线程安全,默认3s刷新,默认1024次执行检查 17 | explicit LogFile(const char* basename, int rollSize, bool threadSafe = true, 18 | int flushInterval = 3, int checkEveryN = 1024); 19 | ~LogFile() = default; 20 | 21 | void append(const char* line, int len); 22 | void flush(); 23 | void rollFile(const time_t* now = nullptr); 24 | 25 | private: 26 | void append_unlocked(const char* line, int len); 27 | 28 | private: 29 | enum { kRollPerSeconds = 60 * 60 * 24 }; 30 | const char* m_basename; 31 | std::unique_ptr m_mtx; 32 | std::unique_ptr m_file; // 真正写入文件系统的appender 33 | time_t m_lastPeriod{}; // 以天为单位的time,用取整计算表示 34 | time_t m_lastRoll{}; // 上一次roll日志的时间(精确到秒的时间) 35 | time_t m_lastFlush{}; // 上一次flush的时间 36 | 37 | const int m_rollSize; 38 | const int m_flushInterval; 39 | const int m_checkEveryN; 40 | int m_count{}; // 计算Log的次数 41 | }; 42 | } // namespace detail 43 | LBLOG_NAMESPACE_END 44 | -------------------------------------------------------------------------------- /include/elog/logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "async_logging.h" 6 | #include "config.h" 7 | #include "context.h" 8 | #include "fmt/ranges.h" 9 | #include "source_location.h" 10 | 11 | LBLOG_NAMESPACE_BEGIN 12 | 13 | using std::string; 14 | using ConfigPtr = std::unique_ptr; 15 | 16 | namespace detail { 17 | class LoggerImpl : noncopyable 18 | { 19 | private: 20 | LoggerImpl(); 21 | 22 | public: 23 | ~LoggerImpl(); 24 | 25 | static LoggerImpl& GetInstance(); 26 | 27 | void waitForDone(); 28 | 29 | /** 30 | * Not thread-safe,please use this before all log printing starts 31 | * @param name 32 | * @param config 33 | */ 34 | void registerConfig(StringView name, ConfigPtr config); 35 | 36 | Config* getConfig(StringView name); 37 | 38 | void DoLog(SharedContext const& ctx); 39 | 40 | // 自定义化的config打印 41 | void DoConfigLog(Config* config, SharedContext const& ctx); 42 | 43 | // 打印Logger内部情况 44 | static void DoInternalLog(SharedContext const& ctx); 45 | 46 | void LogFile(Config* config, SharedContext const& ctx); 47 | 48 | void LogConsole(Config* config, SharedContext const& ctx); 49 | 50 | [[maybe_unused]] static void LogConsoleUnsafe(Config* config, 51 | SharedContext const& ctx); 52 | 53 | private: 54 | void init_data(); 55 | 56 | private: 57 | std::unordered_map m_localConfigFactory; 58 | std::unique_ptr m_logging; 59 | // locked console 60 | std::mutex m_mutex; 61 | }; 62 | 63 | inline void DoLog(const SharedContext& ctx) 64 | { 65 | detail::LoggerImpl::GetInstance().DoLog(ctx); 66 | } 67 | 68 | inline void DoInternalLog(SharedContext const& ctx) 69 | { 70 | detail::LoggerImpl::DoInternalLog(ctx); 71 | } 72 | 73 | /** 74 | * Get the string length at compile time 75 | * @param str 76 | * @param len 77 | * @return 78 | */ 79 | inline constexpr size_t GetStrLen(const char* str, size_t len = 0) // NOLINT 80 | { 81 | return str[len] ? GetStrLen(str, len + 1) + 1 : 0; 82 | } 83 | 84 | /** 85 | * Get the file name based on the path at compile time 86 | * @param filename 87 | * @param len 88 | * @return 89 | */ 90 | inline constexpr const char* GetShortName(const char* filename, 91 | size_t len = string::npos) // NOLINT 92 | { 93 | return len == string::npos 94 | ? (len = GetStrLen(filename), GetShortName(filename, len)) 95 | : (len > 0 && (filename[len - 1] == '/' || filename[len - 1] == '\\')) 96 | ? filename + len 97 | : (len > 0) ? GetShortName(filename, len - 1) 98 | : filename; 99 | } 100 | 101 | } // namespace detail 102 | 103 | /** 104 | * @brief 105 | * The interface provided to the outside to customize the config output, 106 | * the default provided interface only supports the global config, 107 | * when your project needs multiple config configuration output, 108 | * you should need this interface 109 | * @param config 110 | * @param ctx 111 | */ 112 | inline void DoConfigLog(Config* config, SharedContext const& ctx) 113 | { 114 | detail::LoggerImpl::GetInstance().DoConfigLog(config, ctx); 115 | } 116 | 117 | // A small helper for print any pointer. 118 | template 119 | void* Ptr(T* ptr) 120 | { 121 | return ptr; 122 | } 123 | 124 | // A small helper for CHECK(). 125 | struct logger_helper; 126 | 127 | logger_helper Check(bool cond, source_location const& location); 128 | 129 | // A small helper for CHECK_EQ, CHECK_NE, CHECK_LE, CHECK_LT, 130 | // CHECK_GE,CHECK_GT,CHECK_NOTNULL(). 131 | void CheckIfFatal(bool cond, source_location const& location, const char* text); 132 | 133 | // wait for async logging done 134 | inline void WaitForDone() { detail::LoggerImpl::GetInstance().waitForDone(); } 135 | 136 | template 137 | using format_string_t = fmt::format_string; 138 | using loc = elog::source_location; 139 | 140 | // log by class 141 | class Log 142 | { 143 | Log() = default; 144 | 145 | public: 146 | friend struct logger_helper; 147 | 148 | explicit Log(Levels level) : m_level(level) {} 149 | 150 | explicit Log(Levels level, StringView config_name) 151 | : m_level(level), m_config(GetConfig(config_name)) 152 | { 153 | } 154 | 155 | /** 156 | * Not thread-safe,please use this before all log printing starts 157 | * @param name 158 | * @param config 159 | */ 160 | static void RegisterConfig(StringView name, ConfigPtr config) 161 | { 162 | detail::LoggerImpl::GetInstance().registerConfig(name, std::move(config)); 163 | } 164 | 165 | static Config* GetConfig(StringView name) 166 | { 167 | return detail::LoggerImpl::GetInstance().getConfig(name); 168 | } 169 | 170 | void set_level(Levels level) { m_level = level; } 171 | 172 | Levels get_level() { return m_level; } 173 | 174 | template 175 | void println(T&& first, Args&&... args) const 176 | { 177 | auto ctx = context::New(); 178 | buffer_t buffer; 179 | fmt::format_to(std::back_inserter(buffer), "{}, ", 180 | std::forward(first)); 181 | println_(ctx, buffer, std::forward(args)...); 182 | } 183 | 184 | template 185 | void println(source_location const& loc, T&& first, Args&&... args) const 186 | { 187 | auto ctx = context::New(); 188 | init_context_(ctx, loc); 189 | buffer_t buffer; 190 | fmt::format_to(std::back_inserter(buffer), "{}, ", first); 191 | ctx->text = fmt::format("{}", first); 192 | println_(ctx, buffer, std::forward(args)...); 193 | } 194 | 195 | #define ELOG_DECAY_IS(a, b) std::is_same::type, b>::value 196 | 197 | #define ELOG_ENABLE_NOT_STRING \ 198 | (!ELOG_DECAY_IS(T, StringView) && !ELOG_DECAY_IS(T, std::string) && \ 199 | !ELOG_DECAY_IS(T, const char*) && !ELOG_DECAY_IS(T, char*)) 200 | 201 | #define ELOG_ENABLE_IS_STRING \ 202 | (ELOG_DECAY_IS(T, StringView) || ELOG_DECAY_IS(T, std::string) || \ 203 | ELOG_DECAY_IS(T, const char*) || ELOG_DECAY_IS(T, char*)) 204 | 205 | template ::type = true> 207 | void println(T&& first) const 208 | { 209 | auto ctx = context::New(); 210 | ctx->text = first; 211 | log_it_(ctx); 212 | } 213 | 214 | template ::type = true> 216 | void println(T&& first) const 217 | { 218 | auto ctx = context::New(); 219 | ctx->text = fmt::format("{}", first); 220 | log_it_(ctx); 221 | } 222 | 223 | template ::type = true> 225 | void println(source_location const& loc, T&& first) const 226 | { 227 | auto ctx = context::New(); 228 | init_context_(ctx, loc); 229 | ctx->text = std::string(std::forward(first)); 230 | log_it_(ctx); 231 | } 232 | 233 | template ::type = true> 235 | void println(source_location const& loc, T&& first) const 236 | { 237 | auto ctx = context::New(); 238 | init_context_(ctx, loc); 239 | ctx->text = fmt::format("{}", std::forward(first)); 240 | log_it_(ctx); 241 | } 242 | 243 | template 244 | void printf(T&& first) const 245 | { 246 | println(first); 247 | } 248 | 249 | template 250 | void printf(source_location const& loc, T&& first) const 251 | { 252 | println(loc, first); 253 | } 254 | 255 | template 256 | void printf(format_string_t format, Args&&... args) const 257 | { 258 | auto ctx = context::New(); 259 | ctx->text = fmt::format(format, std::forward(args)...); 260 | log_it_(ctx); 261 | } 262 | 263 | template 264 | void printf(source_location const& loc, format_string_t format, 265 | Args&&... args) const 266 | { 267 | auto ctx = context::New(); 268 | init_context_(ctx, loc); 269 | ctx->text = fmt::format(format, std::forward(args)...); 270 | log_it_(ctx); 271 | } 272 | 273 | inline static Log& Get(Levels level) 274 | { 275 | Log& ret = instance(); 276 | ret.m_level = level; 277 | return ret; 278 | } 279 | 280 | template 281 | inline static void trace(format_string_t format, Args&&... args) 282 | { 283 | if (should_log_(kTrace)) 284 | Get(kTrace).printf(format, std::forward(args)...); 285 | } 286 | 287 | template 288 | inline static void trace(source_location const& loc, 289 | format_string_t format, Args&&... args) 290 | { 291 | if (should_log_(kTrace)) 292 | Get(kTrace).printf(loc, format, std::forward(args)...); 293 | } 294 | 295 | template 296 | inline static void debug(format_string_t format, Args&&... args) 297 | { 298 | if (should_log_(kDebug)) 299 | Get(kDebug).printf(format, std::forward(args)...); 300 | } 301 | 302 | template 303 | inline static void debug(source_location const& loc, 304 | format_string_t format, Args&&... args) 305 | { 306 | if (should_log_(kDebug)) 307 | Get(kDebug).printf(loc, format, std::forward(args)...); 308 | } 309 | 310 | template 311 | inline static void info(format_string_t format, Args&&... args) 312 | { 313 | if (should_log_(kInfo)) 314 | Get(kInfo).printf(format, std::forward(args)...); 315 | } 316 | 317 | template 318 | inline static void info(source_location const& loc, 319 | format_string_t format, Args&&... args) 320 | { 321 | if (should_log_(kInfo)) 322 | Get(kInfo).printf(loc, format, std::forward(args)...); 323 | } 324 | 325 | template 326 | inline static void warn(format_string_t format, Args&&... args) 327 | { 328 | if (should_log_(kWarn)) 329 | Get(kWarn).printf(format, std::forward(args)...); 330 | } 331 | 332 | template 333 | inline static void warn(source_location const& loc, 334 | format_string_t format, Args&&... args) 335 | { 336 | if (should_log_(kWarn)) 337 | Get(kWarn).printf(loc, format, std::forward(args)...); 338 | } 339 | 340 | template 341 | inline static void error(format_string_t format, Args&&... args) 342 | { 343 | if (should_log_(kError)) 344 | Get(kError).printf(format, std::forward(args)...); 345 | } 346 | 347 | template 348 | inline static void error(source_location const& loc, 349 | format_string_t format, Args&&... args) 350 | { 351 | if (should_log_(kError)) 352 | Get(kError).printf(loc, format, std::forward(args)...); 353 | } 354 | 355 | template 356 | inline static void fatal(format_string_t format, Args&&... args) 357 | { 358 | if (should_log_(kFatal)) 359 | Get(kFatal).printf(format, std::forward(args)...); 360 | } 361 | 362 | template 363 | inline static void fatal(source_location const& loc, 364 | format_string_t format, Args&&... args) 365 | { 366 | if (should_log_(kFatal)) 367 | Get(kFatal).printf(loc, format, std::forward(args)...); 368 | } 369 | 370 | template 371 | inline static void trace(T const& format) 372 | { 373 | if (should_log_(kTrace)) Get(kTrace).printf(format); 374 | } 375 | 376 | template 377 | inline static void trace(source_location const& loc, T const& format) 378 | { 379 | if (should_log_(kTrace)) Get(kTrace).printf(loc, format); 380 | } 381 | 382 | template 383 | inline static void debug(T const& format) 384 | { 385 | if (should_log_(kDebug)) Get(kDebug).printf(format); 386 | } 387 | 388 | template 389 | inline static void debug(source_location const loc, T const& format) 390 | { 391 | if (should_log_(kDebug)) Get(kDebug).printf(loc, format); 392 | } 393 | 394 | template 395 | inline static void info(T const& format) 396 | { 397 | if (should_log_(kInfo)) Get(kInfo).printf(format); 398 | } 399 | 400 | template 401 | inline static void info(source_location const& loc, T const& format) 402 | { 403 | if (should_log_(kInfo)) Get(kInfo).printf(loc, format); 404 | } 405 | 406 | template 407 | inline static void warn(T const& format) 408 | { 409 | if (should_log_(kWarn)) Get(kWarn).printf(format); 410 | } 411 | 412 | template 413 | inline static void warn(source_location const& loc, T const& format) 414 | { 415 | if (should_log_(kWarn)) Get(kWarn).printf(loc, format); 416 | } 417 | 418 | template 419 | inline static void error(T const& format) 420 | { 421 | if (should_log_(kError)) Get(kError).printf(format); 422 | } 423 | 424 | template 425 | inline static void error(source_location const& loc, T const& format) 426 | { 427 | if (should_log_(kError)) Get(kError).printf(loc, format); 428 | } 429 | 430 | template 431 | inline static void fatal(T const& format) 432 | { 433 | if (should_log_(kFatal)) Get(kFatal).printf(format); 434 | } 435 | 436 | template 437 | inline static void fatal(source_location const& loc, T const& format) 438 | { 439 | if (should_log_(kFatal)) Get(kFatal).printf(loc, format); 440 | } 441 | 442 | private: 443 | static Log& instance(); 444 | 445 | void log_it_(SharedContext& ctx) const; 446 | 447 | static bool should_log_(Levels level) 448 | { 449 | return GlobalConfig::Get().level() <= level; 450 | } 451 | 452 | template 453 | void println_(SharedContext& ctx, buffer_t& buffer, T&& first, 454 | Args&&... args) const 455 | { 456 | fmt::format_to(std::back_inserter(buffer), "{}, ", first); 457 | println_(ctx, buffer, std::forward(args)...); 458 | } 459 | 460 | template 461 | void println_(SharedContext& ctx, buffer_t& buffer, T&& first) const 462 | { 463 | fmt::format_to(std::back_inserter(buffer), "{}", first); 464 | ctx->text = fmt::to_string(buffer); 465 | log_it_(ctx); 466 | } 467 | 468 | static void init_context_(SharedContext& ctx, 469 | source_location const& location) 470 | { 471 | ctx->line = location.line(); // NOLINT 472 | ctx->func_name = location.function_name(); 473 | ctx->long_filename = location.file_name(); 474 | ctx->short_filename = detail::GetShortName(ctx->long_filename); 475 | } 476 | 477 | private: 478 | Levels m_level{}; 479 | Config* m_config{}; 480 | }; 481 | 482 | struct logger_helper 483 | { 484 | explicit logger_helper(source_location const& source) 485 | : source_(source), log_() 486 | { 487 | } 488 | 489 | logger_helper() : source_(), log_() {} 490 | 491 | template 492 | inline void trace(format_string_t format, Args&&... args) 493 | { 494 | if (!should_log_()) return; 495 | log_.set_level(kTrace); 496 | log_.printf(source_, format, std::forward(args)...); 497 | } 498 | 499 | template 500 | inline void debug(format_string_t format, Args&&... args) 501 | { 502 | if (!should_log_()) return; 503 | log_.set_level(kDebug); 504 | log_.printf(source_, format, std::forward(args)...); 505 | } 506 | 507 | template 508 | inline void info(format_string_t format, Args&&... args) 509 | { 510 | if (!should_log_()) return; 511 | log_.set_level(kInfo); 512 | log_.printf(source_, format, std::forward(args)...); 513 | } 514 | 515 | template 516 | inline void warn(format_string_t format, Args&&... args) 517 | { 518 | if (!should_log_()) return; 519 | log_.set_level(kWarn); 520 | log_.printf(source_, format, std::forward(args)...); 521 | } 522 | 523 | template 524 | inline void error(format_string_t format, Args&&... args) 525 | { 526 | if (!should_log_()) return; 527 | log_.set_level(kError); 528 | log_.printf(source_, format, std::forward(args)...); 529 | } 530 | 531 | template 532 | inline void fatal(format_string_t format, Args&&... args) 533 | { 534 | if (!should_log_()) return; 535 | log_.set_level(kFatal); 536 | log_.printf(source_, format, std::forward(args)...); 537 | } 538 | 539 | private: 540 | bool should_log_() { return source_.file_name() != nullptr; } 541 | 542 | source_location source_; 543 | Log log_; 544 | }; 545 | 546 | LBLOG_NAMESPACE_END 547 | 548 | #ifdef ENABLE_ELG_CHECK 549 | // check micro 550 | #define ELG_CHECK(condition) \ 551 | elog::Check(condition, elog::source_location::current()) 552 | 553 | #define ELG_ASSERT_IF(cond) \ 554 | elog::CheckIfFatal((cond), elog::source_location::current(), \ 555 | "assertion failed:\"" #cond "\"") 556 | 557 | #define ELG_CHECK_NOTNULL(ptr) (ELG_ASSERT_IF(ptr != nullptr), ptr) 558 | 559 | #define ELG_CHECK_EQ(v1, v2) ELG_ASSERT_IF(v1 == v2) 560 | 561 | #define ELG_CHECK_NE(v1, v2) ELG_ASSERT_IF(v1 != v2) 562 | 563 | #define ELG_CHECK_LE(v1, v2) ELG_ASSERT_IF(v1 <= v2) 564 | 565 | #define ELG_CHECK_LT(v1, v2) ELG_ASSERT_IF(v1 < v2) 566 | 567 | #define ELG_CHECK_GE(v1, v2) ELG_ASSERT_IF(v1 >= v2) 568 | 569 | #define ELG_CHECK_GT(v1, v2) ELG_ASSERT_IF(v1 > v2) 570 | 571 | #endif 572 | 573 | #ifdef ENABLE_ELG_LOG 574 | // log with position micro 575 | #define ELG_TRACE(fmt, ...) \ 576 | elog::Log::trace(elog::loc::current(__FILE__, __LINE__), fmt, ##__VA_ARGS__) 577 | 578 | #define ELG_DEBUG(fmt, ...) \ 579 | elog::Log::debug(elog::loc::current(__FILE__, __LINE__), fmt, ##__VA_ARGS__) 580 | 581 | #define ELG_INFO(fmt, ...) \ 582 | elog::Log::info(elog::loc::current(__FILE__, __LINE__), fmt, ##__VA_ARGS__) 583 | 584 | #define ELG_WARN(fmt, ...) \ 585 | elog::Log::warn(elog::loc::current(__FILE__, __LINE__), fmt, ##__VA_ARGS__) 586 | 587 | #define ELG_ERROR(fmt, ...) \ 588 | elog::Log::error(elog::loc::current(__FILE__, __LINE__), fmt, ##__VA_ARGS__) 589 | 590 | #define ELG_FATAL(fmt, ...) \ 591 | elog::Log::fatal(elog::loc::current(__FILE__, __LINE__), fmt, ##__VA_ARGS__) 592 | #endif -------------------------------------------------------------------------------- /include/elog/logger_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace elog { 5 | struct Util 6 | { 7 | static const char* getCurDateTime(bool isTime, std::time_t* now = nullptr); 8 | static const char* getErrorInfo(int error_code); 9 | static const char* getLogFileName(const char* basename, time_t& now); 10 | }; 11 | } // namespace elog -------------------------------------------------------------------------------- /include/elog/micros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define LBLOG_NAMESPACE elog 4 | #define LBLOG_NAMESPACE_BEGIN namespace LBLOG_NAMESPACE { 5 | #define LBLOG_NAMESPACE_END } 6 | #define USING_LBLOG using namespace LBLOG_NAMESPACE; 7 | #define USING_LBLOG_DETAIL using namespace LBLOG_NAMESPACE::detail; -------------------------------------------------------------------------------- /include/elog/noncopyable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | namespace elog { 3 | class noncopyable 4 | { 5 | public: 6 | noncopyable(const noncopyable&) = delete; 7 | void operator=(const noncopyable&) = delete; 8 | 9 | protected: 10 | noncopyable() = default; 11 | ~noncopyable() = default; 12 | }; 13 | } // namespace elog -------------------------------------------------------------------------------- /include/elog/output_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "any.h" 9 | #include "string_view.h" 10 | 11 | namespace elog { 12 | using buffer_t = fmt::memory_buffer; 13 | 14 | class buffer_helper 15 | { 16 | public: 17 | buffer_t* buf{}; 18 | buffer_helper() = default; 19 | explicit buffer_helper(buffer_t* fmt_buf) : buf(fmt_buf) {} 20 | auto startWith(const StringView& sv) -> bool 21 | { 22 | auto buf_sv = make_view(); 23 | if (sv.size() > buf_sv.size()) { return false; } 24 | return buf_sv.substr(0, sv.size()) == sv; 25 | } 26 | auto endWith(const StringView& sv) -> bool 27 | { 28 | auto buf_sv = make_view(); 29 | if (sv.size() > buf_sv.size()) { return false; } 30 | return buf_sv.substr(buf_sv.size() - sv.size(), sv.size()) == sv; 31 | } 32 | auto find(const StringView& sv) -> size_t { return make_view().find(sv); } 33 | auto rfind(const StringView& sv) -> size_t { return make_view().rfind(sv); } 34 | auto equal(const StringView& sv) -> bool { return make_view() == sv; } 35 | void append(const StringView& sv) const 36 | { 37 | assert(buf != nullptr); 38 | if (sv.empty()) { return; } 39 | buf->append(sv); 40 | } 41 | 42 | void push_back(char ch) const 43 | { 44 | assert(buf != nullptr); 45 | buf->push_back(ch); 46 | } 47 | 48 | template 49 | void formatTo(fmt::format_string fmt, T&&... args) 50 | { 51 | assert(buf != nullptr); 52 | format_to(std::back_inserter(*buf), fmt, std::forward(args)...); 53 | } 54 | 55 | private: 56 | [[nodiscard]] auto make_view() const -> StringView 57 | { 58 | assert(buf != nullptr); 59 | return StringView{buf->data(), buf->size()}; 60 | } 61 | }; 62 | 63 | class OutputBuffer : public buffer_helper 64 | { 65 | private: 66 | Any_t m_ctx; 67 | 68 | public: 69 | OutputBuffer() = default; 70 | explicit OutputBuffer(buffer_t* buf) : buffer_helper(buf) {} 71 | 72 | void setContext(Any_t v) { m_ctx = std::move(v); } 73 | [[nodiscard]] auto getContext() const -> const Any_t& { return m_ctx; } 74 | auto getMutableContext() -> Any_t& { return m_ctx; } 75 | }; 76 | 77 | using output_buf_t = OutputBuffer; 78 | using callback_t = std::function; 79 | 80 | // RAII for trigger before and after 81 | struct trigger_helper 82 | { 83 | output_buf_t* buffer{}; 84 | callback_t* after{}; 85 | trigger_helper(output_buf_t* buffer_, callback_t* before_, 86 | callback_t* after_) 87 | : buffer(buffer_), after(after_) 88 | { 89 | assert(buffer_ != nullptr); 90 | if (before_ && *before_) { before_->operator()(*buffer_); } 91 | } 92 | ~trigger_helper() 93 | { 94 | assert(buffer != nullptr); 95 | if (after && *after) { after->operator()(*buffer); } 96 | buffer->push_back('\n'); 97 | } 98 | }; 99 | 100 | } // namespace elog -------------------------------------------------------------------------------- /include/elog/processinfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace elog { 4 | using tid_t = unsigned int; 5 | using pid_t = unsigned int; 6 | namespace ProcessInfo { 7 | pid_t GetPid(); 8 | tid_t GetTid(); 9 | const char* GetHostname(); 10 | } // namespace ProcessInfo 11 | } -------------------------------------------------------------------------------- /include/elog/source_location.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef _MSC_VER 5 | #pragma warning(push) 6 | #pragma warning(disable : 4067) 7 | #endif 8 | 9 | namespace elog { 10 | struct source_location 11 | { 12 | public: 13 | source_location() 14 | : fileName(nullptr), functionName(nullptr), lineNumber(0), columnOffset(0) 15 | { 16 | } 17 | #if defined(_WIN32) and _MSC_VER >= 1920 18 | 19 | static constexpr source_location current( 20 | const char* fileName = __builtin_FILE(), 21 | const uint_least32_t lineNumber = __builtin_LINE(), 22 | const char* functionName = __builtin_FUNCTION(), 23 | const uint_least32_t columnOffset = __builtin_COLUMN()) noexcept 24 | #elif defined(__apple_build_version__) and defined(__clang__) and \ 25 | (__clang_major__ >= 9) 26 | static constexpr source_location current( 27 | const char* fileName = __builtin_FILE(), 28 | const uint_least32_t lineNumber = __builtin_LINE(), 29 | const char* functionName = __builtin_FUNCTION(), 30 | const uint_least32_t columnOffset = __builtin_COLUMN()) noexcept 31 | #elif defined(__GNUC__) and \ 32 | (__GNUC__ > 4 or (__GNUC__ == 4 and __GNUC_MINOR__ >= 8)) 33 | static constexpr source_location current( 34 | const char* fileName = __builtin_FILE(), 35 | const uint_least32_t lineNumber = __builtin_LINE(), 36 | const char* functionName = __builtin_FUNCTION(), 37 | const uint_least32_t columnOffset = 0) noexcept 38 | #else 39 | #warning "unsupported source_location" 40 | static constexpr source_location current( 41 | const char* fileName = "unsupported", const uint_least32_t lineNumber = 0, 42 | const char* functionName = "unsupported", 43 | const uint_least32_t columnOffset = 0) noexcept 44 | #endif 45 | { 46 | return source_location(fileName, functionName, lineNumber, columnOffset); 47 | } 48 | 49 | source_location(const source_location&) = default; 50 | source_location(source_location&&) = default; 51 | 52 | constexpr const char* file_name() const noexcept { return fileName; } 53 | 54 | constexpr const char* function_name() const noexcept { return functionName; } 55 | 56 | constexpr uint_least32_t line() const noexcept { return lineNumber; } 57 | 58 | constexpr std::uint_least32_t column() const noexcept 59 | { 60 | return columnOffset; 61 | } 62 | 63 | private: 64 | constexpr source_location(const char* fileName, const char* functionName, 65 | const uint_least32_t lineNumber, 66 | const uint_least32_t columnOffset) noexcept 67 | : fileName(fileName), 68 | functionName(functionName), 69 | lineNumber(lineNumber), 70 | columnOffset(columnOffset) 71 | { 72 | } 73 | 74 | const char* fileName; 75 | const char* functionName; 76 | const std::uint_least32_t lineNumber; 77 | const std::uint_least32_t columnOffset; 78 | }; 79 | } // namespace elog 80 | 81 | #ifdef _MSC_VER 82 | #pragma warning(pop) 83 | #endif -------------------------------------------------------------------------------- /include/elog/string_view.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if (__cplusplus && __cplusplus >= 201703L) || \ 4 | (_MSC_VER && _MSVC_LANG >= 201703L) 5 | #include 6 | namespace elog { 7 | using StringView = std::string_view; 8 | } 9 | #else 10 | #include "dependencies/string_vew_lite.h" 11 | namespace elog { 12 | using StringView = nonstd::string_view; 13 | } 14 | 15 | #endif -------------------------------------------------------------------------------- /include/elog/switch_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "string_view.h" 3 | namespace elog { 4 | 5 | inline constexpr int OP_INT(const StringView& sv) 6 | { 7 | return sv.empty() ? 0 8 | : sv[sv.size() - 1] + OP_INT({sv.data(), sv.size() - 1}); 9 | } 10 | 11 | inline constexpr int operator""_i(const char* op, size_t len) 12 | { 13 | return OP_INT({op, len}); 14 | } 15 | } // namespace elog -------------------------------------------------------------------------------- /include/elog/systemcall_wrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "micros.h" 3 | #if defined(_WIN32) 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #elif defined(__linux__) 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #endif 19 | 20 | LBLOG_NAMESPACE_BEGIN 21 | namespace platform { 22 | 23 | #define F_OK 0 /* Check for file existence */ 24 | #define X_OK 1 /* Check for execute permission. */ 25 | #define W_OK 2 /* Check for write permission */ 26 | #define R_OK 4 /* Check for read permission */ 27 | 28 | #if defined(_WIN32) 29 | 30 | // Code for Windows platform 31 | using PidType = DWORD; 32 | using TidType = DWORD; 33 | 34 | inline auto WSAStartUp() -> bool 35 | { 36 | WORD wVersionRequested; 37 | WSADATA wsaData; 38 | int err; 39 | 40 | /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ 41 | wVersionRequested = MAKEWORD(2, 2); 42 | 43 | err = WSAStartup(wVersionRequested, &wsaData); 44 | if (err != 0) 45 | { 46 | /* Tell the user that we could not find a usable */ 47 | /* Winsock DLL. */ 48 | std::printf("WSAStartup failed with error: %d\n", err); 49 | return false; 50 | } 51 | 52 | /* Confirm that the WinSock DLL supports 2.2.*/ 53 | /* Note that if the DLL supports versions greater */ 54 | /* than 2.2 in addition to 2.2, it will still return */ 55 | /* 2.2 in wVersion since that is the version we */ 56 | /* requested. */ 57 | 58 | if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 59 | { 60 | /* Tell the user that we could not find a usable */ 61 | /* WinSock DLL. */ 62 | std::printf("Could not find a usable version of Winsock.dll\n"); 63 | WSACleanup(); 64 | return false; 65 | } 66 | return true; 67 | } 68 | 69 | struct cleanHelper 70 | { 71 | ~cleanHelper() { WSACleanup(); } 72 | }; 73 | 74 | inline auto GetPid() -> PidType { return GetCurrentProcessId(); } 75 | inline auto GetTid() -> TidType { return GetCurrentThreadId(); } 76 | inline auto GetHostname(char* name, size_t len) -> int 77 | { 78 | if (!WSAStartUp()) return -1; 79 | cleanHelper h; 80 | return gethostname(name, static_cast(len)); 81 | } 82 | inline auto GetLocalTime_r(std::time_t* timer, std::tm* tm) -> std::tm* 83 | { 84 | if (localtime_s(tm, timer) < 0) { return nullptr; } 85 | return tm; 86 | } 87 | inline auto GetGmTime_r(std::time_t* timer, std::tm* tm) -> std::tm* 88 | { 89 | if (gmtime_s(tm, timer) < 0) { return nullptr; } 90 | return tm; 91 | } 92 | inline auto GetStrError_r(int err_code, char* buf, size_t len) -> char* 93 | { 94 | if (strerror_s(buf, len, err_code) < 0) { return nullptr; } 95 | return buf; 96 | } 97 | 98 | inline auto CallAccess(const char* filename, int perm) -> int 99 | { 100 | return _access(filename, perm); 101 | } 102 | inline auto CallUnlockedWrite(const void* ptr, size_t size, size_t n, 103 | FILE* stream) -> size_t 104 | { 105 | return fwrite(ptr, size, n, stream); 106 | } 107 | inline void CallSetBuffer(FILE* stream, char* buf, unsigned size) 108 | { 109 | (void)std::setvbuf(stream, buf, _IOFBF, size); 110 | } 111 | 112 | inline auto CallFPutsUnlocked(const char* str, FILE* file) -> int 113 | { 114 | return fputs(str, file); 115 | } 116 | 117 | #elif defined(__linux__) 118 | // Code for Linux platform 119 | using PidType = pid_t; 120 | using TidType = pid_t; 121 | 122 | inline auto GetPid() -> PidType { return getpid(); } 123 | inline auto GetTid() -> TidType 124 | { 125 | return static_cast(syscall(SYS_gettid)); 126 | } 127 | inline auto GetHostname(char* name, size_t len) -> int 128 | { 129 | return ::gethostname(name, len); 130 | } 131 | inline auto GetLocalTime_r(std::time_t* timer, std::tm* tm) -> std::tm* 132 | { 133 | return ::localtime_r(timer, tm); 134 | } 135 | inline auto GetGmTime_r(std::time_t* timer, std::tm* tm) -> std::tm* 136 | { 137 | return ::gmtime_r(timer, tm); 138 | } 139 | inline auto GetStrError_r(int err_code, char* buf, size_t len) -> char* 140 | { 141 | return ::strerror_r(err_code, buf, len); 142 | } 143 | inline auto CallAccess(const char* filename, int perm) -> int 144 | { 145 | return ::access(filename, perm); 146 | } 147 | inline auto CallUnlockedWrite(const void* ptr, size_t size, size_t n, 148 | FILE* stream) -> size_t 149 | { 150 | return ::fwrite_unlocked(ptr, size, n, stream); 151 | } 152 | inline void CallSetBuffer(FILE* stream, char* buf, unsigned size) 153 | { 154 | ::setbuffer(stream, buf, size); 155 | } 156 | inline auto CallFPutsUnlocked(const char* str, FILE* file) -> int 157 | { 158 | return ::fputs_unlocked(str, file); 159 | } 160 | #else 161 | #error "not supported compiler" 162 | #endif 163 | } // namespace platform 164 | LBLOG_NAMESPACE_END -------------------------------------------------------------------------------- /include/elog/trace.impl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "logger.h" 3 | 4 | #define LOG_NAMESPACE lblog:: 5 | #define LOG_DETAIL_NAMESPACE lblog::detail:: 6 | #define LOG_CONTEXT_NAME _ctx 7 | #define LTRACE 0 8 | 9 | #define INIT_LOG_(level_) \ 10 | _ctx.level = level_; \ 11 | _ctx.line = __LINE__; \ 12 | _ctx.long_filename = __FILE__; \ 13 | _ctx.short_filename = lblog::detail::GetShortName(__FILE__); \ 14 | _ctx.func_name = __FUNCTION__; 15 | 16 | #ifdef ELOG_TRACE_ 17 | // LB_TRACE_用于跟踪logger日志系统内部情况 18 | #define LB_TRACE_(fmt_, ...) \ 19 | do { \ 20 | LOG_NAMESPACE context LOG_CONTEXT_NAME; \ 21 | LOG_CONTEXT_NAME.text = fmt::format(fmt_, ##__VA_ARGS__); \ 22 | INIT_LOG_(LTRACE) \ 23 | LOG_DETAIL_NAMESPACE DoInternalLog(LOG_CONTEXT_NAME); \ 24 | } while (false) 25 | 26 | #else 27 | #define LB_TRACE_(format, ...) 28 | #endif -------------------------------------------------------------------------------- /src/async_logging.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-21. 3 | // 4 | 5 | #include "elog/async_logging.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "elog/config.h" 12 | #include "elog/log_file.h" 13 | #include "elog/logger_util.h" 14 | 15 | using namespace elog::detail; 16 | 17 | AsyncLogging::AsyncLogging(const char* basename, int rollSize, 18 | int flushInterval) 19 | 20 | : m_flushInterval(flushInterval), 21 | m_rollSize(rollSize), 22 | m_done(false), 23 | m_basename(basename), 24 | m_latch(1) 25 | { 26 | // 由于构造函数抛出异常不会调用析构,所以需要做异常安全处理 27 | try 28 | { 29 | #if __cplusplus >= 201403L || (_MSVC_LANG >= 201403L) 30 | m_thread = std::make_unique([this]() { thread_worker(); }); 31 | #else 32 | m_thread = std::unique_ptr( 33 | new std::thread([this]() { thread_worker(); })); 34 | #endif 35 | m_latch.wait(); // 等待线程任务的资源初始化完成 36 | } 37 | catch (...) 38 | { 39 | do_done(); // 需要做的额外安全处理 40 | throw std::runtime_error( 41 | "AsyncLogging create thread or buffer alloc error"); 42 | } 43 | } 44 | 45 | AsyncLogging::~AsyncLogging() 46 | { 47 | if (m_done.load(std::memory_order::memory_order_acquire)) { return; } 48 | do_done(); 49 | } 50 | 51 | // 如果发生异常,则需要维护最后的资源安全退出 52 | void AsyncLogging::do_done() 53 | { 54 | m_done.store(true, std::memory_order::memory_order_release); 55 | 56 | m_cv.notify_one(); // 由于只控制一个线程 57 | 58 | if (m_thread && m_thread->joinable()) { m_thread->join(); } 59 | } 60 | 61 | void AsyncLogging::waitDone() 62 | { 63 | if (m_done.load(std::memory_order::memory_order_acquire)) { return; } 64 | do_done(); 65 | } 66 | 67 | // 双缓冲关键代码 68 | void AsyncLogging::pushMsg(inner_message const& msg) 69 | { 70 | // 下面为关键逻辑,采取双缓冲机制,如果缓存足够则push进去,否则将缓存转移到vector中待flush到磁盘 71 | // 虽然是这样说双缓冲,实际上不存在两个层级的缓存,第二个层级只是存储待push缓存的指针,并不会有拷贝 72 | // 为了减少重复申请内存的性能开销,又增加了备用内存nextBuffer,如果缓冲区需要新的内存,则直接使用它 73 | assert(!m_done); 74 | std::lock_guard lock(m_mtx); 75 | if (m_curBuffer.avail() > 0) 76 | { 77 | // 缓存足够 78 | m_curBuffer.push(msg); 79 | return; 80 | } 81 | 82 | // 缓存满了,需要flush 83 | m_buffers.push_back(std::move(m_curBuffer)); // 转移指针所有权 84 | if (m_nextBuffer.valid()) 85 | { 86 | // 如果备用内存还存在,则直接使用备用内存 87 | m_curBuffer = std::move(m_nextBuffer); 88 | } 89 | else 90 | { 91 | // 否则重新申请 92 | m_curBuffer = Buffer(); 93 | } 94 | m_curBuffer.push(msg); 95 | m_cv.notify_one(); // 通知消费 96 | } 97 | 98 | // 异步写入和内存复用 99 | void AsyncLogging::thread_worker() 100 | { 101 | try 102 | { 103 | // 不需要线程安全的写入,因本身使用它就会加锁 104 | LogFile output(m_basename, m_rollSize, false); 105 | // 防止重复内存申请的内存 106 | Buffer newBuffer1; 107 | Buffer newBuffer2; 108 | // 用于帮助m_buffers写入磁盘的vector,每次通过swap将m_buffers的内容重置,目的是缩小临界区 109 | BufferVector buffersToWrite; 110 | buffersToWrite.reserve(16); // 提前预分配 111 | 112 | while (!m_done.load(std::memory_order::memory_order_acquire)) 113 | { 114 | { 115 | std::unique_lock lock(m_mtx); 116 | if (m_buffers.empty()) 117 | { 118 | std::call_once(m_onceFlag, [&]() { 119 | // 只执行一次,外界会等待thread任务执行到这 120 | m_latch.countDown(); 121 | }); 122 | 123 | m_cv.wait_for(lock, std::chrono::seconds(m_flushInterval)); 124 | } 125 | // 等待flush_interval后,如果有任务 126 | if (m_curBuffer.avail() > 0) 127 | { 128 | m_buffers.push_back(std::move(m_curBuffer)); 129 | m_curBuffer = std::move(newBuffer1); // NOLINT 130 | } 131 | // 交换到buffer减小临界区 132 | buffersToWrite.swap(m_buffers); 133 | if (!m_nextBuffer.valid()) 134 | { 135 | // 顺便更新下nextBuffer的内存 136 | m_nextBuffer = std::move(newBuffer2); // NOLINT 137 | } 138 | } 139 | 140 | if (buffersToWrite.empty()) { continue; } 141 | 142 | if (buffersToWrite.size() > 100) 143 | { // FIXME 144 | // 如果刷入的内存块超过100个,则丢弃到只剩下两个,后续可以加入告警通知异常 145 | char buf[256]; 146 | snprintf(buf, sizeof buf, 147 | "Dropped log messages at %s, %zd larger buffers\n", 148 | Util::getCurDateTime(true), buffersToWrite.size() - 2); 149 | fputs(buf, stderr); 150 | output.append(buf, static_cast(strlen(buf))); 151 | buffersToWrite.erase(buffersToWrite.begin() + 2, 152 | buffersToWrite.end()); 153 | } 154 | 155 | // 开始格式化并写入 156 | for (auto& buffer : buffersToWrite) 157 | { 158 | for (auto& it : buffer) 159 | { 160 | buffer_t formatBuffer; 161 | // 格式化 162 | it.config->log_formatter(it.config, it.source, formatBuffer, 163 | kFile); 164 | // 开始写入文件 165 | output.append(formatBuffer.data(), 166 | static_cast(formatBuffer.size())); 167 | } 168 | } 169 | 170 | if (buffersToWrite.size() < 2) 171 | { 172 | // 保留两个,可以进行资源的利用,即使没有2个,resize也会为你开两个空的 173 | buffersToWrite.resize(2); 174 | } 175 | 176 | if (!newBuffer1.valid()) 177 | { 178 | // 由于前面必定存留两个,则可以直接利用 179 | assert(!buffersToWrite.empty()); 180 | newBuffer1 = std::move(buffersToWrite.back()); 181 | buffersToWrite.pop_back(); 182 | newBuffer1.reset(); // 记得重新设定内部读指针 183 | } 184 | 185 | if (!newBuffer2.valid()) 186 | { // 由于前面必定存留两个,则可以直接利用 187 | assert(!buffersToWrite.empty()); 188 | newBuffer2 = std::move(buffersToWrite.back()); 189 | buffersToWrite.pop_back(); 190 | newBuffer2.reset(); 191 | } 192 | 193 | buffersToWrite.clear(); 194 | output.flush(); 195 | } 196 | output.flush(); 197 | } 198 | catch (std::exception const& e) 199 | { 200 | fprintf(stderr, "log thread exit: %s\n", e.what()); 201 | m_thread.reset(); 202 | } 203 | } -------------------------------------------------------------------------------- /src/config.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-23. 3 | // 4 | #include "elog/config.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "elog/switch_helper.h" 12 | 13 | using namespace elog; 14 | 15 | auto GlobalConfig::Get() -> GlobalConfig& 16 | { 17 | static GlobalConfig instance; 18 | return instance; 19 | } 20 | 21 | auto GlobalConfig::setRollSize(int size) -> GlobalConfig& 22 | { 23 | log_rollSize = size; 24 | return *this; 25 | } 26 | auto GlobalConfig::setFlushInterval(int flushInterval) -> GlobalConfig& 27 | { 28 | log_flushInterval = flushInterval; 29 | return *this; 30 | } 31 | auto GlobalConfig::setFilepath(const char* basedir) -> GlobalConfig& 32 | { 33 | this->log_filepath = basedir; 34 | return *this; 35 | } 36 | auto GlobalConfig::enableConsole(bool s) -> GlobalConfig& 37 | { 38 | this->log_console = s; 39 | return *this; 40 | } 41 | 42 | auto Config::setFlag(Flags flag) -> Config& 43 | { 44 | this->log_flag = flag; 45 | return *this; 46 | } 47 | auto Config::setLevel(Levels level) -> Config& 48 | { 49 | this->log_level = level; 50 | return *this; 51 | } 52 | 53 | auto Config::setBefore(callback_t const& function) -> Config& 54 | { 55 | this->log_before = function; 56 | return *this; 57 | } 58 | auto Config::setAfter(callback_t const& function) -> Config& 59 | { 60 | this->log_after = function; 61 | return *this; 62 | } 63 | 64 | auto Config::setFormatter(formatter_t const& formatter) -> Config& 65 | { 66 | log_formatter = formatter; 67 | return *this; 68 | } 69 | auto Config::setName(const char* name) -> Config& 70 | { 71 | log_name = name; 72 | return *this; 73 | } 74 | 75 | namespace detail { 76 | struct config 77 | { 78 | AUTO_GEN_INTRUSIVE(detail::config, roll_size, flush_interval, out_console, 79 | out_file, flag, level, formatter) 80 | int roll_size{}; 81 | int flush_interval{}; 82 | bool out_console{}; 83 | StringView out_file; 84 | StringView flag; 85 | StringView level; 86 | StringView formatter; 87 | StringView fmt_string; 88 | 89 | static const char* to_formatter(const formatter_t& formatter) 90 | { 91 | auto f = formatter.target(); 93 | if (!f) return nullptr; 94 | if (*f == formatter::defaultFormatter) { return "default"; } 95 | if (*f == formatter::colorfulFormatter) { return "colorful"; } 96 | if (*f == formatter::jsonFormatter) { return "json"; } 97 | return "custom"; 98 | } 99 | static formatter_t from_formatter(const StringView& formatter, 100 | const char* format_string = nullptr) 101 | { 102 | switch (OP_INT(formatter)) 103 | { 104 | case "default"_i: return formatter::defaultFormatter; 105 | case "json"_i: return formatter::jsonFormatter; 106 | case "colorful"_i: return formatter::colorfulFormatter; 107 | case "custom"_i: 108 | if (format_string) 109 | return formatter::customFromString(format_string); 110 | else 111 | std::cerr << "you use custom formatter but not define," 112 | "format_string.default formatter will be used."; 113 | default: 114 | std::cerr << "not valid formatter.default formatter will be used."; 115 | } 116 | return formatter::defaultFormatter; 117 | } 118 | 119 | static void add_flag(int& flag, StringView op) 120 | { 121 | switch (OP_INT(op)) 122 | { 123 | case "date"_i: flag |= kDate; return; 124 | case "time"_i: flag |= kTime; return; 125 | case "file"_i: flag |= kLongname; return; 126 | case "short_file"_i: flag |= kShortname; return; 127 | case "tid"_i: flag |= kThreadId; return; 128 | case "line"_i: flag |= kLine; return; 129 | case "func"_i: flag |= kFuncName; return; 130 | case "default"_i: flag |= kStdFlags; return; 131 | default: std::cerr << "invalid format_flag:" << op; 132 | } 133 | } 134 | 135 | static int from_flags(const StringView& flags) 136 | { 137 | size_t start = 0, i = 0; 138 | int ret = 0; 139 | // trim left 140 | while (i < flags.size() && std::isspace(flags[i])) ++i; 141 | start = i; 142 | while (i < flags.size()) 143 | { 144 | if (std::isspace(flags[i]) || flags[i] == '+') 145 | { 146 | add_flag(ret, flags.substr(start, i - start)); 147 | // skip to next start 148 | ++i; 149 | while (i < flags.size() && 150 | (std::isspace(flags[i]) || flags[i] == '+')) 151 | ++i; 152 | if (i >= flags.size()) { return ret; } 153 | start = i; 154 | continue; 155 | } 156 | ++i; 157 | } 158 | add_flag(ret, flags.substr(start, i - start)); 159 | return ret; 160 | } 161 | 162 | static void flags_append(char* flags, const char* data) 163 | { 164 | size_t i = 0; 165 | while (data[i]) 166 | { 167 | flags[i] = data[i]; 168 | ++i; 169 | } 170 | } 171 | 172 | static const char* to_flags(int flags) 173 | { 174 | thread_local char buf[8 * 12]; 175 | std::memset(buf, 0, sizeof(buf)); 176 | size_t size{}; 177 | if (flags | kDate) 178 | { 179 | flags_append(buf + size, "date+"); 180 | size += 5; 181 | } 182 | if (flags | kTime) 183 | { 184 | flags_append(buf + size, "time+"); 185 | size += 5; 186 | } 187 | if (flags | kLine) 188 | { 189 | flags_append(buf + size, "line+"); 190 | size += 5; 191 | } 192 | if (flags | kThreadId) 193 | { 194 | flags_append(buf + size, "tid+"); 195 | size += 4; 196 | } 197 | if (flags | kFuncName) 198 | { 199 | flags_append(buf + size, "func+"); 200 | size += 5; 201 | } 202 | if (flags | kLongname) 203 | { 204 | flags_append(buf + size, "file+"); 205 | size += 5; 206 | } 207 | if (flags | kShortname) 208 | { 209 | flags_append(buf + size, "short_file+"); 210 | size += 11; 211 | } 212 | buf[size - 1] = '\0'; 213 | return buf; 214 | } 215 | static int from_level(const StringView& level) 216 | { 217 | switch (OP_INT(level)) 218 | { 219 | case "trace"_i: return kTrace; 220 | case "debug"_i: return kDebug; 221 | case "info"_i: return kInfo; 222 | case "warn"_i: return kWarn; 223 | case "error"_i: return kError; 224 | case "fatal"_i: return kFatal; 225 | default: std::cerr << "not valid level,default use debug level."; 226 | } 227 | return kDebug; 228 | } 229 | static const char* to_level(int level) 230 | { 231 | switch (level) 232 | { 233 | case kTrace: return "trace"; 234 | case kDebug: return "debug"; 235 | case kInfo: return "info"; 236 | case kWarn: return "warn"; 237 | case kError: return "error"; 238 | case kFatal: return "fatal"; 239 | default: 240 | std::cerr 241 | << "error in to_level:not valid level,default use debug level"; 242 | } 243 | return "debug"; 244 | } 245 | }; 246 | } // namespace detail 247 | 248 | #ifdef _MSC_VER 249 | #pragma warning(push) 250 | #pragma warning(disable : 4996) 251 | #endif 252 | 253 | auto GlobalConfig::loadFromJSON(const char* filename) -> GlobalConfig& 254 | { 255 | thread_local detail::config t_config; 256 | thread_local char t_filepath[1024]; 257 | thread_local char t_log_name[1024]; 258 | thread_local char t_fmt_string[1024]; 259 | 260 | auto& object = ejson::Parser::FromFile(filename).at("elog").ref; 261 | from_json(object, t_config); 262 | 263 | // str copy 264 | if (t_config.out_file.empty() || t_config.out_file.compare("null") == 0) 265 | { 266 | log_filepath = nullptr; 267 | } 268 | else 269 | { 270 | if (t_config.out_file.size() > 1024) 271 | { 272 | EJSON_THROW_ERROR_POS("`filepath` length must be less than 1024"); 273 | } 274 | std::strncpy(t_filepath, t_config.out_file.data(), 275 | t_config.out_file.size()); 276 | log_filepath = t_filepath; 277 | } 278 | if (object.has_key("fmt_string")) 279 | { 280 | auto data = object.at("fmt_string").ref.cast(); 281 | if (data.size() > 1024) 282 | { 283 | EJSON_THROW_ERROR_POS("`fmt_string` length must be less than 1024"); 284 | } 285 | std::strncpy(t_fmt_string, data.data(), data.size()); 286 | } 287 | 288 | if (object.has_key("name")) 289 | { 290 | auto data = object.at("name").ref.cast(); 291 | if (data.size() > 1024) 292 | { 293 | EJSON_THROW_ERROR_POS("`name` length must be less than 1024"); 294 | } 295 | std::strncpy(t_log_name, data.data(), data.size()); 296 | } 297 | 298 | // 以mb为单位 299 | if (t_config.roll_size > 128) 300 | { 301 | std::cerr << "roll_size must be less than 128mb,now you set to " 302 | << t_config.roll_size << ".use default roll_size = 4mb"; 303 | t_config.roll_size = 4; 304 | } 305 | log_rollSize = t_config.roll_size * (1024 * 1024); 306 | log_flushInterval = t_config.flush_interval; 307 | log_console = t_config.out_console; 308 | log_flag = static_cast(detail::config::from_flags(t_config.flag)); 309 | log_level = static_cast(detail::config::from_level(t_config.level)); 310 | log_formatter = detail::config::from_formatter( 311 | t_config.formatter, object.has_key("fmt_string") ? t_fmt_string : nullptr); 312 | log_name = object.has_key("name") ? t_log_name : nullptr; 313 | 314 | return *this; 315 | } 316 | 317 | #pragma warning(default : 4996) 318 | 319 | auto GlobalConfig::loadToJSON(const char* filename) -> GlobalConfig& 320 | { 321 | thread_local detail::config t_config; 322 | // 以mb为单位 323 | t_config.roll_size = log_rollSize / (1024 * 1024); 324 | t_config.flush_interval = log_flushInterval; 325 | t_config.out_console = log_console; 326 | t_config.out_file = log_filepath ? log_filepath : "null"; 327 | t_config.flag = detail::config::to_flags(log_flag); 328 | t_config.level = detail::config::to_level(log_level); 329 | t_config.formatter = detail::config::to_formatter(log_formatter); 330 | auto object = ejson::JObject::Dict(); 331 | object.at("elog").get_from(t_config); 332 | auto list = ejson::JObject::List(); 333 | list.push_back( 334 | "The following values are default-generated comments intended to explain " 335 | "considerations for filling in the parameters:"); 336 | list.push_back("name: optional parameter, if left empty by default, the log " 337 | "output will lack a name."); 338 | list.push_back( 339 | "roll_size:Threshold for rolling logs, measured in megabytes (MB)"); 340 | list.push_back("flush_interval:The time interval for the log backend to " 341 | "flush, measured in seconds."); 342 | list.push_back("out_console:Whether to enable console output, represented " 343 | "as a boolean value."); 344 | list.push_back( 345 | "out_file:" 346 | "是否开启输出日志文件,不开启请使用null值,开启请用一个文件夹目录"); 347 | list.push_back( 348 | "flag:The data content used to enable corresponding log output has seven " 349 | "options: `date`, `time`, `line`, `file`, `short_file`, `tid`, `func`. " 350 | "You can activate them simultaneously using the plus sign (+), or simply " 351 | "use 'default' to represent all options except 'tid'."); 352 | list.push_back( 353 | "level:\n" 354 | "It is used to define the global minimum output level, with options " 355 | "including `trace`, `debug`, `info`, `warn`, `error`, `fatal`. By " 356 | "default, it uses `debug` as the minimum output level."); 357 | list.push_back("formatter:\n" 358 | "It's used to specify the global log formatting style, " 359 | "offering three options: default, colorful, custom. By " 360 | "default, it uses the default formatting style. If 'custom' " 361 | "is chosen, you'll need to add 'fmt_string'."); 362 | list.push_back( 363 | "fmt_string:" 364 | "This is the format used exclusively when the 'custom' formatter is " 365 | "selected. The corresponding data representation is as follows: %T for " 366 | "time, %t for thread ID, %F for file path, %f for function, %e for error " 367 | "info, %L for long level text, %l for short level text, %v for message, " 368 | "%c for color start, and %C for color end."); 369 | object.at("comments").ref = std::move(list); 370 | ejson::Parser::ToFile(filename, object, 2); 371 | 372 | return *this; 373 | } 374 | 375 | #ifdef _MSC_VER 376 | #pragma warning(pop) 377 | #endif -------------------------------------------------------------------------------- /src/count_down_latch.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-23. 3 | // 4 | 5 | #include "elog/count_down_latch.h" 6 | 7 | USING_LBLOG_DETAIL 8 | 9 | CountDownLatch::CountDownLatch(int count) : m_mtx(), m_cv(), m_count(count) {} 10 | 11 | void CountDownLatch::wait() 12 | { 13 | std::unique_lock lock(m_mtx); 14 | while (m_count > 0) { m_cv.wait(lock); } 15 | } 16 | 17 | void CountDownLatch::countDown() 18 | { 19 | std::lock_guard lock(m_mtx); 20 | --m_count; 21 | if (m_count == 0) { m_cv.notify_all(); } 22 | } 23 | 24 | int CountDownLatch::getCount() 25 | { 26 | std::lock_guard lock(m_mtx); 27 | return m_count; 28 | } -------------------------------------------------------------------------------- /src/file_appender.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-21. 3 | // 4 | #include "elog/file_appender.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "elog/logger_util.h" 11 | 12 | #if defined(_WIN32) 13 | #include 14 | #else 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #endif 21 | 22 | USING_LBLOG_DETAIL 23 | 24 | //'e' 代表O_CLOEXEC 防止fork多进程文件描述符未关闭 25 | FileAppender::FileAppender(const char* filename) { init(filename); } 26 | 27 | void FileAppender::init(const char* filename) 28 | { 29 | // 判断写入文件的文件夹是否存在,以及最终文件是否正常打开 30 | auto filepos = ::strrchr(filename, '/'); 31 | if (!filepos) 32 | { 33 | // LB_TRACE_("无效的文件路径 {}", filename); 34 | throw std::runtime_error(std::string("invalid filepath ") + 35 | std::string(filename)); 36 | } 37 | const_cast(filepos)[0] = '\0'; 38 | #if defined(_WIN32) 39 | int ret = _access(filename, 0); 40 | #else 41 | int ret = ::access(filename, F_OK); 42 | #endif 43 | if (ret == -1) 44 | { 45 | throw std::runtime_error(std::string("file directory not exist: ") + 46 | filename); 47 | } 48 | const_cast(filepos)[0] = '/'; 49 | 50 | assert(const_cast(filepos)[0] != '\0'); 51 | 52 | #if defined(_WIN32) 53 | ::fopen_s(&m_file, filename, "a"); 54 | #else 55 | m_file = ::fopen(filename, "ae"); 56 | #endif 57 | if (m_file == nullptr) 58 | { 59 | int err = ferror(m_file); 60 | auto* errorInfo = Util::getErrorInfo(err); 61 | // LB_TRACE_("FileAppender 初始化失败 error:{}", errorInfo); 62 | fprintf(stderr, "FileAppender error in open file:%s erron:%s \r\n", 63 | filename, errorInfo); 64 | throw std::runtime_error("panic:FILE* is null"); 65 | } 66 | std::setvbuf(m_file, m_buffer, _IOFBF, sizeof(m_buffer)); 67 | } 68 | #pragma warning(default : 4996) 69 | 70 | FileAppender::~FileAppender() 71 | { 72 | if (m_file != nullptr) 73 | { 74 | ::fflush(m_file); 75 | ::fclose(m_file); 76 | } 77 | } 78 | 79 | void FileAppender::append(const char* line, size_t len) 80 | { 81 | size_t written = 0; 82 | 83 | while (written != len) 84 | { 85 | size_t remain = len - written; 86 | size_t n = write(line + written, remain); 87 | if (n != remain) 88 | { 89 | int err = ferror(m_file); 90 | if (err) 91 | { 92 | // LB_TRACE_("write写入预期内容失败且发生错误,error:{}", 93 | // Util::getErrorInfo(err)); 94 | fprintf(stderr, "AppendFile::append() failed %s\n", 95 | Util::getErrorInfo(err)); 96 | break; 97 | } 98 | if (n == 0) { throw std::runtime_error("write 出错,FILE*为空"); } 99 | } 100 | written += n; 101 | } 102 | 103 | m_writenBytes += written; 104 | } 105 | 106 | void FileAppender::flush() 107 | { 108 | if (m_file) { ::fflush(m_file); } 109 | } 110 | 111 | size_t FileAppender::write(const char* line, size_t len) 112 | { 113 | size_t sz = 0; 114 | if (m_file) 115 | { 116 | #if !defined(__linux__) 117 | sz = ::fwrite(line, 1, len, m_file); 118 | #else 119 | sz = ::fwrite_unlocked(line, 1, len, m_file); 120 | #endif 121 | } 122 | return sz; 123 | } 124 | -------------------------------------------------------------------------------- /src/formatter.cc: -------------------------------------------------------------------------------- 1 | #include "elog/formatter.h" 2 | 3 | #include "elog/config.h" 4 | #include "elog/logger_util.h" 5 | #include "elog/micros.h" 6 | #include "elog/switch_helper.h" 7 | #include "fmt/color.h" 8 | 9 | using namespace elog; 10 | 11 | namespace { 12 | 13 | #define LEVEL_COUNT static_cast(Levels::kLevelCount) 14 | 15 | const char* s_level_text[LEVEL_COUNT + 1] = {"TRACE", "DEBUG", "INFO", "WARN", 16 | "ERROR", "FATAL", "UNKOW"}; 17 | const char* s_simple_text[LEVEL_COUNT + 1] = {"TRC", "DEB", "INF", "WAR", 18 | "ERR", "FAL", "UNK"}; 19 | 20 | fmt::color s_color[LEVEL_COUNT + 1] = { 21 | fmt::color::light_blue, fmt::color::teal, fmt::color::green, 22 | fmt::color::yellow, fmt::color::red, fmt::color::purple, 23 | fmt::color::orange_red, // if not in range 24 | }; 25 | 26 | const char* s_ansi_color[LEVEL_COUNT + 1] = { 27 | "\033[36m", // light_blue 28 | "\033[34m", // teal 29 | "\033[32m", // green 30 | "\033[33m", // yellow 31 | "\033[31m", // red 32 | "\033[35m", // purple 33 | "\033[4;31m", // red and underline 34 | }; 35 | 36 | inline const char* GET_LEVEL_TEXT(int level, bool simple = false) 37 | { 38 | if (simple) 39 | { 40 | if (level >= LEVEL_COUNT || level < 0) 41 | return s_simple_text[LEVEL_COUNT]; // level not in range 42 | return s_simple_text[level]; 43 | } 44 | if (level >= LEVEL_COUNT || level < 0) 45 | return s_level_text[LEVEL_COUNT]; // level not in range 46 | return s_level_text[level]; 47 | } 48 | 49 | inline fmt::color GET_COLOR_BY_LEVEL(int level) 50 | { 51 | if (level >= LEVEL_COUNT || level < 0) 52 | return s_color[LEVEL_COUNT]; // level not in range 53 | return s_color[level]; 54 | } 55 | inline const char* GET_ANSI_COLOR_BY_LEVEL(int level) 56 | { 57 | if (level >= LEVEL_COUNT || level < 0) 58 | return s_ansi_color[LEVEL_COUNT]; // level not in range 59 | return s_ansi_color[level]; 60 | } 61 | 62 | #define INT(x) static_cast(x) 63 | #define IS_SET(log_flag_, flags_) (INT(log_flag_) & INT(flags_)) 64 | } // namespace 65 | 66 | void formatter::defaultFormatter(Config* config, SharedContext const& ctx, 67 | buffer_t& buffer, Appenders outType) 68 | { 69 | assert(config != nullptr); 70 | output_buf_t t_outputBuffer; 71 | t_outputBuffer.buf = &buffer; 72 | // RAII before after call 73 | auto helper = 74 | trigger_helper(&t_outputBuffer, &config->log_before, &config->log_after); 75 | 76 | // prepare data 77 | auto* levelText = GET_LEVEL_TEXT(ctx->level); 78 | auto* funcName = 79 | IS_SET(config->log_flag, Flags::kFuncName) ? ctx->func_name : nullptr; 80 | auto* filename = 81 | IS_SET(config->log_flag, Flags::kLongname) 82 | ? ctx->long_filename 83 | : (IS_SET(config->log_flag, Flags::kShortname) ? ctx->short_filename 84 | : nullptr); 85 | Flags logFlag = config->log_flag; 86 | // log name 87 | if (config->log_name) 88 | { 89 | fmt::format_to(fmt::appender(buffer), "[{}]", config->log_name); 90 | } 91 | 92 | // log date 93 | if (IS_SET(logFlag, Flags::kDate)) // y-m-d 94 | { 95 | fmt::format_to(fmt::appender(buffer), "[{}]", 96 | Util::getCurDateTime(IS_SET(logFlag, Flags::kTime))); 97 | } 98 | // log level 99 | fmt::format_to(fmt::appender(buffer), "[{}]", levelText); 100 | 101 | if (IS_SET(logFlag, Flags::kThreadId)) 102 | { // log thread id 103 | fmt::format_to(fmt::appender(buffer), "[tid:{:d}]", ctx->tid); 104 | } 105 | if (filename) 106 | { 107 | fmt::format_to(fmt::appender(buffer), "[{}:{:d}]", filename, 108 | ctx->line); // log file-line 109 | } 110 | // log func_name 111 | if (funcName) 112 | { 113 | fmt::format_to(fmt::appender(buffer), "[func:{}]", 114 | funcName); // log file-line 115 | } 116 | 117 | fmt::string_view text{ctx->text.data(), ctx->text.size()}; 118 | 119 | if (ctx->level >= INT(Levels::kError) && ctx->err != 0) 120 | { // if level >= Error,get the error info 121 | fmt::format_to(fmt::appender(buffer), ": {} ^system error code:{}", text, 122 | ctx->err); // 打印系统错误提示信息 123 | } 124 | else 125 | { 126 | fmt::format_to(fmt::appender(buffer), ": {}", 127 | text); // log info 128 | } 129 | } 130 | 131 | void formatter::colorfulFormatter(Config* config, SharedContext const& ctx, 132 | buffer_t& buffer, Appenders outType) 133 | { 134 | assert(config != nullptr); 135 | output_buf_t t_outputBuffer; 136 | t_outputBuffer.buf = &buffer; 137 | // RAII before after call 138 | auto helper = 139 | trigger_helper(&t_outputBuffer, &config->log_before, &config->log_after); 140 | 141 | // prepare data 142 | auto* levelText = GET_LEVEL_TEXT(ctx->level); 143 | auto* funcName = 144 | IS_SET(config->log_flag, Flags::kFuncName) ? ctx->func_name : nullptr; 145 | auto* filename = 146 | IS_SET(config->log_flag, Flags::kLongname) 147 | ? ctx->long_filename 148 | : (IS_SET(config->log_flag, Flags::kShortname) ? ctx->short_filename 149 | : nullptr); 150 | Flags logFlag = config->log_flag; 151 | // log name 152 | if (config->log_name) 153 | { 154 | if (outType == kConsole) 155 | { 156 | fmt::format_to(std::back_inserter(buffer), 157 | fg(GET_COLOR_BY_LEVEL(ctx->level)), "[{}]", 158 | config->log_name); 159 | } 160 | else 161 | { 162 | fmt::format_to(std::back_inserter(buffer), "[{}]", config->log_name); 163 | } 164 | } 165 | 166 | // log date 167 | if (IS_SET(logFlag, Flags::kDate)) // y-m-d 168 | { 169 | fmt::format_to(std::back_inserter(buffer), "[{}]", 170 | Util::getCurDateTime(IS_SET(logFlag, Flags::kTime))); 171 | } 172 | // log level 173 | if (outType == Appenders::kConsole) // colorful 174 | { 175 | fmt::format_to(std::back_inserter(buffer), 176 | fg(GET_COLOR_BY_LEVEL(ctx->level)), "[{}]", levelText); 177 | } 178 | else 179 | { // nocolor 180 | fmt::format_to(std::back_inserter(buffer), "[{}]", levelText); 181 | } 182 | if (IS_SET(logFlag, Flags::kThreadId)) 183 | { // log thread id 184 | fmt::format_to(std::back_inserter(buffer), "[tid:{:d}]", ctx->tid); 185 | } 186 | if (filename) 187 | { 188 | fmt::format_to(std::back_inserter(buffer), "[{}:{:d}]", filename, 189 | ctx->line); // log file-line 190 | } 191 | // log func_name 192 | if (funcName) 193 | { 194 | fmt::format_to(std::back_inserter(buffer), "[func:{}]", 195 | funcName); // log file-line 196 | } 197 | fmt::string_view text{ctx->text.data(), ctx->text.size()}; 198 | if (outType == Appenders::kConsole) // colorful 199 | { 200 | if (ctx->level >= INT(Levels::kError) && ctx->err != 0) 201 | { // if level >= Error,get the error info 202 | fmt::format_to(std::back_inserter(buffer), 203 | fg(GET_COLOR_BY_LEVEL(ctx->level)), 204 | ": {} ^system error code:{}", text, 205 | ctx->err); // 打印系统错误提示信息 206 | } 207 | else 208 | { 209 | fmt::format_to(std::back_inserter(buffer), 210 | fg(GET_COLOR_BY_LEVEL(ctx->level)), ": {}", 211 | text); // log info 212 | } 213 | } 214 | else 215 | { // nocolor 216 | if (ctx->level >= INT(Levels::kError) && ctx->err != 0) 217 | { // if level >= Error,get the error info 218 | fmt::format_to(std::back_inserter(buffer), 219 | ": {} ^system error code:{}", text, 220 | ctx->err); // 打印提示信息 221 | } 222 | else 223 | { 224 | fmt::format_to(std::back_inserter(buffer), ": {}", 225 | text); // log info 226 | } 227 | } 228 | } 229 | 230 | void formatter::jsonFormatter(Config* config, SharedContext const& ctx, 231 | buffer_t& buffer, Appenders outType) 232 | { 233 | assert(config != nullptr); 234 | output_buf_t t_outputBuffer; 235 | t_outputBuffer.buf = &buffer; 236 | // RAII before after call 237 | auto helper = 238 | trigger_helper(&t_outputBuffer, &config->log_before, &config->log_after); 239 | 240 | // prepare data 241 | auto* dateText = 242 | Util::getCurDateTime(IS_SET(config->log_flag, Flags::kTime)); 243 | auto* levelText = GET_LEVEL_TEXT(ctx->level, true); 244 | auto* funcName = 245 | IS_SET(config->log_flag, Flags::kFuncName) ? ctx->func_name : nullptr; 246 | auto* filename = 247 | IS_SET(config->log_flag, Flags::kLongname) 248 | ? ctx->long_filename 249 | : (IS_SET(config->log_flag, Flags::kShortname) ? ctx->short_filename 250 | : nullptr); 251 | t_outputBuffer.push_back('{'); 252 | if (config->log_name) 253 | { 254 | t_outputBuffer.formatTo(R"("name":"{}", )", config->log_name); 255 | } 256 | if (dateText) { t_outputBuffer.formatTo(R"("time":"{}")", dateText); } 257 | if (levelText) { t_outputBuffer.formatTo(R"(, "level":"{}")", levelText); } 258 | if (IS_SET(config->log_flag, Flags::kThreadId)) 259 | { 260 | t_outputBuffer.formatTo(R"(, "tid":"{:d}")", ctx->tid); 261 | } 262 | if (filename) 263 | { 264 | if (IS_SET(config->log_flag, Flags::kLine)) 265 | { 266 | t_outputBuffer.formatTo(R"(, "file":"{}:{:d}")", filename, ctx->line); 267 | } 268 | else { t_outputBuffer.formatTo(R"(, "file":"{}")", filename); } 269 | } 270 | if (funcName) { t_outputBuffer.formatTo(R"(, "func":"{}")", funcName); } 271 | 272 | t_outputBuffer.formatTo( 273 | R"(, "message":"{}")", 274 | fmt::string_view{ctx->text.data(), ctx->text.size()}); 275 | t_outputBuffer.push_back('}'); 276 | } 277 | 278 | // use %T:time,%t:tid,%F:filepath,%f:func, %e:error info 279 | // %L:long levelText,%l:short levelText,%n:name,%v:message ,%c color start %C 280 | // color end 281 | void formatter::customStringFormatter(const char* raw_format_str, 282 | Config* config, SharedContext const& ctx, 283 | buffer_t& buffer, Appenders outType) 284 | { 285 | assert(config != nullptr); 286 | output_buf_t outputBuffer; 287 | outputBuffer.buf = &buffer; 288 | // RAII before after call 289 | auto helper = 290 | trigger_helper(&outputBuffer, &config->log_before, &config->log_after); 291 | StringView format_str{raw_format_str}; 292 | size_t index = format_str.find('%'); 293 | // not find format flag 294 | if (index == std::string::npos) 295 | { 296 | outputBuffer.append(format_str); 297 | return; 298 | } 299 | // start custom format 300 | outputBuffer.append(format_str.substr(0, index)); 301 | for (;;) 302 | { 303 | auto op = format_str.substr(index, 2); 304 | switch (OP_INT(op)) 305 | { 306 | case "%T"_i: { 307 | auto* dateText = 308 | Util::getCurDateTime(IS_SET(config->log_flag, Flags::kTime)); 309 | outputBuffer.append(dateText); 310 | break; 311 | } 312 | case "%t"_i: { 313 | outputBuffer.formatTo("{:d}", ctx->tid); 314 | break; 315 | } 316 | case "%F"_i: { 317 | auto* filepath = IS_SET(config->log_flag, Flags::kLongname) 318 | ? ctx->long_filename 319 | : (IS_SET(config->log_flag, Flags::kShortname) 320 | ? ctx->short_filename 321 | : nullptr); 322 | if (filepath) 323 | { 324 | if (IS_SET(config->log_flag, Flags::kLine)) 325 | outputBuffer.formatTo("{}:{:d}", filepath, ctx->line); 326 | 327 | else outputBuffer.append(filepath); 328 | } 329 | break; 330 | } 331 | case "%f"_i: { 332 | auto* funcName = IS_SET(config->log_flag, Flags::kFuncName) 333 | ? ctx->func_name 334 | : nullptr; 335 | if (funcName) outputBuffer.append(funcName); 336 | break; 337 | } 338 | case "%e"_i: { 339 | if (ctx->level < INT(Levels::kError) || ctx->err == 0) break; 340 | outputBuffer.append(Util::getErrorInfo(ctx->err)); 341 | break; 342 | } 343 | case "%L"_i: { 344 | auto* longLevelText = GET_LEVEL_TEXT(ctx->level); 345 | if (longLevelText) outputBuffer.append(longLevelText); 346 | break; 347 | } 348 | case "%l"_i: { 349 | auto* shortLevelText = GET_LEVEL_TEXT(ctx->level, true); 350 | if (shortLevelText) outputBuffer.append(shortLevelText); 351 | break; 352 | } 353 | case "%v"_i: { 354 | outputBuffer.append(ctx->text); 355 | break; 356 | } 357 | case "%n"_i: { 358 | if (config->log_name) outputBuffer.append(config->log_name); 359 | break; 360 | } 361 | case "%c"_i: { 362 | // only console support 363 | if (outType != Appenders::kConsole) break; 364 | // color start 365 | outputBuffer.append(GET_ANSI_COLOR_BY_LEVEL(ctx->level)); 366 | break; 367 | } 368 | case "%C"_i: { 369 | // only console support 370 | if (outType != Appenders::kConsole) break; 371 | // color end 372 | outputBuffer.append("\033[0m"); 373 | break; 374 | } 375 | case "%%"_i: { 376 | outputBuffer.push_back('%'); 377 | break; 378 | } 379 | default: { 380 | outputBuffer.append(op); 381 | } 382 | } 383 | // if the op str is last one 384 | if (index + op.size() >= format_str.size()) { break; } 385 | index += op.size(); 386 | // push_back until ch is '%' 387 | while (index < format_str.size() && format_str[index] != '%') 388 | outputBuffer.push_back(format_str[index++]); 389 | if (index == format_str.size()) break; 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/log_file.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-21. 3 | // 4 | #include "elog/log_file.h" 5 | 6 | #include 7 | 8 | #include "elog/logger_util.h" 9 | 10 | using namespace elog::detail; 11 | 12 | LogFile::LogFile(const char* basename, int rollSize, bool threadSafe, 13 | int flushInterval, int checkEveryN) 14 | : m_basename(basename), 15 | m_rollSize(rollSize), 16 | m_flushInterval(flushInterval), 17 | m_checkEveryN(checkEveryN), 18 | m_mtx(threadSafe ? new std::mutex() : nullptr) 19 | { 20 | rollFile(); 21 | } 22 | 23 | void LogFile::append(const char* line, int len) 24 | { 25 | // 如果有线程安全标记,则加锁,否则不加 26 | if (m_mtx) 27 | { 28 | std::lock_guard lock(*m_mtx); 29 | append_unlocked(line, len); 30 | } 31 | else { append_unlocked(line, len); } 32 | } 33 | 34 | void LogFile::flush() 35 | { 36 | // 如果有线程安全标记,则加锁,否则不加 37 | if (m_mtx) 38 | { 39 | std::lock_guard lock(*m_mtx); 40 | m_file->flush(); 41 | } 42 | else { m_file->flush(); } 43 | } 44 | 45 | 46 | #ifdef _MSC_VER 47 | #pragma warning(push) 48 | #pragma warning(disable:4018) 49 | #endif 50 | 51 | void LogFile::append_unlocked(const char* line, int len) 52 | { 53 | m_file->append(line, len); 54 | if (m_file->writtenBytes() > m_rollSize) 55 | { // 单个文件超出规定大小,滚动日志(重新创建新文件写入 56 | // LB_TRACE_("文件大小超过限定大小 {:d}kb,开始创建新文件进行写入", 57 | // m_rollSize / 1024); 58 | rollFile(); 59 | m_file->resetWritten(); 60 | } 61 | else 62 | { 63 | ++m_count; 64 | if (m_count >= m_checkEveryN) 65 | { // 开始检查 66 | // LB_TRACE_("开始检查flushInterval和curPeriod"); 67 | m_count = 0; 68 | time_t now = ::time(nullptr); 69 | time_t curPeriod = now / kRollPerSeconds * kRollPerSeconds; 70 | if (curPeriod != m_lastPeriod) { rollFile(&now); } 71 | else if (now - m_lastFlush > m_flushInterval) 72 | { 73 | m_lastFlush = now; 74 | m_file->flush(); 75 | } 76 | } 77 | } 78 | } 79 | 80 | void LogFile::rollFile(const time_t* cache_now) 81 | { 82 | time_t now; 83 | if (cache_now != nullptr) { now = *cache_now; } 84 | else { now = time(nullptr); } 85 | 86 | auto filename = Util::getLogFileName(m_basename, now); 87 | auto start = now / kRollPerSeconds * kRollPerSeconds; // 更新天的数据 88 | 89 | if (now > m_lastRoll) 90 | { 91 | // LB_TRACE_("开始执行roll操作,filename:{}", filename); 92 | 93 | m_lastRoll = now; 94 | m_lastFlush = now; 95 | m_lastPeriod = start; 96 | #if __cplusplus < 201403L 97 | m_file = std::unique_ptr( 98 | new FileAppender(filename)); // 创建新的文件 99 | #else 100 | m_file = std::make_unique(filename); // 创建新的文件 101 | #endif 102 | } 103 | } 104 | #ifdef _MSC_VER 105 | #pragma warning(pop) 106 | #endif 107 | -------------------------------------------------------------------------------- /src/logger.cc: -------------------------------------------------------------------------------- 1 | #include "elog/logger.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "elog/processinfo.h" 7 | 8 | #if defined(_WIN32) 9 | #include 10 | #endif 11 | 12 | USING_LBLOG 13 | USING_LBLOG_DETAIL 14 | 15 | #define GLOB_CONFIG elog::GlobalConfig::Get() 16 | 17 | LoggerImpl::LoggerImpl() { init_data(); } 18 | 19 | LoggerImpl::~LoggerImpl() = default; 20 | 21 | LoggerImpl& LoggerImpl::GetInstance() 22 | { 23 | static LoggerImpl logger; 24 | return logger; 25 | } 26 | 27 | void LoggerImpl::init_data() 28 | { 29 | // Determine whether to output logs to a file based on the config 30 | if (GLOB_CONFIG.log_filepath != nullptr) 31 | { 32 | m_logging = elog::make_unique( 33 | GLOB_CONFIG.log_filepath, GLOB_CONFIG.log_rollSize, 34 | GLOB_CONFIG.log_flushInterval); 35 | } 36 | } 37 | 38 | void LoggerImpl::waitForDone() 39 | { 40 | if (m_logging) m_logging->waitDone(); 41 | } 42 | 43 | void LoggerImpl::registerConfig(StringView name, ConfigPtr config) 44 | { 45 | auto key = std::string{name.data(), name.size()}; 46 | if (m_localConfigFactory.count(key)) 47 | { 48 | fmt::print( 49 | "don't supported update memory in LoggerImpl::registerConfig()"); 50 | return; 51 | } 52 | m_localConfigFactory[key] = std::move(config); 53 | } 54 | 55 | Config* LoggerImpl::getConfig(StringView name) 56 | { 57 | auto item = m_localConfigFactory.find(std::string{name.data(), name.size()}); 58 | if (item == m_localConfigFactory.end()) { return nullptr; } 59 | return item->second.get(); 60 | } 61 | 62 | void LoggerImpl::LogFile(Config* config, SharedContext const& ctx) 63 | { 64 | assert(config != nullptr); 65 | m_logging->pushMsg({config, ctx}); 66 | } 67 | 68 | void LoggerImpl::LogConsole(Config* config, const SharedContext& ctx) 69 | { 70 | assert(config != nullptr); 71 | 72 | auto buffer = buffer_t{}; 73 | config->log_formatter(config, ctx, buffer, Appenders::kConsole); 74 | 75 | { 76 | std::lock_guard lk(m_mutex); // Lock the I/O device 77 | #if !defined(__linux__) 78 | ::fwrite(buffer.data(), 1, buffer.size(), stdout); 79 | #else 80 | ::fwrite_unlocked(buffer.data(), 1, buffer.size(), stdout); 81 | #endif 82 | } 83 | std::fflush(stdout); 84 | } 85 | 86 | void LoggerImpl::LogConsoleUnsafe(Config* config, const SharedContext& ctx) 87 | { 88 | assert(config != nullptr); 89 | 90 | auto buffer = buffer_t{}; 91 | config->log_formatter(config, ctx, buffer, Appenders::kConsole); 92 | #if !defined(__linux__) 93 | ::fwrite(buffer.data(), 1, buffer.size(), stdout); 94 | #else 95 | ::fwrite_unlocked(buffer.data(), 1, buffer.size(), stdout); 96 | #endif 97 | std::fflush(stdout); 98 | } 99 | 100 | void LoggerImpl::DoInternalLog(const SharedContext& ctx) 101 | { 102 | if (GLOB_CONFIG.log_level != Levels::kTrace) { return; } 103 | if (GLOB_CONFIG.log_console) { GetInstance().LogConsole(&GLOB_CONFIG, ctx); } 104 | } 105 | 106 | // 全局config logger输出 107 | void LoggerImpl::DoLog(SharedContext const& ctx) 108 | { 109 | DoConfigLog(&GLOB_CONFIG, ctx); 110 | } 111 | 112 | // 自定义config logger输出 113 | void LoggerImpl::DoConfigLog(Config* config, const SharedContext& ctx) 114 | { 115 | assert(config != nullptr); 116 | // FIXME 117 | // 输出到控制台没有进行任何性能优化,输出控制台方便开发时调试,输出到文件可以方便后续问题的跟踪 118 | if (ctx->level < static_cast(config->log_level)) return; 119 | if (m_logging) LogFile(config, ctx); 120 | if (GLOB_CONFIG.log_console) LogConsole(config, ctx); 121 | 122 | if (ctx->level == static_cast(Levels::kFatal)) 123 | { 124 | waitForDone(); // 等待刷盘落地 125 | throw std::runtime_error("fatal error occurred"); 126 | } 127 | } 128 | 129 | Log& Log::instance() 130 | { 131 | thread_local Log t_log; 132 | return t_log; 133 | } 134 | 135 | void Log::log_it_(SharedContext& ctx) const 136 | { 137 | ctx->level = m_level; 138 | ctx->tid = elog::ProcessInfo::GetTid(); 139 | ctx->err = errno; 140 | detail::LoggerImpl::GetInstance().DoConfigLog( 141 | m_config ? m_config : &GlobalConfig::Get(), ctx); 142 | } 143 | 144 | logger_helper elog::Check(bool cond, source_location const& location) 145 | { 146 | if (!cond) { return logger_helper{location}; } 147 | return {}; 148 | } 149 | 150 | void elog::CheckIfFatal(bool cond, source_location const& location, 151 | const char* text) 152 | { 153 | if (!cond) { Check(false, location).fatal(text); } 154 | } 155 | -------------------------------------------------------------------------------- /src/logger_util.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-21. 3 | // 4 | 5 | #include "elog/logger_util.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "elog/processinfo.h" 13 | 14 | using namespace elog; 15 | 16 | constexpr int kSumLength = 1024; 17 | 18 | thread_local char t_filename[kSumLength]; 19 | thread_local char t_errnobuf[512]; 20 | thread_local char t_time[64]; 21 | thread_local int t_timezone = -1; 22 | thread_local struct std::tm t_tm; 23 | thread_local struct std::tm t_gmtm; 24 | thread_local time_t t_lastSecond; 25 | 26 | using namespace std; 27 | 28 | const char* Util::getCurDateTime(bool isTime, time_t* now) 29 | { 30 | time_t timer = time(nullptr); 31 | if (now != nullptr) 32 | { // to reduce system call 33 | *now = timer; 34 | } 35 | 36 | if (t_lastSecond != timer) 37 | { 38 | t_lastSecond = timer; 39 | #if defined(_WIN32) 40 | ::localtime_s(&t_tm, &timer); 41 | #else 42 | ::localtime_r(&timer,&t_tm); 43 | #endif 44 | } 45 | // to subtract gmtime and localtime for obtain timezone 46 | if (t_timezone == -1) 47 | { 48 | #if defined(_WIN32) 49 | ::gmtime_s(&t_gmtm, &timer); 50 | time_t gm_time = std::mktime(&t_gmtm); 51 | #else 52 | time_t gm_time = std::mktime(::gmtime_r(&timer, &t_gmtm)); 53 | #endif 54 | t_timezone = static_cast(timer - gm_time) / 3600; 55 | } 56 | int len; 57 | if (isTime) 58 | { 59 | len = std::snprintf(t_time, sizeof(t_time), 60 | "%4d-%02d-%02d %02d:%02d:%02d +%02d", 61 | t_tm.tm_year + 1900, t_tm.tm_mon + 1, t_tm.tm_mday, 62 | t_tm.tm_hour, t_tm.tm_min, t_tm.tm_sec, t_timezone); 63 | assert(len == 23); 64 | } 65 | else 66 | { 67 | len = std::snprintf(t_time, sizeof(t_time), "%4d-%02d-%02d", 68 | t_tm.tm_year + 1900, t_tm.tm_mon + 1, t_tm.tm_mday); 69 | assert(len == 10); 70 | } 71 | (void)len; 72 | return t_time; 73 | } 74 | 75 | const char* Util::getErrorInfo(int error_code) 76 | { 77 | #if defined(_WIN32) 78 | ::strerror_s(t_errnobuf, sizeof(t_errnobuf), error_code); 79 | #else 80 | auto p = ::strerror_r(error_code, t_errnobuf, sizeof(t_errnobuf)); 81 | (void)p; 82 | #endif 83 | return t_errnobuf; 84 | } 85 | 86 | // Don't have same name If it is less than one 87 | // second since the last refresh time 88 | const char* Util::getLogFileName(const char* basename, time_t& now) 89 | { 90 | auto baseSz = std::strlen(basename); 91 | assert(baseSz < kSumLength - 200); 92 | // prevent strcpy no copy if `basename == t_filename[0:baseSz]` 93 | t_filename[baseSz] = '\0'; 94 | char* filename = t_filename; 95 | #if defined(_WIN32) 96 | ::strncpy_s(t_filename, basename, baseSz); 97 | #else 98 | ::strncpy(t_filename, basename, baseSz); 99 | #endif 100 | assert(filename && filename[baseSz] == '\0'); 101 | 102 | size_t remainLen = kSumLength - baseSz; 103 | std::tm tm{}; 104 | #if defined(_WIN32) 105 | ::localtime_s(&tm, &now); 106 | #else 107 | ::localtime_r(&now, &tm); 108 | #endif 109 | size_t tsz = 110 | std::strftime(filename + baseSz, remainLen, ".%Y%m%d-%H%M%S.", &tm); 111 | size_t nsz = std::snprintf(filename + baseSz + tsz, remainLen - tsz, 112 | "%s.%d.log", ProcessInfo::GetHostname(), 113 | static_cast(ProcessInfo::GetPid())); 114 | assert(filename[baseSz + tsz + nsz] == '\0'); 115 | return filename; 116 | } 117 | -------------------------------------------------------------------------------- /src/processinfo.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alone on 2022-9-21. 3 | // 4 | 5 | #include "elog/processinfo.h" 6 | #if defined(_WIN32) 7 | #include 8 | #include 9 | 10 | #include 11 | #elif defined(__linux__) 12 | #include 13 | #include 14 | #else 15 | #include 16 | 17 | #include 18 | #endif 19 | 20 | using namespace elog; 21 | 22 | elog::pid_t ProcessInfo::GetPid() 23 | { 24 | #if defined(_WIN32) 25 | thread_local auto pid = GetCurrentProcessId(); 26 | #else 27 | thread_local auto pid = getpid(); 28 | #endif 29 | return pid; 30 | } 31 | 32 | elog::tid_t ProcessInfo::GetTid() 33 | { 34 | #if defined(_WIN32) 35 | thread_local auto tid = GetCurrentThreadId(); 36 | #elif defined(__linux__) 37 | auto tid = syscall(SYS_gettid); 38 | #else 39 | elog::tid_t tid; 40 | 41 | auto id = std::this_thread::get_id(); 42 | tid = *reinterpret_cast(&id); 43 | #endif 44 | return tid; 45 | } 46 | 47 | #if defined(_WIN32) 48 | void WSAStartUp() 49 | { 50 | WORD wVersionRequested; 51 | WSADATA wsaData; 52 | int err; 53 | 54 | /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ 55 | wVersionRequested = MAKEWORD(2, 2); 56 | 57 | err = WSAStartup(wVersionRequested, &wsaData); 58 | if (err != 0) 59 | { 60 | /* Tell the user that we could not find a usable */ 61 | /* Winsock DLL. */ 62 | std::printf("WSAStartup failed with error: %d\n", err); 63 | return; 64 | } 65 | 66 | /* Confirm that the WinSock DLL supports 2.2.*/ 67 | /* Note that if the DLL supports versions greater */ 68 | /* than 2.2 in addition to 2.2, it will still return */ 69 | /* 2.2 in wVersion since that is the version we */ 70 | /* requested. */ 71 | 72 | if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 73 | { 74 | /* Tell the user that we could not find a usable */ 75 | /* WinSock DLL. */ 76 | std::printf("Could not find a usable version of Winsock.dll\n"); 77 | WSACleanup(); 78 | return; 79 | } 80 | } 81 | struct WSARegister 82 | { 83 | WSARegister() { WSAStartUp(); } 84 | ~WSARegister() { WSACleanup(); } 85 | }; 86 | static WSARegister s_wsa_register; 87 | #else 88 | #endif 89 | 90 | const char* ProcessInfo::GetHostname() 91 | { 92 | thread_local char buf[256]{}; 93 | if (buf[0] == -1) { return buf + 1; } 94 | if (::gethostname(buf + 1, sizeof(buf) - 1) == 0) 95 | { 96 | buf[0] = -1; 97 | return buf + 1; 98 | } 99 | return "unknownhost"; 100 | } -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CPMAddPackage( 2 | doctest 3 | GIT_REPOSITORY https://github.com/doctest/doctest.git 4 | GIT_TAG v2.4.9 5 | GIT_SHALLOW TRUE 6 | ) 7 | CPMAddPackage( 8 | nanobench 9 | GIT_REPOSITORY https://github.com/martinus/nanobench.git 10 | GIT_TAG v4.1.0 11 | GIT_SHALLOW TRUE) 12 | 13 | add_definitions(-DPROJECT_ROOT="${PROJECT_SOURCE_DIR}/") 14 | 15 | file(GLOB TEST_FILES 16 | "${PROJECT_SOURCE_DIR}/tests/test_*.cc" 17 | ) 18 | file(GLOB BENCH_FILES 19 | "${PROJECT_SOURCE_DIR}/tests/bench_*.cc" 20 | ) 21 | 22 | message(STATUS "TEST_FILES:${TEST_FILES}\n BENCH_FILES:${BENCH_FILES}") 23 | 24 | 25 | add_executable(unittest ${TEST_FILES}) 26 | target_link_libraries(unittest PRIVATE doctest_with_main elog nanobench) 27 | add_test(NAME ${PROJECT_NAME}-unittest COMMAND unittest) 28 | 29 | add_executable(benchtest ${BENCH_FILES}) 30 | target_link_libraries(benchtest PRIVATE doctest_with_main elog nanobench) 31 | 32 | add_executable(perf perf.cc) 33 | target_link_libraries(perf elog) -------------------------------------------------------------------------------- /tests/bench_elog.cc: -------------------------------------------------------------------------------- 1 | #include "bench_interface.h" 2 | #include "common_func.h" 3 | #define ENABLE_ELG_LOG 4 | #include "elog/logger.h" 5 | 6 | using namespace elog; 7 | 8 | void before_bench() 9 | { 10 | GlobalConfig::Get() 11 | .loadFromJSON("../../config.json") 12 | .setFilepath(PROJECT_ROOT "tests/test_log/") 13 | .enableConsole(false); 14 | } 15 | 16 | void one_thread_async(int bench_n, int test_n) 17 | { 18 | bench_start( 19 | "elog:one_thread", 20 | [&]() { test_one_thread(test_n, []() { ELG_INFO(OUTPUT_TEXT); }); }, 21 | bench_n); 22 | } 23 | 24 | void one_thread_sync(int bench_n, int test_n) 25 | { 26 | bench_start( 27 | "elog:one_thread", 28 | [&]() { test_one_thread(test_n, []() { ELG_INFO(OUTPUT_TEXT); }); }, 29 | bench_n); 30 | } 31 | 32 | void multi_thread_sync(int bench_n, int test_n) 33 | { 34 | bench_start( 35 | "elog:multi_thread", 36 | [&]() { test_multi_thread(test_n, []() { ELG_INFO(OUTPUT_TEXT); }); }, 37 | bench_n); 38 | } 39 | 40 | void multi_thread_async(int bench_n, int test_n) 41 | { 42 | bench_start( 43 | "elog:multi_thread", 44 | [&]() { test_multi_thread(test_n, []() { ELG_INFO(OUTPUT_TEXT); }); }, 45 | bench_n); 46 | } 47 | -------------------------------------------------------------------------------- /tests/bench_interface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #define OUTPUT_TEXT "abcdefasdjfkdjfjdsaafdsa123432432543" 5 | 6 | void before_bench(); 7 | //void before_third_part_bench(); 8 | 9 | void one_thread_async(int bench_n,int test_n); 10 | //void one_thread_async_third_part(int bench_n,int test_n); 11 | 12 | void one_thread_sync(int bench_n,int test_n); 13 | //void one_thread_sync_third_part(int bench_n,int test_n); 14 | 15 | void multi_thread_async(int bench_n,int test_n); 16 | //void multi_thread_async_third_part(int bench_n,int test_n); 17 | 18 | void multi_thread_sync(int bench_n,int test_n); 19 | //void multi_thread_sync_third_part(int bench_n,int test_n); 20 | -------------------------------------------------------------------------------- /tests/bench_start.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bench_interface.h" 4 | 5 | struct before_hook 6 | { 7 | explicit before_hook() 8 | { 9 | before_bench(); 10 | // before_third_part_bench(); 11 | } 12 | }; 13 | 14 | before_hook s_h; 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable : 4244) 19 | #endif 20 | 21 | TEST_CASE("bench start") 22 | { 23 | one_thread_sync(1e3, 100); 24 | // one_thread_sync_third_part(1e5, 1); 25 | 26 | // one_thread_async(1e5, 5); 27 | // one_thread_async_third_part(1e5, 5); 28 | 29 | multi_thread_sync(1e3, 100); 30 | // multi_thread_sync_third_part(1e4, 10); 31 | 32 | // multi_thread_async(1e3, 100); 33 | // multi_thread_async_third_part(1e3, 100); 34 | } 35 | 36 | #ifdef _MSC_VER 37 | #pragma warning(pop) 38 | #endif -------------------------------------------------------------------------------- /tests/common_func.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "nanobench.h" 3 | #include 4 | #include 5 | 6 | inline void bench_start(const char* bench_name,const std::function&bench_func,int num = 10){ 7 | ankerl::nanobench::Bench().minEpochIterations(num).run(bench_name,bench_func); 8 | } 9 | 10 | inline void test_multi_thread(const int test_n,const std::function&logger_func) 11 | { 12 | std::thread th1{ [&](){for (int i = 0; i < test_n; i++)logger_func();}}; 13 | std::thread th2{ [&](){for (int i = 0; i < test_n; i++)logger_func();}}; 14 | std::thread th3{ [&](){for (int i = 0; i < test_n; i++)logger_func();}}; 15 | std::thread th4{ [&](){for (int i = 0; i < test_n; i++) logger_func();}}; 16 | std::thread th5{ [&](){for (int i = 0; i < test_n; i++)logger_func();}}; 17 | th1.join(); 18 | th2.join(); 19 | th3.join(); 20 | th4.join(); 21 | th5.join(); 22 | } 23 | 24 | inline void test_one_thread(const int test_n,const std::function&logger_func) 25 | { 26 | for(int i=0;i 2 | 3 | #include 4 | 5 | using namespace elog; 6 | 7 | void log_n_times(int n) 8 | { 9 | for (int i = 0; i < n; i++) { Log::info("hello elog4cpp"); } 10 | } 11 | 12 | int main() 13 | { 14 | GlobalConfig::Get().loadFromJSON(PROJECT_ROOT "config.json"); 15 | std::thread th1(log_n_times, 1); 16 | std::thread th2(log_n_times, 1); 17 | std::thread th3(log_n_times, 1); 18 | std::thread th4(log_n_times, 1); 19 | 20 | th1.join(); 21 | th2.join(); 22 | th3.join(); 23 | th4.join(); 24 | } 25 | -------------------------------------------------------------------------------- /tests/test_common.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace elog; 6 | 7 | TEST_SUITE_BEGIN("common test"); 8 | 9 | TEST_CASE("test buffer_helper") 10 | { 11 | buffer_t buffer; 12 | buffer.push_back('a'); 13 | elog::buffer_helper helper{&buffer}; 14 | helper.formatTo("{}b{}", "a", 123); 15 | 16 | REQUIRE_EQ(to_string(buffer), "aab123"); 17 | } 18 | 19 | TEST_CASE("test OutputBuffer") 20 | { 21 | { 22 | buffer_t buffer; 23 | buffer.push_back('a'); 24 | output_buf_t output(&buffer); 25 | output.formatTo("{}b{}", "a", 123); 26 | REQUIRE_EQ(to_string(buffer), "aab123"); 27 | } 28 | { 29 | buffer_t buffer; 30 | output_buf_t out(&buffer); 31 | out.setContext("String"); 32 | REQUIRE_EQ(std::string(any_cast(out.getContext())), 33 | "String"); 34 | out.setContext(324); 35 | REQUIRE_EQ(any_cast(out.getContext()), 324); 36 | } 37 | } 38 | 39 | TEST_CASE("test const_util") 40 | { 41 | REQUIRE_EQ(elog::detail::GetStrLen("abc"), 3); 42 | REQUIRE_EQ(elog::detail::GetStrLen("a"), 1); 43 | REQUIRE_EQ(elog::detail::GetStrLen("ab"), 2); 44 | 45 | REQUIRE_EQ(elog::detail::GetShortName("a/b/c"), std::string("c")); 46 | REQUIRE_EQ(elog::detail::GetShortName("/"), std::string("")); 47 | REQUIRE_EQ(elog::detail::GetShortName("asfds/afsa/saf"), std::string("saf")); 48 | REQUIRE_EQ(elog::detail::GetShortName("asfds\\afsa\\sdf"), 49 | std::string("sdf")); 50 | } 51 | 52 | TEST_SUITE_END; -------------------------------------------------------------------------------- /tests/test_config_micros.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define ENABLE_ELG_CHECK 5 | #define ENABLE_ELG_LOG 6 | #include "doctest/doctest.h" 7 | #include "elog/logger.h" 8 | #include "nanobench.h" 9 | 10 | using namespace elog; 11 | 12 | struct Timer 13 | { 14 | std::chrono::time_point startPoint; 15 | void start() { startPoint = std::chrono::high_resolution_clock::now(); } 16 | [[nodiscard]] int64_t end() const 17 | { 18 | auto endPoint = std::chrono::high_resolution_clock::now(); 19 | return std::chrono::time_point_cast(endPoint) 20 | .time_since_epoch() 21 | .count() - 22 | std::chrono::time_point_cast(startPoint) 23 | .time_since_epoch() 24 | .count(); 25 | } 26 | }; 27 | 28 | void set_console_json_config() 29 | { 30 | GlobalConfig::Get() 31 | .enableConsole(true) 32 | .setLevel(elog::kInfo) 33 | .setFormatter(formatter::jsonFormatter) 34 | .setFlag(kStdFlags + kThreadId); 35 | } 36 | 37 | void set_console_colorful_config() 38 | { 39 | GlobalConfig::Get() 40 | .enableConsole(true) 41 | .setFormatter(formatter::colorfulFormatter) 42 | .setFlag(kStdFlags + kThreadId); 43 | } 44 | 45 | void set_custom_config() 46 | { 47 | GlobalConfig::Get() 48 | .enableConsole(true) 49 | .setFormatter( 50 | formatter::customFromString("[%n][%T][tid:%t][%L][%F][%f]: %v")) 51 | .setFlag(kStdFlags + kThreadId); 52 | } 53 | 54 | void set_timer_callback_and_load_config() 55 | { 56 | GlobalConfig::Get() 57 | .loadFromJSON(PROJECT_ROOT "config.json") 58 | .setBefore([](output_buf_t& bf) { 59 | bf.setContext(Timer{}); 60 | auto& tm = any_cast(bf.getMutableContext()); 61 | tm.start(); 62 | }) 63 | .setAfter([](output_buf_t& bf) { 64 | auto& tm = any_cast(bf.getMutableContext()); 65 | auto consume = tm.end(); 66 | bf.formatTo("-----formatter takes:{}ns", consume); 67 | }); 68 | } 69 | 70 | void test_timer_callback_and_load_config() 71 | { 72 | set_timer_callback_and_load_config(); 73 | auto info = Log(elog::kInfo); 74 | auto warn = Log(elog::kWarn); 75 | for (int i = 0; i < 10; i++) 76 | { 77 | Timer tm; 78 | tm.start(); 79 | info.println("test1", "test2", "test3", std::vector{3243, 242, 324}); 80 | warn.printf("sum of time:{}ns", tm.end()); 81 | } 82 | } 83 | 84 | void test_localConfig() 85 | { 86 | // register local config 87 | auto config = elog::make_unique(); 88 | config->log_before = [](output_buf_t& out) { out.append("before"); }; 89 | config->log_after = [](output_buf_t& out) { out.append("after"); }; 90 | config->log_flag = kStdFlags + kThreadId; 91 | config->log_name = "test_local"; 92 | config->log_level = kInfo; 93 | config->log_formatter = formatter::colorfulFormatter; 94 | Log::RegisterConfig("test_local", std::move(config)); 95 | 96 | // create Log by localConfig 97 | auto log = Log(kInfo, "test_local"); 98 | for (int i = 0; i < 10; i++) log.println("hello world"); 99 | } 100 | 101 | void test_console_json_config() 102 | { 103 | set_console_json_config(); 104 | elog::Log::info("hello ejson4cpp"); 105 | } 106 | 107 | void test_custom_config() 108 | { 109 | set_custom_config(); 110 | elog::Log::info("hello ejson4cpp"); 111 | } 112 | 113 | void test_console_colorful_config() 114 | { 115 | set_console_colorful_config(); 116 | elog::Log::info("hello ejson4cpp"); 117 | } 118 | 119 | TEST_SUITE_BEGIN("test configµs"); 120 | 121 | TEST_CASE("test config") 122 | { 123 | // test config with callback 124 | test_timer_callback_and_load_config(); 125 | // test local config 126 | test_localConfig(); 127 | // test json formatter 128 | test_console_json_config(); 129 | // test custom formatter 130 | test_custom_config(); 131 | // test colorful formatter 132 | test_console_colorful_config(); 133 | } 134 | 135 | TEST_CASE("test log micros") 136 | { 137 | GlobalConfig::Get().setLevel(elog::kTrace); 138 | ELG_TRACE("hello {}", "world"); 139 | ELG_DEBUG("hello {}", "world"); 140 | ELG_INFO("hello {}", "world"); 141 | ELG_WARN("hello {}", "world"); 142 | ELG_ERROR("hello {}", "world"); 143 | CHECK_THROWS_AS(ELG_FATAL("hello {}", "world");, std::runtime_error); 144 | } 145 | 146 | TEST_CASE("test check micros") 147 | { 148 | int a = 1, b = 1; 149 | ELG_CHECK_EQ(a, b); 150 | auto* ptr = new elog::context; 151 | ptr->text = "abc"; 152 | auto* p = ELG_CHECK_NOTNULL(ptr); 153 | REQUIRE_EQ(p->text, "abc"); 154 | ptr = nullptr; 155 | ELG_CHECK_NE(ptr, p); 156 | a = 0; 157 | ELG_CHECK_LT(a, b); 158 | a = 1; 159 | ELG_CHECK_LE(a, b); 160 | ELG_CHECK_GE(a, b); 161 | a = 2; 162 | ELG_CHECK_GT(a, b); 163 | delete p; 164 | } 165 | 166 | TEST_SUITE_END; --------------------------------------------------------------------------------