weh
This commit is contained in:
parent
693fa17d10
commit
500138ce67
331 changed files with 12348 additions and 60593 deletions
Binary file not shown.
Binary file not shown.
|
@ -1,45 +0,0 @@
|
|||
AccessModifierOffset: -2
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignOperands: false
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackParameters: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Allman
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: ''
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: BeforeHash
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyExcessCharacter: 1
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||
PointerAlignment: Left
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
Cpp11BracedListStyle: true
|
||||
Standard: c++20
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
|
@ -1,5 +0,0 @@
|
|||
ignore:
|
||||
- "examples"
|
||||
- "quill/include/quill/bundled"
|
||||
- "quill/src/bundled"
|
||||
- "quill/test"
|
|
@ -1,33 +0,0 @@
|
|||
name: coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Configure
|
||||
run: cmake -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=17 -DQUILL_BUILD_TESTS=ON -DQUILL_CODE_COVERAGE=ON -DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j4
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cd build
|
||||
ctest --build-config Debug
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
111
subprojects/quill-4.2.0/.github/workflows/linux.yml
vendored
111
subprojects/quill-4.2.0/.github/workflows/linux.yml
vendored
|
@ -1,111 +0,0 @@
|
|||
name: linux
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
cxx: [ g++-8, g++-10 ]
|
||||
build_type: [ Debug, Release ]
|
||||
std: [ 17 ]
|
||||
os: [ ubuntu-20.04 ]
|
||||
with_tests: [ ON ]
|
||||
|
||||
include:
|
||||
# Build and with g++8
|
||||
- cxx: g++-8
|
||||
std: 17
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
install: sudo apt -o Acquire::Retries=5 install g++-8
|
||||
|
||||
# Build and test as shared library
|
||||
- cxx: g++-10
|
||||
build_type: Release
|
||||
std: 17
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
cmake_options: -DBUILD_SHARED_LIBS=ON -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON
|
||||
|
||||
# Builds with no exceptions
|
||||
- cxx: g++-10
|
||||
build_type: Release
|
||||
std: 17
|
||||
os: ubuntu-20.04
|
||||
with_tests: OFF
|
||||
cmake_options: -DQUILL_NO_EXCEPTIONS=ON
|
||||
|
||||
# Build and test with valgrind, sanitizers
|
||||
- cxx: g++-10
|
||||
build_type: Release
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
cmake_options: -DQUILL_USE_VALGRIND=ON
|
||||
ctest_options: -T memcheck
|
||||
install: sudo apt -o Acquire::Retries=5 install valgrind
|
||||
|
||||
# Build and test sanitizers
|
||||
- cxx: clang++-12
|
||||
build_type: Release
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
cmake_options: -DQUILL_SANITIZE_ADDRESS=ON
|
||||
|
||||
# Build and test sanitizers
|
||||
- cxx: clang++-12
|
||||
build_type: Release
|
||||
std: 20
|
||||
os: ubuntu-20.04
|
||||
with_tests: ON
|
||||
cmake_options: -DQUILL_SANITIZE_THREAD=ON
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: |
|
||||
sudo apt-get update
|
||||
${{matrix.install}}
|
||||
cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
env:
|
||||
CXX: ${{matrix.cxx}}
|
||||
CXXFLAGS: ${{matrix.cxxflags}}
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.cmake_options}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=${{matrix.with_tests}} \
|
||||
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`nproc`
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`nproc`
|
||||
ctest --build-config ${{matrix.build_type}} ${{matrix.ctest_options}} --parallel $threads --output-on-failure
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: True
|
|
@ -1,50 +0,0 @@
|
|||
name: macos
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-14 ]
|
||||
build_type: [ Debug, Release ]
|
||||
std: [ 17, 20 ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=ON \
|
||||
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`sysctl -n hw.logicalcpu`
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
threads=`sysctl -n hw.logicalcpu`
|
||||
ctest --build-config ${{matrix.build_type}} --parallel $threads --output-on-failure
|
|
@ -1,78 +0,0 @@
|
|||
|
||||
name: windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# windows-2016 and windows-2019 have MSVC 2017 and 2019 installed
|
||||
# respectively: https://github.com/actions/virtual-environments.
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-2019, windows-2022 ]
|
||||
platform: [ x64 ]
|
||||
build_type: [ Debug, Release ]
|
||||
std: [ 17, 20 ]
|
||||
with_tests: [ ON ]
|
||||
|
||||
include:
|
||||
# Builds with no exceptions
|
||||
- os: windows-2019
|
||||
platform: x64
|
||||
build_type: Release
|
||||
std: 17
|
||||
with_tests: "OFF"
|
||||
cmake_options: -DQUILL_NO_EXCEPTIONS=ON
|
||||
|
||||
# Builds for ARM
|
||||
- os: windows-2019
|
||||
platform: ARM64
|
||||
build_type: Release
|
||||
std: 17
|
||||
with_tests: "OFF"
|
||||
|
||||
- os: windows-2019
|
||||
platform: ARM
|
||||
build_type: Release
|
||||
std: 17
|
||||
with_tests: "OFF"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure
|
||||
shell: bash # Use a bash shell for $GITHUB_WORKSPACE.
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.cmake_options}} \
|
||||
-A ${{matrix.platform}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -DQUILL_BUILD_TESTS=${{matrix.with_tests}} \
|
||||
-DQUILL_BUILD_EXAMPLES=ON -DQUILL_VERBOSE_MAKEFILE=ON $GITHUB_WORKSPACE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
||||
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: |
|
||||
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
||||
ctest --build-config ${{matrix.build_type}} --parallel $threads --output-on-failure
|
8
subprojects/quill-4.2.0/.gitignore
vendored
8
subprojects/quill-4.2.0/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
|||
# Project exclude paths
|
||||
/cmake-build-debug/
|
||||
/.vs
|
||||
/out/build/x64-Debug
|
||||
/CMakeSettings.json
|
||||
/out/build/x64-Release
|
||||
/out/build/Mingw64-Debug
|
||||
/out/build/x64-Debug-2
|
|
@ -1 +0,0 @@
|
|||
768ccd8048e2f53838c17ea6480236bb8d89fa785fb08f378539bfb0aa61ac1f
|
|
@ -1,22 +0,0 @@
|
|||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# We recommend specifying your dependencies to enable reproducible builds:
|
||||
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
File diff suppressed because it is too large
Load diff
|
@ -1,166 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.8)
|
||||
project(quill)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Options
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
|
||||
# Builds Quill without exceptions by adding the -fno-exceptions flag to the compiler.
|
||||
option(QUILL_NO_EXCEPTIONS "Build without exceptions using -fno-exceptions flag" OFF)
|
||||
|
||||
# Disables features that rely on retrieving the thread name, which is not supported in older versions of Windows (e.g., Windows Server 2012/2016).
|
||||
# Enabling this option ensures compatibility with older Windows versions.
|
||||
option(QUILL_NO_THREAD_NAME_SUPPORT "Disable features not supported on Windows Server 2012/2016" OFF)
|
||||
|
||||
# Enables the use of _mm_prefetch, _mm_clflush, and _mm_clflushopt instructions to enhance cache coherence performance on x86 architectures.
|
||||
# When enabled, Quill will utilize these instructions on the frontend's queue operations.
|
||||
# Ensure to specify the target architecture with -march="..." when compiling to maximize compatibility and performance.
|
||||
option(QUILL_X86ARCH "Enable optimizations for cache coherence on x86 architectures using specific CPU instructions" OFF)
|
||||
|
||||
# When enabled, removes the non-prefixed `LOG_*` macros, leaving only `QUILL_LOG_*` macros available.
|
||||
# This is useful in scenarios where the original macro names conflict with those of an existing logging library.
|
||||
option(QUILL_DISABLE_NON_PREFIXED_MACROS "Disable non-prefixed macros" OFF)
|
||||
|
||||
option(QUILL_DISABLE_POSITION_INDEPENDENT_CODE "Disable position-independent code" OFF)
|
||||
|
||||
option(QUILL_BUILD_EXAMPLES "Build the examples" OFF)
|
||||
|
||||
option(QUILL_BUILD_TESTS "Build the tests (Requires https://github.com/google/googletest to be installed)" OFF)
|
||||
|
||||
option(QUILL_BUILD_BENCHMARKS "Build the benchmarks" OFF)
|
||||
|
||||
option(QUILL_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
|
||||
|
||||
option(QUILL_SANITIZE_THREAD "Enable thread sanitizer in tests (Using this option with any other compiler except clang may result to false positives)" OFF)
|
||||
|
||||
option(QUILL_CODE_COVERAGE "Enable code coverage" OFF)
|
||||
|
||||
option(QUILL_USE_VALGRIND "Use valgrind as the default memcheck tool in CTest (Requires Valgrind)" OFF)
|
||||
|
||||
option(QUILL_ENABLE_INSTALL "Enable CMake Install when Quill is not a master project" OFF)
|
||||
|
||||
option(QUILL_DOCS_GEN "Generate documentation" OFF)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Use newer policies if possible, up to most recent tested version of CMake.
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Include common compiler options
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
include(${PROJECT_SOURCE_DIR}/cmake/SetCommonCompileOptions.cmake)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Determine if quill is built as a subproject (using add_subdirectory) or if it is the master project.
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
set(QUILL_MASTER_PROJECT FALSE CACHE BOOL "Master Project" FORCE)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(QUILL_MASTER_PROJECT TRUE CACHE BOOL "Master Project" FORCE)
|
||||
endif ()
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Custom cmake functions
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/quill/cmake")
|
||||
include(Utility)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Resolve version
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
quill_extract_version()
|
||||
|
||||
project(quill VERSION ${QUILL_VERSION} LANGUAGES CXX)
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Set default build to release
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build" FORCE)
|
||||
endif ()
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# Compiler config
|
||||
#---------------------------------------------------------------------------------------
|
||||
if (NOT CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
endif ()
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Required Packages
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if (QUILL_BUILD_TESTS)
|
||||
enable_testing()
|
||||
|
||||
if (QUILL_USE_VALGRIND)
|
||||
# find valgrind
|
||||
find_program(MEMORYCHECK_COMMAND NAMES valgrind)
|
||||
if (NOT MEMORYCHECK_COMMAND)
|
||||
message(WARNING "Valgrind not found")
|
||||
endif ()
|
||||
|
||||
# set valgrind params
|
||||
set(MEMORYCHECK_COMMAND_OPTIONS "--tool=memcheck --leak-check=full --leak-resolution=med --show-leak-kinds=all --track-origins=yes --vgdb=no --fair-sched=yes")
|
||||
|
||||
# add memcheck test action to ctest
|
||||
include(CTest)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
# Log Info
|
||||
#-------------------------------------------------------------------------------------------------------
|
||||
if (QUILL_MASTER_PROJECT)
|
||||
option(QUILL_VERBOSE_MAKEFILE "Verbose make output" OFF)
|
||||
|
||||
message(STATUS "CMAKE_BUILD_TYPE: " ${CMAKE_BUILD_TYPE})
|
||||
message(STATUS "QUILL_VERSION: ${QUILL_VERSION}")
|
||||
endif ()
|
||||
|
||||
message(STATUS "QUILL_NO_EXCEPTIONS: " ${QUILL_NO_EXCEPTIONS})
|
||||
message(STATUS "QUILL_NO_THREAD_NAME_SUPPORT: " ${QUILL_NO_THREAD_NAME_SUPPORT})
|
||||
message(STATUS "QUILL_X86ARCH: " ${QUILL_X86ARCH})
|
||||
message(STATUS "QUILL_DISABLE_NON_PREFIXED_MACROS: " ${QUILL_DISABLE_NON_PREFIXED_MACROS})
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
# Verbose make file option
|
||||
#---------------------------------------------------------------------------------------
|
||||
if (QUILL_VERBOSE_MAKEFILE)
|
||||
set(CMAKE_VERBOSE_MAKEFILE TRUE CACHE BOOL "Verbose output" FORCE)
|
||||
endif ()
|
||||
|
||||
# address sanitizer flags
|
||||
if (QUILL_SANITIZE_ADDRESS)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer -g")
|
||||
endif ()
|
||||
|
||||
# thread sanitizer flags
|
||||
if (QUILL_SANITIZE_THREAD)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
endif ()
|
||||
|
||||
# Append extra options for coverage
|
||||
if (QUILL_CODE_COVERAGE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")
|
||||
endif ()
|
||||
|
||||
# Build Examples
|
||||
if (QUILL_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif ()
|
||||
|
||||
if (QUILL_BUILD_BENCHMARKS)
|
||||
add_subdirectory(benchmarks)
|
||||
endif ()
|
||||
|
||||
add_subdirectory(quill)
|
||||
|
||||
if (QUILL_DOCS_GEN)
|
||||
# Add the cmake folder so the FindSphinx module is found
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
|
||||
add_subdirectory(docs)
|
||||
endif ()
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 - present, Odysseas Georgoudis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2021 The Meson development team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,511 +0,0 @@
|
|||
<div align="center">
|
||||
<a href="https://github.com/odygrd/quill">
|
||||
<img width="125" src="https://i.postimg.cc/DZrH8HkX/quill-circle-photos-v2-x2-colored-toned.png" alt="Quill logo">
|
||||
</a>
|
||||
<h1>Quill</h1>
|
||||
|
||||
<div>
|
||||
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Alinux">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/linux.yml?branch=master&label=linux&logo=linux&style=flat-square" alt="linux-ci" />
|
||||
</a>
|
||||
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Amacos">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/macos.yml?branch=master&label=macos&logo=apple&logoColor=white&style=flat-square" alt="macos-ci" />
|
||||
</a>
|
||||
<a href="https://github.com/odygrd/quill/actions?query=workflow%3Awindows">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/odygrd/quill/windows.yml?branch=master&label=windows&logo=windows&logoColor=blue&style=flat-square" alt="windows-ci" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="https://codecov.io/gh/odygrd/quill">
|
||||
<img src="https://img.shields.io/codecov/c/gh/odygrd/quill/master.svg?logo=codecov&style=flat-square" alt="Codecov" />
|
||||
</a>
|
||||
<a href="https://app.codacy.com/gh/odygrd/quill/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
|
||||
<img src="https://img.shields.io/codacy/grade/cd387bc34658475d98bff84db3ad5287?logo=codacy&style=flat-square" alt="Codacy" />
|
||||
</a>
|
||||
<a href="https://www.codefactor.io/repository/github/odygrd/quill">
|
||||
<img src="https://img.shields.io/codefactor/grade/github/odygrd/quill?logo=codefactor&style=flat-square" alt="CodeFactor" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="https://opensource.org/licenses/MIT">
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="license" />
|
||||
</a>
|
||||
<a href="https://en.wikipedia.org/wiki/C%2B%2B17">
|
||||
<img src="https://img.shields.io/badge/language-C%2B%2B17-red.svg?style=flat-square" alt="language" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p><b>Asynchronous Low Latency C++ Logging Library</b></p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Documentation](#documentation)
|
||||
- [Features](#features)
|
||||
- [Caveats](#caveats)
|
||||
- [Performance](#performance)
|
||||
- [Quick Start](#quick-start)
|
||||
- [CMake Integration](#cmake-integration)
|
||||
- [Design](#design)
|
||||
- [License](#license)
|
||||
|
||||
| homebrew | vcpkg | conan |
|
||||
|:--------------------:|:---------------------:|:-----------------:|
|
||||
| `brew install quill` | `vcpkg install quill` | `quill/[>=1.2.3]` |
|
||||
|
||||
## Introduction
|
||||
|
||||
Quill is a high-performance, cross-platform logging library designed for C++17 and onwards.
|
||||
Quill is a production-ready logging library that has undergone extensive unit testing. It has been successfully utilized
|
||||
in production environments, including financial trading applications, providing high-performance and reliable logging
|
||||
capabilities.
|
||||
|
||||
## Documentation
|
||||
|
||||
For detailed documentation and usage instructions, please visit
|
||||
the [Quill Documentation on Read the Docs](http://quillcpp.readthedocs.io/). It provides comprehensive information on
|
||||
how to integrate and utilize Quill in your C++ applications.
|
||||
|
||||
Additionally, you can explore the [examples](http://github.com/odygrd/quill/tree/master/examples) folder in the Quill
|
||||
repository on GitHub. These examples serve as valuable resources to understand different usage scenarios and demonstrate
|
||||
the capabilities of the library.
|
||||
|
||||
## Features
|
||||
|
||||
- **Low Latency Logging**: Achieve fast logging performance with low latency. Refer to
|
||||
the [Benchmarks](http://github.com/odygrd/quill#performance) for more details.
|
||||
- **Asynchronous logging**: Log arguments and messages are formatted in a backend logging thread, effectively offloading
|
||||
the formatting overhead from the critical path.
|
||||
- **Custom Formatters**: Customize log formatting based on user-defined patterns.
|
||||
Explore [Formatters](http://quillcpp.readthedocs.io/en/latest/tutorial.html#formatters) for further details.
|
||||
- **Flexible Timestamp Generation**: Choose between rdtsc, chrono, or custom clocks (useful for simulations) for
|
||||
log message timestamp generation.
|
||||
- **Log Stack Traces**: Store log messages in a ring buffer and display them later in response to a higher severity log
|
||||
statement or on demand. Refer
|
||||
to [Backtrace Logging](http://quillcpp.readthedocs.io/en/latest/tutorial.html#backtrace-logging) for more information.
|
||||
- **Multiple Logging Sinks**: Utilize various logging targets, including:
|
||||
- Console logging with color support.
|
||||
- File logging.
|
||||
- Rotating log files based on time or size.
|
||||
- JSON logging.
|
||||
- Custom sinks.
|
||||
- **Log Message Filtering**: Apply filters to selectively process log messages. Learn more
|
||||
about [Filters](http://quillcpp.readthedocs.io/en/latest/tutorial.html#filters).
|
||||
- **Structured Logging**: Generate JSON structured logs.
|
||||
See [Structured-Log](http://quillcpp.readthedocs.io/en/latest/tutorial.html#json-log) for details.
|
||||
- **Blocking or Dropping Message Modes**: Choose between `blocking` or `dropping` message modes in the library.
|
||||
In `blocking` mode, the hot threads pause and wait when the queue is full until space becomes available, ensuring no
|
||||
message loss but introducing potential latency. In `dropping` mode, log messages beyond the queue's capacity may be
|
||||
dropped to prioritize low latency. The library provides reports on dropped messages, queue reallocations, and blocked
|
||||
hot threads for monitoring purposes.
|
||||
- **Queue Types**: The library supports different types of queues for transferring logs from the hot path to the backend
|
||||
thread: bounded queues with a fixed capacity and unbounded queues that start small and can dynamically grow.
|
||||
- **Wide Character Support**: Wide strings compatible with ASCII encoding are supported, applicable to Windows only.
|
||||
Additionally, there is support for logging STL containers consisting of wide strings. Note that chaining STL types,
|
||||
such as `std::vector<std::vector<std::wstring>>` is not supported for wide strings.
|
||||
- **Ordered Log Statements**: Log statements are ordered by timestamp even when produced by different threads,
|
||||
facilitating easier debugging of multithreaded applications.
|
||||
- **Compile-Time Log Level Stripping**: Completely strip out log levels at compile time, reducing `if` branches.
|
||||
- **Clean and Warning-Free Codebase**: Ensure a clean and warning-free codebase, even with high compiler warning levels.
|
||||
- **Crash-Safe Behavior**: Benefit from crash-safe behavior with a built-in signal handler.
|
||||
- **Type-Safe API**: Type safe api using the excellent [{fmt}](http://github.com/fmtlib/fmt) library.
|
||||
- **Huge Pages**: Benefit from support for huge pages on the hot path. This feature allows for improved performance and
|
||||
efficiency.
|
||||
|
||||
## Caveats
|
||||
|
||||
Quill may not work well with `fork()` since it spawns a background thread and `fork()` doesn't work well
|
||||
with multithreading.
|
||||
|
||||
If your application uses `fork()` and you want to log in the child processes as well, you should call
|
||||
`quill::start()` after the `fork()` call. Additionally, you should ensure that you write to different
|
||||
files in the parent and child processes to avoid conflicts.
|
||||
|
||||
For example :
|
||||
|
||||
```c++
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// DO NOT CALL THIS BEFORE FORK
|
||||
// quill::Backend::start();
|
||||
|
||||
if (fork() == 0)
|
||||
{
|
||||
quill::Backend::start();
|
||||
|
||||
// Get or create a handler to the file - Write to a different file
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"child.log");
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
QUILL_LOG_INFO(logger, "Hello from Child {}", 123);
|
||||
}
|
||||
else
|
||||
{
|
||||
quill::Backend::start();
|
||||
|
||||
// Get or create a handler to the file - Write to a different file
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"parent.log");
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
QUILL_LOG_INFO(logger, "Hello from Parent {}", 123);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Latency
|
||||
|
||||
The results presented in the tables below are measured in `nanoseconds (ns)`.
|
||||
|
||||
#### Logging Numbers
|
||||
|
||||
The following message is logged 100'000 times for each thread:
|
||||
|
||||
`LOG_INFO(logger, "Logging int: {}, int: {}, double: {}", i, j, d)`.
|
||||
|
||||
##### 1 Thread Logging
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 7 | 7 | 8 | 8 | 9 | 10 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 8 | 8 | 9 | 9 | 10 | 13 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 8 | 9 | 9 | 10 | 13 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 8 | 9 | 9 | 10 | 13 |
|
||||
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 11 | 12 | 13 | 14 | 15 | 20 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 21 | 21 | 22 | 24 | 28 | 57 |
|
||||
| [Reckless](http://github.com/mattiasflodin/reckless) | 41 | 45 | 47 | 48 | 49 | 69 |
|
||||
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 51 | 54 | 63 | 81 | 113 | 160 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 148 | 153 | 159 | 163 | 169 | 176 |
|
||||
| [g3log](http://github.com/KjellKod/g3log) | 1192 | 1282 | 1363 | 1440 | 1624 | 1802 |
|
||||
|
||||
##### 4 Threads Logging Simultaneously
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 7 | 8 | 9 | 9 | 10 | 13 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 8 | 8 | 9 | 9 | 11 | 13 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 8 | 9 | 10 | 10 | 11 | 13 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 9 | 9 | 10 | 11 | 12 | 15 |
|
||||
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 12 | 13 | 13 | 14 | 15 | 19 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 21 | 21 | 22 | 22 | 29 | 62 |
|
||||
| [Reckless](http://github.com/mattiasflodin/reckless) | 42 | 46 | 47 | 48 | 54 | 78 |
|
||||
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 53 | 62 | 93 | 122 | 150 | 216 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 209 | 236 | 276 | 304 | 409 | 700 |
|
||||
| [g3log](http://github.com/KjellKod/g3log) | 1344 | 1415 | 1489 | 1557 | 1815 | 5855 |
|
||||
|
||||
#### Logging Large Strings
|
||||
|
||||
The following message is logged 100'000 times for each thread:
|
||||
|
||||
`LOG_INFO(logger, "Logging int: {}, int: {}, string: {}", i, j, large_string)`.
|
||||
|
||||
The large string used in the log message is over 35 characters to prevent the short string optimization
|
||||
of `std::string`.
|
||||
|
||||
##### 1 Thread Logging
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 10 | 12 | 13 | 13 | 14 | 16 |
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 11 | 12 | 13 | 14 | 15 | 17 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 11 | 12 | 13 | 14 | 15 | 17 |
|
||||
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 13 | 14 | 15 | 15 | 17 | 19 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 13 | 14 | 16 | 16 | 17 | 21 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 22 | 23 | 23 | 25 | 30 | 59 |
|
||||
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 52 | 55 | 64 | 83 | 114 | 160 |
|
||||
| [Reckless](http://github.com/mattiasflodin/reckless) | 102 | 122 | 134 | 137 | 143 | 153 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 120 | 123 | 127 | 130 | 138 | 145 |
|
||||
| [g3log](http://github.com/KjellKod/g3log) | 955 | 1049 | 1129 | 1190 | 1351 | 1539 |
|
||||
|
||||
##### 4 Threads Logging Simultaneously
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 11 | 12 | 13 | 15 | 16 | 18 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 11 | 12 | 13 | 15 | 16 | 18 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 12 | 13 | 14 | 15 | 16 | 19 |
|
||||
| [PlatformLab NanoLog](http://github.com/PlatformLab/NanoLog) | 13 | 15 | 15 | 16 | 17 | 20 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 14 | 16 | 17 | 18 | 19 | 22 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 23 | 24 | 24 | 25 | 31 | 62 |
|
||||
| [Iyengar NanoLog](http://github.com/Iyengar111/NanoLog) | 53 | 60 | 92 | 121 | 149 | 212 |
|
||||
| [Reckless](http://github.com/mattiasflodin/reckless) | 101 | 121 | 133 | 136 | 143 | 160 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 186 | 215 | 266 | 297 | 381 | 641 |
|
||||
| [g3log](http://github.com/KjellKod/g3log) | 1089 | 1164 | 1252 | 1328 | 1578 | 5563 |
|
||||
|
||||
#### Logging Complex Types
|
||||
|
||||
The following message is logged 100'000 times for each thread:
|
||||
|
||||
`LOG_INFO(logger, "Logging int: {}, int: {}, vector: {}", i, j, v)`.
|
||||
|
||||
Logging `std::vector<std::string> v` containing 16 large strings, each ranging from 50 to 60 characters.
|
||||
The strings used in the log message are over 35 characters to prevent the short string optimization of `std::string`.
|
||||
|
||||
##### 1 Thread Logging
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 52 | 54 | 56 | 58 | 60 | 63 |
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 53 | 55 | 57 | 59 | 62 | 103 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 66 | 70 | 79 | 81 | 84 | 91 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 632 | 651 | 676 | 698 | 737 | 1049 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 724 | 752 | 776 | 789 | 814 | 857 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 6242 | 6331 | 6438 | 6523 | 6782 | 7341 |
|
||||
|
||||
##### 4 Threads Logging Simultaneously
|
||||
|
||||
| Library | 50th | 75th | 90th | 95th | 99th | 99.9th |
|
||||
|---------------------------------------------------------------------|:----:|:----:|:----:|:----:|:----:|:------:|
|
||||
| [Quill v4.1 Bounded Dropping Queue](http://github.com/odygrd/quill) | 55 | 57 | 59 | 61 | 64 | 77 |
|
||||
| [MS BinLog](http://github.com/Morgan-Stanley/binlog) | 70 | 74 | 83 | 85 | 88 | 102 |
|
||||
| [Quill v4.1 Unbounded Queue](http://github.com/odygrd/quill) | 92 | 100 | 110 | 118 | 135 | 157 |
|
||||
| [Quill v3.8 Unbounded Queue](http://github.com/odygrd/quill) | 674 | 694 | 736 | 762 | 805 | 884 |
|
||||
| [fmtlog](http://github.com/MengRao/fmtlog) | 789 | 813 | 833 | 845 | 872 | 908 |
|
||||
| [spdlog](http://github.com/gabime/spdlog) | 6500 | 6596 | 6724 | 6848 | 7560 | 9036 |
|
||||
|
||||
The benchmark was conducted on `Linux RHEL 9` with an `Intel Core i5-12600` at 4.8 GHz.
|
||||
The cpus are isolated on this system and each thread was pinned to a different CPU. `GCC 13.1` was used as the compiler.
|
||||
|
||||
The benchmark methodology involved logging 20 messages in a loop, calculating and storing the average latency for those
|
||||
20 messages, then waiting around ~2 milliseconds, and repeating this process for a specified number of iterations.
|
||||
|
||||
_In the `Quill Bounded Dropping` benchmarks, the dropping queue size is set to `262,144` bytes, which is double the
|
||||
default size of `131,072` bytes._
|
||||
|
||||
You can find the benchmark code on the [logger_benchmarks](http://github.com/odygrd/logger_benchmarks) repository.
|
||||
|
||||
### Throughput
|
||||
|
||||
The maximum throughput is measured by determining the maximum number of log messages the backend logging thread can
|
||||
write to the log file per second.
|
||||
|
||||
When measured on the same system as the latency benchmarks mentioned above the average throughput of the backend
|
||||
logging thread is `4.56 million msgs/sec`
|
||||
|
||||
While the primary focus of the library is not on throughput, it does provide efficient handling of log messages across
|
||||
multiple threads. The backend logging thread, responsible for formatting and ordering log messages from hot threads,
|
||||
ensures that all queues are emptied on a high priority basis. The backend thread internally buffers the log messages
|
||||
and then writes them later when the caller thread queues are empty or when a predefined limit,
|
||||
`backend_thread_transit_events_soft_limit`, is reached. This approach prevents the need for allocating new queues
|
||||
or dropping messages on the hot path.
|
||||
|
||||
Comparing throughput with other logging libraries in an asynchronous logging scenario has proven challenging. Some
|
||||
libraries may drop log messages, resulting in smaller log files than expected, while others only offer asynchronous
|
||||
flush, making it difficult to determine when the logging thread has finished processing all messages.
|
||||
In contrast, Quill provides a blocking flush log guarantee, ensuring that every log message from the hot threads up to
|
||||
that point is flushed to the file.
|
||||
|
||||
For benchmarking purposes, you can find the
|
||||
code [here](https://github.com/odygrd/quill/blob/master/benchmarks/backend_throughput/quill_backend_throughput.cpp).
|
||||
|
||||
### Compilation Time
|
||||
|
||||
Compile times are measured using `clang 15` and for `Release` build.
|
||||
|
||||
Below, you can find the additional headers that the library will include when you need to log, following
|
||||
the [recommended_usage](https://github.com/odygrd/quill/blob/master/examples/recommended_usage/recommended_usage.cpp)
|
||||
example
|
||||
|
||||

|
||||
|
||||
There is also a compile-time benchmark measuring the compilation time of 2000 auto-generated log statements with
|
||||
various arguments. You can find
|
||||
it [here](https://github.com/odygrd/quill/blob/master/benchmarks/compile_time/compile_time_bench.cpp). It takes approximately 30
|
||||
seconds to compile.
|
||||
|
||||

|
||||
|
||||
## Quick Start
|
||||
|
||||
```c++
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Log to file
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"example_file_logging.log");
|
||||
|
||||
quill::Logger* logger =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
// set the log level of the logger to trace_l3 (default is info)
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_INFO(logger, "Welcome to Quill!");
|
||||
LOG_ERROR(logger, "An error message. error code {}", 123);
|
||||
LOG_WARNING(logger, "A warning message.");
|
||||
LOG_CRITICAL(logger, "A critical error.");
|
||||
LOG_DEBUG(logger, "Debugging foo {}", 1234);
|
||||
LOG_TRACE_L1(logger, "{:>30}", "right aligned");
|
||||
LOG_TRACE_L2(logger, "Positional arguments are {1} {0} ", "too", "supported");
|
||||
LOG_TRACE_L3(logger, "Support for floats {:03.2f}", 1.23456);
|
||||
}
|
||||
```
|
||||
|
||||
```c++
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_INFO(logger, "Welcome to Quill!");
|
||||
LOG_ERROR(logger, "An error message. error code {}", 123);
|
||||
LOG_WARNING(logger, "A warning message.");
|
||||
LOG_CRITICAL(logger, "A critical error.");
|
||||
LOG_DEBUG(logger, "Debugging foo {}", 1234);
|
||||
LOG_TRACE_L1(logger, "{:>30}", "right aligned");
|
||||
LOG_TRACE_L2(logger, "Positional arguments are {1} {0} ", "too", "supported");
|
||||
LOG_TRACE_L3(logger, "Support for floats {:03.2f}", 1.23456);
|
||||
}
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
[](http://postimg.cc/LnZ95M4z)
|
||||
|
||||
## CMake-Integration
|
||||
|
||||
#### External
|
||||
|
||||
##### Building and Installing Quill
|
||||
|
||||
```
|
||||
git clone http://github.com/odygrd/quill.git
|
||||
mkdir cmake_build
|
||||
cd cmake_build
|
||||
cmake ..
|
||||
make install
|
||||
```
|
||||
|
||||
Note: To install in custom directory invoke cmake with `-DCMAKE_INSTALL_PREFIX=/quill/install-dir/`
|
||||
|
||||
Then use the library from a CMake project, you can locate it directly with `find_package()`
|
||||
|
||||
##### Directory Structure
|
||||
|
||||
```
|
||||
my_project/
|
||||
├── CMakeLists.txt
|
||||
├── main.cpp
|
||||
```
|
||||
|
||||
##### CMakeLists.txt
|
||||
|
||||
```cmake
|
||||
# Set only if needed - quill was installed under a custom non-standard directory
|
||||
set(CMAKE_PREFIX_PATH /test_quill/usr/local/)
|
||||
|
||||
find_package(quill REQUIRED)
|
||||
|
||||
# Linking your project against quill
|
||||
add_executable(example main.cpp)
|
||||
target_link_libraries(example PUBLIC quill::quill)
|
||||
```
|
||||
|
||||
#### Embedded
|
||||
|
||||
To embed the library directly, copy the source [folder](http://github.com/odygrd/quill/tree/master/quill/quill) to your
|
||||
project and call `add_subdirectory()` in your `CMakeLists.txt` file
|
||||
|
||||
##### Directory Structure
|
||||
|
||||
```
|
||||
my_project/
|
||||
├── quill/ (source folder)
|
||||
├── CMakeLists.txt
|
||||
├── main.cpp
|
||||
```
|
||||
|
||||
##### CMakeLists.txt
|
||||
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
project(my_project)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
add_subdirectory(quill)
|
||||
|
||||
add_executable(my_project main.cpp)
|
||||
target_link_libraries(my_project PUBLIC quill::quill)
|
||||
```
|
||||
|
||||
#### Building Quill for Android NDK
|
||||
|
||||
To build Quill for Android NDK add the following `CMake` flags when configuring the build:
|
||||
|
||||
```
|
||||
-DQUILL_NO_THREAD_NAME_SUPPORT:BOOL=ON
|
||||
```
|
||||
|
||||
## Design
|
||||
|
||||
### Frontend (caller-thread)
|
||||
|
||||
When invoking a `LOG_` macro:
|
||||
|
||||
1. Creates a static constexpr metadata object to store `Metadata` such as the format string and source location.
|
||||
|
||||
2. Pushes the data SPSC lock-free queue. For each log message, the following variables are pushed
|
||||
|
||||
| Variable | Description |
|
||||
|------------|:--------------------------------------------------------------------------------------------------------------:|
|
||||
| timestamp | Current timestamp |
|
||||
| Metadata* | Pointer to metadata information |
|
||||
| Logger* | Pointer to the logger instance |
|
||||
| DecodeFunc | A pointer to a templated function containing all the log message argument types, used for decoding the message |
|
||||
| Args... | A serialized binary copy of each log message argument that was passed to the `LOG_` macro |
|
||||
|
||||
### Backend
|
||||
|
||||
Consumes each message from the SPSC queue, retrieves all the necessary information and then formats the message.
|
||||
Subsequently, forwards the log message to all Sinks associated with the Logger.
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
Quill is licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
|
||||
Quill depends on third party libraries with separate copyright notices and license terms.
|
||||
Your use of the source code for these subcomponents is subject to the terms and conditions of the following licenses.
|
||||
|
||||
- ([MIT License](http://opensource.org/licenses/MIT)) {fmt} (http://github.com/fmtlib/fmt/blob/master/LICENSE.rst)
|
||||
- ([MIT License](http://opensource.org/licenses/MIT)) doctest (http://github.com/onqtam/doctest/blob/master/LICENSE.txt)
|
|
@ -1,3 +0,0 @@
|
|||
add_subdirectory(hot_path_latency)
|
||||
add_subdirectory(backend_throughput)
|
||||
add_subdirectory(compile_time)
|
|
@ -1,7 +0,0 @@
|
|||
add_executable(BENCHMARK_quill_backend_throughput quill_backend_throughput.cpp)
|
||||
set_common_compile_options(BENCHMARK_quill_backend_throughput)
|
||||
target_link_libraries(BENCHMARK_quill_backend_throughput quill)
|
||||
|
||||
add_executable(BENCHMARK_quill_backend_throughput_no_buffering quill_backend_throughput_no_buffering.cpp)
|
||||
set_common_compile_options(BENCHMARK_quill_backend_throughput_no_buffering)
|
||||
target_link_libraries(BENCHMARK_quill_backend_throughput_no_buffering quill)
|
|
@ -1,65 +0,0 @@
|
|||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
static constexpr size_t total_iterations = 4'000'000;
|
||||
|
||||
/**
|
||||
* The backend worker just spins, so we just measure the total time elapsed for total_iterations
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
// main thread affinity
|
||||
quill::detail::set_cpu_affinity(0);
|
||||
|
||||
quill::BackendOptions backend_options;
|
||||
backend_options.backend_cpu_affinity = 5;
|
||||
|
||||
// Start the logging backend thread and give it some tiem to init
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
|
||||
// Create a file sink to write to a file
|
||||
std::shared_ptr<quill::Sink> file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"quill_backend_total_time.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger(
|
||||
"bench_logger", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location) %(log_level) %(message)");
|
||||
|
||||
quill::Frontend::preallocate();
|
||||
|
||||
// start counting the time until backend worker finishes
|
||||
auto const start_time = std::chrono::steady_clock::now();
|
||||
for (size_t iteration = 0; iteration < total_iterations; ++iteration)
|
||||
{
|
||||
LOG_INFO(logger, "Iteration: {} int: {} double: {}", iteration, iteration * 2,
|
||||
static_cast<double>(iteration) / 2);
|
||||
}
|
||||
|
||||
// block until all messages are flushed
|
||||
logger->flush_log();
|
||||
|
||||
auto const end_time = std::chrono::steady_clock::now();
|
||||
auto const delta = end_time - start_time;
|
||||
auto delta_d = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();
|
||||
|
||||
std::cout << fmtquill::format(
|
||||
"Throughput is {:.2f} million msgs/sec average, total time elapsed: {} ms for {} "
|
||||
"log messages \n",
|
||||
total_iterations / delta_d / 1e6,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(delta).count(), total_iterations)
|
||||
<< std::endl;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
static constexpr size_t total_iterations = 4'000'000;
|
||||
|
||||
/**
|
||||
* The backend worker just spins, so we just measure the total time elapsed for total_iterations
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
// main thread affinity
|
||||
quill::detail::set_cpu_affinity(0);
|
||||
|
||||
quill::BackendOptions backend_options;
|
||||
backend_options.backend_cpu_affinity = 5;
|
||||
backend_options.transit_events_hard_limit = 1;
|
||||
|
||||
// Start the logging backend thread and give it some tiem to init
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
|
||||
// Create a file sink to write to a file
|
||||
std::shared_ptr<quill::Sink> file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"quill_backend_total_time.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger(
|
||||
"bench_logger", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location) %(log_level) %(message)");
|
||||
|
||||
quill::Frontend::preallocate();
|
||||
|
||||
// start counting the time until backend worker finishes
|
||||
auto const start_time = std::chrono::steady_clock::now();
|
||||
for (size_t iteration = 0; iteration < total_iterations; ++iteration)
|
||||
{
|
||||
LOG_INFO(logger, "Iteration: {} int: {} double: {}", iteration, iteration * 2,
|
||||
static_cast<double>(iteration) / 2);
|
||||
}
|
||||
|
||||
// block until all messages are flushed
|
||||
logger->flush_log();
|
||||
|
||||
auto const end_time = std::chrono::steady_clock::now();
|
||||
auto const delta = end_time - start_time;
|
||||
auto delta_d = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();
|
||||
|
||||
std::cout << fmtquill::format(
|
||||
"Throughput is {:.2f} million msgs/sec average, total time elapsed: {} ms for {} "
|
||||
"log messages \n",
|
||||
total_iterations / delta_d / 1e6,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(delta).count(), total_iterations)
|
||||
<< std::endl;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
add_subdirectory(qwrapper)
|
||||
|
||||
add_executable(BENCHMARK_quill_compile_time compile_time_bench.cpp)
|
||||
set_common_compile_options(BENCHMARK_quill_compile_time)
|
||||
target_link_libraries(BENCHMARK_quill_compile_time qwrapper_compile_time_bench)
|
File diff suppressed because it is too large
Load diff
|
@ -1,57 +0,0 @@
|
|||
import random
|
||||
|
||||
|
||||
def generate_log_statements(num_statements):
|
||||
argument_types = [
|
||||
'1', '2', '3.0', '4.0f', '5L', '6LL', '7UL', '8ULL', 'true', 'false',
|
||||
'"example1"', '"example2"', '"example3"', 'std::string("str1")',
|
||||
'std::string("str2")', 'std::string_view("view1")', 'std::string_view("view2")',
|
||||
'static_cast<short>(9)', 'static_cast<unsigned short>(10)'
|
||||
]
|
||||
random_words = ["quick", "brown", "fox", "jumps", "over", "lazy", "dog", "logging", "test", "example"]
|
||||
|
||||
statements = []
|
||||
for i in range(num_statements):
|
||||
num_args = random.randint(1, 10) # Number of arguments for the log statement
|
||||
args = random.sample(argument_types, num_args)
|
||||
placeholders = ' '.join(["{}" for _ in args])
|
||||
num_words = random.randint(3, 4) # Number of random words in the log message
|
||||
words = ' '.join(random.sample(random_words, num_words))
|
||||
statement = f' LOG_INFO(logger, "{words} {placeholders}", {", ".join(args)});'
|
||||
statements.append(statement)
|
||||
|
||||
return statements
|
||||
|
||||
|
||||
def write_to_file(filename, statements):
|
||||
with open(filename, 'w') as f:
|
||||
f.write('#include "quill/Backend.h"\n')
|
||||
f.write('#include "quill/Frontend.h"\n')
|
||||
f.write('#include "quill/LogMacros.h"\n')
|
||||
f.write('#include "quill/Logger.h"\n')
|
||||
f.write('#include "quill/sinks/ConsoleSink.h"\n')
|
||||
f.write('#include <string>\n')
|
||||
f.write('#include <utility>\n\n')
|
||||
f.write('/**\n')
|
||||
f.write(' * Trivial logging example to console\n')
|
||||
f.write(' */\n\n')
|
||||
f.write('int main()\n')
|
||||
f.write('{\n')
|
||||
f.write(' // Start the backend thread\n')
|
||||
f.write(' quill::BackendOptions backend_options;\n')
|
||||
f.write(' quill::Backend::start(backend_options);\n\n')
|
||||
f.write(' // Frontend\n')
|
||||
f.write(' auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");\n')
|
||||
f.write(' quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));\n\n')
|
||||
|
||||
for statement in statements:
|
||||
f.write(f'{statement}\n')
|
||||
|
||||
f.write('\n return 0;\n')
|
||||
f.write('}\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
num_statements = 2000
|
||||
statements = generate_log_statements(num_statements)
|
||||
write_to_file('log_benchmark.cpp', statements)
|
|
@ -1,16 +0,0 @@
|
|||
set(LIB_NAME qwrapper_compile_time_bench)
|
||||
|
||||
add_library(${LIB_NAME} STATIC
|
||||
include/qwrapper/qwrapper.h
|
||||
include/qwrapper/qwrapper.cpp)
|
||||
|
||||
# Add include directories for this library
|
||||
target_include_directories(${LIB_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Link quill dependency
|
||||
target_link_libraries(${LIB_NAME} PUBLIC quill::quill)
|
|
@ -1,18 +0,0 @@
|
|||
#include "qwrapper.h"
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
void setup_quill(char const* log_file)
|
||||
{
|
||||
quill::Backend::start();
|
||||
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("s1");
|
||||
|
||||
quill::Frontend::create_or_get_logger("root", std::move(console_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) "
|
||||
"LOG_%(log_level:<9) %(logger:<12) %(message)",
|
||||
"%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void setup_quill(char const* log_file);
|
|
@ -1,7 +0,0 @@
|
|||
add_executable(BENCHMARK_quill_hot_path_rdtsc_clock hot_path_bench_config.h hot_path_bench.h quill_hot_path_rdtsc_clock.cpp)
|
||||
set_common_compile_options(BENCHMARK_quill_hot_path_rdtsc_clock)
|
||||
target_link_libraries(BENCHMARK_quill_hot_path_rdtsc_clock quill)
|
||||
|
||||
add_executable(BENCHMARK_quill_hot_path_system_clock hot_path_bench_config.h hot_path_bench.h quill_hot_path_system_clock.cpp)
|
||||
set_common_compile_options(BENCHMARK_quill_hot_path_system_clock)
|
||||
target_link_libraries(BENCHMARK_quill_hot_path_system_clock quill)
|
|
@ -1,204 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hot_path_bench_config.h"
|
||||
|
||||
#include "quill/backend/BackendUtilities.h"
|
||||
#include "quill/backend/RdtscClock.h"
|
||||
#include "quill/core/Rdtsc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
|
||||
inline uint16_t get_cpu_to_pin_thread(uint16_t thread_num)
|
||||
{
|
||||
auto const num_cores = static_cast<uint16_t>(std::thread::hardware_concurrency());
|
||||
|
||||
// If hardware_concurrency feature is not supported, zero value is returned.
|
||||
if (num_cores == 0)
|
||||
return 0;
|
||||
|
||||
return thread_num % num_cores;
|
||||
}
|
||||
|
||||
// Instead of sleep
|
||||
inline void wait(std::chrono::nanoseconds min, std::chrono::nanoseconds max)
|
||||
{
|
||||
#ifdef PERF_ENABLED
|
||||
// when in perf use sleep as the other variables add noise
|
||||
std::this_thread::sleep_for(max);
|
||||
#else
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static std::uniform_int_distribution<> dis(static_cast<int>(min.count()), static_cast<int>(max.count()));
|
||||
|
||||
auto const start_time = std::chrono::steady_clock::now();
|
||||
auto const end_time = start_time.time_since_epoch() + std::chrono::nanoseconds{dis(gen)};
|
||||
std::chrono::nanoseconds time_now;
|
||||
do
|
||||
{
|
||||
time_now = std::chrono::steady_clock::now().time_since_epoch();
|
||||
} while (time_now < end_time);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef PERF_ENABLED
|
||||
/***/
|
||||
inline void run_log_benchmark(size_t num_iterations, size_t messages_per_iteration,
|
||||
std::function<void()> on_thread_start,
|
||||
std::function<void(uint64_t, uint64_t, double)> log_func,
|
||||
std::function<void()> on_thread_exit, size_t current_thread_num)
|
||||
{
|
||||
// running thread affinity
|
||||
quill::detail::set_cpu_affinity(get_cpu_to_pin_thread(current_thread_num));
|
||||
|
||||
on_thread_start();
|
||||
|
||||
unsigned int aux;
|
||||
// Main Benchmark
|
||||
for (size_t iteration = 0; iteration < num_iterations; ++iteration)
|
||||
{
|
||||
double const d = iteration + (0.1 * iteration);
|
||||
|
||||
auto const start = __rdtscp(&aux);
|
||||
for (size_t i = 0; i < messages_per_iteration; ++i)
|
||||
{
|
||||
log_func(iteration, i, d);
|
||||
}
|
||||
auto const end = __rdtscp(&aux);
|
||||
|
||||
// send the next batch of messages after x time
|
||||
wait(MIN_WAIT_DURATION, MAX_WAIT_DURATION);
|
||||
}
|
||||
|
||||
on_thread_exit();
|
||||
}
|
||||
#else
|
||||
/***/
|
||||
inline void run_log_benchmark(size_t num_iterations, size_t messages_per_iteration,
|
||||
std::function<void()> const& on_thread_start,
|
||||
std::function<void(uint64_t, uint64_t, double)> const& log_func,
|
||||
std::function<void()> const& on_thread_exit, uint16_t current_thread_num,
|
||||
std::vector<uint64_t>& latencies, double rdtsc_ns_per_tick)
|
||||
{
|
||||
// running thread affinity
|
||||
quill::detail::set_cpu_affinity(get_cpu_to_pin_thread(current_thread_num));
|
||||
|
||||
on_thread_start();
|
||||
|
||||
unsigned int aux;
|
||||
// Main Benchmark
|
||||
for (size_t iteration = 0; iteration < num_iterations; ++iteration)
|
||||
{
|
||||
double const d = static_cast<double>(iteration) + (0.1 * static_cast<double>(iteration));
|
||||
|
||||
auto const start = __rdtscp(&aux);
|
||||
for (size_t i = 0; i < messages_per_iteration; ++i)
|
||||
{
|
||||
log_func(iteration, i, d);
|
||||
}
|
||||
auto const end = __rdtscp(&aux);
|
||||
|
||||
uint64_t const latency{static_cast<uint64_t>(
|
||||
static_cast<double>((end - start)) / static_cast<double>(messages_per_iteration) * rdtsc_ns_per_tick)};
|
||||
latencies.push_back(latency);
|
||||
|
||||
// send the next batch of messages after x time
|
||||
wait(MIN_WAIT_DURATION, MAX_WAIT_DURATION);
|
||||
}
|
||||
|
||||
on_thread_exit();
|
||||
}
|
||||
#endif
|
||||
|
||||
/***/
|
||||
inline void run_benchmark(char const* benchmark_name, uint16_t thread_count, size_t num_iterations,
|
||||
size_t messages_per_iteration, std::function<void()> const& on_thread_start,
|
||||
std::function<void(uint64_t, uint64_t, double)> const& log_func,
|
||||
std::function<void()> const& on_thread_exit)
|
||||
{
|
||||
// main thread affinity
|
||||
quill::detail::set_cpu_affinity(0);
|
||||
|
||||
#ifndef PERF_ENABLED
|
||||
std::cout << "running for " << thread_count << " thread(s)" << std::endl;
|
||||
|
||||
quill::detail::RdtscClock rdtsc_clock{std::chrono::minutes{30}};
|
||||
|
||||
// each thread gets a vector of latencies
|
||||
std::vector<std::vector<uint64_t>> latencies;
|
||||
latencies.resize(thread_count);
|
||||
for (auto& elem : latencies)
|
||||
{
|
||||
elem.reserve(num_iterations);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(thread_count);
|
||||
for (uint16_t thread_num = 0; thread_num < thread_count; ++thread_num)
|
||||
{
|
||||
#ifdef PERF_ENABLED
|
||||
// Spawn num threads
|
||||
threads.emplace_back(run_log_benchmark, num_iterations, (messages_per_iteration / thread_count),
|
||||
on_thread_start, log_func, on_thread_exit, thread_num + 1);
|
||||
#else
|
||||
// Spawn num threads
|
||||
threads.emplace_back(run_log_benchmark, num_iterations,
|
||||
static_cast<size_t>(messages_per_iteration / thread_count),
|
||||
std::ref(on_thread_start), std::ref(log_func), std::ref(on_thread_exit),
|
||||
static_cast<uint16_t>(thread_num + 1u), std::ref(latencies[thread_num]),
|
||||
rdtsc_clock.nanoseconds_per_tick());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Wait for threads to finish
|
||||
for (uint16_t i = 0; i < thread_count; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
|
||||
#ifndef PERF_ENABLED
|
||||
// All threads have finished we can read all latencies
|
||||
std::vector<uint64_t> latencies_combined;
|
||||
latencies_combined.reserve(num_iterations * thread_count);
|
||||
for (auto const& elem : latencies)
|
||||
{
|
||||
latencies_combined.insert(latencies_combined.end(), elem.begin(), elem.end());
|
||||
}
|
||||
|
||||
// Sort all latencies
|
||||
std::sort(latencies_combined.begin(), latencies_combined.end());
|
||||
|
||||
std::cout
|
||||
<< "Thread Count " << thread_count << " - Total messages " << latencies_combined.size() * messages_per_iteration
|
||||
<< " - " << benchmark_name << "\n | 50th | 75th | 90th | 95th | 99th | 99.9th | Worst |\n"
|
||||
<< " | "
|
||||
<< latencies_combined[static_cast<size_t>(static_cast<double>(num_iterations * thread_count) * 0.5)] << " | "
|
||||
<< latencies_combined[static_cast<size_t>(static_cast<double>(num_iterations * thread_count) * 0.75)]
|
||||
<< " | "
|
||||
<< latencies_combined[static_cast<size_t>(static_cast<double>(num_iterations * thread_count) * 0.9)] << " | "
|
||||
<< latencies_combined[static_cast<size_t>(static_cast<double>(num_iterations * thread_count) * 0.95)]
|
||||
<< " | "
|
||||
<< latencies_combined[static_cast<size_t>(static_cast<double>(num_iterations * thread_count) * 0.99)]
|
||||
<< " | "
|
||||
<< latencies_combined[static_cast<size_t>(static_cast<double>(num_iterations * thread_count) * 0.999)]
|
||||
<< " | " << latencies_combined[static_cast<size_t>(latencies_combined.size() - 1)] << " |\n\n";
|
||||
#endif
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <chrono>
|
||||
|
||||
/**
|
||||
* When running the benchmark using e.g. perf, enable this definition to remove extra noise
|
||||
* from calculating and printing the results.
|
||||
*
|
||||
* To see shared cached lines :
|
||||
* perf c2c record -g --call-graph dwarf,8192 ./benchmark_quill_call_site_latency
|
||||
* perf c2c report -NN -g --call-graph -c pid,iaddr --stdio
|
||||
*/
|
||||
// #define PERF_ENABLED
|
||||
|
||||
#define THREAD_LIST_COUNT \
|
||||
std::vector<uint16_t> { 1, 4 }
|
||||
|
||||
#define MESSAGES_PER_ITERATION \
|
||||
std::size_t { 20 }
|
||||
|
||||
#define ITERATIONS \
|
||||
std::size_t { 100000 }
|
||||
|
||||
/**
|
||||
* Min-Max wait duration between each iteration - This lets the backend thread catch up
|
||||
* a little bit with the caller thread, because the caller thread is so much faster.
|
||||
* When the backend thread can't catch up it will cause the caller thread on the hot path
|
||||
* to reallocate more space in the queue slowing it down.
|
||||
* This benchmark is measuring latency not high throughput
|
||||
* **/
|
||||
#define MIN_WAIT_DURATION \
|
||||
std::chrono::microseconds { 2000 }
|
||||
|
||||
#define MAX_WAIT_DURATION \
|
||||
std::chrono::microseconds { 2200 }
|
|
@ -1,93 +0,0 @@
|
|||
/**
|
||||
* Adding a benchmark for a another logger should be straight forward by duplicating and modifying
|
||||
* this file.
|
||||
*/
|
||||
|
||||
#include "hot_path_bench.h"
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
struct FrontendOptions
|
||||
{
|
||||
static constexpr quill::QueueType queue_type = quill::QueueType::UnboundedBlocking;
|
||||
static constexpr uint32_t initial_queue_capacity = 131'072;
|
||||
static constexpr uint32_t blocking_queue_retry_interval_ns = 800;
|
||||
static constexpr bool huge_pages_enabled = false;
|
||||
};
|
||||
|
||||
using Frontend = quill::FrontendImpl<FrontendOptions>;
|
||||
using Logger = quill::LoggerImpl<FrontendOptions>;
|
||||
|
||||
/***/
|
||||
void quill_benchmark(std::vector<uint16_t> const& thread_count_array,
|
||||
size_t num_iterations_per_thread, size_t messages_per_iteration)
|
||||
{
|
||||
/** - MAIN THREAD START - Logger setup if any **/
|
||||
|
||||
/** - Setup Quill **/
|
||||
// main thread affinity
|
||||
quill::detail::set_cpu_affinity(0);
|
||||
|
||||
quill::BackendOptions backend_options;
|
||||
backend_options.backend_cpu_affinity = 5;
|
||||
|
||||
// Start the logging backend thread and give it some tiem to init
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
|
||||
// wait for the backend thread to start
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
// Create a file sink to write to a file
|
||||
std::shared_ptr<quill::Sink> file_sink = Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"quill_hot_path_rdtsc_clock.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
Logger* logger = Frontend::create_or_get_logger(
|
||||
"bench_logger", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location) %(log_level) %(message)");
|
||||
|
||||
/** LOGGING THREAD FUNCTIONS - on_start, on_exit, log_func must be implemented **/
|
||||
/** those run on a several thread(s). It can be one or multiple threads based on THREAD_LIST_COUNT config */
|
||||
auto on_start = []() {
|
||||
// on thread start
|
||||
Frontend::preallocate();
|
||||
};
|
||||
|
||||
auto on_exit = [logger]()
|
||||
{
|
||||
// on thread exit we block flush, so the next benchmark starts with the backend thread ready
|
||||
// to process the messages
|
||||
logger->flush_log();
|
||||
};
|
||||
|
||||
// on main
|
||||
auto log_func = [logger](uint64_t k, uint64_t i, double d) {
|
||||
// Main logging function
|
||||
// This will get called MESSAGES_PER_ITERATION * ITERATIONS for each caller thread.
|
||||
// MESSAGES_PER_ITERATION will get averaged to a single number
|
||||
|
||||
LOG_INFO(logger, "Logging iteration: {}, message: {}, double: {}", k, i, d);
|
||||
};
|
||||
|
||||
/** ALWAYS REQUIRED **/
|
||||
// Run the benchmark for n threads
|
||||
for (auto thread_count : thread_count_array)
|
||||
{
|
||||
run_benchmark("Logger: Quill - Benchmark: Hot Path Latency / Nanoseconds", thread_count,
|
||||
num_iterations_per_thread, messages_per_iteration, on_start, log_func, on_exit);
|
||||
}
|
||||
}
|
||||
|
||||
/***/
|
||||
int main(int, char**) { quill_benchmark(THREAD_LIST_COUNT, ITERATIONS, MESSAGES_PER_ITERATION); }
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* Adding a benchmark for a another logger should be straight forward by duplicating and modifying
|
||||
* this file.
|
||||
*/
|
||||
|
||||
#include "hot_path_bench.h"
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
struct FrontendOptions
|
||||
{
|
||||
static constexpr quill::QueueType queue_type = quill::QueueType::UnboundedBlocking;
|
||||
static constexpr uint32_t initial_queue_capacity = 131'072;
|
||||
static constexpr uint32_t blocking_queue_retry_interval_ns = 800;
|
||||
static constexpr bool huge_pages_enabled = false;
|
||||
};
|
||||
|
||||
using Frontend = quill::FrontendImpl<FrontendOptions>;
|
||||
using Logger = quill::LoggerImpl<FrontendOptions>;
|
||||
|
||||
/***/
|
||||
void quill_benchmark(std::vector<uint16_t> const& thread_count_array,
|
||||
size_t num_iterations_per_thread, size_t messages_per_iteration)
|
||||
{
|
||||
/** - MAIN THREAD START - Logger setup if any **/
|
||||
|
||||
/** - Setup Quill **/
|
||||
// main thread affinity
|
||||
quill::detail::set_cpu_affinity(0);
|
||||
|
||||
quill::BackendOptions backend_options;
|
||||
backend_options.backend_cpu_affinity = 5;
|
||||
|
||||
// Start the logging backend thread and give it some tiem to init
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
|
||||
// wait for the backend thread to start
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
// Create a file sink to write to a file
|
||||
std::shared_ptr<quill::Sink> file_sink = Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"quill_hot_path_rdtsc_clock.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
Logger* logger = Frontend::create_or_get_logger(
|
||||
"bench_logger", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location) %(log_level) %(message)", "%H:%M:%S.%Qns",
|
||||
quill::Timezone::LocalTime, quill::ClockSourceType::System);
|
||||
|
||||
/** LOGGING THREAD FUNCTIONS - on_start, on_exit, log_func must be implemented **/
|
||||
/** those run on a several thread(s). It can be one or multiple threads based on THREAD_LIST_COUNT config */
|
||||
auto on_start = []()
|
||||
{
|
||||
// on thread start
|
||||
Frontend::preallocate();
|
||||
};
|
||||
|
||||
auto on_exit = [logger]()
|
||||
{
|
||||
// on thread exit we block flush, so the next benchmark starts with the backend thread ready
|
||||
// to process the messages
|
||||
logger->flush_log();
|
||||
};
|
||||
|
||||
// on main
|
||||
auto log_func = [logger](uint64_t k, uint64_t i, double d)
|
||||
{
|
||||
// Main logging function
|
||||
// This will get called MESSAGES_PER_ITERATION * ITERATIONS for each caller thread.
|
||||
// MESSAGES_PER_ITERATION will get averaged to a single number
|
||||
|
||||
LOG_INFO(logger, "Logging iteration: {}, message: {}, double: {}", k, i, d);
|
||||
};
|
||||
|
||||
/** ALWAYS REQUIRED **/
|
||||
// Run the benchmark for n threads
|
||||
for (auto thread_count : thread_count_array)
|
||||
{
|
||||
run_benchmark("Logger: Quill - Benchmark: Hot Path Latency / Nanoseconds", thread_count,
|
||||
num_iterations_per_thread, messages_per_iteration, on_start, log_func, on_exit);
|
||||
}
|
||||
}
|
||||
|
||||
/***/
|
||||
int main(int, char**) { quill_benchmark(THREAD_LIST_COUNT, ITERATIONS, MESSAGES_PER_ITERATION); }
|
|
@ -1,11 +0,0 @@
|
|||
#Look for an executable called sphinx-build
|
||||
find_program(SPHINX_EXECUTABLE
|
||||
NAMES sphinx-build
|
||||
DOC "Path to sphinx-build executable")
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
#Handle standard arguments to find_package like REQUIRED and QUIET
|
||||
find_package_handle_standard_args(Sphinx
|
||||
"Failed to find sphinx-build executable"
|
||||
SPHINX_EXECUTABLE)
|
|
@ -1,21 +0,0 @@
|
|||
# Define the function to set common compile options
|
||||
function(set_common_compile_options target_name)
|
||||
cmake_parse_arguments(COMPILE_OPTIONS "" "VISIBILITY" "" ${ARGN})
|
||||
|
||||
# Set default visibility to PRIVATE if not provided
|
||||
if (NOT DEFINED COMPILE_OPTIONS_VISIBILITY)
|
||||
set(COMPILE_OPTIONS_VISIBILITY PRIVATE)
|
||||
endif ()
|
||||
|
||||
target_compile_options(${target_name} ${COMPILE_OPTIONS_VISIBILITY}
|
||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
|
||||
-Wall -Wextra -Wconversion -pedantic -Wfatal-errors -Wno-unused-private-field -Wno-gnu-zero-variadic-macro-arguments -Wno-unused-parameter>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/bigobj /WX /W4 /wd4324 /wd4189 /wd4996 /wd4100 /wd4127 /wd4702>)
|
||||
|
||||
# Additional MSVC specific options
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
if (NOT QUILL_NO_EXCEPTIONS)
|
||||
target_compile_options(${target_name} ${COMPILE_OPTIONS_VISIBILITY} /EHsc)
|
||||
endif ()
|
||||
endif ()
|
||||
endfunction()
|
|
@ -1,189 +0,0 @@
|
|||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
doctest
|
||||
-----
|
||||
|
||||
This module defines a function to help use the doctest test framework.
|
||||
|
||||
The :command:`doctest_discover_tests` discovers tests by asking the compiled test
|
||||
executable to enumerate its tests. This does not require CMake to be re-run
|
||||
when tests change. However, it may not work in a cross-compiling environment,
|
||||
and setting test properties is less convenient.
|
||||
|
||||
This command is intended to replace use of :command:`add_test` to register
|
||||
tests, and will create a separate CTest test for each doctest test case. Note
|
||||
that this is in some cases less efficient, as common set-up and tear-down logic
|
||||
cannot be shared by multiple test cases executing in the same instance.
|
||||
However, it provides more fine-grained pass/fail information to CTest, which is
|
||||
usually considered as more beneficial. By default, the CTest test name is the
|
||||
same as the doctest name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
|
||||
.. command:: doctest_discover_tests
|
||||
|
||||
Automatically add tests with CTest by querying the compiled test executable
|
||||
for available tests::
|
||||
|
||||
doctest_discover_tests(target
|
||||
[TEST_SPEC arg1...]
|
||||
[EXTRA_ARGS arg1...]
|
||||
[WORKING_DIRECTORY dir]
|
||||
[TEST_PREFIX prefix]
|
||||
[TEST_SUFFIX suffix]
|
||||
[PROPERTIES name1 value1...]
|
||||
[ADD_LABELS value]
|
||||
[TEST_LIST var]
|
||||
[JUNIT_OUTPUT_DIR dir]
|
||||
)
|
||||
|
||||
``doctest_discover_tests`` sets up a post-build command on the test executable
|
||||
that generates the list of tests by parsing the output from running the test
|
||||
with the ``--list-test-cases`` argument. This ensures that the full
|
||||
list of tests is obtained. Since test discovery occurs at build time, it is
|
||||
not necessary to re-run CMake when the list of tests changes.
|
||||
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
|
||||
in order to function in a cross-compiling environment.
|
||||
|
||||
Additionally, setting properties on tests is somewhat less convenient, since
|
||||
the tests are not available at CMake time. Additional test properties may be
|
||||
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
|
||||
more fine-grained test control is needed, custom content may be provided
|
||||
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
|
||||
directory property. The set of discovered tests is made accessible to such a
|
||||
script via the ``<target>_TESTS`` variable.
|
||||
|
||||
The options are:
|
||||
|
||||
``target``
|
||||
Specifies the doctest executable, which must be a known CMake executable
|
||||
target. CMake will substitute the location of the built executable when
|
||||
running the test.
|
||||
|
||||
``TEST_SPEC arg1...``
|
||||
Specifies test cases, wildcarded test cases, tags and tag expressions to
|
||||
pass to the doctest executable with the ``--list-test-cases`` argument.
|
||||
|
||||
``EXTRA_ARGS arg1...``
|
||||
Any extra arguments to pass on the command line to each test case.
|
||||
|
||||
``WORKING_DIRECTORY dir``
|
||||
Specifies the directory in which to run the discovered test cases. If this
|
||||
option is not provided, the current binary directory is used.
|
||||
|
||||
``TEST_PREFIX prefix``
|
||||
Specifies a ``prefix`` to be prepended to the name of each discovered test
|
||||
case. This can be useful when the same test executable is being used in
|
||||
multiple calls to ``doctest_discover_tests()`` but with different
|
||||
``TEST_SPEC`` or ``EXTRA_ARGS``.
|
||||
|
||||
``TEST_SUFFIX suffix``
|
||||
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
|
||||
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
|
||||
be specified.
|
||||
|
||||
``PROPERTIES name1 value1...``
|
||||
Specifies additional properties to be set on all tests discovered by this
|
||||
invocation of ``doctest_discover_tests``.
|
||||
|
||||
``ADD_LABELS value``
|
||||
Specifies if the test labels should be set automatically.
|
||||
|
||||
``TEST_LIST var``
|
||||
Make the list of tests available in the variable ``var``, rather than the
|
||||
default ``<target>_TESTS``. This can be useful when the same test
|
||||
executable is being used in multiple calls to ``doctest_discover_tests()``.
|
||||
Note that this variable is only available in CTest.
|
||||
|
||||
``JUNIT_OUTPUT_DIR dir``
|
||||
If specified, the parameter is passed along with ``--reporters=junit``
|
||||
and ``--out=`` to the test executable. The actual file name is the same
|
||||
as the test target, including prefix and suffix. This should be used
|
||||
instead of EXTRA_ARGS to avoid race conditions writing the XML result
|
||||
output when using parallel test execution.
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
function(doctest_discover_tests TARGET)
|
||||
cmake_parse_arguments(
|
||||
""
|
||||
""
|
||||
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;JUNIT_OUTPUT_DIR"
|
||||
"TEST_SPEC;EXTRA_ARGS;PROPERTIES;ADD_LABELS"
|
||||
${ARGN}
|
||||
)
|
||||
|
||||
if(NOT _WORKING_DIRECTORY)
|
||||
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
if(NOT _TEST_LIST)
|
||||
set(_TEST_LIST ${TARGET}_TESTS)
|
||||
endif()
|
||||
|
||||
## Generate a unique name based on the extra arguments
|
||||
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}")
|
||||
string(SUBSTRING ${args_hash} 0 7 args_hash)
|
||||
|
||||
# Define rule to generate test list for aforementioned test executable
|
||||
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
|
||||
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
|
||||
get_property(crosscompiling_emulator
|
||||
TARGET ${TARGET}
|
||||
PROPERTY CROSSCOMPILING_EMULATOR
|
||||
)
|
||||
add_custom_command(
|
||||
TARGET ${TARGET} POST_BUILD
|
||||
BYPRODUCTS "${ctest_tests_file}"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-D "TEST_TARGET=${TARGET}"
|
||||
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
|
||||
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
|
||||
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
|
||||
-D "TEST_SPEC=${_TEST_SPEC}"
|
||||
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
|
||||
-D "TEST_PROPERTIES=${_PROPERTIES}"
|
||||
-D "TEST_ADD_LABELS=${_ADD_LABELS}"
|
||||
-D "TEST_PREFIX=${_TEST_PREFIX}"
|
||||
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
|
||||
-D "TEST_LIST=${_TEST_LIST}"
|
||||
-D "TEST_JUNIT_OUTPUT_DIR=${_JUNIT_OUTPUT_DIR}"
|
||||
-D "CTEST_FILE=${ctest_tests_file}"
|
||||
-P "${_DOCTEST_DISCOVER_TESTS_SCRIPT}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
file(WRITE "${ctest_include_file}"
|
||||
"if(EXISTS \"${ctest_tests_file}\")\n"
|
||||
" include(\"${ctest_tests_file}\")\n"
|
||||
"else()\n"
|
||||
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
|
||||
"endif()\n"
|
||||
)
|
||||
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.10)
|
||||
# Add discovered tests to directory TEST_INCLUDE_FILES
|
||||
set_property(DIRECTORY
|
||||
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
|
||||
)
|
||||
else()
|
||||
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
|
||||
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
|
||||
if(NOT ${test_include_file_set})
|
||||
set_property(DIRECTORY
|
||||
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"Cannot set more than one TEST_INCLUDE_FILE"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
###############################################################################
|
||||
|
||||
set(_DOCTEST_DISCOVER_TESTS_SCRIPT
|
||||
${CMAKE_CURRENT_LIST_DIR}/doctestAddTests.cmake
|
||||
)
|
|
@ -1,120 +0,0 @@
|
|||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
set(prefix "${TEST_PREFIX}")
|
||||
set(suffix "${TEST_SUFFIX}")
|
||||
set(spec ${TEST_SPEC})
|
||||
set(extra_args ${TEST_EXTRA_ARGS})
|
||||
set(properties ${TEST_PROPERTIES})
|
||||
set(add_labels ${TEST_ADD_LABELS})
|
||||
set(junit_output_dir "${TEST_JUNIT_OUTPUT_DIR}")
|
||||
set(script)
|
||||
set(suite)
|
||||
set(tests)
|
||||
|
||||
function(add_command NAME)
|
||||
set(_args "")
|
||||
foreach(_arg ${ARGN})
|
||||
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
|
||||
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
|
||||
else()
|
||||
set(_args "${_args} ${_arg}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Run test executable to get list of available tests
|
||||
if(NOT EXISTS "${TEST_EXECUTABLE}")
|
||||
message(FATAL_ERROR
|
||||
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
|
||||
)
|
||||
endif()
|
||||
|
||||
if("${spec}" MATCHES .)
|
||||
set(spec "--test-case=${spec}")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-cases
|
||||
OUTPUT_VARIABLE output
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
)
|
||||
if(NOT ${result} EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||
" Result: ${result}\n"
|
||||
" Output: ${output}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
string(REPLACE "\n" ";" output "${output}")
|
||||
|
||||
# Parse output
|
||||
foreach(line ${output})
|
||||
if("${line}" STREQUAL "===============================================================================" OR "${line}" MATCHES [==[^\[doctest\] ]==])
|
||||
continue()
|
||||
endif()
|
||||
set(test ${line})
|
||||
set(labels "")
|
||||
if(${add_labels})
|
||||
# get test suite that test belongs to
|
||||
execute_process(
|
||||
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" --test-case=${test} --list-test-suites
|
||||
OUTPUT_VARIABLE labeloutput
|
||||
RESULT_VARIABLE labelresult
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
)
|
||||
if(NOT ${labelresult} EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||
" Result: ${labelresult}\n"
|
||||
" Output: ${labeloutput}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
string(REPLACE "\n" ";" labeloutput "${labeloutput}")
|
||||
foreach(labelline ${labeloutput})
|
||||
if("${labelline}" STREQUAL "===============================================================================" OR "${labelline}" MATCHES [==[^\[doctest\] ]==])
|
||||
continue()
|
||||
endif()
|
||||
list(APPEND labels ${labelline})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(NOT "${junit_output_dir}" STREQUAL "")
|
||||
# turn testname into a valid filename by replacing all special characters with "-"
|
||||
string(REGEX REPLACE "[/\\:\"|<>]" "-" test_filename "${test}")
|
||||
set(TEST_JUNIT_OUTPUT_PARAM "--reporters=junit" "--out=${junit_output_dir}/${prefix}${test_filename}${suffix}.xml")
|
||||
else()
|
||||
unset(TEST_JUNIT_OUTPUT_PARAM)
|
||||
endif()
|
||||
# use escape commas to handle properly test cases with commas inside the name
|
||||
string(REPLACE "," "\\," test_name ${test})
|
||||
# ...and add to script
|
||||
add_command(add_test
|
||||
"${prefix}${test}${suffix}"
|
||||
${TEST_EXECUTOR}
|
||||
"${TEST_EXECUTABLE}"
|
||||
"--test-case=${test_name}"
|
||||
"${TEST_JUNIT_OUTPUT_PARAM}"
|
||||
${extra_args}
|
||||
)
|
||||
add_command(set_tests_properties
|
||||
"${prefix}${test}${suffix}"
|
||||
PROPERTIES
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
${properties}
|
||||
LABELS ${labels}
|
||||
)
|
||||
unset(labels)
|
||||
list(APPEND tests "${prefix}${test}${suffix}")
|
||||
endforeach()
|
||||
|
||||
# Create a list of all discovered tests, which users may use to e.g. set
|
||||
# properties on the tests
|
||||
add_command(set ${TEST_LIST} ${tests})
|
||||
|
||||
# Write CTest script
|
||||
file(WRITE "${CTEST_FILE}" "${script}")
|
|
@ -1,55 +0,0 @@
|
|||
find_package(Doxygen REQUIRED)
|
||||
|
||||
# Find all the public headers
|
||||
get_target_property(QUILL_PUBLIC_HEADER_DIR quill INTERFACE_INCLUDE_DIRECTORIES)
|
||||
file(GLOB_RECURSE QUILL_PUBLIC_HEADERS ${QUILL_PUBLIC_HEADER_DIR}/*.h)
|
||||
|
||||
set(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}/quill)
|
||||
set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen)
|
||||
set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/xml/index.xml)
|
||||
set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
|
||||
set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
|
||||
|
||||
#Replace variables inside @@ with the current values
|
||||
configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)
|
||||
|
||||
file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) #Doxygen won't create this for us
|
||||
add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE}
|
||||
DEPENDS ${QUILL_PUBLIC_HEADERS}
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT}
|
||||
MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN}
|
||||
COMMENT "Generating docs")
|
||||
|
||||
add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE})
|
||||
|
||||
find_package(Sphinx REQUIRED)
|
||||
|
||||
set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx)
|
||||
set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html)
|
||||
|
||||
# Only regenerate Sphinx when:
|
||||
# - Doxygen has rerun
|
||||
# - Our doc files have been updated
|
||||
# - The Sphinx config has been updated
|
||||
add_custom_command(OUTPUT ${SPHINX_INDEX_FILE}
|
||||
COMMAND
|
||||
${SPHINX_EXECUTABLE} -b html
|
||||
# Tell Breathe where to find the Doxygen output
|
||||
-Dbreathe_projects.Quill=${DOXYGEN_OUTPUT_DIR}/xml
|
||||
${SPHINX_SOURCE} ${SPHINX_BUILD}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEPENDS
|
||||
# Other docs files you want to track should go here (or in some variable)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/index.rst
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/users-api.rst
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/install.rst
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/usage.rst
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tutorial.rst
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/features.rst
|
||||
${DOXYGEN_INDEX_FILE}
|
||||
MAIN_DEPENDENCY ${SPHINX_SOURCE}/conf.py
|
||||
COMMENT "Generating documentation with Sphinx")
|
||||
|
||||
# Nice named target so we can run the job easily
|
||||
add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE})
|
File diff suppressed because it is too large
Load diff
|
@ -1,53 +0,0 @@
|
|||
import subprocess, os
|
||||
|
||||
|
||||
def configureDoxyfile(input_dir, output_dir):
|
||||
with open('Doxyfile.in', 'r') as file:
|
||||
filedata = file.read()
|
||||
|
||||
filedata = filedata.replace('@DOXYGEN_INPUT_DIR@', input_dir)
|
||||
filedata = filedata.replace('@DOXYGEN_OUTPUT_DIR@', output_dir)
|
||||
|
||||
with open('Doxyfile', 'w') as file:
|
||||
file.write(filedata)
|
||||
|
||||
|
||||
# Check if we're running on Read the Docs' servers
|
||||
read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
|
||||
breathe_projects = {}
|
||||
|
||||
if read_the_docs_build:
|
||||
input_dir = '../quill'
|
||||
output_dir = 'build'
|
||||
configureDoxyfile(input_dir, output_dir)
|
||||
subprocess.call('doxygen', shell=True)
|
||||
breathe_projects['Quill'] = output_dir + '/xml'
|
||||
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'Quill'
|
||||
copyright = '2024, Odysseas Georgoudis'
|
||||
author = 'Odysseas Georgoudis'
|
||||
release = 'v4.0.0'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = ["breathe", "sphinx.ext.autosectionlabel"]
|
||||
breathe_default_project = "Quill"
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_static_path = ['_static']
|
|
@ -1,139 +0,0 @@
|
|||
<mxfile host="app.diagrams.net" modified="2024-05-16T00:07:38.477Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0" etag="VW8ihnyupRLc_PgVfbaT" version="22.1.21" type="device">
|
||||
<diagram name="Page-1" id="kLzpoFuRwS6fnHKDuZMH">
|
||||
<mxGraphModel dx="2066" dy="1145" grid="1" gridSize="12" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1000" pageHeight="1200" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-1" value="hot thread" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="258" y="66" width="100" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-2" value="hot thread" style="ellipse;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="653" y="66" width="100" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-5" value="" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="498" y="91" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-6" value="" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="576.5" y="91" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-7" value="" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="418" y="91" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-10" value="thread local context<br><br><br><br><br><br><br><br><br><br><br>" style="verticalLabelPosition=middle;verticalAlign=middle;html=1;shape=mxgraph.basic.rect;fillColor2=none;strokeWidth=1;size=26.67;indent=5;labelPosition=center;align=center;" parent="1" vertex="1">
|
||||
<mxGeometry x="108" y="246" width="280" height="190" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-14" value="SPSC queue<br>(bounded or unbounded)" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.donut;dx=25;fillColor=#fff2cc;strokeColor=#000000;container=0;perimeterSpacing=1;labelPosition=center;align=center;whiteSpace=wrap;" parent="1" vertex="1">
|
||||
<mxGeometry x="268" y="291" width="90" height="90" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-15" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;dashed=1;dashPattern=12 12;labelPosition=left;verticalLabelPosition=top;align=right;verticalAlign=bottom;entryX=0.46;entryY=0.026;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-1" target="BmINEKKr7EmUC2mtGokD-14" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="458" y="306" as="sourcePoint" />
|
||||
<mxPoint x="478" y="266" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-16" value="TransitEvent buffer (unbounded)" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.donut;dx=25;fillColor=#e1d5e7;strokeColor=#000000;container=0;shadow=0;perimeterSpacing=1;whiteSpace=wrap;" parent="1" vertex="1">
|
||||
<mxGeometry x="128" y="291" width="90" height="90" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-19" value="1. A hot thread pushes a header and a copy of (args...) to a queue" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="403" y="176" width="210" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-20" value="Backend logging thread<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>" style="ellipse;whiteSpace=wrap;html=1;shadow=0;sketch=0;strokeColor=#000000;" parent="1" vertex="1">
|
||||
<mxGeometry x="268" y="694" width="520" height="440" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-24" value="thread local context<br><br><br><br><br><br><br><br><br><br><br>" style="verticalLabelPosition=middle;verticalAlign=middle;html=1;shape=mxgraph.basic.rect;fillColor2=none;strokeWidth=1;size=26.67;indent=5;labelPosition=center;align=center;" parent="1" vertex="1">
|
||||
<mxGeometry x="628" y="246" width="280" height="190" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-25" value="SPSC queue<br>(bounded or unbounded)" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.donut;dx=25;fillColor=#fff2cc;strokeColor=#000000;container=0;perimeterSpacing=1;labelPosition=center;align=center;whiteSpace=wrap;" parent="1" vertex="1">
|
||||
<mxGeometry x="658" y="296" width="90" height="90" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-26" value="TransitEvent buffer (unbounded)" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.donut;dx=25;fillColor=#e1d5e7;strokeColor=#000000;container=0;shadow=0;perimeterSpacing=1;whiteSpace=wrap;" parent="1" vertex="1">
|
||||
<mxGeometry x="788" y="296" width="90" height="90" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-27" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;dashed=1;dashPattern=12 12;labelPosition=left;verticalLabelPosition=top;align=right;verticalAlign=bottom;entryX=0.511;entryY=0.001;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-2" target="BmINEKKr7EmUC2mtGokD-25" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="388" y="171" as="sourcePoint" />
|
||||
<mxPoint x="340.0857142857144" y="300" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-29" value="" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="498" y="316" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-31" value="" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="576.5" y="316" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-32" value="" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="418" y="316" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-34" value="2. The backend thread pops all messages from all queues, decodes them and stores&nbsp;<br>them in the TransitEvent buffer" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="418" y="456" width="194" height="84" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-61" value="" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;sketch=0;strokeColor=#000000;" parent="1" vertex="1">
|
||||
<mxGeometry x="473" y="800" width="110" height="160" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-43" value="3.TransitEvents are processed after all SPSC queues are empty or when a max limit is reached. The TransitEvent with the min timestamp is passed to the all the registered Sinks." style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="108" y="648" width="240" height="90" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-46" value="" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;sketch=0;strokeColor=#000000;" parent="1" vertex="1">
|
||||
<mxGeometry x="508" y="910" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-45" value="" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;sketch=0;strokeColor=#000000;" parent="1" vertex="1">
|
||||
<mxGeometry x="508" y="826" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-48" value="" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="518" y="870" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-49" value="" style="shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="518" y="890" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-52" value="Sinks" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="498" y="800" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-53" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;dashPattern=12 12;strokeColor=default;exitX=0.822;exitY=0.87;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.419;entryY=0.008;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-14" target="BmINEKKr7EmUC2mtGokD-20" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="458" y="516" as="sourcePoint" />
|
||||
<mxPoint x="538" y="666" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-54" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;dashPattern=12 12;strokeColor=default;exitX=0.156;exitY=0.862;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.571;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-25" target="BmINEKKr7EmUC2mtGokD-20" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="321.98799999999983" y="392" as="sourcePoint" />
|
||||
<mxPoint x="598" y="666" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-56" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;dashPattern=12 12;strokeColor=default;entryX=0.909;entryY=0.826;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.356;exitY=0.006;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-20" target="BmINEKKr7EmUC2mtGokD-16" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="518" y="666" as="sourcePoint" />
|
||||
<mxPoint x="508" y="466" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-57" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;dashPattern=12 12;strokeColor=default;entryX=0.17;entryY=0.928;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.637;exitY=0.003;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-20" target="BmINEKKr7EmUC2mtGokD-26" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="568" y="656" as="sourcePoint" />
|
||||
<mxPoint x="220.62800000000016" y="375.99199999999996" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-58" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;dashPattern=12 12;strokeColor=default;exitX=0.442;exitY=1.051;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-16" target="BmINEKKr7EmUC2mtGokD-61" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="352.6239999999998" y="380.03999999999996" as="sourcePoint" />
|
||||
<mxPoint x="488" y="826" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-59" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;dashPattern=12 12;strokeColor=default;exitX=0.812;exitY=0.899;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.827;entryY=0.007;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-26" target="BmINEKKr7EmUC2mtGokD-61" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="177.66400000000021" y="396.692" as="sourcePoint" />
|
||||
<mxPoint x="508" y="826" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-62" value="" style="endArrow=classic;html=1;rounded=0;dashed=1;dashPattern=12 12;strokeColor=default;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="BmINEKKr7EmUC2mtGokD-61" target="BmINEKKr7EmUC2mtGokD-65" edge="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="458" y="766" as="sourcePoint" />
|
||||
<mxPoint x="502" y="1036" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="BmINEKKr7EmUC2mtGokD-65" value="4. Sink output&nbsp;" style="rounded=1;whiteSpace=wrap;html=1;shadow=0;sketch=0;strokeColor=#000000;" parent="1" vertex="1">
|
||||
<mxGeometry x="432.5" y="1006" width="191" height="90" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
Binary file not shown.
Before Width: | Height: | Size: 89 KiB |
|
@ -1,86 +0,0 @@
|
|||
.. _features:
|
||||
|
||||
##############################################################################
|
||||
Features
|
||||
##############################################################################
|
||||
|
||||
Thread Safety
|
||||
=============
|
||||
|
||||
All components and API offered to the user is intended to be thread-safe without any special work needing to be done.
|
||||
|
||||
:cpp:class:`quill::LoggerImpl` are thread safe by default. The same instance can be used to log by any thread.
|
||||
Any thread can safely modify the active log level of the logger.
|
||||
|
||||
Logging types
|
||||
=====================================================
|
||||
|
||||
For primitive types, std::string, and std::string_view, the library will perform a deep copy, and all formatting will occur asynchronously in the backend thread.
|
||||
However, for standard library types or user-defined types, they need to be converted to a string beforehand before being passed to the logger.
|
||||
|
||||
Guaranteed logging
|
||||
=======================
|
||||
|
||||
Quill employs a thread-local single-producer-single-consumer queue to relay logs to the backend thread,
|
||||
ensuring that log messages are never dropped.
|
||||
Initially, an unbounded queue with a small size is used for performance optimization.
|
||||
However, if the queue reaches full capacity, a new queue will be allocated, incurring a slight performance penalty for the frontend.
|
||||
|
||||
The default unbounded queue can expand up to a size of 2GB. Should this limit be reached, the frontend thread will block.
|
||||
However, it's possible to alter the queue type within the FrontendOptions.
|
||||
|
||||
Customising the queue size and type
|
||||
--------------------------------------
|
||||
|
||||
The queue size and type is configurable in runtime by creating a custom FrontendOptions class.
|
||||
|
||||
Flush
|
||||
===============================
|
||||
|
||||
You can explicitly instruct the frontend thread to wait until the log is flushed.
|
||||
|
||||
.. note:: The thread that calls :cpp:func:`flush_log` will **block** until every message up to that point is flushed.
|
||||
|
||||
.. doxygenfunction:: flush_log
|
||||
|
||||
Application Crash Policy
|
||||
========================
|
||||
|
||||
When the program is terminated gracefully, quill will go through its destructor where all messages are guaranteed to be logged.
|
||||
|
||||
However, if the applications crashes, log messages can be lost.
|
||||
|
||||
To avoid losing messages when the application crashes due to a signal interrupt the user must setup it’s own signal
|
||||
handler and call :cpp:func:`flush_log` inside the signal handler.
|
||||
|
||||
There is a built-in signal handler that offers this crash-safe behaviour and can be enabled in :cpp:func:`start_with_signal_handler<quill::FrontendOptions>`
|
||||
|
||||
Log Messages Timestamp Order
|
||||
==============================
|
||||
|
||||
Quill creates a single worker backend thread which orders the messages in all queues by timestamp before printing them to the log file.
|
||||
|
||||
Number of Backend Threads
|
||||
============================
|
||||
|
||||
Quill focus is on low latency and not high throughput. Therefore, there is only one backend thread that will process all logs.
|
||||
|
||||
Latency of the first log message
|
||||
====================================
|
||||
|
||||
A queue and an internal buffer will be allocated on the first log message of each thread. If the latency of the first
|
||||
log message is important it is recommended to call :cpp:func:`quill::preallocate`
|
||||
|
||||
.. doxygenfunction:: preallocate()
|
||||
|
||||
Configuration
|
||||
======================
|
||||
|
||||
Quill offers a few customization options, which are also well-documented.
|
||||
|
||||
This customization can be applied to either the frontend or the backend.
|
||||
|
||||
Frontend configuration occurs at compile time, thus requiring a custom FrontendOptions class to be provided
|
||||
:cpp:class:`quill::FrontendOptions`
|
||||
|
||||
For customizing the backend, refer to :cpp:class:`quill::BackendOptions`
|
|
@ -1,26 +0,0 @@
|
|||
.. _index:
|
||||
|
||||
##############################################################################
|
||||
Quill - Asynchronous Low Latency Logging Library for C++
|
||||
##############################################################################
|
||||
|
||||
Quill is an open source, cross platform C++17 logging library designed for latency sensitive applications.
|
||||
|
||||
`Github <http://github.com/odygrd/quill>`_
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 2
|
||||
:caption: General Information
|
||||
|
||||
install
|
||||
tutorial
|
||||
features
|
||||
usage
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 2
|
||||
:caption: API
|
||||
|
||||
users-api
|
|
@ -1,82 +0,0 @@
|
|||
.. _install:
|
||||
|
||||
##############################################################################
|
||||
Install
|
||||
##############################################################################
|
||||
|
||||
Package Managers
|
||||
=================
|
||||
|
||||
====================== ======================= ===================
|
||||
Homebrew vcpkg Conan
|
||||
====================== ======================= ===================
|
||||
``brew install quill`` ``vcpkg install quill`` ``quill/[>=1.2.3]``
|
||||
====================== ======================= ===================
|
||||
|
||||
CMake-Integration
|
||||
=================
|
||||
|
||||
External
|
||||
--------
|
||||
|
||||
Building and Installing Quill as Static Library
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: bash
|
||||
|
||||
git clone https://github.com/odygrd/quill.git
|
||||
mkdir cmake_build
|
||||
cd cmake_build
|
||||
cmake ..
|
||||
make install
|
||||
|
||||
Note: To install in custom directory invoke cmake with ``-DCMAKE_INSTALL_PREFIX=/quill/install-dir/``
|
||||
|
||||
Then use the library from a CMake project, you can locate it directly with ``find_package()``
|
||||
|
||||
Directory Structure
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
my_project/
|
||||
├── CMakeLists.txt
|
||||
├── main.cpp
|
||||
|
||||
CMakeLists.txt
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: cmake
|
||||
|
||||
# Set only if needed - quill was installed under a custom non-standard directory
|
||||
set(CMAKE_PREFIX_PATH /test_quill/usr/local/)
|
||||
|
||||
find_package(quill REQUIRED)
|
||||
|
||||
# Linking your project against quill
|
||||
add_executable(example main.cpp)
|
||||
target_link_libraries(example PRIVATE quill::quill)
|
||||
|
||||
Embedded
|
||||
--------
|
||||
|
||||
To embed the library directly, copy the source to your project and call ``add_subdirectory()`` in your ``CMakeLists.txt`` file
|
||||
|
||||
Directory Structure
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
my_project/
|
||||
├── quill/ (source folder)
|
||||
├── CMakeLists.txt
|
||||
├── main.cpp
|
||||
|
||||
CMakeLists.txt
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: cmake
|
||||
|
||||
add_subdirectory(quill)
|
||||
add_executable(my_project main.cpp)
|
||||
target_link_libraries(my_project PRIVATE quill::quill)
|
|
@ -1,35 +0,0 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
Binary file not shown.
Before Width: | Height: | Size: 128 KiB |
Binary file not shown.
Before Width: | Height: | Size: 202 KiB |
Binary file not shown.
Before Width: | Height: | Size: 145 KiB |
Binary file not shown.
Before Width: | Height: | Size: 154 KiB |
Binary file not shown.
Before Width: | Height: | Size: 176 KiB |
|
@ -1,2 +0,0 @@
|
|||
breathe
|
||||
sphinx_rtd_theme
|
|
@ -1,525 +0,0 @@
|
|||
.. _tutorial:
|
||||
|
||||
##############################################################################
|
||||
Tutorial
|
||||
##############################################################################
|
||||
|
||||
Basic Example
|
||||
=============
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
LOG_INFO(logger, "This is a log info example {}", 123);
|
||||
}
|
||||
|
||||
In the above example a logger to ``stdout`` is created with it's name set to “root”.
|
||||
|
||||
Each :cpp:class:`quill::LoggerImpl` contains a :cpp:class:`quill::PatternFormatter` object which is responsible for the
|
||||
formatting of the message.
|
||||
|
||||
Moreover, each :cpp:class:`quill::LoggerImpl` contains single or multiple :cpp:class:`quill::Sink` objects. The sink
|
||||
objects actually deliver the log message to their output source.
|
||||
|
||||
A single backend thread is checking for new log messages periodically.
|
||||
|
||||
Starting the backend thread is the responsibility of the user. The backend thread will automatically stop at the end
|
||||
of `main` printing every message, as long as the application is terminated gracefully.
|
||||
|
||||
Use of macros is unavoidable in order to achieve better runtime performance. The static information of a log
|
||||
(such as format string, log level, location) is created in compile time. It is passed along with the type of each
|
||||
argument to a decoding function. A template instantiation per log statement is created.
|
||||
|
||||
Logging Macros
|
||||
================
|
||||
|
||||
The following macros are provided for logging:
|
||||
|
||||
.. c:macro:: LOG_TRACE_L3(logger, log_message_format, args)
|
||||
.. c:macro:: LOG_TRACE_L2(logger, log_message_format, args)
|
||||
.. c:macro:: LOG_TRACE_L1(logger, log_message_format, args)
|
||||
.. c:macro:: LOG_DEBUG(logger, log_message_format, args)
|
||||
.. c:macro:: LOG_INFO(logger, log_message_format, args)
|
||||
.. c:macro:: LOG_WARNING(logger, log_message_format, args)
|
||||
.. c:macro:: LOG_ERROR(logger, log_message_format, args)
|
||||
.. c:macro:: LOG_CRITICAL(logger, log_message_format, args)
|
||||
.. c:macro:: LOG_BACKTRACE(logger, log_message_format, args)
|
||||
|
||||
Sinks
|
||||
========
|
||||
|
||||
Sinks are the objects responsible for writing logs to their respective targets.
|
||||
|
||||
A :cpp:class:`quill::Sink` object serves as the base class for various sink-derived classes.
|
||||
|
||||
Each sink handles outputting logs to a single target, such as a file, console, or database.
|
||||
|
||||
Upon creation, a sink object is registered and owned by a central manager object, the quill::detail::SinkManager.
|
||||
|
||||
For files, one sink is created per filename, and the file is opened once. If a sink is requested that refers to an already opened file, the existing Sink object is returned. Users can create multiple stdout or stderr handles by providing a unique ID per handle.
|
||||
|
||||
When creating a logger, one or more sinks for that logger can be specified. Sinks can only be registered during the logger creation.
|
||||
|
||||
Sharing sinks between loggers
|
||||
==================================
|
||||
|
||||
It is possible to share the same sink object between multiple logger objects.
|
||||
For example when all logger objects are writing to the same file. The following code is also thread-safe.
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
auto file_sink = Frontend::create_or_get_sink<FileSink>(
|
||||
filename,
|
||||
[]()
|
||||
{
|
||||
FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
return cfg;
|
||||
}(),
|
||||
FileEventNotifier{});
|
||||
|
||||
quill::Logger* logger_a = Frontend::create_or_get_logger("logger_a", file_sink);
|
||||
quill::Logger* logger_b = Frontend::create_or_get_logger("logger_b", file_sink);
|
||||
|
||||
Sink Types
|
||||
==================================
|
||||
|
||||
ConsoleSink
|
||||
--------------
|
||||
|
||||
The ``ConsoleSink`` class sends logging output to streams ``stdout`` or ``stderr``.
|
||||
Printing colour codes to terminal or windows console is also supported.
|
||||
|
||||
FileHandler
|
||||
-----------
|
||||
|
||||
Logging to file
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Frontend
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"trivial_logging.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
quill::Logger* logger =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
LOG_INFO(logger, "log something {}", 123);
|
||||
}
|
||||
|
||||
RotatingFileSink
|
||||
-------------------
|
||||
|
||||
Rotating log by size or time
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Frontend
|
||||
auto rotating_file_sink = quill::Frontend::create_or_get_sink<quill::RotatingFileSink>(
|
||||
"rotating_file.log",
|
||||
[]()
|
||||
{
|
||||
// See RotatingFileSinkConfig for more options
|
||||
quill::RotatingFileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
cfg.set_rotation_time_daily("18:30");
|
||||
cfg.set_rotation_max_file_size(1024);
|
||||
return cfg;
|
||||
}());
|
||||
|
||||
quill::Logger* logger =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(rotating_file_sink));
|
||||
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
LOG_INFO(logger, "Hello from rotating logger, index is {}", i);
|
||||
}
|
||||
|
||||
JsonFileSink
|
||||
-----------------------
|
||||
|
||||
Json log
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Frontend
|
||||
|
||||
// Create a json file for output
|
||||
auto json_sink = quill::Frontend::create_or_get_sink<quill::JsonFileSink>(
|
||||
"json_sink_logging.log",
|
||||
[]()
|
||||
{
|
||||
quill::JsonFileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
// When using the JsonFileSink, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
|
||||
quill::Logger* json_logger = quill::Frontend::create_or_get_logger(
|
||||
"json_logger", std::move(json_sink), "", "%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
LOG_INFO(json_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i);
|
||||
}
|
||||
|
||||
// It is also possible to create a logger than logs to both the json file and stdout
|
||||
// with the appropriate format
|
||||
auto json_sink_2 = quill::Frontend::get_sink("json_sink_logging.log");
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console_sink_id_1");
|
||||
|
||||
// We set a custom format pattern here to also include the named_args
|
||||
quill::Logger* hybrid_logger = quill::Frontend::create_or_get_logger(
|
||||
"hybrid_logger", {std::move(json_sink_2), std::move(console_sink)},
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<20) "
|
||||
"%(message) [%(named_args)]");
|
||||
|
||||
for (int i = 2; i < 4; ++i)
|
||||
{
|
||||
LOG_INFO(hybrid_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i);
|
||||
}
|
||||
|
||||
Filters
|
||||
==================================
|
||||
|
||||
A Filter class that can be used for filtering log records in the backend working thread.
|
||||
|
||||
This is a simple way to ensure that a logger or sink will only output desired log messages.
|
||||
|
||||
One or several :cpp:class:`quill::Filter` can be added to a :cpp:class:`quill::Sink` instance using the
|
||||
:cpp:func:`void add_filter(std::unique_ptr<Filter> filter)`
|
||||
|
||||
The sink stores all added filters in a vector. The final log message is logged if all filters of the sink return `true`.
|
||||
|
||||
Filtering per sink
|
||||
-----------------------
|
||||
|
||||
The below example logs all WARNING and higher log level messages to console and all INFO and lower level messages to a file.
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
// Filter class for our file sink
|
||||
class FileFilter : public quill::Filter
|
||||
{
|
||||
public:
|
||||
FileFilter() : quill::Filter("FileFilter"){};
|
||||
|
||||
QUILL_NODISCARD bool filter(quill::MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id,
|
||||
std::string_view thread_name, std::string_view logger_name,
|
||||
quill::LogLevel log_level, std::string_view log_message) noexcept override
|
||||
{
|
||||
if (log_metadata->log_level() < quill::LogLevel::Warning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Filter for the stdout sink
|
||||
class StdoutFilter : public quill::Filter
|
||||
{
|
||||
public:
|
||||
StdoutFilter() : quill::Filter("StdoutFilter"){};
|
||||
|
||||
QUILL_NODISCARD bool filter(quill::MacroMetadata const* log_metadata, uint64_t log_timestamp, std::string_view thread_id,
|
||||
std::string_view thread_name, std::string_view logger_name,
|
||||
quill::LogLevel log_level, std::string_view log_message) noexcept override
|
||||
{
|
||||
if (log_metadata->log_level() >= quill::LogLevel::Warning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the logging backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Get a sink to the file
|
||||
// The first time this function is called a file sink is created for this filename.
|
||||
// Calling the function with the same filename will return the existing sink
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"example_filters.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
// Create and add the filter to our sink
|
||||
file_sink->add_filter(std::make_unique<FileFilter>());
|
||||
|
||||
// Also create an stdout sink
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
|
||||
// Create and add the filter to our sink
|
||||
console_sink->add_filter(std::make_unique<StdoutFilter>());
|
||||
|
||||
// Create a logger using this sink
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("logger", {std::move(file_sink), std::move(console_sink)});
|
||||
|
||||
LOG_INFO(logger, "test");
|
||||
LOG_ERROR(logger, "test");
|
||||
}
|
||||
|
||||
Formatters
|
||||
==================================
|
||||
The :cpp:class:`quill::PatternFormatter` specifies the layout of log records in the final output.
|
||||
|
||||
Each :cpp:class:`quill::LoggerImpl` object owns a PatternFormatter object.
|
||||
This means that each Logger can be customised to output in a different format.
|
||||
|
||||
Customising the format output only be done during the creation of the logger.
|
||||
|
||||
If no custom format is set each newly created Sink uses the same formatting as the default logger.
|
||||
|
||||
The format output can be customised by providing a string of certain
|
||||
attributes.
|
||||
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| Name | Format | Description |
|
||||
+=========================+==========================+========================================+
|
||||
| time | %(time) | Human-readable time when the LogRecord |
|
||||
| | | was created. By default this is of the |
|
||||
| | | form '2003-07-08 16:49:45.896' (the |
|
||||
| | | numbers after the period are the |
|
||||
| | | millisecond portion of the time). |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| file_name | %(file_name) | Filename portion of pathname. |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| full_path | %(full_path) | Full path of the source file where the |
|
||||
| | | logging call was issued. |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| caller_function | %(caller_function) | Name of function containing the |
|
||||
| | | logging call. |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| log_level | %(log_level) | Text logging level for the message |
|
||||
| | | (‘TRACEL3’, ‘TRACEL2’, ‘TRACEL1’, |
|
||||
| | | ‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, |
|
||||
| | | ‘CRITICAL’, ‘BACKTRACE’). |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| log_level_id | %(log_level_id) | Abbreviated level name (‘T3’, ‘T2’, |
|
||||
| | | ‘T1’, ‘D’, ‘I’, ‘W’, ‘E’, ‘C’, ‘BT’). |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| line_number | %(line_number) | Source line number where the logging |
|
||||
| | | call was issued (if available). |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| logger | %(logger) | Name of the logger used to log the |
|
||||
| | | call. |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| message | %(message) | The logged message, computed as msg % |
|
||||
| | | args. This is set when Formatter. |
|
||||
| | | format() is invoked. |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| thread_id | %(thread_id) | Thread ID (if available). |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| thread_name | %(thread_name) | Thread name if set. The name of the |
|
||||
| | | thread must be set prior to issuing |
|
||||
| | | any log statement on that thread. |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| process_id | %(process_id) | Process ID |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| source_location | %(source_location) | Full source file path and line number |
|
||||
| | | as a single string |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| short_source_location | %(short_source_location) | Full source file path and line |
|
||||
| | | number as a single string |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| tags | %(tags) | Additional custom tags appended to the |
|
||||
| | | message when _WITH_TAGS macros are |
|
||||
| | | used. |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
| named_args | %(named_args) | Key-value pairs appended to the |
|
||||
| | | message. Only applicable with |
|
||||
| | | for a named args log format; |
|
||||
| | | remains empty otherwise. |
|
||||
+-------------------------+--------------------------+----------------------------------------+
|
||||
|
||||
|
||||
Customising the timestamp
|
||||
-----------------------------
|
||||
|
||||
The timestamp is customisable by :
|
||||
|
||||
- Format. Same format specifiers as ``strftime(...)`` format without the additional ``.Qms`` ``.Qus`` ``.Qns`` arguments.
|
||||
- Local timezone or GMT timezone. Local timezone is used by default.
|
||||
- Fractional second precision. Using the additional fractional second specifiers in the timestamp format string.
|
||||
|
||||
========= ============
|
||||
Specifier Description
|
||||
========= ============
|
||||
%Qms Milliseconds
|
||||
%Qus Microseconds
|
||||
%Qns Nanoseconds
|
||||
========= ============
|
||||
|
||||
By default ``"%H:%M:%S.%Qns"`` is used.
|
||||
|
||||
.. note:: MinGW does not support all ``strftime(...)`` format specifiers and you might get a ``bad alloc`` if the format specifier is not supported
|
||||
|
||||
Setting a custom format for logging to stdout
|
||||
----------------------------------------------------------
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
quill::Logger* logger =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) "
|
||||
"LOG_%(log_level:<9) %(logger:<12) %(message)",
|
||||
"%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
|
||||
Logger
|
||||
-----------------------------
|
||||
|
||||
Logger instances can be created by the user with the desired name, sinks and formatter.
|
||||
The logger object are never instantiated directly. Instead they first have to get created
|
||||
|
||||
:cpp:func:`Frontend::create_or_get_logger(std::string const& logger_name, std::shared_ptr<Sink> sink, std::string const& format_pattern = "%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) %(message)", std::string const& time_pattern = "%H:%M:%S.%Qns", Timezone timestamp_timezone = Timezone::LocalTime, ClockSourceType clock_source = ClockSourceType::Tsc, UserClockSource* user_clock = nullptr)`
|
||||
|
||||
:cpp:func:`Frontend::create_or_get_logger(std::string const& logger_name, std::initializer_list<std::shared_ptr<Sink>> sinks, std::string const& format_pattern = "%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) %(message)", std::string const& time_pattern = "%H:%M:%S.%Qns", Timezone timestamp_timezone = Timezone::LocalTime, ClockSourceType clock_source = ClockSourceType::Tsc, UserClockSource* user_clock = nullptr)`
|
||||
|
||||
Logger access
|
||||
-----------------------------
|
||||
|
||||
:cpp:func:`Frontend::get_logger(std::string const& name)`
|
||||
|
||||
Logger creation
|
||||
-----------------------------
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
LOG_INFO(logger, "Hello from {}", "library foo");
|
||||
|
||||
Avoiding the use of Logger objects
|
||||
---------------------------------------
|
||||
For some applications the use of the single root logger might be enough. In that case passing the logger everytime
|
||||
to the macro becomes inconvenient. The solution is to store the created Logger as a static variable and create your
|
||||
own macros. See `example <https://github.com/odygrd/quill/blob/master/examples/recommended_usage/quill_wrapper/include/quill_wrapper/overwrite_macros.h>`_
|
||||
|
||||
Backtrace Logging
|
||||
====================
|
||||
|
||||
Backtrace logging enables log messages to be stored in a ring buffer and either
|
||||
|
||||
- displayed later on demand or
|
||||
- when a high severity log message is logged
|
||||
|
||||
Backtrace logging needs to be enabled first on the instance of :cpp:class:`quill::LoggerImpl`
|
||||
|
||||
.. doxygenfunction:: init_backtrace
|
||||
.. doxygenfunction:: flush_backtrace
|
||||
|
||||
.. note:: Backtrace log messages store the original timestamp of the message. Since they are kept and flushed later the
|
||||
timestamp in the log file will be out of order.
|
||||
|
||||
.. note:: Backtrace log messages are still pushed to the SPSC queue from the frontend to the backend.
|
||||
|
||||
Store messages in the ring buffer and display them when ``LOG_ERROR`` is logged
|
||||
--------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
// a LOG_ERROR(...) or higher severity log message occurs via this logger.
|
||||
// Enable the backtrace with a max ring buffer size of 2 messages which will get flushed when
|
||||
// Backtrace has to be enabled only once in the beginning before calling LOG_BACKTRACE(...) for the first time.
|
||||
logger->init_backtrace(2, quill::LogLevel::Error);
|
||||
|
||||
LOG_INFO(logger, "BEFORE backtrace Example {}", 1);
|
||||
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 1);
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 2);
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 3);
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 4);
|
||||
|
||||
// Backtrace is not flushed yet as we requested to flush on errors
|
||||
LOG_INFO(logger, "AFTER backtrace Example {}", 1);
|
||||
|
||||
// log message with severity error - This will also flush_sink the backtrace which has 2 messages
|
||||
LOG_ERROR(logger, "An error has happened, Backtrace is also flushed.");
|
||||
|
||||
// The backtrace is flushed again after LOG_ERROR but in this case it is empty
|
||||
LOG_ERROR(logger, "An second error has happened, but backtrace is now empty.");
|
||||
|
||||
// Log more backtrace messages
|
||||
LOG_BACKTRACE(logger, "Another Backtrace log {}", 1);
|
||||
LOG_BACKTRACE(logger, "Another Backtrace log {}", 2);
|
||||
|
||||
// Nothing is logged at the moment
|
||||
LOG_INFO(logger, "Another log info");
|
||||
|
||||
// Still nothing logged - the error message is on a different logger object
|
||||
quill::LoggerImpl* logger_2 = quill::get_logger("example_1_1");
|
||||
|
||||
LOG_CRITICAL(logger_2, "A critical error from different logger.");
|
||||
|
||||
// The new backtrace is flushed again due to LOG_CRITICAL
|
||||
LOG_CRITICAL(logger, "A critical error from the logger we had a backtrace.");
|
||||
|
||||
Store messages in the ring buffer and display them on demand
|
||||
--------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. code:: cpp
|
||||
// Store maximum of two log messages. By default they will never be flushed since no LogLevel severity is specified
|
||||
logger->init_backtrace(2);
|
||||
|
||||
LOG_INFO(logger, "BEFORE backtrace Example {}", 2);
|
||||
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 100);
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 200);
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 300);
|
||||
|
||||
LOG_INFO(logger, "AFTER backtrace Example {}", 2);
|
||||
|
||||
// an error has happened - flush_log_messages the backtrace manually
|
||||
logger->flush_backtrace();
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
.. _usage:
|
||||
|
||||
##############################################################################
|
||||
Usage
|
||||
##############################################################################
|
||||
|
||||
Quickstart
|
||||
===========
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Trivial logging example to console
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_TRACE_L3(logger, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(logger, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
LOG_TRACE_L1(logger, "This is a log trace l1 {} example", "string");
|
||||
LOG_DEBUG(logger, "This is a log debug example {}", 4);
|
||||
LOG_INFO(logger, "This is a log info example {}", sizeof(std::string));
|
||||
LOG_WARNING(logger, "This is a log warning example {}", sizeof(std::string));
|
||||
LOG_ERROR(logger, "This is a log error example {}", sizeof(std::string));
|
||||
LOG_CRITICAL(logger, "This is a log critical example {}", sizeof(std::string));
|
||||
}
|
||||
|
||||
|
||||
Log to file
|
||||
======================
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"trivial_logging.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
quill::Logger* logger =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) "
|
||||
"LOG_%(log_level:<9) %(logger:<12) %(message)",
|
||||
"%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
|
||||
// set the log level of the logger to debug (default is info)
|
||||
logger->set_log_level(quill::LogLevel::Debug);
|
||||
|
||||
LOG_INFO(logger, "log something {}", 123);
|
||||
LOG_DEBUG(logger, "something else {}", 456);
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
.. _users-api:
|
||||
|
||||
##############################################################################
|
||||
User's API
|
||||
##############################################################################
|
||||
|
||||
Backend Options
|
||||
=====================
|
||||
|
||||
.. doxygenstruct:: quill::BackendOptions
|
||||
:members:
|
||||
|
||||
Backend Class
|
||||
=====================
|
||||
|
||||
.. doxygenclass:: quill::Backend
|
||||
:members:
|
||||
|
||||
Frontend Options
|
||||
=====================
|
||||
|
||||
.. doxygenstruct:: quill::FrontendOptions
|
||||
:members:
|
||||
|
||||
Frontend Class
|
||||
=====================
|
||||
|
||||
.. doxygenclass:: quill::FrontendImpl
|
||||
:members:
|
||||
|
||||
Log Levels
|
||||
=====================
|
||||
|
||||
.. doxygenenum:: quill::LogLevel
|
||||
|
||||
Logger Class
|
||||
=====================
|
||||
|
||||
.. doxygenclass:: quill::LoggerImpl
|
||||
:members:
|
||||
|
||||
PatternFormatter Class
|
||||
=========================
|
||||
|
||||
.. doxygenclass:: quill::PatternFormatter
|
||||
:members:
|
||||
|
||||
Sink Class
|
||||
=====================
|
||||
|
||||
.. doxygenclass:: quill::Sink
|
||||
:members:
|
||||
|
||||
Filter Class
|
||||
=====================
|
||||
|
||||
.. doxygenclass:: quill::Filter
|
||||
:members:
|
||||
|
||||
FileSinkConfig Class
|
||||
=====================
|
||||
|
||||
.. doxygenclass:: quill::FileSinkConfig
|
||||
:members:
|
||||
|
||||
RotatingFileSinkConfig Class
|
||||
==============================
|
||||
|
||||
.. doxygenclass:: quill::RotatingFileSinkConfig
|
||||
:members:
|
|
@ -1,74 +0,0 @@
|
|||
add_subdirectory(recommended_usage)
|
||||
add_subdirectory(advanced)
|
||||
|
||||
add_executable(quill_example_backend_thread_notify backend_thread_notify.cpp)
|
||||
set_common_compile_options(quill_example_backend_thread_notify)
|
||||
target_link_libraries(quill_example_backend_thread_notify quill)
|
||||
|
||||
add_executable(quill_example_backtrace_logging backtrace_logging.cpp)
|
||||
set_common_compile_options(quill_example_backtrace_logging)
|
||||
target_link_libraries(quill_example_backtrace_logging quill)
|
||||
|
||||
add_executable(quill_example_bounded_dropping_queue_frontend bounded_dropping_queue_frontend.cpp)
|
||||
set_common_compile_options(quill_example_bounded_dropping_queue_frontend)
|
||||
target_link_libraries(quill_example_bounded_dropping_queue_frontend quill)
|
||||
|
||||
add_executable(quill_example_console_logging console_logging.cpp)
|
||||
set_common_compile_options(quill_example_console_logging)
|
||||
target_link_libraries(quill_example_console_logging quill)
|
||||
|
||||
add_executable(quill_example_custom_console_colours custom_console_colours.cpp)
|
||||
set_common_compile_options(quill_example_custom_console_colours)
|
||||
target_link_libraries(quill_example_custom_console_colours quill)
|
||||
|
||||
add_executable(quill_example_rotating_file_logging rotating_file_logging.cpp)
|
||||
set_common_compile_options(quill_example_rotating_file_logging)
|
||||
target_link_libraries(quill_example_rotating_file_logging quill)
|
||||
|
||||
add_executable(quill_example_signal_handler signal_handler.cpp)
|
||||
set_common_compile_options(quill_example_signal_handler)
|
||||
target_link_libraries(quill_example_signal_handler quill)
|
||||
|
||||
add_executable(quill_example_logger_removal_with_file_event_notifier logger_removal_with_file_event_notifier.cpp)
|
||||
set_common_compile_options(quill_example_logger_removal_with_file_event_notifier)
|
||||
target_link_libraries(quill_example_logger_removal_with_file_event_notifier quill)
|
||||
|
||||
add_executable(quill_example_custom_frontend_options custom_frontend_options.cpp)
|
||||
set_common_compile_options(quill_example_custom_frontend_options)
|
||||
target_link_libraries(quill_example_custom_frontend_options quill)
|
||||
|
||||
add_executable(quill_example_file_logging file_logging.cpp)
|
||||
set_common_compile_options(quill_example_file_logging)
|
||||
target_link_libraries(quill_example_file_logging quill)
|
||||
|
||||
add_executable(quill_example_filter_logging filter_logging.cpp)
|
||||
set_common_compile_options(quill_example_filter_logging)
|
||||
target_link_libraries(quill_example_filter_logging quill)
|
||||
|
||||
add_executable(quill_example_system_clock_logging system_clock_logging.cpp)
|
||||
set_common_compile_options(quill_example_system_clock_logging)
|
||||
target_link_libraries(quill_example_system_clock_logging quill)
|
||||
|
||||
add_executable(quill_example_user_clock_source user_clock_source.cpp)
|
||||
set_common_compile_options(quill_example_user_clock_source)
|
||||
target_link_libraries(quill_example_user_clock_source quill)
|
||||
|
||||
add_executable(quill_example_user_defined_filter user_defined_filter.cpp)
|
||||
set_common_compile_options(quill_example_user_defined_filter)
|
||||
target_link_libraries(quill_example_user_defined_filter quill)
|
||||
|
||||
add_executable(quill_example_user_defined_sink user_defined_sink.cpp)
|
||||
set_common_compile_options(quill_example_user_defined_sink)
|
||||
target_link_libraries(quill_example_user_defined_sink quill)
|
||||
|
||||
add_executable(quill_example_tags_logging tags_logging.cpp)
|
||||
set_common_compile_options(quill_example_tags_logging)
|
||||
target_link_libraries(quill_example_tags_logging quill)
|
||||
|
||||
add_executable(quill_example_json_sink_logging json_sink_logging.cpp)
|
||||
set_common_compile_options(quill_example_json_sink_logging)
|
||||
target_link_libraries(quill_example_json_sink_logging quill)
|
||||
|
||||
add_executable(quill_example_user_defined_types_logging user_defined_types_logging.cpp)
|
||||
set_common_compile_options(quill_example_user_defined_types_logging)
|
||||
target_link_libraries(quill_example_user_defined_types_logging quill)
|
|
@ -1,5 +0,0 @@
|
|||
add_subdirectory(quill_wrapper)
|
||||
|
||||
add_executable(quill_example_advanced advanced.cpp)
|
||||
set_common_compile_options(quill_example_advanced)
|
||||
target_link_libraries(quill_example_advanced quill_wrapper_advanced)
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
* This example showcases passing user-defined types as arguments to the logger, with their
|
||||
* formatting deferred asynchronously to the backend. It's particularly useful in scenarios where
|
||||
* string formatting latency is unacceptable and the code operates on the critical path.
|
||||
*
|
||||
* For a more straightforward approach, it's generally recommended to pass these types as strings,
|
||||
* formatting them in the frontend, as demonstrated in the 'user_defined_types_logging.cpp' example.
|
||||
*/
|
||||
|
||||
// Include our wrapper lib for setup_quill
|
||||
#include "quill_wrapper/quill_wrapper.h"
|
||||
|
||||
// Header required for quill::Frontend::get_logger
|
||||
#include "quill/Frontend.h"
|
||||
|
||||
// We need only these two headers in order to log
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
|
||||
// user defined type header
|
||||
#include "user.h"
|
||||
|
||||
// user defined type codec header
|
||||
#include "user_quill_codec.h"
|
||||
|
||||
// Required only when passing to logger std::vector<User> for offloading the formatting to the backend
|
||||
#include "quill/std/Vector.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
setup_quill("recommended_usage.log");
|
||||
quill::Logger* logger = quill::Frontend::get_logger("root");
|
||||
|
||||
User user;
|
||||
user.name = "Quill";
|
||||
user.surname = "Library";
|
||||
user.age = 4;
|
||||
user.favorite_colors[0] = "red";
|
||||
user.favorite_colors[1] = "green";
|
||||
user.favorite_colors[2] = "blue";
|
||||
|
||||
LOG_INFO(logger, "The user is {}", user);
|
||||
|
||||
std::vector<User> const users = {{"Alice", "Doe", 25, {"red", "green"}},
|
||||
{"Bob", "Smith", 30, {"blue", "yellow"}},
|
||||
{"Charlie", "Johnson", 35, {"green", "orange"}},
|
||||
{"David", "Brown", 40, {"red", "blue", "yellow"}}};
|
||||
|
||||
LOG_INFO(logger, "The users are {}", users);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
set(LIB_NAME quill_wrapper_advanced)
|
||||
|
||||
add_library(${LIB_NAME} STATIC
|
||||
include/quill_wrapper/quill_wrapper.h
|
||||
include/quill_wrapper/quill_wrapper.cpp)
|
||||
|
||||
# Add include directories for this library
|
||||
target_include_directories(${LIB_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Link quill dependency
|
||||
target_link_libraries(${LIB_NAME} PUBLIC quill::quill)
|
|
@ -1,21 +0,0 @@
|
|||
#include "quill_wrapper.h"
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
void setup_quill(char const* log_file)
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Setup sink and logger
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console_sink");
|
||||
|
||||
// Create and store the logger
|
||||
quill::Frontend::create_or_get_logger("root", std::move(console_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) "
|
||||
"LOG_%(log_level:<9) %(logger:<12) %(message)",
|
||||
"%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void setup_quill(char const* log_file);
|
|
@ -1,16 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* User defined type
|
||||
*/
|
||||
struct User
|
||||
{
|
||||
std::string name;
|
||||
std::string surname;
|
||||
uint32_t age;
|
||||
std::array<std::string, 3> favorite_colors;
|
||||
};
|
|
@ -1,85 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// Always required
|
||||
#include "quill/core/Codec.h"
|
||||
#include "quill/core/DynamicFormatArgStore.h"
|
||||
|
||||
// To serialise the std::array member of User you need Array.h otherwise you don't need to include this
|
||||
#include "quill/std/Array.h"
|
||||
|
||||
#include "user.h"
|
||||
|
||||
#include <utility> // for declval only required if you do the decoding manualy and use declval
|
||||
|
||||
/***/
|
||||
template <>
|
||||
struct fmtquill::formatter<User>
|
||||
{
|
||||
template <typename FormatContext>
|
||||
constexpr auto parse(FormatContext& ctx)
|
||||
{
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(::User const& user, FormatContext& ctx) const
|
||||
{
|
||||
return fmtquill::format_to(ctx.out(), "Name: {}, Surname: {}, Age: {}, Favorite Colors: {}",
|
||||
user.name, user.surname, user.age, user.favorite_colors);
|
||||
}
|
||||
};
|
||||
|
||||
/***/
|
||||
template <>
|
||||
struct quill::detail::ArgSizeCalculator<User>
|
||||
{
|
||||
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache, ::User const& user) noexcept
|
||||
{
|
||||
// pass as arguments the class members you want to serialize
|
||||
return calculate_total_size(conditional_arg_size_cache, user.name, user.surname, user.age, user.favorite_colors);
|
||||
}
|
||||
};
|
||||
|
||||
/***/
|
||||
template <>
|
||||
struct quill::detail::Encoder<User>
|
||||
{
|
||||
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
|
||||
uint32_t& conditional_arg_size_cache_index, ::User const& user) noexcept
|
||||
{
|
||||
// You must encode the same members and in the same order as in the ArgSizeCalculator::calculate
|
||||
encode_members(buffer, conditional_arg_size_cache, conditional_arg_size_cache_index, user.name,
|
||||
user.surname, user.age, user.favorite_colors);
|
||||
}
|
||||
};
|
||||
|
||||
/***/
|
||||
template <>
|
||||
struct quill::detail::Decoder<User>
|
||||
{
|
||||
static ::User decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
|
||||
{
|
||||
// You must decode the same members and in the same order as in the Encoder::encode
|
||||
::User user;
|
||||
decode_and_assign_members(buffer, args_store, user, user.name, user.surname, user.age, user.favorite_colors);
|
||||
return user;
|
||||
|
||||
// note:
|
||||
// If the object is not default constructible you have to do it manually without
|
||||
// decode_members helper
|
||||
|
||||
// auto name = Decoder<decltype(std::declval<::User>().name)>::decode(buffer, nullptr);
|
||||
// auto surname = Decoder<decltype(std::declval<::User>().surname)>::decode(buffer, nullptr);
|
||||
// auto age = Decoder<decltype(std::declval<::User>().age)>::decode(buffer, nullptr);
|
||||
// auto favorite_colors = Decoder<decltype(std::declval<::User>().favorite_colors)>::decode(buffer, nullptr);
|
||||
|
||||
// ::User user{name, surname, age, favorite_colors};
|
||||
|
||||
// if (args_store)
|
||||
// {
|
||||
// args_store->push_back(user);
|
||||
// }
|
||||
|
||||
// return user;
|
||||
}
|
||||
};
|
|
@ -1,59 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example demonstrates how to manually wake up the backend thread when configuring
|
||||
* it with a longer sleep duration.
|
||||
*
|
||||
* Note: It's generally advised not to set the sleep duration higher than 1 second.
|
||||
* However, it's still possible to do so.
|
||||
* The only practical use case for this is when you want to prevent the backend thread
|
||||
* from waking up periodically to conserve CPU cycles.
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
backend_options.sleep_duration = std::chrono::hours{24};
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// To demonstrate the example we have to wait for the backend thread to start and then
|
||||
// go into sleep as there will be nothing to log, so we wait here a bit
|
||||
std::this_thread::sleep_for(std::chrono::seconds{1});
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
LOG_INFO(logger, "This is a log info example {}", 5);
|
||||
LOG_WARNING(logger, "This is a log warning example {}", 6);
|
||||
LOG_ERROR(logger, "This is a log error example {}", 7);
|
||||
|
||||
// sleep for 5 seconds, the backend thread will also be sleeping so no logs are displayed
|
||||
std::cout << "waiting for the backend thread..." << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::seconds{5});
|
||||
std::cout << "notifying for the backend thread..." << std::endl;
|
||||
quill::Backend::notify();
|
||||
std::this_thread::sleep_for(std::chrono::seconds{1}); // let backend sleep again
|
||||
|
||||
LOG_INFO(logger, "This is a log info example {}", 15);
|
||||
LOG_WARNING(logger, "This is a log warning example {}", 16);
|
||||
LOG_ERROR(logger, "This is a log error example {}", 17);
|
||||
|
||||
std::cout << "waiting for the backend thread..." << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::seconds{5});
|
||||
std::cout << "notifying for the backend thread..." << std::endl;
|
||||
quill::Backend::notify();
|
||||
std::this_thread::sleep_for(std::chrono::seconds{1}); // let backend sleep again
|
||||
|
||||
LOG_INFO(logger, "Done, backend thread will always wake up and log on destruction");
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example showcases the usage of LOG_BACKTRACE macros. Log messages generated using these
|
||||
* macros are enqueued from the frontend to the backend. However, the backend will only log them if
|
||||
* a specific condition is met or if they are manually flushed using flush_backtrace().
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
logger->set_log_level(quill::LogLevel::Debug);
|
||||
|
||||
// Enable the backtrace with a ring buffer capacity of 2 messages to get flushed when
|
||||
// a LOG_ERROR(...) or higher severity log message occurs via this logger.
|
||||
// Backtrace has to be enabled only once in the beginning before calling LOG_BACKTRACE(...) for the first time.
|
||||
logger->init_backtrace(2u, quill::LogLevel::Error);
|
||||
|
||||
LOG_INFO(logger, "Begin example {}", 1);
|
||||
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 1);
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 2);
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 3);
|
||||
LOG_BACKTRACE(logger, "Backtrace log {}", 4);
|
||||
|
||||
LOG_INFO(logger, "Backtrace is not flushed yet as we requested to flush on errors {}", 1);
|
||||
|
||||
// log message with severity error - This will also flush the backtrace which has 2 messages
|
||||
LOG_ERROR(logger, "An error has happened, Backtrace is also flushed.");
|
||||
|
||||
// The backtrace is flushed again after LOG_ERROR but in this case it is empty
|
||||
LOG_ERROR(logger,
|
||||
"Another second error has happened, backtrace is flushed again but it is now empty.");
|
||||
|
||||
// Log more backtrace messages
|
||||
LOG_BACKTRACE(logger, "Another Backtrace log {}", 1);
|
||||
LOG_BACKTRACE(logger, "Another Backtrace log {}", 2);
|
||||
|
||||
LOG_DEBUG(logger, "No backtrace logs yet");
|
||||
LOG_INFO(logger, "Manually flush backtrace");
|
||||
|
||||
logger->flush_backtrace();
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example demonstrates how to change the type of the Single Producer Single Consumer (SPSC)
|
||||
* queue used by the frontend.
|
||||
*
|
||||
* By default, the library uses an UnboundedBlocking queue, which starts small with
|
||||
* initial_queue_capacity and reallocates up to 2GB as needed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a custom frontend config
|
||||
*/
|
||||
struct CustomFrontendOptions
|
||||
{
|
||||
// Set the queue to BoundedDropping
|
||||
static constexpr quill::QueueType queue_type = quill::QueueType::BoundedDropping;
|
||||
|
||||
// Set small capacity to demonstrate dropping messages in this example
|
||||
static constexpr uint32_t initial_queue_capacity = 256;
|
||||
|
||||
static constexpr uint32_t blocking_queue_retry_interval_ns = 800;
|
||||
static constexpr bool huge_pages_enabled = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* A new Frontend and Logger should be defined to use the custom frontend options.
|
||||
*/
|
||||
using CustomFrontend = quill::FrontendImpl<CustomFrontendOptions>;
|
||||
using CustommLogger = quill::LoggerImpl<CustomFrontendOptions>;
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto console_sink = CustomFrontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
CustommLogger* logger = CustomFrontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
LOG_INFO(logger, "Bounded queue example log message num {}", i);
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Trivial logging example to console
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_TRACE_L3(logger, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(logger, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
LOG_TRACE_L1(logger, "This is a log trace l1 {} example", "string");
|
||||
LOG_DEBUG(logger, "This is a log debug example {}", 4);
|
||||
LOG_INFO(logger, "This is a log info example {}", sizeof(std::string));
|
||||
LOG_WARNING(logger, "This is a log warning example {}", sizeof(std::string));
|
||||
LOG_ERROR(logger, "This is a log error example {}", sizeof(std::string));
|
||||
LOG_CRITICAL(logger, "This is a log critical example {}", sizeof(std::string));
|
||||
|
||||
// libfmt format specification mini language is supported
|
||||
// note: named arguments are not supported
|
||||
LOG_INFO(logger, "Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
|
||||
LOG_INFO(logger, "Easy padding in numbers like {:08d}", 12);
|
||||
LOG_INFO(logger, "{:>30}", "right aligned");
|
||||
LOG_INFO(logger, "Positional arguments {1} {2} {0} ", "too", "are", "supported");
|
||||
LOG_INFO(logger, "Support for precision {:.4f}", 1.23456);
|
||||
|
||||
// To log with a different format to the same sink, just create another logger
|
||||
auto console_sink_2 = quill::Frontend::get_sink("sink_id_1"); // get the created sink
|
||||
quill::Logger* logger_2 = quill::Frontend::create_or_get_logger(
|
||||
"logger_2", std::move(console_sink_2), "%(time) %(log_level:<9) %(logger:<12) %(message)");
|
||||
|
||||
logger_2->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_TRACE_L3(logger_2, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(logger_2, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
LOG_TRACE_L1(logger_2, "This is a log trace l1 {} example", "string");
|
||||
LOG_DEBUG(logger_2, "This is a log debug example {}", 4);
|
||||
LOG_INFO(logger_2, "This is a log info example {}", sizeof(std::string));
|
||||
LOG_WARNING(logger_2, "This is a log warning example {}", sizeof(std::string));
|
||||
LOG_ERROR(logger_2, "This is a log error example {}", sizeof(std::string));
|
||||
LOG_CRITICAL(logger_2, "This is a log critical example {}", sizeof(std::string));
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* The example demonstrates how to customise the console colours
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
quill::ConsoleColours custom_console_colours;
|
||||
custom_console_colours.set_default_colours();
|
||||
custom_console_colours.set_colour(quill::LogLevel::Info, quill::ConsoleColours::blue); // overwrite the colour for INFO
|
||||
|
||||
// Create the sink
|
||||
auto console_sink =
|
||||
quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1", custom_console_colours);
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_TRACE_L3(logger, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(logger, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
LOG_TRACE_L1(logger, "This is a log trace l1 {} example", "string");
|
||||
LOG_DEBUG(logger, "This is a log debug example {}", 4);
|
||||
LOG_INFO(logger, "This is a log info example {}", sizeof(std::string));
|
||||
LOG_WARNING(logger, "This is a log warning example {}", sizeof(std::string));
|
||||
LOG_ERROR(logger, "This is a log error example {}", sizeof(std::string));
|
||||
LOG_CRITICAL(logger, "This is a log critical example {}", sizeof(std::string));
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
* This example demonstrates defining and utilizing custom FrontendOptions.
|
||||
* It's useful when you need to modify the queue type or capacity.
|
||||
* FrontendOptions are compile-time options and must be passed as a template argument.
|
||||
*/
|
||||
|
||||
// Backend - required to start the backend thread
|
||||
#include "quill/Backend.h"
|
||||
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
// Include only Logger, LogMacros as they are the minimal required files for logging functionality
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
// define your own FrontendOptions, see "core/FrontendOptions.h" for details
|
||||
struct CustomFrontendOptions
|
||||
{
|
||||
static constexpr quill::QueueType queue_type = quill::QueueType::BoundedDropping;
|
||||
static constexpr uint32_t initial_queue_capacity = 131'072;
|
||||
static constexpr uint32_t blocking_queue_retry_interval_ns = 800;
|
||||
static constexpr bool huge_pages_enabled = false;
|
||||
};
|
||||
|
||||
// To utilize our custom FrontendOptions, we define a Frontend class using CustomFrontendOptions
|
||||
using CustomFrontend = quill::FrontendImpl<CustomFrontendOptions>;
|
||||
|
||||
// The Logger type must also be defined
|
||||
using CustomLogger = quill::LoggerImpl<CustomFrontendOptions>;
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options); // or quill::Backend::start_with_signal_handler<CustomFrontendOptions>();
|
||||
|
||||
// All frontend operations must utilize CustomFrontend instead of quill::Frontend
|
||||
auto console_sink = CustomFrontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
CustomLogger* logger = CustomFrontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// log something
|
||||
LOG_INFO(logger, "This is a log info example {}", 123);
|
||||
LOG_WARNING(logger, "This is a log warning example {}", 123);
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Logging to the same file using multiple loggers
|
||||
*/
|
||||
|
||||
void log_from_new_logger()
|
||||
{
|
||||
// Obtain the existing created sink, and create a new logger to log
|
||||
auto file_sink = quill::Frontend::get_sink("example_file_logging.log");
|
||||
quill::Logger* logger_2 = quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
LOG_INFO(logger_2, "log from new logger {}", 123);
|
||||
}
|
||||
|
||||
void log_from_existing_logger()
|
||||
{
|
||||
// Obtain existing logger to log
|
||||
quill::Logger* logger = quill::Frontend::get_logger("root");
|
||||
LOG_INFO(logger, "log again {}", 123312);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
"example_file_logging.log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
quill::Logger* logger =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) "
|
||||
"LOG_%(log_level:<9) %(logger:<12) %(message)",
|
||||
"%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
|
||||
// set the log level of the logger to debug (default is info)
|
||||
logger->set_log_level(quill::LogLevel::Debug);
|
||||
|
||||
LOG_INFO(logger, "log something {}", 123);
|
||||
LOG_DEBUG(logger, "something else {}", 456);
|
||||
|
||||
log_from_new_logger();
|
||||
log_from_existing_logger();
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example demonstrates the usage of the built-in filter, which can filter log levels.
|
||||
* When a filter is applied, log messages are still enqueued from the frontend to the backend.
|
||||
* Subsequently, the backend dynamically filters them based on a given condition.
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
|
||||
// Set a filter for the sink
|
||||
console_sink->set_log_level_filter(quill::LogLevel::Info);
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// Change the LogLevel to send everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_TRACE_L3(logger, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(logger, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
LOG_TRACE_L1(logger, "This is a log trace l1 {} example", "string");
|
||||
LOG_DEBUG(logger, "This is a log debug example {}", 4);
|
||||
LOG_INFO(logger, "This is a log info example {}", sizeof(std::string));
|
||||
LOG_WARNING(logger, "This is a log warning example {}", sizeof(std::string));
|
||||
LOG_ERROR(logger, "This is a log error example {}", sizeof(std::string));
|
||||
LOG_CRITICAL(logger, "This is a log critical example {}", sizeof(std::string));
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
#include "quill/sinks/JsonFileSink.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example showcases the usage of the JsonFileSink to generate JSON-formatted logs.
|
||||
* Additionally, it demonstrates how to simultaneously log in both the standard logger output
|
||||
* format, e.g., to console and the corresponding JSON format to a JSON output sink.
|
||||
*
|
||||
* For successful JSON logging, it's essential to use named placeholders within the provided
|
||||
* format string, such as "{method}" and "{endpoint}".
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
|
||||
// Create a json file for output
|
||||
auto json_sink = quill::Frontend::create_or_get_sink<quill::JsonFileSink>(
|
||||
"json_sink_logging.log",
|
||||
[]()
|
||||
{
|
||||
quill::JsonFileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
// When using the JsonFileSink, it is ideal to set the logging pattern to empty to avoid unnecessary message formatting.
|
||||
quill::Logger* json_logger = quill::Frontend::create_or_get_logger(
|
||||
"json_logger", std::move(json_sink), "", "%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
LOG_INFO(json_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i);
|
||||
}
|
||||
|
||||
// It is also possible to create a logger than logs to both the json file and stdout
|
||||
// with the appropriate format
|
||||
auto json_sink_2 = quill::Frontend::get_sink("json_sink_logging.log");
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console_sink_id_1");
|
||||
|
||||
// We set a custom format pattern here to also include the named_args
|
||||
quill::Logger* hybrid_logger = quill::Frontend::create_or_get_logger(
|
||||
"hybrid_logger", {std::move(json_sink_2), std::move(console_sink)},
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<20) "
|
||||
"%(message) [%(named_args)]");
|
||||
|
||||
for (int i = 2; i < 4; ++i)
|
||||
{
|
||||
LOG_INFO(hybrid_logger, "{method} to {endpoint} took {elapsed} ms", "POST", "http://", 10 * i);
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example demonstrates the creation and removal of a logger object for each instance of the Session class.
|
||||
* Each session instance logs to a new log file using a unique logger.
|
||||
* When the session ends, the logger is removed, and the associated file is closed.
|
||||
* Additionally, it showcases the usage of the FileEventNotifier, which provides notifications for file changes.
|
||||
*/
|
||||
|
||||
class Session
|
||||
{
|
||||
public:
|
||||
explicit Session(std::string const& unique_name)
|
||||
{
|
||||
// Set up a FileEventNotifier so we are notified on file changes
|
||||
quill::FileEventNotifier file_notifier;
|
||||
|
||||
file_notifier.before_open = [](quill::fs::path const& filename)
|
||||
{ std::cout << "file_notifier - preparing to open file " << filename << std::endl; };
|
||||
|
||||
file_notifier.after_open = [](quill::fs::path const& filename, FILE* f)
|
||||
{ std::cout << "file_notifier - opened file " << filename << std::endl; };
|
||||
|
||||
file_notifier.before_close = [](quill::fs::path const& filename, FILE* f)
|
||||
{ std::cout << "file_notifier - preparing to close file " << filename << std::endl; };
|
||||
|
||||
file_notifier.after_close = [](quill::fs::path const& filename)
|
||||
{ std::cout << "file_notifier - closed file " << filename << std::endl; };
|
||||
|
||||
// Create a new log file for this session
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
std::string{"session_"} + unique_name + ".log",
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::None);
|
||||
return cfg;
|
||||
}(),
|
||||
file_notifier);
|
||||
|
||||
// Create a session specific logger for the current session
|
||||
_logger = quill::Frontend::create_or_get_logger(unique_name, std::move(file_sink));
|
||||
|
||||
LOG_INFO(_logger, "Hello from session {}", unique_name);
|
||||
}
|
||||
|
||||
~Session()
|
||||
{
|
||||
// Remove the logger when the session is done. That will also remove the associated file_handler
|
||||
// and close the file as long as no other logger is using that file_handler
|
||||
quill::Frontend::remove_logger(_logger);
|
||||
}
|
||||
|
||||
private:
|
||||
quill::Logger* _logger{nullptr};
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
{
|
||||
Session session_1 = Session{"SessionA"};
|
||||
std::this_thread::sleep_for(std::chrono::seconds{3});
|
||||
|
||||
Session session_2 = Session{"SessionB"};
|
||||
std::this_thread::sleep_for(std::chrono::seconds{3});
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds{3});
|
||||
|
||||
{
|
||||
Session session_3 = Session{"SessionC"};
|
||||
std::this_thread::sleep_for(std::chrono::seconds{3});
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds{3});
|
||||
|
||||
Session session_4 = Session{"SessionD"};
|
||||
std::this_thread::sleep_for(std::chrono::seconds{3});
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
add_subdirectory(quill_wrapper)
|
||||
|
||||
add_executable(quill_example_recommended_usage recommended_usage.cpp)
|
||||
set_common_compile_options(quill_example_recommended_usage)
|
||||
target_link_libraries(quill_example_recommended_usage quill_wrapper_recommended)
|
||||
|
||||
add_executable(quill_example_use_overwrite_macros use_overwrite_macros.cpp)
|
||||
set_common_compile_options(quill_example_use_overwrite_macros)
|
||||
target_link_libraries(quill_example_use_overwrite_macros quill_wrapper_recommended)
|
|
@ -1,17 +0,0 @@
|
|||
set(LIB_NAME quill_wrapper_recommended)
|
||||
|
||||
add_library(${LIB_NAME} STATIC
|
||||
include/quill_wrapper/overwrite_macros.h
|
||||
include/quill_wrapper/quill_wrapper.h
|
||||
include/quill_wrapper/quill_wrapper.cpp)
|
||||
|
||||
# Add include directories for this library
|
||||
target_include_directories(${LIB_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Link quill dependency
|
||||
target_link_libraries(${LIB_NAME} PUBLIC quill::quill)
|
|
@ -1,28 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
/**
|
||||
* This file is optional and demonstrates how users can replace the library macros with their own.
|
||||
*
|
||||
* It becomes useful when opting for a single Logger object over several logger objects.
|
||||
*
|
||||
* However, using multiple logger objects provides greater flexibility in applications with multiple
|
||||
* components. With multiple loggers, it's easier to control the log level of each component
|
||||
* individually, whereas using a single logger, as shown in this example, may present limitations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* By defining QUILL_DISABLE_NON_PREFIXED_MACROS before including LogMacros, we disable the
|
||||
* default 'LOG_' and then create our own macros using the global logger.
|
||||
*/
|
||||
#define QUILL_DISABLE_NON_PREFIXED_MACROS
|
||||
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
|
||||
// The logger we defined in quill_wrapper.cpp
|
||||
extern quill::Logger* global_logger_a;
|
||||
|
||||
// Define custom log macros using global_logger_a. Two examples are provided here for demonstration.
|
||||
#define LOG_INFO(fmt, ...) QUILL_LOG_INFO(global_logger_a, fmt, ##__VA_ARGS__)
|
||||
#define LOG_WARNING(fmt, ...) QUILL_LOG_WARNING(global_logger_a, fmt, ##__VA_ARGS__)
|
||||
// etc ..
|
|
@ -1,35 +0,0 @@
|
|||
#include "quill_wrapper.h"
|
||||
|
||||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/FileSink.h"
|
||||
|
||||
// Define a global variable for a logger to avoid looking up the logger each time.
|
||||
// Additional global variables can be defined for additional loggers if needed.
|
||||
quill::Logger* global_logger_a;
|
||||
|
||||
void setup_quill(char const* log_file)
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::Backend::start();
|
||||
|
||||
// Setup sink and logger
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
|
||||
log_file,
|
||||
[]()
|
||||
{
|
||||
quill::FileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
return cfg;
|
||||
}(),
|
||||
quill::FileEventNotifier{});
|
||||
|
||||
// Create and store the logger
|
||||
global_logger_a =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) "
|
||||
"LOG_%(log_level:<9) %(logger:<12) %(message)",
|
||||
"%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void setup_quill(char const* log_file);
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* This example demonstrates the recommended setup for the Quill library.
|
||||
*
|
||||
* It is advisable to encapsulate the header-only library into a static library, which
|
||||
* you build once and link to your main application.
|
||||
* This library should include `quill/backend` in the .cpp files.
|
||||
*
|
||||
* In your application, include only the following headers for logging:
|
||||
*
|
||||
* - For logger lookup or creation:
|
||||
* #include "quill/Frontend.h"
|
||||
*
|
||||
* - For sink creation:
|
||||
* #include "quill/sinks/.."
|
||||
*
|
||||
* - For logging:
|
||||
* #include "quill/Logger.h"
|
||||
* #include "quill/LogMacros.h"
|
||||
*/
|
||||
|
||||
// Include our wrapper lib
|
||||
#include "quill_wrapper/quill_wrapper.h"
|
||||
|
||||
// We need only those two headers in order to log
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
|
||||
// We utilize the global_logger_a from the quill_wrapper library.
|
||||
// The use of a global logger is optional.
|
||||
// Alternatively, we could include "quill/Frontend.h" and use `quill::Frontend::get_logger(..)`
|
||||
// to obtain the created logger, or we could store it as a class member.
|
||||
extern quill::Logger* global_logger_a;
|
||||
|
||||
int main()
|
||||
{
|
||||
setup_quill("recommended_usage.log");
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
global_logger_a->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_TRACE_L3(global_logger_a, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(global_logger_a, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
LOG_TRACE_L1(global_logger_a, "This is a log trace l1 {} example", "string");
|
||||
LOG_DEBUG(global_logger_a, "This is a log debug example {}", 4);
|
||||
LOG_INFO(global_logger_a, "This is a log info example {}", 5);
|
||||
LOG_WARNING(global_logger_a, "This is a log warning example {}", 6);
|
||||
LOG_ERROR(global_logger_a, "This is a log error example {}", 7);
|
||||
LOG_CRITICAL(global_logger_a, "This is a log critical example {}", 118);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
#include "quill_wrapper/overwrite_macros.h"
|
||||
#include "quill_wrapper/quill_wrapper.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
setup_quill("use_overwrite_macros.log");
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
global_logger_a->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_INFO("This is a log info example {}", 5);
|
||||
LOG_WARNING("This is a log warning example {}", 6);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/RotatingFileSink.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example demonstrates how to create a RotatingFileSink with daily rotation and automatic rotation based on maximum file size.
|
||||
* For additional configuration options, refer to RotatingFileSinkConfig.
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto rotating_file_sink = quill::Frontend::create_or_get_sink<quill::RotatingFileSink>(
|
||||
"rotating_file.log",
|
||||
[]()
|
||||
{
|
||||
// See RotatingFileSinkConfig for more options
|
||||
quill::RotatingFileSinkConfig cfg;
|
||||
cfg.set_open_mode('w');
|
||||
cfg.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
|
||||
cfg.set_rotation_time_daily("18:30");
|
||||
cfg.set_rotation_max_file_size(1024); // small value to demonstrate the example
|
||||
return cfg;
|
||||
}());
|
||||
|
||||
quill::Logger* logger =
|
||||
quill::Frontend::create_or_get_logger("root", std::move(rotating_file_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) "
|
||||
"LOG_%(log_level:<9) %(logger:<12) %(message)",
|
||||
"%H:%M:%S.%Qns", quill::Timezone::GmtTime);
|
||||
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
LOG_INFO(logger, "Hello from rotating logger, index is {}", i);
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
void cause_segfault(quill::Logger* logger)
|
||||
{
|
||||
LOG_INFO(logger, "Crashing");
|
||||
|
||||
int* p = (int*)0x12345678;
|
||||
*p = 0;
|
||||
}
|
||||
|
||||
void infinite_loop(quill::Logger* logger)
|
||||
{
|
||||
LOG_INFO(logger, "Crashing");
|
||||
|
||||
/* break out with ctrl+c to test SIGINT handling */
|
||||
while (1)
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
void illegal_instruction(quill::Logger* logger)
|
||||
{
|
||||
LOG_INFO(logger, "Crashing");
|
||||
raise(SIGILL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal handler example
|
||||
* The signal handler flushes the log when the application crashes or gets terminated
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
// NOTE: On windows a signal handler must be installed on each new thread
|
||||
quill::init_signal_handler<quill::FrontendOptions>();
|
||||
#endif
|
||||
|
||||
// Start the logging backend thread with a signal handler
|
||||
// On Linux/Macos one signal handler is set to handle POSIX style signals
|
||||
// On Windows an exception handler and a Ctrl-C handler is set.
|
||||
quill::Backend::start_with_signal_handler<quill::FrontendOptions>();
|
||||
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// create threads that will cause a segfault
|
||||
std::vector<std::thread> threads;
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
threads.emplace_back(
|
||||
[logger]()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
// NOTE: On windows the signal handler must be installed on each new thread
|
||||
quill::init_signal_handler<quill::FrontendOptions>();
|
||||
#endif
|
||||
// sleep for 1 second so all threads are ready
|
||||
std::this_thread::sleep_for(std::chrono::seconds{1});
|
||||
|
||||
for (size_t i = 0; i < 10; ++i)
|
||||
{
|
||||
// log 10 messages
|
||||
LOG_INFO(logger, "Log from thread {}", i);
|
||||
}
|
||||
|
||||
// After 10 messages Crash - Uncomment any of the below :
|
||||
|
||||
// illegal_instruction(logger);
|
||||
// cause_segfault(logger);
|
||||
});
|
||||
}
|
||||
|
||||
for (uint32_t cnt{0}; cnt < 1000; ++cnt)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{300});
|
||||
LOG_INFO(logger, "Log from main {}", cnt);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Trivial logging example to console using std::chrono::now for timestamps instead of the
|
||||
* default rdtsc clock
|
||||
*/
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger(
|
||||
"root", std::move(console_sink), "%(time) [%(process_id)] [%(thread_id)] %(logger) - %(message)",
|
||||
"%D %H:%M:%S.%Qms %z", quill::Timezone::GmtTime, quill::ClockSourceType::System);
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_TRACE_L3(logger, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(logger, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
LOG_TRACE_L1(logger, "This is a log trace l1 {} example", "string");
|
||||
LOG_DEBUG(logger, "This is a log debug example {}", 4);
|
||||
LOG_INFO(logger, "This is a log info example {}", sizeof(std::string));
|
||||
LOG_WARNING(logger, "This is a log warning example {}", sizeof(std::string));
|
||||
LOG_ERROR(logger, "This is a log error example {}", sizeof(std::string));
|
||||
LOG_CRITICAL(logger, "This is a log critical example {}", sizeof(std::string));
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/Utility.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example showcases the usage of the _WITH_TAGS macros, which allow for logging additional
|
||||
* tags with each log message.
|
||||
*
|
||||
* The example demonstrates the construction of custom tags at compile time using
|
||||
* the quill::CustomTags interface.
|
||||
*
|
||||
* One important limitation to note is that quill::CustomTags instances must be constructed
|
||||
* at compile time.
|
||||
*/
|
||||
|
||||
class MyTagsA : public quill::Tags
|
||||
{
|
||||
public:
|
||||
constexpr MyTagsA(char const* tag_a, uint32_t tag_b) : _tag_a(tag_a), _tag_b(tag_b) {}
|
||||
|
||||
void format(std::string& out) const override
|
||||
{
|
||||
out.append(_tag_a);
|
||||
out.append(":");
|
||||
out.append(std::to_string(_tag_b));
|
||||
}
|
||||
|
||||
private:
|
||||
char const* _tag_a;
|
||||
uint32_t _tag_b;
|
||||
};
|
||||
|
||||
// Define another CustomTags class
|
||||
class MyTagsB : public quill::Tags
|
||||
{
|
||||
public:
|
||||
constexpr MyTagsB(char const* tag_a, uint32_t tag_b) : _tag_a(tag_a), _tag_b(tag_b) {}
|
||||
|
||||
void format(std::string& out) const override
|
||||
{
|
||||
out.append(_tag_a);
|
||||
out.append(":");
|
||||
out.append(std::to_string(_tag_b));
|
||||
}
|
||||
|
||||
private:
|
||||
char const* _tag_a;
|
||||
uint32_t _tag_b;
|
||||
};
|
||||
|
||||
static constexpr MyTagsA tags_a{"CUSTOM_TAG_A", 12};
|
||||
static constexpr MyTagsB tags_b{"CUSTOM_TAG_B", 23};
|
||||
|
||||
// Combine different tags
|
||||
static constexpr quill::utility::CombinedTags<MyTagsA, MyTagsB> tags_ab{tags_a, tags_b};
|
||||
|
||||
static constexpr quill::utility::CombinedTags<MyTagsA, MyTagsB> tags_ab_custom_format_delimiter{
|
||||
tags_a, tags_b, " -- "};
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
|
||||
// It is important to change the default logging pattern to include the custom tags
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger(
|
||||
"root", std::move(console_sink),
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) %(log_level:<9) %(logger:<16) "
|
||||
"[%(custom_tags)] %(message)",
|
||||
"%Y-%m-%d %H:%M:%S.%Qms", quill::Timezone::GmtTime);
|
||||
|
||||
LOG_TRACE_L3_WITH_TAGS(logger, tags_a, "TraceL3 with custom tags");
|
||||
LOG_TRACE_L2_WITH_TAGS(logger, tags_b, "TraceL2 with custom tags");
|
||||
LOG_TRACE_L1_WITH_TAGS(logger, tags_b, "TraceL1 with custom tags");
|
||||
LOG_DEBUG_WITH_TAGS(logger, tags_a, "Debug with custom tags");
|
||||
LOG_INFO_WITH_TAGS(logger, tags_a, "Info with custom tags");
|
||||
LOG_WARNING_WITH_TAGS(logger, tags_b, "Warning with custom tags");
|
||||
LOG_ERROR_WITH_TAGS(logger, tags_a, "Error with custom tags");
|
||||
|
||||
LOG_INFO_WITH_TAGS(logger, tags_ab, "Info with combined custom tags");
|
||||
|
||||
LOG_INFO_WITH_TAGS(logger, tags_ab_custom_format_delimiter,
|
||||
"Combined custom tags custom delimiter");
|
||||
|
||||
LOG_INFO(logger, "Without custom tags");
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/UserClockSource.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Example demonstrating custom clock usage for logging. This is particularly useful
|
||||
* when simulating events from a specific time period, allowing logs to align with
|
||||
* the simulated time.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class needs to be thread-safe, unless only a single thread in the application calling
|
||||
* LOG_ macros from the same logger
|
||||
*/
|
||||
class SimulatedClock : public quill::UserClockSource
|
||||
{
|
||||
public:
|
||||
SimulatedClock() = default;
|
||||
|
||||
/**
|
||||
* Required by TimestampClock
|
||||
* @return current time now, in nanoseconds since epoch
|
||||
*/
|
||||
uint64_t now() const override { return _timestamp_ns.load(std::memory_order_relaxed); }
|
||||
|
||||
/**
|
||||
* set custom timestamp
|
||||
* @param time_since_epoch timestamp
|
||||
*/
|
||||
void set_timestamp(std::chrono::seconds time_since_epoch)
|
||||
{
|
||||
// always convert to nanos
|
||||
_timestamp_ns.store(static_cast<uint64_t>(std::chrono::nanoseconds{time_since_epoch}.count()),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uint64_t> _timestamp_ns{0}; // time since epoch - must always be in nanoseconds
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Create a simulated timestamp class. Quill takes a pointer to this class,
|
||||
// and the user is responsible for its lifetime.
|
||||
// Ensure that the instance of this class remains alive for as long as the logger
|
||||
// object exists, until the logger is removed.
|
||||
SimulatedClock simulated_clock;
|
||||
|
||||
// Get the console sink and also create a logger using the simulated clock
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger(
|
||||
"root", std::move(console_sink),
|
||||
"%(time) %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) %(message)",
|
||||
"%D %H:%M:%S.%Qns", quill::Timezone::LocalTime, quill::ClockSourceType::User, &simulated_clock);
|
||||
|
||||
// Change the LogLevel to print everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
// Set our timestamp to Sunday 12 June 2022
|
||||
simulated_clock.set_timestamp(std::chrono::seconds{1655007309});
|
||||
LOG_TRACE_L3(logger, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(logger, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
|
||||
// update our timestamp
|
||||
simulated_clock.set_timestamp(std::chrono::seconds{1655039000});
|
||||
LOG_INFO(logger, "This is a log info {} example", "string");
|
||||
LOG_DEBUG(logger, "This is a log debug example {}", 4);
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/filters/Filter.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example demonstrates the creation usage of a user defined filter.
|
||||
* When a filter is applied, log messages are still enqueued from the frontend to the backend.
|
||||
* Subsequently, the backend dynamically filters them based on a given condition.
|
||||
*/
|
||||
|
||||
class UserFilter : public quill::Filter
|
||||
{
|
||||
public:
|
||||
UserFilter() : quill::Filter("filter_1"){};
|
||||
|
||||
bool filter(quill::MacroMetadata const* log_metadata, uint64_t log_timestamp,
|
||||
std::string_view thread_id, std::string_view thread_name, std::string_view logger_name,
|
||||
quill::LogLevel log_level, std::string_view log_message) noexcept override
|
||||
{
|
||||
// for example filter out lines 47 and 48 of any file
|
||||
return (std::stoi(log_metadata->line()) != 47) && (std::stoi(log_metadata->line()) != 48);
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
|
||||
// Add the filter - adding filters is thread safe and can be called anytime
|
||||
console_sink->add_filter(std::make_unique<UserFilter>());
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
// Change the LogLevel to send everything
|
||||
logger->set_log_level(quill::LogLevel::TraceL3);
|
||||
|
||||
LOG_TRACE_L3(logger, "This is a log trace l3 example {}", 1);
|
||||
LOG_TRACE_L2(logger, "This is a log trace l2 example {} {}", 2, 2.3);
|
||||
LOG_TRACE_L1(logger, "This is a log trace l1 {} example", "string");
|
||||
LOG_DEBUG(logger, "This is a log debug example {}", 4);
|
||||
LOG_INFO(logger, "This is a log info example {}", sizeof(std::string));
|
||||
LOG_WARNING(logger, "This is a log warning example {}", sizeof(std::string));
|
||||
LOG_ERROR(logger, "This is a log error example {}", sizeof(std::string));
|
||||
LOG_CRITICAL(logger, "This is a log critical example {}", sizeof(std::string));
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/Sink.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* This example demonstrates how to implement a custom Sink
|
||||
*/
|
||||
|
||||
class UserSink final : public quill::Sink
|
||||
{
|
||||
public:
|
||||
UserSink() = default;
|
||||
|
||||
/***/
|
||||
void write_log_message(quill::MacroMetadata const* log_metadata, uint64_t log_timestamp,
|
||||
std::string_view thread_id, std::string_view thread_name,
|
||||
std::string_view logger_name, quill::LogLevel log_level,
|
||||
std::vector<std::pair<std::string, std::string>> const* named_args,
|
||||
std::string_view log_message) override
|
||||
{
|
||||
// Called by the logger backend worker thread for each LOG_* macro
|
||||
// last character is '\n' and we exclude it using size() - 1
|
||||
_messages.push_back(std::string{log_message.data(), log_message.size() - 1});
|
||||
}
|
||||
|
||||
/***/
|
||||
void flush_sink() noexcept override
|
||||
{
|
||||
// This is not called for each LOG_* invocation like the write function, instead it is called
|
||||
// periodically or when there are no more LOG_* writes left to process or when logger->flush()
|
||||
for (auto const& message : _messages)
|
||||
{
|
||||
std::cout << message << std::endl;
|
||||
}
|
||||
_messages.clear();
|
||||
}
|
||||
|
||||
/***/
|
||||
void run_periodic_tasks() noexcept override
|
||||
{
|
||||
// Executes periodic user-defined tasks. This function is frequently invoked by the backend thread's main loop.
|
||||
// Avoid including heavy tasks here to prevent slowing down the backend thread.
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> _messages;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto file_sink = quill::Frontend::create_or_get_sink<UserSink>("sink_id_1");
|
||||
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(file_sink));
|
||||
|
||||
logger->set_log_level(quill::LogLevel::Debug);
|
||||
|
||||
LOG_INFO(logger, "Hello from {}", "sink example");
|
||||
LOG_DEBUG(logger, "Invoking user sink flush");
|
||||
|
||||
logger->flush_log();
|
||||
|
||||
LOG_INFO(logger, "Log more {}", 123);
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
#include "quill/Backend.h"
|
||||
#include "quill/Frontend.h"
|
||||
#include "quill/LogMacros.h"
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/sinks/ConsoleSink.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* This example illustrates logging user-defined types.
|
||||
*
|
||||
* Starting from version 4.0.0, direct passing of user-defined and standard library types to the
|
||||
* 'LOG_' macros is no longer supported. Instead, these types must be converted to strings
|
||||
* before being passed to the logger.
|
||||
*
|
||||
* In this example, std::ostringstream is used for simplicity, but it's recommended to use modern
|
||||
* formatting libraries such as std::format or fmt::format.
|
||||
*/
|
||||
|
||||
class User
|
||||
{
|
||||
public:
|
||||
User(std::string name, std::string surname, uint32_t age)
|
||||
: name(std::move(name)), surname(std::move(surname)), age(age){};
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, User const& obj)
|
||||
{
|
||||
os << "name: " << obj.name << ", surname: " << obj.surname << ", age: " << obj.age;
|
||||
return os;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::string surname;
|
||||
uint32_t age;
|
||||
};
|
||||
|
||||
// Helper to convert to string
|
||||
template <typename T>
|
||||
std::string to_string(T const& obj)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << obj;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// Start the backend thread
|
||||
quill::BackendOptions backend_options;
|
||||
quill::Backend::start(backend_options);
|
||||
|
||||
// Frontend
|
||||
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
|
||||
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));
|
||||
|
||||
User user_1{"Super", "User", 1};
|
||||
|
||||
LOG_INFO(logger, "User is [{}]", to_string(user_1));
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
project('quill', 'cpp', version : '4.2.0', default_options : ['warning_level=3', 'cpp_std=c++17'])
|
||||
|
||||
inc_dirs = include_directories('quill/include', is_system: true)
|
||||
|
||||
# Conditional compiler arguments for specific compiler
|
||||
interface_compile_args = meson.get_compiler('cpp').get_supported_arguments('-Wno-gnu-zero-variadic-macro-arguments')
|
||||
|
||||
quill_dep = declare_dependency(include_directories : inc_dirs,
|
||||
dependencies : [dependency('threads')],
|
||||
compile_args : interface_compile_args)
|
||||
|
||||
install_subdir('quill/include/quill', install_dir: get_option('includedir'), install_tag: 'devel')
|
||||
|
||||
pkg_mod = import('pkgconfig')
|
||||
pkg_mod.generate(
|
||||
name : 'quill',
|
||||
filebase : 'quill',
|
||||
description : 'Asynchronous Low Latency C++ Logging Library',
|
||||
subdirs : 'quill'
|
||||
)
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
if (NOT QUILL_MASTER_PROJECT)
|
||||
find_package(Threads REQUIRED)
|
||||
endif ()
|
||||
|
||||
# library name
|
||||
set(TARGET_NAME quill)
|
||||
|
||||
# header files
|
||||
set(HEADER_FILES
|
||||
include/quill/backend/BackendManager.h
|
||||
include/quill/backend/BackendOptions.h
|
||||
include/quill/backend/BackendWorker.h
|
||||
include/quill/backend/BacktraceStorage.h
|
||||
include/quill/backend/PatternFormatter.h
|
||||
include/quill/backend/RdtscClock.h
|
||||
include/quill/backend/SignalHandler.h
|
||||
include/quill/backend/StringFromTime.h
|
||||
include/quill/backend/TimestampFormatter.h
|
||||
include/quill/backend/TransitEvent.h
|
||||
include/quill/backend/TransitEventBuffer.h
|
||||
include/quill/backend/BackendUtilities.h
|
||||
|
||||
include/quill/bundled/fmt/args.h
|
||||
include/quill/bundled/fmt/chrono.h
|
||||
include/quill/bundled/fmt/color.h
|
||||
include/quill/bundled/fmt/compile.h
|
||||
include/quill/bundled/fmt/core.h
|
||||
include/quill/bundled/fmt/format.h
|
||||
include/quill/bundled/fmt/format-inl.h
|
||||
include/quill/bundled/fmt/os.h
|
||||
include/quill/bundled/fmt/ostream.h
|
||||
include/quill/bundled/fmt/printf.h
|
||||
include/quill/bundled/fmt/ranges.h
|
||||
include/quill/bundled/fmt/std.h
|
||||
include/quill/bundled/fmt/xchar.h
|
||||
|
||||
include/quill/core/Attributes.h
|
||||
include/quill/core/BoundedSPSCQueue.h
|
||||
include/quill/core/Common.h
|
||||
include/quill/core/DynamicFormatArgStore.h
|
||||
include/quill/core/Codec.h
|
||||
include/quill/core/Filesystem.h
|
||||
include/quill/core/FormatBuffer.h
|
||||
include/quill/core/FrontendOptions.h
|
||||
include/quill/core/LoggerBase.h
|
||||
include/quill/core/LoggerManager.h
|
||||
include/quill/core/LogLevel.h
|
||||
include/quill/core/MacroMetadata.h
|
||||
include/quill/core/MathUtils.h
|
||||
include/quill/core/QuillError.h
|
||||
include/quill/core/Rdtsc.h
|
||||
include/quill/core/SinkManager.h
|
||||
include/quill/core/ThreadContextManager.h
|
||||
include/quill/core/ThreadUtilities.h
|
||||
include/quill/core/TimeUtilities.h
|
||||
include/quill/core/UnboundedSPSCQueue.h
|
||||
|
||||
include/quill/filters/Filter.h
|
||||
|
||||
include/quill/sinks/ConsoleSink.h
|
||||
include/quill/sinks/FileSink.h
|
||||
include/quill/sinks/JsonConsoleSink.h
|
||||
include/quill/sinks/JsonFileSink.h
|
||||
include/quill/sinks/NullSink.h
|
||||
include/quill/sinks/RotatingFileSink.h
|
||||
include/quill/sinks/Sink.h
|
||||
include/quill/sinks/StreamSink.h
|
||||
|
||||
include/quill/std/Array.h
|
||||
include/quill/std/Deque.h
|
||||
include/quill/std/FilesystemPath.h
|
||||
include/quill/std/ForwardList.h
|
||||
include/quill/std/List.h
|
||||
include/quill/std/Map.h
|
||||
include/quill/std/Optional.h
|
||||
include/quill/std/Pair.h
|
||||
include/quill/std/Set.h
|
||||
include/quill/std/Tuple.h
|
||||
include/quill/std/UnorderedMap.h
|
||||
include/quill/std/UnorderedSet.h
|
||||
include/quill/std/Vector.h
|
||||
|
||||
include/quill/Backend.h
|
||||
include/quill/BackendTscClock.h
|
||||
include/quill/Frontend.h
|
||||
include/quill/Logger.h
|
||||
include/quill/LogMacros.h
|
||||
include/quill/UserClockSource.h
|
||||
include/quill/Utility.h
|
||||
)
|
||||
|
||||
# Add as a library
|
||||
add_library(${TARGET_NAME} INTERFACE)
|
||||
add_library(${TARGET_NAME}::${TARGET_NAME} ALIAS ${TARGET_NAME})
|
||||
|
||||
if (QUILL_NO_EXCEPTIONS)
|
||||
target_compile_definitions(${TARGET_NAME} PUBLIC INTERFACE -DQUILL_NO_EXCEPTIONS)
|
||||
|
||||
if (NOT MSVC)
|
||||
target_compile_options(${TARGET_NAME} PUBLIC INTERFACE -fno-exceptions)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (QUILL_NO_THREAD_NAME_SUPPORT)
|
||||
target_compile_definitions(${TARGET_NAME} PUBLIC INTERFACE -DQUILL_NO_THREAD_NAME_SUPPORT)
|
||||
endif ()
|
||||
|
||||
if (QUILL_X86ARCH)
|
||||
target_compile_definitions(${TARGET_NAME} PUBLIC INTERFACE -DQUILL_X86ARCH)
|
||||
endif ()
|
||||
|
||||
if (QUILL_DISABLE_NON_PREFIXED_MACROS)
|
||||
target_compile_definitions(${TARGET_NAME} PUBLIC INTERFACE -DQUILL_DISABLE_NON_PREFIXED_MACROS)
|
||||
endif ()
|
||||
|
||||
# Add target sources
|
||||
target_sources(${TARGET_NAME} PRIVATE ${HEADER_FILES})
|
||||
|
||||
# Link dependencies
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC INTERFACE Threads::Threads)
|
||||
|
||||
if (MINGW)
|
||||
# strftime requires this when using MinGw to correctly format the time ..
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC INTERFACE ucrtbase)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0)
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC INTERFACE stdc++fs)
|
||||
endif ()
|
||||
|
||||
# Add include directories for this library
|
||||
target_include_directories(${TARGET_NAME}
|
||||
INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
# Properties
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${QUILL_VERSION} SOVERSION ${QUILL_VERSION})
|
||||
|
||||
# ---- Tests ---- #
|
||||
if (QUILL_BUILD_TESTS AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/test)
|
||||
add_subdirectory(test)
|
||||
endif ()
|
||||
|
||||
if (QUILL_MASTER_PROJECT OR QUILL_ENABLE_INSTALL)
|
||||
# ---- Install ---- #
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
set(version_config ${PROJECT_BINARY_DIR}/quill-config-version.cmake)
|
||||
set(project_config ${PROJECT_BINARY_DIR}/quill-config.cmake)
|
||||
set(pkgconfig ${PROJECT_BINARY_DIR}/quill.pc)
|
||||
set(targets_export_name quill-targets)
|
||||
|
||||
set(QUILL_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/quill CACHE STRING
|
||||
"Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
set(QUILL_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||
"Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
set(QUILL_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
|
||||
"Installation directory for include files, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
set(QUILL_PKGCONFIG_DIR ${CMAKE_INSTALL_PREFIX}/pkgconfig CACHE PATH
|
||||
"Installation directory for pkgconfig (.pc) files, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
# Generate pkgconfig
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_LIST_DIR}/cmake/quill.pc.in"
|
||||
"${pkgconfig}"
|
||||
@ONLY)
|
||||
|
||||
# Copy pkgconfig
|
||||
install(FILES "${pkgconfig}" DESTINATION "${QUILL_PKGCONFIG_DIR}")
|
||||
|
||||
# Generate the version, config and target files into the build directory.
|
||||
write_basic_package_version_file(
|
||||
${version_config}
|
||||
VERSION ${QUILL_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion)
|
||||
|
||||
configure_package_config_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/cmake/quill-config.cmake.in
|
||||
${project_config}
|
||||
INSTALL_DESTINATION ${QUILL_CMAKE_DIR})
|
||||
|
||||
# Install version, config files
|
||||
install(FILES ${project_config} ${version_config}
|
||||
DESTINATION ${QUILL_CMAKE_DIR})
|
||||
|
||||
# Install the headers
|
||||
install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/quill DESTINATION ${QUILL_INC_DIR})
|
||||
|
||||
# Install the library
|
||||
install(TARGETS ${TARGET_NAME} EXPORT ${targets_export_name}
|
||||
LIBRARY DESTINATION ${QUILL_LIB_DIR}
|
||||
ARCHIVE DESTINATION ${QUILL_LIB_DIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
# Export the library
|
||||
install(EXPORT ${targets_export_name} DESTINATION ${QUILL_CMAKE_DIR}
|
||||
NAMESPACE quill::)
|
||||
|
||||
# Install the examples
|
||||
if (QUILL_BUILD_EXAMPLES)
|
||||
install(TARGETS
|
||||
quill_example_use_overwrite_macros
|
||||
quill_example_recommended_usage
|
||||
quill_example_backend_thread_notify
|
||||
quill_example_backtrace_logging
|
||||
quill_example_bounded_dropping_queue_frontend
|
||||
quill_example_console_logging
|
||||
quill_example_custom_console_colours
|
||||
quill_example_rotating_file_logging
|
||||
quill_example_signal_handler
|
||||
quill_example_logger_removal_with_file_event_notifier
|
||||
quill_example_custom_frontend_options
|
||||
quill_example_file_logging
|
||||
quill_example_filter_logging
|
||||
quill_example_system_clock_logging
|
||||
quill_example_user_clock_source
|
||||
quill_example_user_defined_filter
|
||||
quill_example_user_defined_sink
|
||||
quill_example_tags_logging
|
||||
quill_example_json_sink_logging
|
||||
quill_example_user_defined_types_logging
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif ()
|
||||
|
||||
# ---- Packaging ---- #
|
||||
set(CPACK_GENERATOR ZIP)
|
||||
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
|
||||
set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR}" "${PROJECT_NAME}" ALL .)
|
||||
set(CPACK_PROJECT_URL "https://github.com/odygrd/quill")
|
||||
set(CPACK_PACKAGE_VENDOR "Odysseas Georgoudis")
|
||||
set(CPACK_PACKAGE_CONTACT "Odysseas Odysseas <odygrd@hotmail.com>")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Asynchronous Low Latency C++ Logging Library")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
||||
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
|
||||
set(CPACK_PACKAGE_RELOCATABLE ON)
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
|
||||
set(CPACK_RPM_PACKAGE_GROUP "System Environment/Libraries")
|
||||
set(CPACK_RPM_PACKAGE_URL ${CPACK_PROJECT_URL})
|
||||
set(CPACK_RPM_PACKAGE_DESCRIPTION "Asynchronous Low Latency C++ Logging Library")
|
||||
include(CPack)
|
||||
endif ()
|
|
@ -1,157 +0,0 @@
|
|||
# 2012-01-31, Lars Bilke
|
||||
# - Enable Code Coverage
|
||||
#
|
||||
# 2013-09-17, Joakim Söderberg
|
||||
# - Added support for Clang.
|
||||
# - Some additional usage instructions.
|
||||
#
|
||||
# 2018-03-31, Bendik Samseth
|
||||
# - Relax debug output.
|
||||
# - Keep a copy of the coverage output for later use.
|
||||
# - Updated coverage exclude patterns.
|
||||
#
|
||||
# 2018-01-03, HenryRLee
|
||||
# - Allow for *Clang compiler names, not just Clang.
|
||||
#
|
||||
# 2018-01-03, Bendik Samseth
|
||||
# - Only check compiler compatibility if in a coverage build.
|
||||
#
|
||||
#
|
||||
# USAGE:
|
||||
|
||||
# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here:
|
||||
# http://stackoverflow.com/a/22404544/80480
|
||||
#
|
||||
# 1. Copy this file into your cmake modules path.
|
||||
#
|
||||
# 2. Add the following line to your CMakeLists.txt:
|
||||
# INCLUDE(CodeCoverage)
|
||||
#
|
||||
# 3. Set compiler flags to turn off optimization and enable coverage:
|
||||
# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
|
||||
# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
|
||||
#
|
||||
# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target
|
||||
# which runs your test executable and produces a lcov code coverage report:
|
||||
# Example:
|
||||
# SETUP_TARGET_FOR_COVERAGE(
|
||||
# my_coverage_target # Name for custom target.
|
||||
# test_driver # Name of the test driver executable that runs the tests.
|
||||
# # NOTE! This should always have a ZERO as exit code
|
||||
# # otherwise the coverage generation will not complete.
|
||||
# coverage # Name of output directory.
|
||||
# )
|
||||
#
|
||||
# 4. Build a Debug build:
|
||||
# cmake -DCMAKE_BUILD_TYPE=Debug ..
|
||||
# make
|
||||
# make my_coverage_target
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
# Param _targetname The name of new the custom make target
|
||||
# Param _testrunner The name of the target which runs the tests.
|
||||
# MUST return ZERO always, even on errors.
|
||||
# If not, no coverage report will be created!
|
||||
# Param _outputname lcov output is generated as _outputname.info
|
||||
# HTML report is generated in _outputname/index.html
|
||||
# Optional fourth parameter is passed as arguments to _testrunner
|
||||
# Pass them in list form, e.g.: "-j;2" for -j 2
|
||||
FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname)
|
||||
|
||||
IF (NOT LCOV_PATH)
|
||||
MESSAGE(FATAL_ERROR "lcov not found! Aborting...")
|
||||
ENDIF () # NOT LCOV_PATH
|
||||
|
||||
IF (NOT GENHTML_PATH)
|
||||
MESSAGE(FATAL_ERROR "genhtml not found! Aborting...")
|
||||
ENDIF () # NOT GENHTML_PATH
|
||||
|
||||
# Setup target
|
||||
ADD_CUSTOM_TARGET(${_targetname}
|
||||
|
||||
# Cleanup lcov
|
||||
${LCOV_PATH} --directory . --zerocounters
|
||||
|
||||
# Run tests
|
||||
COMMAND ${_testrunner} ${ARGV3}
|
||||
|
||||
# Capturing lcov counters and generating report
|
||||
COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info
|
||||
|
||||
COMMAND ${LCOV_PATH} --remove ${_outputname}.info '*/tests/*' '/usr/*' '*/external/*' '/Applications/*' --output-file ${_outputname}.info.cleaned
|
||||
COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned
|
||||
COMMAND ${LCOV_PATH} --list ${_outputname}.info.cleaned
|
||||
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
|
||||
)
|
||||
|
||||
# Show info where to find the report
|
||||
ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD
|
||||
COMMAND ;
|
||||
COMMENT "${BoldMagenta}Open ./${_outputname}/index.html in your browser to view the coverage report.${ColourReset}"
|
||||
)
|
||||
|
||||
ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE
|
||||
|
||||
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" cmake_build_type_tolower)
|
||||
if (cmake_build_type_tolower STREQUAL "coverage")
|
||||
|
||||
|
||||
# Check prereqs
|
||||
FIND_PROGRAM(GCOV_PATH gcov)
|
||||
FIND_PROGRAM(LCOV_PATH lcov)
|
||||
FIND_PROGRAM(GENHTML_PATH genhtml)
|
||||
FIND_PROGRAM(GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests)
|
||||
|
||||
IF (NOT GCOV_PATH)
|
||||
MESSAGE(FATAL_ERROR "gcov not found! Aborting...")
|
||||
ENDIF () # NOT GCOV_PATH
|
||||
|
||||
IF (NOT CMAKE_COMPILER_IS_GNUCXX)
|
||||
IF (NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES ".*Clang")
|
||||
MESSAGE(FATAL_ERROR "Compiler is not GNU gcc or Clang! Aborting...")
|
||||
ENDIF ()
|
||||
ENDIF () # NOT CMAKE_COMPILER_IS_GNUCXX
|
||||
|
||||
SET(CMAKE_CXX_FLAGS_COVERAGE
|
||||
"-g -O0 -fprofile-arcs -ftest-coverage"
|
||||
CACHE STRING "Flags used by the C++ compiler during coverage builds."
|
||||
FORCE)
|
||||
SET(CMAKE_C_FLAGS_COVERAGE
|
||||
"-g -O0 -fprofile-arcs -ftest-coverage"
|
||||
CACHE STRING "Flags used by the C compiler during coverage builds."
|
||||
FORCE)
|
||||
SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
||||
""
|
||||
CACHE STRING "Flags used for linking binaries during coverage builds."
|
||||
FORCE)
|
||||
SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
|
||||
""
|
||||
CACHE STRING "Flags used by the shared libraries linker during coverage builds."
|
||||
FORCE)
|
||||
MARK_AS_ADVANCED(
|
||||
CMAKE_CXX_FLAGS_COVERAGE
|
||||
CMAKE_C_FLAGS_COVERAGE
|
||||
CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
||||
CMAKE_SHARED_LINKER_FLAGS_COVERAGE)
|
||||
|
||||
|
||||
# If unwanted files are included in the coverage reports, you can
|
||||
# adjust the exclude patterns on line 83.
|
||||
SETUP_TARGET_FOR_COVERAGE(
|
||||
coverage # Name for custom target.
|
||||
${TEST_NAME} # Name of the test driver executable that runs the tests.
|
||||
# NOTE! This should always have a ZERO as exit code
|
||||
# otherwise the coverage generation will not complete.
|
||||
coverage_out # Name of output directory.
|
||||
)
|
||||
else ()
|
||||
add_custom_target(coverage
|
||||
COMMAND echo "${Red}Code coverage only available in coverage builds."
|
||||
COMMAND echo "${Green}Make a new build directory and rerun cmake with -DCMAKE_BUILD_TYPE=Coverage to enable this target.${ColorReset}"
|
||||
)
|
||||
endif ()
|
|
@ -1,24 +0,0 @@
|
|||
# Get Quill version from include/quill/Version.h and store it as QUILL_VERSION
|
||||
function(quill_extract_version)
|
||||
file(READ "${CMAKE_CURRENT_LIST_DIR}/quill/include/quill/Backend.h" file_contents)
|
||||
|
||||
string(REGEX MATCH "constexpr uint32_t VersionMajor{([0-9]+)}" _ "${file_contents}")
|
||||
if (NOT CMAKE_MATCH_COUNT EQUAL 1)
|
||||
message(FATAL_ERROR "Failed to extract major version number from quill/Backend.h")
|
||||
endif ()
|
||||
set(version_major ${CMAKE_MATCH_1})
|
||||
|
||||
string(REGEX MATCH "constexpr uint32_t VersionMinor{([0-9]+)}" _ "${file_contents}")
|
||||
if (NOT CMAKE_MATCH_COUNT EQUAL 1)
|
||||
message(FATAL_ERROR "Failed to extract minor version number from quill/Backend.h")
|
||||
endif ()
|
||||
set(version_minor ${CMAKE_MATCH_1})
|
||||
|
||||
string(REGEX MATCH "constexpr uint32_t VersionPatch{([0-9]+)}" _ "${file_contents}")
|
||||
if (NOT CMAKE_MATCH_COUNT EQUAL 1)
|
||||
message(FATAL_ERROR "Failed to extract patch version number from quill/Backend.h")
|
||||
endif ()
|
||||
set(version_patch ${CMAKE_MATCH_1})
|
||||
|
||||
set(QUILL_VERSION "${version_major}.${version_minor}.${version_patch}" PARENT_SCOPE)
|
||||
endfunction()
|
|
@ -1,6 +0,0 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake)
|
||||
check_required_components(quill)
|
|
@ -1,10 +0,0 @@
|
|||
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
|
||||
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||
|
||||
Name: quill
|
||||
Description: Low Latency C++ Logging Library
|
||||
URL: https://github.com/odygrd/quill
|
||||
Version: @QUILL_VERSION@
|
||||
CFlags: -I${includedir}
|
||||
Libs: -L${libdir} -lquill -lpthread
|
||||
Requires: @PKG_CONFIG_REQUIRES@
|
|
@ -1,173 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quill/backend/BackendManager.h"
|
||||
#include "quill/backend/BackendOptions.h"
|
||||
#include "quill/backend/SignalHandler.h"
|
||||
#include "quill/core/Attributes.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <mutex>
|
||||
|
||||
/** Version Info **/
|
||||
constexpr uint32_t VersionMajor{4};
|
||||
constexpr uint32_t VersionMinor{2};
|
||||
constexpr uint32_t VersionPatch{0};
|
||||
constexpr uint32_t Version{VersionMajor * 10000 + VersionMinor * 100 + VersionPatch};
|
||||
|
||||
namespace quill
|
||||
{
|
||||
class Backend
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Starts the backend thread.
|
||||
* @param options Backend options to configure the backend behavior.
|
||||
*/
|
||||
QUILL_ATTRIBUTE_COLD static void start(BackendOptions const& options = BackendOptions{})
|
||||
{
|
||||
std::call_once(detail::BackendManager::instance().get_start_once_flag(),
|
||||
[options]()
|
||||
{
|
||||
// Run the backend worker thread, we wait here until the thread enters the main loop
|
||||
detail::BackendManager::instance().start_backend_thread(options);
|
||||
|
||||
// Setup an exit handler to call stop when the main application exits.
|
||||
// always call stop on destruction to log everything. std::atexit seems to be
|
||||
// working better with dll on windows compared to using ~LogManagerSingleton().
|
||||
std::atexit([]() { detail::BackendManager::instance().stop_backend_thread(); });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the backend thread and initialises a signal handler
|
||||
*
|
||||
* @param options Backend options to configure the backend behavior.
|
||||
* @param catchable_signals List of signals that the backend should catch if with_signal_handler
|
||||
* is enabled.
|
||||
* @param signal_handler_timeout_seconds This variable defines the timeout duration in seconds for
|
||||
* the signal handler alarm. It is only available on Linux, as Windows does not support the alarm
|
||||
* function. The signal handler sets up an alarm to ensure that the process will terminate if it
|
||||
* does not complete within the specified time frame. This is particularly useful to prevent the
|
||||
* process from hanging indefinitely in case the signal handler encounters an issue.
|
||||
*
|
||||
* @note When using the SignalHandler on Linux/MacOS, ensure that each spawned thread in your
|
||||
* application has performed one of the following actions:
|
||||
* i) Logged at least once.
|
||||
* ii) Called Frontend::preallocate().
|
||||
* iii) Blocked signals on that thread to prevent the signal handler from running on it.
|
||||
* This requirement is because the built-in signal handler utilizes a lock-free queue to issue log
|
||||
* statements and await the log flushing. The queue is constructed on its first use with `new()`.
|
||||
* Failing to meet any of the above criteria means the queue was never used, and it will be
|
||||
* constructed inside the signal handler. The `new` operation is not an async signal-safe function
|
||||
* and may lead to potential issues. However, when the queue is already created, no `new` call is
|
||||
* made, and the remaining functions invoked internally by the built-in signal handler are async
|
||||
* safe.
|
||||
*/
|
||||
template <typename TFrontendOptions>
|
||||
QUILL_ATTRIBUTE_COLD static void start_with_signal_handler(
|
||||
BackendOptions const& options = BackendOptions{},
|
||||
QUILL_MAYBE_UNUSED std::initializer_list<int> const& catchable_signals =
|
||||
std::initializer_list<int>{SIGTERM, SIGINT, SIGABRT, SIGFPE, SIGILL, SIGSEGV},
|
||||
uint32_t signal_handler_timeout_seconds = 20u)
|
||||
{
|
||||
std::call_once(detail::BackendManager::instance().get_start_once_flag(),
|
||||
[options, catchable_signals, signal_handler_timeout_seconds]()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
(void)catchable_signals;
|
||||
detail::init_exception_handler<TFrontendOptions>();
|
||||
#else
|
||||
// We do not want signal handler to run in the backend worker thread
|
||||
// Block signals in the main thread so when we spawn the backend worker thread it inherits
|
||||
// the master
|
||||
sigset_t set, oldset;
|
||||
sigfillset(&set);
|
||||
sigprocmask(SIG_SETMASK, &set, &oldset);
|
||||
detail::init_signal_handler<TFrontendOptions>(catchable_signals);
|
||||
#endif
|
||||
|
||||
// Run the backend worker thread, we wait here until the thread enters the main loop
|
||||
detail::BackendManager::instance().start_backend_thread(options);
|
||||
|
||||
detail::SignalHandlerContext::instance().signal_handler_timeout_seconds.store(
|
||||
signal_handler_timeout_seconds);
|
||||
|
||||
// We need to update the signal handler with some backend thread details
|
||||
detail::SignalHandlerContext::instance().backend_thread_id.store(
|
||||
detail::BackendManager::instance().get_backend_thread_id());
|
||||
|
||||
#if defined(_WIN32)
|
||||
// nothing to do
|
||||
#else
|
||||
// Unblock signals in the main thread so subsequent threads do not inherit the blocked mask
|
||||
sigprocmask(SIG_SETMASK, &oldset, nullptr);
|
||||
#endif
|
||||
|
||||
// Set up an exit handler to call stop when the main application exits.
|
||||
// always call stop on destruction to log everything. std::atexit seems to be
|
||||
// working better with dll on windows compared to using ~LogManagerSingleton().
|
||||
std::atexit([]() { detail::BackendManager::instance().stop_backend_thread(); });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the backend thread.
|
||||
* @note thread-safe
|
||||
*/
|
||||
QUILL_ATTRIBUTE_COLD static void stop() noexcept
|
||||
{
|
||||
detail::BackendManager::instance().stop_backend_thread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the backend thread to wake up.
|
||||
* It is possible to use a long backend sleep_duration and then notify the backend to wake up
|
||||
* from any frontend thread.
|
||||
*
|
||||
* @note thread-safe
|
||||
*/
|
||||
static void notify() noexcept { detail::BackendManager::instance().notify_backend_thread(); }
|
||||
|
||||
/**
|
||||
* Checks if the backend is currently running.
|
||||
* @return True if the backend is running, false otherwise.
|
||||
*/
|
||||
QUILL_NODISCARD static bool is_running() noexcept
|
||||
{
|
||||
return detail::BackendManager::instance().is_backend_thread_running();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ID of the backend thread.
|
||||
* @return The ID of the backend thread.
|
||||
*/
|
||||
QUILL_NODISCARD static uint32_t get_thread_id() noexcept
|
||||
{
|
||||
return detail::BackendManager::instance().get_backend_thread_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an rdtsc value to epoch time.
|
||||
* This function uses the same clock as the backend and can be called from any frontend thread.
|
||||
* It is useful when using a logger with ClockSourceType::Tsc and you want to obtain a timestamp
|
||||
* synchronized with the log files generated by the backend.
|
||||
*
|
||||
* Alternatively you can use the Clock class from backend/Clock.h
|
||||
* @param rdtsc_value The RDTSC value to convert.
|
||||
* @return The epoch time corresponding to the RDTSC value.
|
||||
*/
|
||||
QUILL_NODISCARD static uint64_t convert_rdtsc_to_epoch_time(uint64_t rdtsc_value) noexcept
|
||||
{
|
||||
return detail::BackendManager::instance().convert_rdtsc_to_epoch_time(rdtsc_value);
|
||||
}
|
||||
};
|
||||
} // namespace quill
|
|
@ -1,94 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quill/backend/BackendManager.h"
|
||||
#include "quill/core/Attributes.h"
|
||||
#include "quill/core/Rdtsc.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace quill
|
||||
{
|
||||
/**
|
||||
* @brief A utility class for accessing the Time Stamp Counter (TSC) clock used by the backend logging thread.
|
||||
*
|
||||
* This class provides access to the TSC clock maintained by the backend logging thread,
|
||||
* allowing for synchronized timestamp retrieval.
|
||||
*
|
||||
* Other threads can obtain timestamps synchronized with the TSC clock of the backend logging
|
||||
* thread, ensuring synchronization with log statement timestamps.
|
||||
*
|
||||
* If `ClockSourceType::Tsc` is not used by any Logger, this class reverts to using the system clock
|
||||
* for providing a timestamp.
|
||||
*
|
||||
* @note For more accurate timestamps, consider reducing `rdtsc_resync_interval` in `BackendOptions`.
|
||||
* @note All methods of the class are thread-safe.
|
||||
*/
|
||||
class BackendTscClock
|
||||
{
|
||||
public:
|
||||
class RdtscVal
|
||||
{
|
||||
public:
|
||||
RdtscVal(RdtscVal const& other) = default;
|
||||
RdtscVal(RdtscVal&& other) noexcept = default;
|
||||
RdtscVal& operator=(RdtscVal const& other) = default;
|
||||
RdtscVal& operator=(RdtscVal&& other) noexcept = default;
|
||||
|
||||
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT uint64_t value() const noexcept { return _value; }
|
||||
|
||||
private:
|
||||
RdtscVal() noexcept : _value(detail::rdtsc()) {}
|
||||
friend BackendTscClock;
|
||||
|
||||
uint64_t _value;
|
||||
};
|
||||
|
||||
public:
|
||||
using duration = std::chrono::nanoseconds;
|
||||
using rep = duration::rep;
|
||||
using period = duration::period;
|
||||
using time_point = std::chrono::time_point<BackendTscClock, duration>;
|
||||
static constexpr bool is_steady = false;
|
||||
|
||||
/**
|
||||
* Provides the current synchronized timestamp obtained using the TSC clock maintained by the backend logging thread.
|
||||
* @return A wall clock timestamp in nanoseconds since epoch, synchronized with the backend logging thread's TSC clock.
|
||||
*/
|
||||
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static time_point now() noexcept
|
||||
{
|
||||
uint64_t const ts = detail::BackendManager::instance().convert_rdtsc_to_epoch_time(detail::rdtsc());
|
||||
|
||||
return ts ? time_point{std::chrono::nanoseconds{ts}}
|
||||
: time_point{std::chrono::nanoseconds{
|
||||
std::chrono::time_point_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now())
|
||||
.time_since_epoch()
|
||||
.count()}};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the TSC timer maintained by the backend logging thread.
|
||||
* @return The current value of the TSC timer.
|
||||
*/
|
||||
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static RdtscVal rdtsc() noexcept { return RdtscVal{}; }
|
||||
|
||||
/**
|
||||
* Converts a TSC counter value obtained from the backend logging thread's TSC timer to a wall
|
||||
* clock timestamp.
|
||||
*
|
||||
* @param rdtsc The TSC counter value obtained from the backend logging thread's TSC timer.
|
||||
* @warning This function will return `0` when TimestampClockType::Tsc is not enabled in Config.h.
|
||||
* @return Time since epoch in nanoseconds.
|
||||
*/
|
||||
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT static time_point to_time_point(RdtscVal rdtsc) noexcept
|
||||
{
|
||||
return time_point{std::chrono::nanoseconds{
|
||||
detail::BackendManager::instance().convert_rdtsc_to_epoch_time(rdtsc.value())}};
|
||||
}
|
||||
};
|
||||
} // namespace quill
|
|
@ -1,211 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quill/Logger.h"
|
||||
#include "quill/UserClockSource.h"
|
||||
#include "quill/core/Attributes.h"
|
||||
#include "quill/core/Common.h"
|
||||
#include "quill/core/FrontendOptions.h"
|
||||
#include "quill/core/LoggerManager.h"
|
||||
#include "quill/core/QuillError.h"
|
||||
#include "quill/core/SinkManager.h"
|
||||
#include "quill/core/ThreadContextManager.h"
|
||||
#include "quill/sinks/Sink.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace quill
|
||||
{
|
||||
template <typename TFrontendOptions>
|
||||
class FrontendImpl
|
||||
{
|
||||
public:
|
||||
using frontend_options_t = TFrontendOptions;
|
||||
using logger_t = LoggerImpl<frontend_options_t>;
|
||||
|
||||
/**
|
||||
* @brief Pre-allocates the thread-local data needed for the current thread.
|
||||
*
|
||||
* Although optional, it is recommended to invoke this function during the thread initialization
|
||||
* phase before the first log message.
|
||||
*/
|
||||
QUILL_ATTRIBUTE_COLD static void preallocate()
|
||||
{
|
||||
QUILL_MAYBE_UNUSED uint32_t const volatile spsc_queue_capacity =
|
||||
detail::get_local_thread_context<TFrontendOptions>()
|
||||
->template get_spsc_queue<TFrontendOptions::queue_type>()
|
||||
.capacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new sink or retrieves an existing one with the specified name.
|
||||
*
|
||||
* @param sink_name The name of the sink.
|
||||
* @param args The arguments to pass to the sink constructor.
|
||||
* @return std::shared_ptr<Sink> A shared pointer to the created or retrieved sink.
|
||||
*/
|
||||
template <typename TSink, typename... Args>
|
||||
static std::shared_ptr<Sink> create_or_get_sink(std::string const& sink_name, Args&&... args)
|
||||
{
|
||||
return detail::SinkManager::instance().create_or_get_sink<TSink>(sink_name, static_cast<Args&&>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves an existing sink with the specified name.
|
||||
*
|
||||
* @param sink_name The name of the sink.
|
||||
* @return std::shared_ptr<Sink> A shared pointer to the retrieved sink, or nullptr if not found.
|
||||
*/
|
||||
QUILL_NODISCARD static std::shared_ptr<Sink> get_sink(std::string const& sink_name)
|
||||
{
|
||||
return detail::SinkManager::instance().get_sink(sink_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new logger or retrieves an existing one with the specified name.
|
||||
*
|
||||
* @param logger_name The name of the logger.
|
||||
* @param sink A shared pointer to the sink to associate with the logger.
|
||||
* @param format_pattern The format pattern for log messages.
|
||||
* @param time_pattern The time pattern for log timestamps.
|
||||
* @param timestamp_timezone The timezone for log timestamps.
|
||||
* @param clock_source The clock source for log timestamps.
|
||||
* @param user_clock A pointer to a custom user clock.
|
||||
* @return Logger* A pointer to the created or retrieved logger.
|
||||
*/
|
||||
static logger_t* create_or_get_logger(
|
||||
std::string const& logger_name, std::shared_ptr<Sink> sink,
|
||||
std::string const& format_pattern =
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) "
|
||||
"%(message)",
|
||||
std::string const& time_pattern = "%H:%M:%S.%Qns", Timezone timestamp_timezone = Timezone::LocalTime,
|
||||
ClockSourceType clock_source = ClockSourceType::Tsc, UserClockSource* user_clock = nullptr)
|
||||
{
|
||||
std::vector<std::shared_ptr<Sink>> sinks;
|
||||
sinks.push_back(static_cast<std::shared_ptr<Sink>&&>(sink));
|
||||
|
||||
return _cast_to_logger(detail::LoggerManager::instance().create_or_get_logger<logger_t>(
|
||||
logger_name, static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks), format_pattern,
|
||||
time_pattern, timestamp_timezone, clock_source, user_clock));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new logger or retrieves an existing one with the specified name and multiple sinks.
|
||||
*
|
||||
* @param logger_name The name of the logger.
|
||||
* @param sinks An initializer list of shared pointers to sinks to associate with the logger.
|
||||
* @param format_pattern The format pattern for log messages.
|
||||
* @param time_pattern The time pattern for log timestamps.
|
||||
* @param timestamp_timezone The timezone for log timestamps.
|
||||
* @param clock_source The clock source for log timestamps.
|
||||
* @param user_clock A pointer to a custom user clock.
|
||||
* @return Logger* A pointer to the created or retrieved logger.
|
||||
*/
|
||||
static logger_t* create_or_get_logger(
|
||||
std::string const& logger_name, std::initializer_list<std::shared_ptr<Sink>> sinks,
|
||||
std::string const& format_pattern =
|
||||
"%(time) [%(thread_id)] %(short_source_location:<28) LOG_%(log_level:<9) %(logger:<12) "
|
||||
"%(message)",
|
||||
std::string const& time_pattern = "%H:%M:%S.%Qns", Timezone timestamp_timezone = Timezone::LocalTime,
|
||||
ClockSourceType clock_source = ClockSourceType::Tsc, UserClockSource* user_clock = nullptr)
|
||||
{
|
||||
return _cast_to_logger(detail::LoggerManager::instance().create_or_get_logger<logger_t>(
|
||||
logger_name, std::vector<std::shared_ptr<Sink>>{sinks}, format_pattern, time_pattern,
|
||||
timestamp_timezone, clock_source, user_clock));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Asynchronously removes the specified logger.
|
||||
* When a logger is removed, any files associated with its sinks are also closed.
|
||||
*
|
||||
* @note Thread-safe
|
||||
* @param logger A pointer to the logger to remove.
|
||||
*/
|
||||
static void remove_logger(detail::LoggerBase* logger)
|
||||
{
|
||||
detail::LoggerManager::instance().remove_logger(logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves an existing logger with the specified name.
|
||||
*
|
||||
* @param logger_name The name of the logger.
|
||||
* @return Logger* A pointer to the retrieved logger, or nullptr if not found.
|
||||
*/
|
||||
QUILL_NODISCARD static logger_t* get_logger(std::string const& logger_name)
|
||||
{
|
||||
detail::LoggerBase* logger = detail::LoggerManager::instance().get_logger(logger_name);
|
||||
return logger ? _cast_to_logger(logger) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves a map of all registered valid loggers.
|
||||
* @note If `remove_logger()` is called from this or another thread, the return value
|
||||
* of this function will become invalid.
|
||||
* @return A vector containing all registered loggers.
|
||||
*/
|
||||
QUILL_NODISCARD static std::vector<logger_t*> get_all_loggers()
|
||||
{
|
||||
std::vector<detail::LoggerBase*> logger_bases = detail::LoggerManager::instance().get_all_loggers();
|
||||
|
||||
std::vector<logger_t*> loggers;
|
||||
|
||||
for (auto const& logger_base : logger_bases)
|
||||
{
|
||||
loggers.push_back(_cast_to_logger(logger_base));
|
||||
}
|
||||
|
||||
return loggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first valid logger that is found. This is useful when you do not want to use the
|
||||
* std::vector<logger_t*> return value of get_all_loggers.
|
||||
*
|
||||
* @return A pointer to the first valid logger, or nullptr if no valid logger is found.
|
||||
*/
|
||||
QUILL_NODISCARD static logger_t* get_valid_logger() noexcept
|
||||
{
|
||||
detail::LoggerBase* logger = detail::LoggerManager::instance().get_valid_logger();
|
||||
return logger ? _cast_to_logger(logger) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Counts the number of existing loggers, including any invalidated loggers.
|
||||
* This function can be useful for verifying if a logger has been removed after calling
|
||||
* remove_logger() by the backend, as removal occurs asynchronously.
|
||||
* @return The number of loggers.
|
||||
*/
|
||||
QUILL_NODISCARD static size_t get_number_of_loggers() noexcept
|
||||
{
|
||||
return detail::LoggerManager::instance().get_number_of_loggers();
|
||||
}
|
||||
|
||||
private:
|
||||
QUILL_NODISCARD static logger_t* _cast_to_logger(detail::LoggerBase* logger_base)
|
||||
{
|
||||
assert(logger_base);
|
||||
|
||||
auto* logger = dynamic_cast<logger_t*>(logger_base);
|
||||
|
||||
if (QUILL_UNLIKELY(!logger))
|
||||
{
|
||||
QUILL_THROW(QuillError{"Failed to cast logger. Invalid logger type."});
|
||||
}
|
||||
|
||||
return logger;
|
||||
}
|
||||
};
|
||||
|
||||
using Frontend = FrontendImpl<FrontendOptions>;
|
||||
} // namespace quill
|
|
@ -1,284 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quill/core/LogLevel.h"
|
||||
#include "quill/core/MacroMetadata.h"
|
||||
|
||||
/**
|
||||
* Allows compile-time filtering of log messages to completely compile out log levels,
|
||||
* resulting in zero-cost logging.
|
||||
*
|
||||
* Macros like LOG_TRACE_L3(..), LOG_TRACE_L2(..) will expand to empty statements,
|
||||
* reducing branches in compiled code and the number of MacroMetadata constexpr instances.
|
||||
*
|
||||
* The default value of -1 enables all log levels.
|
||||
* Specify a logging level to disable all levels equal to or higher than the specified level.
|
||||
*
|
||||
* For example to only log warnings and above you can use:
|
||||
* add_compile_definitions(-DQUILL_COMPILE_ACTIVE_LOG_LEVEL=QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING)
|
||||
* or
|
||||
* target_compile_definitions(${TARGET} PRIVATE -DQUILL_COMPILE_ACTIVE_LOG_LEVEL=QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING)
|
||||
**/
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L3 0
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L2 1
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L1 2
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_DEBUG 3
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_INFO 4
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING 5
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_ERROR 6
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL_CRITICAL 7
|
||||
|
||||
#if !defined(QUILL_COMPILE_ACTIVE_LOG_LEVEL)
|
||||
#define QUILL_COMPILE_ACTIVE_LOG_LEVEL -1
|
||||
#endif
|
||||
|
||||
#define QUILL_DEFINE_MACRO_METADATA(caller_function, fmt, tags, log_level) \
|
||||
static constexpr quill::MacroMetadata macro_metadata \
|
||||
{ \
|
||||
__FILE__ ":" QUILL_STRINGIFY(__LINE__), caller_function, fmt, tags, log_level, \
|
||||
quill::MacroMetadata::Event::Log \
|
||||
}
|
||||
|
||||
#define QUILL_LOGGER_CALL(likelyhood, logger, log_level, fmt, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (likelyhood(logger->template should_log_message<log_level>())) \
|
||||
{ \
|
||||
QUILL_DEFINE_MACRO_METADATA(__FUNCTION__, fmt, nullptr, log_level); \
|
||||
\
|
||||
logger->log_message(quill::LogLevel::None, ¯o_metadata, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define QUILL_LOGGER_CALL_WITH_TAGS(likelyhood, logger, log_level, tags, fmt, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (likelyhood(logger->template should_log_message<log_level>())) \
|
||||
{ \
|
||||
QUILL_DEFINE_MACRO_METADATA(__FUNCTION__, fmt, &tags, log_level); \
|
||||
\
|
||||
logger->log_message(quill::LogLevel::None, ¯o_metadata, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define QUILL_LOGGER_CALL_LIMIT(min_interval, likelyhood, logger, log_level, fmt, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (likelyhood(logger->template should_log_message<log_level>())) \
|
||||
{ \
|
||||
thread_local std::chrono::time_point<std::chrono::steady_clock> next_log_time; \
|
||||
auto const now = std::chrono::steady_clock::now(); \
|
||||
\
|
||||
if (now < next_log_time) \
|
||||
{ \
|
||||
break; \
|
||||
} \
|
||||
\
|
||||
next_log_time = now + min_interval; \
|
||||
QUILL_LOGGER_CALL(likelyhood, logger, log_level, fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define QUILL_BACKTRACE_LOGGER_CALL(logger, fmt, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (QUILL_LIKELY(logger->template should_log_message<quill::LogLevel::Backtrace>())) \
|
||||
{ \
|
||||
QUILL_DEFINE_MACRO_METADATA(__FUNCTION__, fmt, nullptr, quill::LogLevel::Backtrace); \
|
||||
\
|
||||
logger->log_message(quill::LogLevel::None, ¯o_metadata, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Dynamic runtime log level with a tiny overhead
|
||||
* @Note: Prefer using the compile time log level macros
|
||||
*/
|
||||
#define QUILL_DYNAMIC_LOG_CALL(logger, log_level, fmt, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (logger->should_log_message(log_level)) \
|
||||
{ \
|
||||
QUILL_DEFINE_MACRO_METADATA(__FUNCTION__, fmt, nullptr, quill::LogLevel::Dynamic); \
|
||||
\
|
||||
logger->log_message(log_level, ¯o_metadata, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define QUILL_DYNAMIC_LOG(logger, log_level, fmt, ...) \
|
||||
QUILL_DYNAMIC_LOG_CALL(logger, log_level, fmt, ##__VA_ARGS__)
|
||||
|
||||
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L3
|
||||
#define QUILL_LOG_TRACE_L3(logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL3, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_TRACE_L3_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_UNLIKELY, logger, quill::LogLevel::TraceL3, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_TRACE_L3_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL3, tags, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define QUILL_LOG_TRACE_L3(logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_TRACE_L3_LIMIT(min_interval, logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_TRACE_L3_WITH_TAGS(logger, tags, fmt, ...) (void)0
|
||||
#endif
|
||||
|
||||
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L2
|
||||
#define QUILL_LOG_TRACE_L2(logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL2, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_TRACE_L2_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_UNLIKELY, logger, quill::LogLevel::TraceL2, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_TRACE_L2_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL2, tags, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define QUILL_LOG_TRACE_L2(logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_TRACE_L2_LIMIT(min_interval, logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_TRACE_L2_WITH_TAGS(logger, tags, fmt, ...) (void)0
|
||||
#endif
|
||||
|
||||
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_TRACE_L1
|
||||
#define QUILL_LOG_TRACE_L1(logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL1, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_TRACE_L1_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_UNLIKELY, logger, quill::LogLevel::TraceL1, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_TRACE_L1_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_UNLIKELY, logger, quill::LogLevel::TraceL1, tags, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define QUILL_LOG_TRACE_L1(logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_TRACE_L1_LIMIT(min_interval, logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_TRACE_L1_WITH_TAGS(logger, tags, fmt, ...) (void)0
|
||||
#endif
|
||||
|
||||
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_DEBUG
|
||||
#define QUILL_LOG_DEBUG(logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL(QUILL_UNLIKELY, logger, quill::LogLevel::Debug, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_DEBUG_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_UNLIKELY, logger, quill::LogLevel::Debug, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_DEBUG_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_UNLIKELY, logger, quill::LogLevel::Debug, tags, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define QUILL_LOG_DEBUG(logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_DEBUG_LIMIT(min_interval, logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_DEBUG_WITH_TAGS(logger, tags, fmt, ...) (void)0
|
||||
#endif
|
||||
|
||||
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_INFO
|
||||
#define QUILL_LOG_INFO(logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL(QUILL_LIKELY, logger, quill::LogLevel::Info, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_INFO_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_LIKELY, logger, quill::LogLevel::Info, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_INFO_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_LIKELY, logger, quill::LogLevel::Info, tags, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define QUILL_LOG_INFO(logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_INFO_LIMIT(min_interval, logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_INFO_WITH_TAGS(logger, tags, fmt, ...) (void)0
|
||||
#endif
|
||||
|
||||
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_WARNING
|
||||
#define QUILL_LOG_WARNING(logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL(QUILL_LIKELY, logger, quill::LogLevel::Warning, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_WARNING_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_LIKELY, logger, quill::LogLevel::Warning, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_WARNING_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_LIKELY, logger, quill::LogLevel::Warning, tags, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define QUILL_LOG_WARNING(logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_WARNING_LIMIT(min_interval, logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_WARNING_WITH_TAGS(logger, tags, fmt, ...) (void)0
|
||||
#endif
|
||||
|
||||
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_ERROR
|
||||
#define QUILL_LOG_ERROR(logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL(QUILL_LIKELY, logger, quill::LogLevel::Error, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_ERROR_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_LIKELY, logger, quill::LogLevel::Error, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_ERROR_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_LIKELY, logger, quill::LogLevel::Error, tags, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define QUILL_LOG_ERROR(logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_ERROR_LIMIT(min_interval, logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_ERROR_WITH_TAGS(logger, tags, fmt, ...) (void)0
|
||||
#endif
|
||||
|
||||
#if QUILL_COMPILE_ACTIVE_LOG_LEVEL <= QUILL_COMPILE_ACTIVE_LOG_LEVEL_CRITICAL
|
||||
#define QUILL_LOG_CRITICAL(logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL(QUILL_LIKELY, logger, quill::LogLevel::Critical, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_CRITICAL_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_LIMIT(min_interval, QUILL_LIKELY, logger, quill::LogLevel::Critical, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define QUILL_LOG_CRITICAL_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOGGER_CALL_WITH_TAGS(QUILL_LIKELY, logger, quill::LogLevel::Critical, tags, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define QUILL_LOG_CRITICAL(logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_CRITICAL_LIMIT(min_interval, logger, fmt, ...) (void)0
|
||||
#define QUILL_LOG_CRITICAL_WITH_TAGS(logger, tags, fmt, ...) (void)0
|
||||
#endif
|
||||
|
||||
#define QUILL_LOG_BACKTRACE(logger, fmt, ...) \
|
||||
QUILL_BACKTRACE_LOGGER_CALL(logger, fmt, ##__VA_ARGS__)
|
||||
|
||||
#if !defined(QUILL_DISABLE_NON_PREFIXED_MACROS)
|
||||
#define LOG_TRACE_L3(logger, fmt, ...) QUILL_LOG_TRACE_L3(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_TRACE_L2(logger, fmt, ...) QUILL_LOG_TRACE_L2(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_TRACE_L1(logger, fmt, ...) QUILL_LOG_TRACE_L1(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DEBUG(logger, fmt, ...) QUILL_LOG_DEBUG(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_INFO(logger, fmt, ...) QUILL_LOG_INFO(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_WARNING(logger, fmt, ...) QUILL_LOG_WARNING(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_ERROR(logger, fmt, ...) QUILL_LOG_ERROR(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_CRITICAL(logger, fmt, ...) QUILL_LOG_CRITICAL(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_BACKTRACE(logger, fmt, ...) QUILL_LOG_BACKTRACE(logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DYNAMIC(logger, log_level, fmt, ...) \
|
||||
QUILL_DYNAMIC_LOG(logger, log_level, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_TRACE_L3_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOG_TRACE_L3_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_TRACE_L2_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOG_TRACE_L2_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_TRACE_L1_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOG_TRACE_L1_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DEBUG_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOG_DEBUG_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_INFO_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOG_INFO_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_WARNING_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOG_WARNING_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_ERROR_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOG_ERROR_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
|
||||
#define LOG_CRITICAL_LIMIT(min_interval, logger, fmt, ...) \
|
||||
QUILL_LOG_CRITICAL_LIMIT(min_interval, logger, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_TRACE_L3_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOG_TRACE_L3_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
|
||||
#define LOG_TRACE_L2_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOG_TRACE_L2_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
|
||||
#define LOG_TRACE_L1_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOG_TRACE_L1_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DEBUG_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOG_DEBUG_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
|
||||
#define LOG_INFO_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOG_INFO_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
|
||||
#define LOG_WARNING_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOG_WARNING_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
|
||||
#define LOG_ERROR_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOG_ERROR_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
|
||||
#define LOG_CRITICAL_WITH_TAGS(logger, tags, fmt, ...) \
|
||||
QUILL_LOG_CRITICAL_WITH_TAGS(logger, tags, fmt, ##__VA_ARGS__)
|
||||
#endif
|
|
@ -1,320 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quill/UserClockSource.h"
|
||||
#include "quill/core/Attributes.h"
|
||||
#include "quill/core/Codec.h"
|
||||
#include "quill/core/Common.h"
|
||||
#include "quill/core/FrontendOptions.h"
|
||||
#include "quill/core/LogLevel.h"
|
||||
#include "quill/core/LoggerBase.h"
|
||||
#include "quill/core/MacroMetadata.h"
|
||||
#include "quill/core/Rdtsc.h"
|
||||
#include "quill/core/ThreadContextManager.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace quill
|
||||
{
|
||||
|
||||
/** Forward Declarations **/
|
||||
class Sink;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
class LoggerManager;
|
||||
class BackendWorker;
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @brief Thread safe logger.
|
||||
*
|
||||
* Logger must be obtained from create_or_get_logger(), therefore constructors are private
|
||||
*/
|
||||
template <typename TFrontendOptions>
|
||||
class LoggerImpl : public detail::LoggerBase
|
||||
{
|
||||
public:
|
||||
using frontend_options_t = TFrontendOptions;
|
||||
|
||||
/***/
|
||||
LoggerImpl(LoggerImpl const&) = delete;
|
||||
LoggerImpl& operator=(LoggerImpl const&) = delete;
|
||||
|
||||
~LoggerImpl() override = default;
|
||||
|
||||
/**
|
||||
* Push a log message to the spsc queue to be logged by the backend thread.
|
||||
* One spsc queue per caller thread. This function is enabled only when all arguments are
|
||||
* fundamental types.
|
||||
* This is the fastest way possible to log
|
||||
* @note This function is thread-safe.
|
||||
* @param dynamic_log_level dynamic log level
|
||||
* @param macro_metadata metadata of the log message
|
||||
* @param fmt_args arguments
|
||||
*
|
||||
* @return true if the message is written to the queue, false if it is dropped (when a dropping queue is used)
|
||||
*/
|
||||
template <typename... Args>
|
||||
QUILL_ATTRIBUTE_HOT bool log_message(LogLevel dynamic_log_level,
|
||||
MacroMetadata const* macro_metadata, Args&&... fmt_args)
|
||||
{
|
||||
assert(valid.load(std::memory_order_acquire) && "Invalidated loggers can not log");
|
||||
|
||||
// Store the timestamp of the log statement at the start of the call. This gives more accurate
|
||||
// timestamp especially if the queue is full
|
||||
uint64_t const current_timestamp = (clock_source == ClockSourceType::Tsc) ? detail::rdtsc()
|
||||
: (clock_source == ClockSourceType::System)
|
||||
? static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count())
|
||||
: user_clock->now();
|
||||
|
||||
detail::ThreadContext* const thread_context =
|
||||
quill::detail::get_local_thread_context<frontend_options_t>();
|
||||
|
||||
// Need to reserve additional space as we will be aligning the pointer
|
||||
size_t total_size = sizeof(current_timestamp) + (sizeof(uintptr_t) * 3) +
|
||||
detail::calculate_args_size_and_populate_string_lengths(
|
||||
thread_context->get_conditional_arg_size_cache(), fmt_args...);
|
||||
|
||||
if (dynamic_log_level != LogLevel::None)
|
||||
{
|
||||
// For the dynamic log level we want to add to the total size to store the dynamic log level
|
||||
total_size += sizeof(dynamic_log_level);
|
||||
}
|
||||
|
||||
constexpr bool is_unbounded_queue = (frontend_options_t::queue_type == QueueType::UnboundedUnlimited) ||
|
||||
(frontend_options_t::queue_type == QueueType::UnboundedBlocking) ||
|
||||
(frontend_options_t::queue_type == QueueType::UnboundedDropping);
|
||||
|
||||
std::byte* write_buffer;
|
||||
|
||||
if constexpr (is_unbounded_queue)
|
||||
{
|
||||
write_buffer = thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(
|
||||
static_cast<uint32_t>(total_size), frontend_options_t::queue_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_buffer = thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(
|
||||
static_cast<uint32_t>(total_size));
|
||||
}
|
||||
|
||||
if constexpr (frontend_options_t::queue_type == QueueType::UnboundedUnlimited)
|
||||
{
|
||||
assert(write_buffer &&
|
||||
"Unbounded unlimited queue will always allocate and have enough capacity");
|
||||
}
|
||||
else if constexpr ((frontend_options_t::queue_type == QueueType::BoundedDropping) ||
|
||||
(frontend_options_t::queue_type == QueueType::UnboundedDropping))
|
||||
{
|
||||
if (QUILL_UNLIKELY(write_buffer == nullptr))
|
||||
{
|
||||
// not enough space to push to queue message is dropped
|
||||
thread_context->increment_failure_counter();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if constexpr ((frontend_options_t::queue_type == QueueType::BoundedBlocking) ||
|
||||
(frontend_options_t::queue_type == QueueType::UnboundedBlocking))
|
||||
{
|
||||
if (QUILL_UNLIKELY(write_buffer == nullptr))
|
||||
{
|
||||
thread_context->increment_failure_counter();
|
||||
|
||||
do
|
||||
{
|
||||
if constexpr (frontend_options_t::blocking_queue_retry_interval_ns > 0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds{frontend_options_t::blocking_queue_retry_interval_ns});
|
||||
}
|
||||
|
||||
// not enough space to push to queue, keep trying
|
||||
if constexpr (is_unbounded_queue)
|
||||
{
|
||||
write_buffer = thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(
|
||||
static_cast<uint32_t>(total_size), frontend_options_t::queue_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_buffer = thread_context->get_spsc_queue<frontend_options_t::queue_type>().prepare_write(
|
||||
static_cast<uint32_t>(total_size));
|
||||
}
|
||||
} while (write_buffer == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// we have enough space in this buffer, and we will write to the buffer
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::byte const* const write_begin = write_buffer;
|
||||
assert(write_begin);
|
||||
#endif
|
||||
|
||||
std::memcpy(write_buffer, ¤t_timestamp, sizeof(current_timestamp));
|
||||
write_buffer += sizeof(current_timestamp);
|
||||
|
||||
std::memcpy(write_buffer, ¯o_metadata, sizeof(uintptr_t));
|
||||
write_buffer += sizeof(uintptr_t);
|
||||
|
||||
detail::LoggerBase* logger_context = this;
|
||||
std::memcpy(write_buffer, &logger_context, sizeof(uintptr_t));
|
||||
write_buffer += sizeof(uintptr_t);
|
||||
|
||||
detail::FormatArgsDecoder ftf = detail::decode_and_populate_format_args<detail::remove_cvref_t<Args>...>;
|
||||
std::memcpy(write_buffer, &ftf, sizeof(uintptr_t));
|
||||
write_buffer += sizeof(uintptr_t);
|
||||
|
||||
// encode remaining arguments
|
||||
detail::encode(write_buffer, thread_context->get_conditional_arg_size_cache(), fmt_args...);
|
||||
|
||||
if (dynamic_log_level != LogLevel::None)
|
||||
{
|
||||
// write the dynamic log level
|
||||
// The reason we write it last is that is less likely to break the alignment in the buffer
|
||||
std::memcpy(write_buffer, &dynamic_log_level, sizeof(dynamic_log_level));
|
||||
write_buffer += sizeof(dynamic_log_level);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
assert((write_buffer > write_begin) && "write_buffer must be greater than write_begin");
|
||||
assert(total_size == (static_cast<uint32_t>(write_buffer - write_begin)) &&
|
||||
"The committed write bytes must be equal to the total_size requested bytes");
|
||||
#endif
|
||||
|
||||
thread_context->get_spsc_queue<frontend_options_t::queue_type>().finish_write(static_cast<uint32_t>(total_size));
|
||||
thread_context->get_spsc_queue<frontend_options_t::queue_type>().commit_write();
|
||||
thread_context->get_conditional_arg_size_cache().clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init a backtrace for this logger.
|
||||
* Stores messages logged with LOG_BACKTRACE in a ring buffer messages and displays them later on demand.
|
||||
* @param max_capacity The max number of messages to store in the backtrace
|
||||
* @param flush_level If this loggers logs any message higher or equal to this severity level the backtrace will also get flushed.
|
||||
* Default level is None meaning the user has to call flush_backtrace explicitly
|
||||
*/
|
||||
void init_backtrace(uint32_t max_capacity, LogLevel flush_level = LogLevel::None)
|
||||
{
|
||||
// we do not care about the other fields, except quill::MacroMetadata::Event::InitBacktrace
|
||||
static constexpr MacroMetadata macro_metadata{
|
||||
"", "", "{}", nullptr, LogLevel::Critical, MacroMetadata::Event::InitBacktrace};
|
||||
|
||||
// we pass this message to the queue and also pass capacity as arg
|
||||
// We do not want to drop the message if a dropping queue is used
|
||||
while (!this->log_message(LogLevel::None, ¯o_metadata, max_capacity))
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds{100});
|
||||
}
|
||||
|
||||
// Also store the desired flush log level
|
||||
backtrace_flush_level.store(flush_level, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump any stored backtrace messages
|
||||
*/
|
||||
void flush_backtrace()
|
||||
{
|
||||
// we do not care about the other fields, except quill::MacroMetadata::Event::Flush
|
||||
static constexpr MacroMetadata macro_metadata{
|
||||
"", "", "", nullptr, LogLevel::Critical, MacroMetadata::Event::FlushBacktrace};
|
||||
|
||||
// We do not want to drop the message if a dropping queue is used
|
||||
while (!this->log_message(LogLevel::None, ¯o_metadata))
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds{100});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks the calling thread until all log messages up to the current timestamp are flushed.
|
||||
*
|
||||
* The backend thread will invoke the write operation on all sinks for all loggers up to the point
|
||||
* (timestamp) when this function is invoked.
|
||||
*
|
||||
* @param sleep_duration_ns The duration in nanoseconds to sleep between retries when the
|
||||
* blocking queue is full, and between checks for the flush completion. Default is 100 nanoseconds.
|
||||
*
|
||||
* @note This function should only be called when the backend worker is running after Backend::start(...)
|
||||
* @note This function will block the calling thread until the flush message is processed by the backend thread.
|
||||
* The calling thread can block for up to backend_options.sleep_duration. If you configure a custom
|
||||
* long sleep duration on the backend thread, e.g., backend_options.sleep_duration = std::chrono::minutes{1},
|
||||
* then you should ideally avoid calling this function as you can block for long period of times unless
|
||||
* you use another thread that calls Backend::notify()
|
||||
*/
|
||||
void flush_log(uint32_t sleep_duration_ns = 100)
|
||||
{
|
||||
static constexpr MacroMetadata macro_metadata{
|
||||
"", "", "", nullptr, LogLevel::Critical, MacroMetadata::Event::Flush};
|
||||
|
||||
std::atomic<bool> backend_thread_flushed{false};
|
||||
std::atomic<bool>* backend_thread_flushed_ptr = &backend_thread_flushed;
|
||||
|
||||
// We do not want to drop the message if a dropping queue is used
|
||||
while (!this->log_message(LogLevel::None, ¯o_metadata, reinterpret_cast<uintptr_t>(backend_thread_flushed_ptr)))
|
||||
{
|
||||
if (sleep_duration_ns > 0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds{sleep_duration_ns});
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
// The caller thread keeps checking the flag until the backend thread flushes
|
||||
while (!backend_thread_flushed.load())
|
||||
{
|
||||
if (sleep_duration_ns > 0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds{sleep_duration_ns});
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend class detail::LoggerManager;
|
||||
friend class detail::BackendWorker;
|
||||
|
||||
/***/
|
||||
LoggerImpl(std::string logger_name, std::vector<std::shared_ptr<Sink>> sinks,
|
||||
std::string format_pattern, std::string time_pattern, Timezone timestamp_timezone,
|
||||
ClockSourceType clock_source, UserClockSource* user_clock)
|
||||
: detail::LoggerBase(static_cast<std::string&&>(logger_name),
|
||||
static_cast<std::vector<std::shared_ptr<Sink>>&&>(sinks),
|
||||
static_cast<std::string&&>(format_pattern),
|
||||
static_cast<std::string&&>(time_pattern), timestamp_timezone, clock_source, user_clock)
|
||||
|
||||
{
|
||||
if (this->user_clock)
|
||||
{
|
||||
// if a user clock is provided then set the ClockSourceType to User
|
||||
this->clock_source = ClockSourceType::User;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using Logger = LoggerImpl<FrontendOptions>;
|
||||
} // namespace quill
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quill/core/Attributes.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace quill
|
||||
{
|
||||
/**
|
||||
* @brief Base class that provides a timestamp for log statements based on a user-provided clock source.
|
||||
*
|
||||
* This base class can be derived from to pass a user-generated timestamp to a Logger.
|
||||
*
|
||||
* It is particularly useful for simulations or scenarios where time manipulation is necessary,
|
||||
* such as simulating time in the past and displaying past timestamps in logs.
|
||||
*
|
||||
* @note The derived class must be thread-safe if the Logger object is used across multiple threads.
|
||||
* If a Logger is used within a single thread only, thread safety is not a concern.
|
||||
*/
|
||||
class UserClockSource
|
||||
{
|
||||
public:
|
||||
UserClockSource() = default;
|
||||
virtual ~UserClockSource() = default;
|
||||
|
||||
UserClockSource(UserClockSource const&) = delete;
|
||||
UserClockSource& operator=(UserClockSource const&) = delete;
|
||||
|
||||
/**
|
||||
* Returns time since epoch in nanoseconds
|
||||
*/
|
||||
QUILL_NODISCARD QUILL_ATTRIBUTE_HOT virtual uint64_t now() const = 0;
|
||||
};
|
||||
} // namespace quill
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
#include "quill/core/Attributes.h"
|
||||
#include "quill/core/Common.h"
|
||||
|
||||
/**
|
||||
* Contains useful utilities to assist with logging
|
||||
*/
|
||||
namespace quill::utility
|
||||
{
|
||||
/**
|
||||
* @brief Formats the given buffer to hexadecimal representation.
|
||||
*
|
||||
* This function converts the contents of the input buffer to a hexadecimal string.
|
||||
*
|
||||
* @param buffer Pointer to the input buffer.
|
||||
* @param size Size of the input buffer.
|
||||
* @return A string containing the hexadecimal representation of the given buffer.
|
||||
*/
|
||||
template <typename T>
|
||||
QUILL_NODISCARD std::string to_hex(T* buffer, size_t size) noexcept
|
||||
{
|
||||
static constexpr char hex_chars[] = "0123456789ABCDEF";
|
||||
|
||||
std::string hex_string;
|
||||
hex_string.reserve(3 * size);
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
// 00001111 mask
|
||||
static constexpr uint8_t mask = 0x0Fu;
|
||||
|
||||
// add the first four bits
|
||||
hex_string += hex_chars[(buffer[i] >> 4u) & mask];
|
||||
|
||||
// add the remaining bits
|
||||
hex_string += hex_chars[buffer[i] & mask];
|
||||
|
||||
if (i != (size - 1))
|
||||
{
|
||||
// add a space delimiter
|
||||
hex_string += ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return hex_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper class for combining quill::Tag objects.
|
||||
* This class combines multiple quill::Tag objects into a single Tag object.
|
||||
*/
|
||||
template <typename... TTags>
|
||||
class CombinedTags : public Tags
|
||||
{
|
||||
public:
|
||||
constexpr CombinedTags(TTags... tags, std::string_view delim = ", ")
|
||||
: _tags(std::move(tags)...), _delim(delim)
|
||||
{
|
||||
}
|
||||
|
||||
void format(std::string& out) const override
|
||||
{
|
||||
std::apply([&out, this](const auto&... tags)
|
||||
{ (((tags.format(out)), out.append(_delim.data())), ...); }, _tags);
|
||||
|
||||
if (!out.empty())
|
||||
{
|
||||
// erase last delim
|
||||
out.erase(out.length() - _delim.length(), _delim.length());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<TTags...> _tags;
|
||||
std::string_view _delim;
|
||||
};
|
||||
} // namespace quill::utility
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
|
||||
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quill/backend/BackendOptions.h"
|
||||
#include "quill/backend/BackendWorker.h"
|
||||
#include "quill/core/Attributes.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
namespace quill::detail
|
||||
{
|
||||
/**
|
||||
* Provides access to common collection class that are used by both the frontend and the backend
|
||||
* components of the logging system
|
||||
* There should only be only active active instance of this class which is achieved by the
|
||||
* LogSystemManagerSingleton
|
||||
*/
|
||||
class BackendManager
|
||||
{
|
||||
public:
|
||||
/***/
|
||||
static BackendManager& instance() noexcept
|
||||
{
|
||||
static BackendManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/***/
|
||||
BackendManager(BackendManager const&) = delete;
|
||||
BackendManager& operator=(BackendManager const&) = delete;
|
||||
|
||||
/***/
|
||||
QUILL_ATTRIBUTE_COLD void start_backend_thread(BackendOptions const& options)
|
||||
{
|
||||
// Start the backend worker
|
||||
_backend_worker.run(options);
|
||||
}
|
||||
|
||||
/***/
|
||||
QUILL_ATTRIBUTE_COLD std::once_flag& get_start_once_flag() noexcept { return _start_once_flag; }
|
||||
|
||||
/***/
|
||||
QUILL_ATTRIBUTE_COLD void stop_backend_thread() noexcept { _backend_worker.stop(); }
|
||||
|
||||
/***/
|
||||
QUILL_NODISCARD uint32_t get_backend_thread_id() const noexcept
|
||||
{
|
||||
return _backend_worker.get_backend_thread_id();
|
||||
}
|
||||
|
||||
/***/
|
||||
void notify_backend_thread() noexcept { _backend_worker.notify(); }
|
||||
|
||||
/***/
|
||||
QUILL_NODISCARD bool is_backend_thread_running() const noexcept
|
||||
{
|
||||
return _backend_worker.is_running();
|
||||
}
|
||||
|
||||
/***/
|
||||
QUILL_NODISCARD uint64_t convert_rdtsc_to_epoch_time(uint64_t rdtsc_value) const noexcept
|
||||
{
|
||||
return _backend_worker.time_since_epoch(rdtsc_value);
|
||||
}
|
||||
|
||||
private:
|
||||
/***/
|
||||
BackendManager() = default;
|
||||
~BackendManager() = default;
|
||||
|
||||
private:
|
||||
BackendWorker _backend_worker;
|
||||
std::once_flag _start_once_flag;
|
||||
};
|
||||
} // namespace quill::detail
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue