Compare commits

..

50 Commits

Author SHA1 Message Date
gabime
ce0424bb37 Bump fmt to 11.1.4 2025-04-13 12:46:01 +03:00
Gabi Melman
1f9272eb7d Update logger.h 2025-04-11 14:45:11 +03:00
gabime
22122f3901 Merge PR #3366 from 1.x (Fix zformatter on Apple and POSIX.1-2024 conforming platform) 2025-03-29 13:58:40 +03:00
Jan Moravec
1b0a1dda33 Update CMake minimum version required (#3359)
FILE_SET functionality was introduced in CMake version 3.23.

https://cmake.org/cmake/help/latest/command/target_sources.html
2025-03-17 16:15:12 +02:00
gabime
88715d29e9 cherrypick pr #3661 2025-03-17 16:07:31 +02:00
gabime
22405cf9ae Fixed async bench 2025-02-01 19:23:23 +02:00
gabime
27d8580131 Bump fmt to version 11.1.3 2025-02-01 19:06:19 +02:00
gabime
fbffd38030 CMakeLists.txt format 2025-02-01 19:03:41 +02:00
gabime
3a54caee36 CMakeLists.txt format 2025-02-01 19:02:52 +02:00
gabime
9db0ba648a Refactor SPDLOG_API define 2025-01-18 20:32:34 +02:00
gabime
463e41f049 Refactor exporing in common.h 2025-01-18 19:45:13 +02:00
gabime
ace82f7da6 Try fix windows link error with visiblity 2025-01-18 19:17:45 +02:00
gabime
b93c0f8e8d Update comment 2025-01-18 16:37:18 +02:00
gabime
aec733b7a9 change async queue limits to constexpr 2025-01-18 16:29:14 +02:00
gabime
eb660caa6c Limit max async queue size to 250,000 entries 2025-01-18 16:26:43 +02:00
gabime
af8440b248 Small clean of enqueue_if_have_room in mpmc_blocking_q.h 2025-01-18 16:04:31 +02:00
gabime
214e26e8b2 Make err_helper time printing more robust 2025-01-18 14:09:19 +02:00
gabime
35060923d9 improved err_helper message while catching exceptions 2025-01-18 13:42:55 +02:00
gabime
a8e7527d2d Merge branch 'v2.x' of github.com:gabime/spdlog into v2.x 2025-01-18 13:41:00 +02:00
Gabi Melman
2abfa1628b Gabime/visibilty-hidden 2.x (#3324)
Set CMAKE_CXX_VISIBILITY_PRESET and VISIBILITY_INLINES_HIDDEN when build shared lib
2025-01-18 13:36:01 +02:00
gabime
d6389d696e Fixed analyzer warnings in ansicolor_sink.cpp 2025-01-18 11:42:04 +02:00
gabime
c563b62aea Refactor ansicolor_sink.cpp 2025-01-18 11:40:38 +02:00
Gabi Melman
e3f8349d0c Add missing locks to ansicolor_sink.cpp 2025-01-18 07:32:30 +02:00
Gabi Melman
128a9fcc49 Update test_async.cpp 2025-01-17 23:18:33 +02:00
Gabi Melman
43812ddaf1 Update test_async.cpp 2025-01-17 23:17:35 +02:00
gabime
23b1c4c079 Revert "Small code removal"
This reverts commit b01f15cb26.
2025-01-17 22:35:23 +02:00
gabime
b01f15cb26 Small code removal 2025-01-17 22:34:09 +02:00
gabime
370dad3225 Use chrono literals in async tests 2025-01-17 22:25:28 +02:00
gabime
418a39f6ce Fixed test 2025-01-17 22:09:35 +02:00
gabime
177f2618fb Merge branch 'v2.x' of github.com:gabime/spdlog into v2.x 2025-01-17 22:06:23 +02:00
Gabi Melman
6a794b1dff Update test_async.cpp 2025-01-17 22:02:43 +02:00
gabime
fbe626d828 code format 2025-01-17 21:01:54 +02:00
gabime
47fe6ef92a code format 2025-01-17 20:59:46 +02:00
gabime
b9f0243405 code format 2025-01-17 20:58:19 +02:00
gabime
873026a254 Pad level names in default pattern 2025-01-17 20:47:28 +02:00
gabime
e99e09eba7 Fixed global logger name 2025-01-17 20:26:47 +02:00
gabime
07be1b4767 Bump fmt version to 11.1.2 2025-01-17 20:07:12 +02:00
gabime
f00a6550fa Fixed some tidy warnings 2025-01-17 19:13:36 +02:00
gabime
391eb198bf Added copy and move tests 2025-01-17 19:06:24 +02:00
gabime
3f4cfa72d1 Added test check that cloned custom error handler was indeed cloned 2025-01-17 18:46:52 +02:00
gabime
80f00797e3 Fixed copy ctor of err_helper to be thread safe 2025-01-17 18:35:34 +02:00
gabime
62bbd87bdb Fixed warning 2025-01-17 17:45:27 +02:00
gabime
8736ee28e0 Fixed warnings 2025-01-17 17:39:03 +02:00
gabime
82358e8ebe refactor async sink 2025-01-17 17:16:45 +02:00
gabime
9673c1ba09 Fixed warning about move 2025-01-17 17:01:57 +02:00
gabime
5dce654473 Fixed warning 2025-01-17 16:48:28 +02:00
gabime
be942e0a2d Limit error handler rate to 1/sec 2025-01-17 16:42:06 +02:00
gabime
af7b061773 Added with_all to async sink improved destructor 2025-01-17 16:17:11 +02:00
gabime
bc2eed7913 Added custom error handler support to async sink 2025-01-17 13:30:37 +02:00
gabime
b46b6dcb00 Fixed bin_to_hex tidy warnings 2025-01-17 12:56:38 +02:00
40 changed files with 506 additions and 780 deletions

View File

@@ -1,23 +0,0 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version, and other tools you might need
build:
os: ubuntu-24.04
tools:
python: "3.13"
# Build documentation with Mkdocs
mkdocs:
configuration: docs/mkdocs.yml
# Optionally, but recommended,
# declare the Python requirements required to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
# python:
# install:
# - requirements: docs/requirements.txt

View File

@@ -1,45 +1,34 @@
# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT)
cmake_minimum_required(VERSION 3.14)
# ---------------------------------------------------------------------------------------
# Start spdlog project
# Copyright(c) 2019-present by spdlog authors.
# Distributed under the MIT License (http://opensource.org/licenses/MIT)
# ---------------------------------------------------------------------------------------
cmake_minimum_required(VERSION 3.23)
include(cmake/utils.cmake)
include(cmake/ide.cmake)
spdlog_extract_version()
project(spdlog VERSION ${SPDLOG_VERSION} LANGUAGES CXX)
include(GNUInstallDirs)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# ---------------------------------------------------------------------------------------
# Set default build to release
# ---------------------------------------------------------------------------------------
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
endif ()
# ---------------------------------------------------------------------------------------
# Compiler config
# ---------------------------------------------------------------------------------------
# c++ standard >=17 is required
# C++ standard >=17 is required
if (NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
elseif (CMAKE_CXX_STANDARD LESS 17)
message(FATAL_ERROR "Minimum supported CMAKE_CXX_STANDARD is 17, but it is set to ${CMAKE_CXX_STANDARD}")
endif ()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS" OR CMAKE_SYSTEM_NAME MATCHES "MINGW")
set(CMAKE_CXX_EXTENSIONS ON)
endif ()
# ---------------------------------------------------------------------------------------
# Set SPDLOG_MASTER_PROJECT to ON if we are building spdlog
# ---------------------------------------------------------------------------------------
@@ -51,75 +40,53 @@ if (NOT DEFINED SPDLOG_MASTER_PROJECT)
set(SPDLOG_MASTER_PROJECT OFF)
endif ()
endif ()
# ---------------------------------------------------------------------------------------
# Options
# ---------------------------------------------------------------------------------------
option(SPDLOG_BUILD_ALL "Build all artifacts" OFF)
# build shared option
option(SPDLOG_BUILD_SHARED "Build shared library" OFF)
# example options
option(SPDLOG_BUILD_EXAMPLE "Build example" ${SPDLOG_MASTER_PROJECT})
# testing options
option(SPDLOG_BUILD_TESTS "Build tests" OFF)
# bench options
option(SPDLOG_BUILD_BENCH "Build benchmarks (Requires https://github.com/google/benchmark.git to be installed)" OFF)
# sanitizer options
option(SPDLOG_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
# warning options
option(SPDLOG_BUILD_WARNINGS "Enable compiler warnings" OFF)
# install options
option(SPDLOG_SYSTEM_INCLUDES "Include as system headers (skip for clang-tidy)." OFF)
option(SPDLOG_INSTALL "Generate the install target" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_FMT_EXTERNAL "Use external fmt library instead of of fetching from gitub." OFF)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
option(SPDLOG_CLOCK_COARSE "Use CLOCK_REALTIME_COARSE instead of the regular clock," OFF)
else ()
set(SPDLOG_CLOCK_COARSE OFF CACHE BOOL "non supported option" FORCE)
endif ()
option(SPDLOG_PREVENT_CHILD_FD "Prevent from child processes to inherit log file descriptors" OFF)
option(SPDLOG_NO_THREAD_ID "prevent spdlog from querying the thread id on each log call if thread id is not needed" OFF)
option(SPDLOG_DISABLE_GLOBAL_LOGGER "Disable global logger creation" OFF)
option(SPDLOG_NO_TLS "Disable thread local storage" OFF)
# clang-tidy
option(SPDLOG_TIDY "run clang-tidy" OFF)
if (SPDLOG_TIDY)
set(CMAKE_CXX_CLANG_TIDY "clang-tidy")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
message(STATUS "Enabled clang-tidy")
endif ()
if (SPDLOG_BUILD_SHARED)
set(BUILD_SHARED_LIBS ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif ()
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# place dlls and libs and executables in the same directory
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/$<CONFIG>)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/$<CONFIG>)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/$<CONFIG>)
set(CMAKE_PDB_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/$<CONFIG>)
# make sure __cplusplus is defined
add_compile_options(/Zc:__cplusplus)
# enable parallel build for the solution
add_compile_options(/MP)
endif ()
message(STATUS "spdlog version: ${SPDLOG_VERSION}")
message(STATUS "spdlog build type: " ${CMAKE_BUILD_TYPE})
message(STATUS "spdlog build shared: " ${BUILD_SHARED_LIBS})
message(STATUS "spdlog fmt external: " ${SPDLOG_FMT_EXTERNAL})
# ---------------------------------------------------------------------------------------
# Find {fmt} library
# ---------------------------------------------------------------------------------------
@@ -129,12 +96,10 @@ if (SPDLOG_FMT_EXTERNAL)
else ()
include(cmake/fmtlib.cmake)
endif ()
# ---------------------------------------------------------------------------------------
# Threads library is required
# ---------------------------------------------------------------------------------------
find_package(Threads REQUIRED)
# ---------------------------------------------------------------------------------------
# Library sources
# ---------------------------------------------------------------------------------------
@@ -182,7 +147,6 @@ set(SPDLOG_HEADERS
"include/spdlog/sinks/tcp_sink.h"
"include/spdlog/sinks/udp_sink.h"
"include/spdlog/sinks/async_sink.h")
set(SPDLOG_SRCS
"src/common.cpp"
"src/logger.cpp"
@@ -198,7 +162,6 @@ set(SPDLOG_SRCS
"src/sinks/rotating_file_sink.cpp"
"src/sinks/stdout_sinks.cpp"
"src/sinks/async_sink.cpp")
if (WIN32)
list(APPEND SPDLOG_SRCS
"src/details/os_windows.cpp"
@@ -218,7 +181,6 @@ else ()
"include/spdlog/details/udp_client_unix.h"
"include/spdlog/sinks/ansicolor_sink.h")
endif ()
# ---------------------------------------------------------------------------------------
# Check if fwrite_unlocked/_fwrite_nolock is available
# ---------------------------------------------------------------------------------------
@@ -231,7 +193,6 @@ endif ()
if (HAVE_FWRITE_UNLOCKED)
set(SPDLOG_FWRITE_UNLOCKED 1)
endif ()
# ---------------------------------------------------------------------------------------
# spdlog library
# ---------------------------------------------------------------------------------------
@@ -244,12 +205,12 @@ if (BUILD_SHARED_LIBS)
target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB)
if (MSVC)
# disable dlls related warnings on msvc
target_compile_options(spdlog PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251
/wd4275>)
target_compile_options(spdlog PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251 /wd4275>)
endif ()
else ()
add_library(spdlog STATIC)
endif ()
set_target_properties(spdlog PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON)
add_library(spdlog::spdlog ALIAS spdlog)
target_sources(spdlog PRIVATE ${SPDLOG_SRCS})
target_sources(
@@ -258,42 +219,35 @@ target_sources(
TYPE HEADERS
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include
FILES ${SPDLOG_HEADERS})
set(SPDLOG_INCLUDES_LEVEL "")
if (SPDLOG_SYSTEM_INCLUDES)
set(SPDLOG_INCLUDES_LEVEL "SYSTEM")
endif ()
target_include_directories(spdlog ${SPDLOG_INCLUDES_LEVEL} PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_link_libraries(spdlog PUBLIC Threads::Threads)
target_link_libraries(spdlog PUBLIC fmt::fmt)
spdlog_enable_warnings(spdlog)
set_target_properties(spdlog PROPERTIES VERSION ${SPDLOG_VERSION} SOVERSION
${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR})
set(SPDLOG_NAME spdlog-${SPDLOG_VERSION_MAJOR})
set_target_properties(spdlog PROPERTIES DEBUG_POSTFIX "-${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR}d")
# ---------------------------------------------------------------------------------------
# set source groups for visual studio
# Set prefix and source group for visual studio
# ---------------------------------------------------------------------------------------
if (CMAKE_GENERATOR MATCHES "Visual Studio")
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include PREFIX include FILES ${SPDLOG_HEADERS})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX sources FILES ${SPDLOG_SRCS})
source_group(sources FILES ${VERSION_RC})
endif ()
# ---------------------------------------------------------------------------------------
# Add required libraries for Android CMake build
# Android support
# ---------------------------------------------------------------------------------------
if (ANDROID)
target_link_libraries(spdlog PUBLIC log)
endif ()
# ---------------------------------------------------------------------------------------
# spdlog private defines according to the options
# Private defines according to the options
# ---------------------------------------------------------------------------------------
foreach (SPDLOG_OPTION
SPDLOG_CLOCK_COARSE
@@ -306,42 +260,31 @@ foreach (SPDLOG_OPTION
target_compile_definitions(spdlog PRIVATE ${SPDLOG_OPTION})
endif ()
endforeach ()
# ---------------------------------------------------------------------------------------
# Build binaries
# ---------------------------------------------------------------------------------------
# examples
if (SPDLOG_BUILD_EXAMPLE OR SPDLOG_BUILD_ALL)
message(STATUS "Generating example(s)")
add_subdirectory(example)
spdlog_enable_warnings(example)
endif ()
# tests
if (SPDLOG_BUILD_TESTS OR SPDLOG_BUILD_ALL)
message(STATUS "Generating tests")
enable_testing()
add_subdirectory(tests)
endif ()
# benchmarks
if (SPDLOG_BUILD_BENCH OR SPDLOG_BUILD_ALL)
message(STATUS "Generating benchmarks")
add_subdirectory(bench)
endif ()
# ---------------------------------------------------------------------------------------
# Install
# ---------------------------------------------------------------------------------------
if (SPDLOG_INSTALL)
message(STATUS "Generating install")
set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/cmake/spdlogConfig.cmake.in")
set(project_config_out "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfig.cmake")
set(config_targets_file "spdlogConfigTargets.cmake")
set(version_config_file "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfigVersion.cmake")
set(export_dest_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${SPDLOG_NAME}")
# ---------------------------------------------------------------------------------------
# Include files
# ---------------------------------------------------------------------------------------
set(installed_include_dir "${CMAKE_INSTALL_INCLUDEDIR}/${SPDLOG_NAME}")
install(
TARGETS spdlog
EXPORT spdlogTargets
@@ -351,20 +294,17 @@ if (SPDLOG_INSTALL)
FILE_SET pub_headers
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${SPDLOG_NAME}")
message(STATUS "Installing spdlog in ${CMAKE_INSTALL_LIBDIR}/${SPDLOG_NAME}")
# ---------------------------------------------------------------------------------------
# Install CMake config files
# ---------------------------------------------------------------------------------------
# Install CMake spdlogConfig.cmake, spdlogConfigVersion.cmake and spdlogTargets.cmake
set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/cmake/spdlogConfig.cmake.in")
set(project_config_out "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfig.cmake")
set(config_targets_file "spdlogConfigTargets.cmake")
set(version_config_file "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfigVersion.cmake")
set(export_dest_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${SPDLOG_NAME}")
install(EXPORT spdlogTargets DESTINATION ${export_dest_dir} NAMESPACE spdlog:: FILE ${config_targets_file})
include(CMakePackageConfigHelpers)
configure_package_config_file("${project_config_in}" "${project_config_out}" INSTALL_DESTINATION ${export_dest_dir})
write_basic_package_version_file("${version_config_file}" COMPATIBILITY SameMajorVersion)
install(FILES "${project_config_out}" "${version_config_file}" DESTINATION "${export_dest_dir}")
# ---------------------------------------------------------------------------------------
# Support creation of installable packages
# ---------------------------------------------------------------------------------------
# CPack
include(cmake/spdlogCPack.cmake)
endif ()

View File

@@ -6,14 +6,14 @@
//
// bench.cpp : spdlog benchmarks
//
#include <algorithm>
#include <atomic>
#include <iostream>
#include <fstream>
#include <iostream>
#include <locale>
#include <memory>
#include <string>
#include <thread>
#include <locale>
#include <algorithm>
#include "spdlog/sinks/async_sink.h"
#include "spdlog/sinks/basic_file_sink.h"
@@ -21,8 +21,7 @@
using namespace std;
using namespace std::chrono;
using namespace spdlog;
using namespace spdlog::sinks;
using spdlog::sinks::async_sink;
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count);
@@ -50,8 +49,8 @@ using namespace spdlog::sinks;
int main(int argc, char *argv[]) {
// setlocale to show thousands separators
std::locale::global(std::locale("en_US.UTF-8"));
int howmany = 1000000;
int queue_size = std::min(howmany + 2, 8192);
int howmany = 1'000'000;
int queue_size = async_sink::default_queue_size;
int threads = 10;
int iters = 3;
@@ -66,20 +65,23 @@ int main(int argc, char *argv[]) {
if (argc > 2) threads = atoi(argv[2]);
if (argc > 3) {
queue_size = atoi(argv[3]);
if (queue_size > 500000) {
spdlog::error("Max queue size allowed: 500,000");
exit(1);
}
}
if (argc > 4) iters = atoi(argv[4]);
// validate all argc values
if (howmany < 1 || threads < 1 || queue_size < 1 || iters < 1) {
if (howmany < 1 || threads < 1 || queue_size < 1 || iters < 1) {
spdlog::error("Invalid input values");
exit(1);
}
auto slot_size = sizeof(details::async_log_msg);
constexpr int max_q_size = async_sink::max_queue_size;
if(queue_size > max_q_size)
{
spdlog::error("Queue size too large. Max queue size is {:L}", max_q_size);
exit(1);
}
auto slot_size = sizeof(spdlog::details::async_log_msg);
spdlog::info("-------------------------------------------------");
spdlog::info("Messages : {:L}", howmany);
spdlog::info("Threads : {:L}", threads);
@@ -99,11 +101,11 @@ int main(int argc, char *argv[]) {
auto cfg = async_sink::config();
cfg.queue_size = queue_size;
cfg.sinks.push_back(std::move(file_sink));
auto async_sink = std::make_shared<sinks::async_sink>(cfg);
auto logger = std::make_shared<spdlog::logger>("async_logger", std::move(async_sink));
auto sink = std::make_shared<async_sink>(cfg);
auto logger = std::make_shared<spdlog::logger>("async_logger", std::move(sink));
bench_mt(howmany, std::move(logger), threads);
}
//verify_file(filename, howmany); // in separate scope to ensure logger is destroyed and all logs were written
// verify_file(filename, howmany); // in separate scope to ensure logger is destroyed and all logs were written
}
spdlog::info("");
spdlog::info("*********************************");
@@ -117,8 +119,8 @@ int main(int argc, char *argv[]) {
cfg.queue_size = queue_size;
auto file_sink = std::make_shared<basic_file_sink_mt>(filename, true);
cfg.sinks.push_back(std::move(file_sink));
auto async_sink = std::make_shared<sinks::async_sink>(cfg);
auto logger = std::make_shared<spdlog::logger>("async_logger", std::move(async_sink));
auto sink = std::make_shared<async_sink>(cfg);
auto logger = std::make_shared<spdlog::logger>("async_logger", std::move(sink));
bench_mt(howmany, std::move(logger), threads);
}
spdlog::shutdown();

View File

@@ -124,7 +124,8 @@ int main(int argc, char *argv[]) {
benchmark::RegisterBenchmark("basic_st", bench_logger, std::move(basic_st))->UseRealTime();
// rotating st
auto rotating_st = spdlog::create<rotating_file_sink_st>("rotating_st", "latency_logs/rotating_st.log", file_size, rotating_files);
auto rotating_st =
spdlog::create<rotating_file_sink_st>("rotating_st", "latency_logs/rotating_st.log", file_size, rotating_files);
benchmark::RegisterBenchmark("rotating_st", bench_logger, std::move(rotating_st))->UseRealTime();
// daily st
@@ -142,7 +143,8 @@ int main(int argc, char *argv[]) {
benchmark::RegisterBenchmark("basic_mt", bench_logger, std::move(basic_mt))->Threads(n_threads)->UseRealTime();
// rotating mt
auto rotating_mt = spdlog::create<rotating_file_sink_mt>("rotating_mt", "latency_logs/rotating_mt.log", file_size, rotating_files);
auto rotating_mt =
spdlog::create<rotating_file_sink_mt>("rotating_mt", "latency_logs/rotating_mt.log", file_size, rotating_files);
benchmark::RegisterBenchmark("rotating_mt", bench_logger, std::move(rotating_mt))->Threads(n_threads)->UseRealTime();
// daily mt
@@ -151,7 +153,8 @@ int main(int argc, char *argv[]) {
}
using spdlog::sinks::async_sink;
async_sink::config config;
config.queue_size = 3 * 1024 * 1024;;
config.queue_size = async_sink::default_queue_size;;
config.sinks.push_back(std::make_shared<null_sink_st>());
config.policy = async_sink::overflow_policy::overrun_oldest;
auto async_logger = std::make_shared<spdlog::logger>("async_logger", std::make_shared<async_sink>(config));

View File

@@ -3,8 +3,8 @@ include(FetchContent)
FetchContent_Declare(
fmt
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
URL https://github.com/fmtlib/fmt/archive/refs/tags/11.0.2.tar.gz
URL_HASH SHA256=6cb1e6d37bdcb756dbbe59be438790db409cdb4868c66e888d5df9f13f7c027f)
URL https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz
URL_HASH SHA256=ac366b7b4c2e9f0dde63a59b3feb5ee59b67974b14ee5dc9ea8ad78aa2c1ee1e)
FetchContent_GetProperties(fmt)
if(NOT fmt_POPULATED)

View File

@@ -1,463 +0,0 @@
# spdlog
[![ci](https://github.com/gabime/spdlog/actions/workflows/linux.yml/badge.svg)](https://github.com/gabime/spdlog/actions/workflows/linux.yml)&nbsp;
[![ci](https://github.com/gabime/spdlog/actions/workflows/windows.yml/badge.svg)](https://github.com/gabime/spdlog/actions/workflows/windows.yml)&nbsp;
[![ci](https://github.com/gabime/spdlog/actions/workflows/macos.yml/badge.svg)](https://github.com/gabime/spdlog/actions/workflows/macos.yml)&nbsp;
[![Release](https://img.shields.io/github/release/gabime/spdlog.svg)](https://github.com/gabime/spdlog/releases/latest)
Fast C++ logging library
## Install
```console
$ git clone https://github.com/gabime/spdlog.git
$ cd spdlog && mkdir build && cd build
$ cmake .. && cmake --build .
```
see example [CMakeLists.txt](https://github.com/gabime/spdlog/blob/v2.x/example/CMakeLists.txt) on how to use.
## Platforms
* Linux, FreeBSD, OpenBSD, Solaris, AIX
* Windows (msvc, cygwin)
* macOS
* Android
## Package managers:
* Debian: `sudo apt install libspdlog-dev`
* Homebrew: `brew install spdlog`
* MacPorts: `sudo port install spdlog`
* FreeBSD: `pkg install spdlog`
* Fedora: `dnf install spdlog`
* Gentoo: `emerge dev-libs/spdlog`
* Arch Linux: `pacman -S spdlog`
* openSUSE: `sudo zypper in spdlog-devel`
* vcpkg: `vcpkg install spdlog`
* conan: `conan install --requires=spdlog/[*]`
* conda: `conda install -c conda-forge spdlog`
* build2: ```depends: spdlog ^1.8.2```
## Features
* Very fast (see [benchmarks](#benchmarks) below).
* Headers only or compiled
* Feature-rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library.
* Asynchronous mode (optional)
* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting.
* Multi/Single threaded loggers.
* Various log targets:
* Rotating log files.
* Daily log files.
* Console logging (colors supported).
* syslog.
* Windows event log.
* Windows debugger (```OutputDebugString(..)```).
* Log to Qt widgets ([example](#log-to-qt-with-nice-colors)).
* Easily [extendable](https://github.com/gabime/spdlog/wiki/4.-Sinks#implementing-your-own-sink) with custom log targets.
* Log filtering - log levels can be modified at runtime as well as compile time.
* Support for loading log levels from argv or environment var.
## Usage samples
#### Basic usage
```c++
#include "spdlog/spdlog.h"
int main()
{
spdlog::info("Welcome to spdlog!");
spdlog::error("Some error message with arg: {}", 1);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned");
spdlog::set_level(spdlog::level::debug); // Set global log level to debug
spdlog::debug("This message should be displayed..");
// change log pattern
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
// Compile time log levels
// define SPDLOG_ACTIVE_LEVEL to desired level
SPDLOG_TRACE("Some trace message with param {}", 42);
SPDLOG_DEBUG("Some debug message");
}
```
---
#### Create stdout/stderr logger object
```c++
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
void stdout_example()
{
// create a color multi-threaded logger
auto console = spdlog::stdout_color_mt("console");
auto err_logger = spdlog::stderr_color_mt("stderr");
spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
}
```
---
#### Basic file logger
```c++
#include "spdlog/sinks/basic_file_sink.h"
void basic_logfile_example()
{
try
{
auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt");
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
```
---
#### Rotating files
```c++
#include "spdlog/sinks/rotating_file_sink.h"
void rotating_example()
{
// Create a file rotating logger with 5 MB size max and 3 rotated files
auto max_size = 1048576 * 5;
auto max_files = 3;
auto logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", max_size, max_files);
}
```
---
#### Daily files
```c++
#include "spdlog/sinks/daily_file_sink.h"
void daily_example()
{
// Create a daily logger - a new file is created every day at 2:30 am
auto logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
}
```
---
#### Periodic flush
```c++
// periodically flush all *registered* loggers every 3 seconds:
// warning: only use if all your loggers are thread-safe ("_mt" loggers)
spdlog::flush_every(std::chrono::seconds(3));
```
---
#### Stopwatch
```c++
// Stopwatch support for spdlog
#include "spdlog/stopwatch.h"
void stopwatch_example()
{
spdlog::stopwatch sw;
spdlog::debug("Elapsed {}", sw);
spdlog::debug("Elapsed {:.3}", sw);
}
```
---
#### Log binary data in hex
```c++
// many types of std::container<char> types can be used.
// ranges are supported too.
// format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output into lines.
// {:a} - show ASCII if :n is not set.
#include "spdlog/fmt/bin_to_hex.h"
void binary_example()
{
auto console = spdlog::get("console");
std::array<char, 80> buf;
console->info("Binary example: {}", spdlog::to_hex(buf));
console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
// more examples:
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}
```
---
#### Logger with multi sinks - each with a different format and log level
```c++
// create a logger with 2 targets, with different log levels and formats.
// The console will show only warnings or errors, while the file will log all.
void multi_sink_example()
{
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::warn);
console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
file_sink->set_level(spdlog::level::trace);
spdlog::logger logger("multi_sink", {console_sink, file_sink});
logger.set_level(spdlog::level::debug);
logger.warn("this should appear in both console and file");
logger.info("this message should not appear in the console, only in the file");
}
```
---
#### User-defined callbacks about log events
```c++
// create a logger with a lambda function callback, the callback will be called
// each time something is logged to the logger
void callback_example()
{
auto callback_sink = std::make_shared<spdlog::sinks::callback_sink_mt>([](const spdlog::details::log_msg &msg) {
// for example you can be notified by sending an email to yourself
});
callback_sink->set_level(spdlog::level::err);
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
spdlog::logger logger("custom_callback_logger", {console_sink, callback_sink});
logger.info("some info log");
logger.error("critical issue"); // will notify you
}
```
---
#### Asynchronous logging
```c++
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
void async_example()
{
// TODO
}
```
---
#### User-defined types
```c++
template<>
struct fmt::formatter<my_type> : fmt::formatter<std::string>
{
auto format(my_type my, format_context &ctx) const -> decltype(ctx.out())
{
return format_to(ctx.out(), "[my_type i={}]", my.i);
}
};
void user_defined_example()
{
spdlog::info("user defined type: {}", my_type(14));
}
```
---
#### User-defined flags in the log pattern
```c++
// Log patterns can contain custom flags.
// the following example will add new flag '%*' - which will be bound to a <my_formatter_flag> instance.
#include "spdlog/pattern_formatter.h"
class my_formatter_flag : public spdlog::custom_flag_formatter
{
public:
void format(const spdlog::details::log_msg &, const std::tm &, spdlog::memory_buf_t &dest) override
{
std::string some_txt = "custom-flag";
dest.append(some_txt.data(), some_txt.data() + some_txt.size());
}
std::unique_ptr<custom_flag_formatter> clone() const override
{
return std::make_unique<my_formatter_flag>();
}
};
void custom_flags_example()
{
auto formatter = std::make_unique<spdlog::pattern_formatter>();
formatter->add_flag<my_formatter_flag>('*').set_pattern("[%n] [%*] [%^%l%$] %v");
spdlog::set_formatter(std::move(formatter));
}
```
---
#### Custom error handler
```c++
void err_handler_example()
{
// can be set globally or per logger(logger->set_error_handler(..))
spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); });
spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3);
}
```
---
#### syslog
```c++
#include "spdlog/sinks/syslog_sink.h"
void syslog_example()
{
std::string ident = "spdlog-example";
auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID);
syslog_logger->warn("This is warning that will end up in syslog.");
}
```
---
#### Android example
```c++
#include "spdlog/sinks/android_sink.h"
void android_example()
{
std::string tag = "spdlog-android";
auto android_logger = spdlog::android_logger_mt("android", tag);
android_logger->critical("Use \"adb shell logcat\" to view this message.");
}
```
---
#### Load log levels from the env variable or argv
```c++
#include "spdlog/cfg/env.h"
int main (int argc, char *argv[])
{
spdlog::cfg::load_env_levels();
// or from the command line:
// ./example SPDLOG_LEVEL=info,mylogger=trace
// #include "spdlog/cfg/argv.h" // for loading levels from argv
// spdlog::cfg::load_argv_levels(argc, argv);
}
```
So then you can:
```console
$ export SPDLOG_LEVEL=info,mylogger=trace
$ ./example
```
---
#### Log file open/close event handlers
```c++
// You can get callbacks from spdlog before/after a log file has been opened or closed.
// This is useful for cleanup procedures or for adding something to the start/end of the log file.
void file_events_example()
{
// pass the spdlog::file_event_handlers to file sinks for open/close log file notifications
spdlog::file_event_handlers handlers;
handlers.before_open = [](spdlog::filename_t filename) { spdlog::info("Before opening {}", filename); };
handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("After opening\n", fstream); };
handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("Before closing\n", fstream); };
handlers.after_close = [](spdlog::filename_t filename) { spdlog::info("After closing {}", filename); };
auto my_logger = spdlog::basic_logger_st("some_logger", "logs/events-sample.txt", true, handlers);
}
```
---
#### Replace the Default Logger
```c++
void replace_global_logger_example()
{
auto new_logger = spdlog::basic_logger_mt("new_global_logger", "logs/new-default-log.txt", true);
spdlog::set_global_logger(new_logger);
spdlog::info("new logger log message");
}
```
---
#### Log to Qt with nice colors
```c++
#include "spdlog/spdlog.h"
#include "spdlog/sinks/qt_sinks.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
setMinimumSize(640, 480);
auto log_widget = new QTextEdit(this);
setCentralWidget(log_widget);
int max_lines = 500; // keep the text widget to max 500 lines. remove old lines if needed.
auto logger = spdlog::qt_color_logger_mt("qt_logger", log_widget, max_lines);
logger->info("Some info message");
}
```
---
## Benchmarks
Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v2.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz
#### Synchronous mode
```
[info] **************************************************************
[info] Single thread, 1,000,000 iterations
[info] **************************************************************
[info] basic_st Elapsed: 0.17 secs 5,777,626/sec
[info] rotating_st Elapsed: 0.18 secs 5,475,894/sec
[info] daily_st Elapsed: 0.20 secs 5,062,659/sec
[info] empty_logger Elapsed: 0.07 secs 14,127,300/sec
[info] **************************************************************
[info] C-string (400 bytes). Single thread, 1,000,000 iterations
[info] **************************************************************
[info] basic_st Elapsed: 0.41 secs 2,412,483/sec
[info] rotating_st Elapsed: 0.72 secs 1,389,196/sec
[info] daily_st Elapsed: 0.42 secs 2,393,298/sec
[info] null_st Elapsed: 0.04 secs 27,446,957/sec
[info] **************************************************************
[info] 10 threads, competing over the same logger object, 1,000,000 iterations
[info] **************************************************************
[info] basic_mt Elapsed: 0.60 secs 1,659,613/sec
[info] rotating_mt Elapsed: 0.62 secs 1,612,493/sec
[info] daily_mt Elapsed: 0.61 secs 1,638,305/sec
[info] null_mt Elapsed: 0.16 secs 6,272,758/sec
```
#### Asynchronous mode
```
[info] -------------------------------------------------
[info] Messages : 1,000,000
[info] Threads : 10
[info] Queue : 8,192 slots
[info] Queue memory : 8,192 x 272 = 2,176 KB
[info] -------------------------------------------------
[info]
[info] *********************************
[info] Queue Overflow Policy: block
[info] *********************************
[info] Elapsed: 1.70784 secs 585,535/sec
[info] Elapsed: 1.69805 secs 588,910/sec
[info] Elapsed: 1.7026 secs 587,337/sec
[info]
[info] *********************************
[info] Queue Overflow Policy: overrun
[info] *********************************
[info] Elapsed: 0.372816 secs 2,682,285/sec
[info] Elapsed: 0.379758 secs 2,633,255/sec
[info] Elapsed: 0.373532 secs 2,677,147/sec
```
## Documentation
Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages.
---
Thanks to [JetBrains](https://www.jetbrains.com/?from=spdlog) for donating product licenses to help develop **spdlog** <a href="https://www.jetbrains.com/?from=spdlog"><img src="logos/jetbrains-variant-4.svg" width="94" align="center" /></a>

View File

@@ -1,8 +0,0 @@
docs_dir: .
site_name: spdlog
nav:
- Home: index.md
theme:
name: mkdocs
color_mode: dark

View File

@@ -88,7 +88,7 @@ void stdout_logger_example() {
// Create color multithreading logger.
auto console = spdlog::create<stdout_color_sink_mt>("console");
// or for stderr:
//auto console = spdlog::create<stderr_color_sink_mt>("console");
// auto console = spdlog::create<stderr_color_sink_mt>("console");
}
#include "spdlog/sinks/basic_file_sink.h"

View File

@@ -51,7 +51,7 @@ public:
// do not use begin() and end() to avoid collision with fmt/ranges
It get_begin() const { return begin_; }
It get_end() const { return end_; }
size_t size_per_line() const { return size_per_line_; }
[[nodiscard]] size_t size_per_line() const { return size_per_line_; }
private:
It begin_, end_;
@@ -90,7 +90,7 @@ inline details::dump_info<It> to_hex(const It range_begin, const It range_end, s
template <typename T>
struct fmt::formatter<spdlog::details::dump_info<T>, char> {
const char delimiter = ' ';
char delimiter = ' ';
bool put_newlines = true;
bool put_delimiters = true;
bool use_uppercase = false;
@@ -138,14 +138,14 @@ struct fmt::formatter<spdlog::details::dump_info<T>, char> {
auto inserter = ctx.out();
int size_per_line = static_cast<int>(the_range.size_per_line());
auto start_of_line = the_range.get_begin();
for (auto i = the_range.get_begin(); i != the_range.get_end(); i++) {
for (auto i = the_range.get_begin(); i != the_range.get_end(); ++i) {
auto ch = static_cast<unsigned char>(*i);
if (put_newlines && (i == the_range.get_begin() || i - start_of_line >= size_per_line)) {
if (show_ascii && i != the_range.get_begin()) {
*inserter++ = delimiter;
*inserter++ = delimiter;
for (auto j = start_of_line; j < i; j++) {
for (auto j = start_of_line; j < i; ++j) {
auto pc = static_cast<unsigned char>(*j);
*inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.';
}
@@ -181,7 +181,7 @@ struct fmt::formatter<spdlog::details::dump_info<T>, char> {
}
*inserter++ = delimiter;
*inserter++ = delimiter;
for (auto j = start_of_line; j != the_range.get_end(); j++) {
for (auto j = start_of_line; j != the_range.get_end(); ++j) {
auto pc = static_cast<unsigned char>(*j);
*inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.';
}

View File

@@ -6,31 +6,33 @@
#include <array>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <exception>
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <string_view>
#include <cstdint>
#include "./source_loc.h"
#include "fmt/base.h"
#include "fmt/xchar.h"
#if defined(SPDLOG_SHARED_LIB)
#if defined(_WIN32)
#ifdef spdlog_EXPORTS
#define SPDLOG_API __declspec(dllexport)
#else // !spdlog_EXPORTS
#define SPDLOG_API __declspec(dllimport)
#endif
#else // !defined(_WIN32)
#define SPDLOG_API __attribute__((visibility("default")))
// Define SPDLOG_API according to current build settings
#ifndef SPDLOG_SHARED_LIB
#define SPDLOG_API
#elif defined(_WIN32)
#ifdef spdlog_EXPORTS
#define SPDLOG_API __declspec(dllexport) // Export symbols when building the library
#else
#define SPDLOG_API __declspec(dllimport) // Import symbols when using the library
#endif
#else // !defined(SPDLOG_SHARED_LIB)
#elif (defined(__GNUC__) || defined(__clang__))
#define SPDLOG_API __attribute__((visibility("default"))) // Export symbols for shared libraries
#else
#define SPDLOG_API
#endif
// End of SPDLOG_API definition
#define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string)
#define SPDLOG_FMT_STRING(format_string) FMT_STRING(format_string)

View File

@@ -4,29 +4,27 @@
#pragma once
#include <cstdint>
#include "./log_msg.h"
namespace spdlog {
namespace details {
// Extend log_msg with internal buffer to store its payload.
// This is needed since log_msg holds string_views that points to stack data.
class SPDLOG_API async_log_msg : public log_msg {
public:
enum class type:std::uint8_t { log, flush, terminate };
enum class type : std::uint8_t { log, flush, terminate };
async_log_msg() = default;
explicit async_log_msg(type type);
async_log_msg(type type, const log_msg &orig_msg);
~async_log_msg() = default;
async_log_msg(const async_log_msg &other);
async_log_msg(async_log_msg &&other) noexcept;
async_log_msg &operator=(const async_log_msg &other);
async_log_msg &operator=(async_log_msg &&other) noexcept;
[[nodiscard]] type message_type() const { return msg_type_; }
type message_type() const {return msg_type_;}
private:
type msg_type_{type::log};
memory_buf_t buffer_;

View File

@@ -3,21 +3,29 @@
#pragma once
#include <string>
#include <mutex>
#include <chrono>
#include <exception>
#include <mutex>
#include <string>
#include "spdlog/common.h"
// by default, prints the error to stderr, thread safe
// by default, prints the error to stderr, at max rate of 1/sec thread safe
namespace spdlog {
namespace details {
class SPDLOG_API err_helper {
err_handler custom_err_handler_;
std::chrono::steady_clock::time_point last_report_time_;
mutable std::mutex mutex_;
public:
void handle_ex(const std::string& origin, const source_loc& loc, const std::exception& ex) const noexcept;
void handle_unknown_ex(const std::string& origin, const source_loc& loc) const noexcept;
err_helper() = default;
~err_helper() = default;
err_helper(const err_helper& other);
err_helper(err_helper&& other) noexcept;
void handle_ex(const std::string& origin, const source_loc& loc, const std::exception& ex) noexcept;
void handle_unknown_ex(const std::string& origin, const source_loc& loc) noexcept;
void set_err_handler(err_handler handler);
};
}} // namespace spdlog::details
} // namespace details
} // namespace spdlog

View File

@@ -14,12 +14,12 @@ namespace details {
namespace fmt_helper {
inline void append_string_view(spdlog::string_view_t view, memory_buf_t &dest) {
auto *buf_ptr = view.data();
const auto *buf_ptr = view.data();
dest.append(buf_ptr, buf_ptr + view.size());
}
template <typename T>
inline void append_int(T n, memory_buf_t &dest) {
void append_int(T n, memory_buf_t &dest) {
fmt::format_int i(n);
dest.append(i.data(), i.data() + i.size());
}
@@ -36,14 +36,14 @@ constexpr unsigned int count_digits_fallback(T n) {
if (n < 100) return count + 1;
if (n < 1000) return count + 2;
if (n < 10000) return count + 3;
n /= 10000u;
n /= 10000U;
count += 4;
}
}
template <typename T>
inline unsigned int count_digits(T n) {
using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type;
using count_type = std::conditional_t<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>;
return static_cast<unsigned int>(fmt::
// fmt 7.0.0 renamed the internal namespace to detail.
@@ -59,8 +59,8 @@ inline unsigned int count_digits(T n) {
inline void pad2(int n, memory_buf_t &dest) {
if (n >= 0 && n < 100) // 0-99
{
dest.push_back(static_cast<char>('0' + n / 10));
dest.push_back(static_cast<char>('0' + n % 10));
dest.push_back(static_cast<char>('0' + (n / 10)));
dest.push_back(static_cast<char>('0' + (n % 10)));
} else // unlikely, but just in case, let fmt deal with it
{
fmt_lib::format_to(std::back_inserter(dest), SPDLOG_FMT_STRING("{:02}"), n);
@@ -69,18 +69,18 @@ inline void pad2(int n, memory_buf_t &dest) {
template <typename T>
inline void pad_uint(T n, unsigned int width, memory_buf_t &dest) {
static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T");
for (auto digits = count_digits(n); digits < width; digits++) {
static_assert(std::is_unsigned_v<T>, "pad_uint must get unsigned T");
for (auto digits = count_digits(n); digits < width; ++digits) {
dest.push_back('0');
}
append_int(n, dest);
}
template <typename T>
inline void pad3(T n, memory_buf_t &dest) {
static_assert(std::is_unsigned<T>::value, "pad3 must get unsigned T");
void pad3(T n, memory_buf_t &dest) {
static_assert(std::is_unsigned_v<T>, "pad3 must get unsigned T");
if (n < 1000) {
dest.push_back(static_cast<char>(n / 100 + '0'));
dest.push_back(static_cast<char>((n / 100) + '0'));
n = n % 100;
dest.push_back(static_cast<char>((n / 10) + '0'));
dest.push_back(static_cast<char>((n % 10) + '0'));
@@ -90,12 +90,12 @@ inline void pad3(T n, memory_buf_t &dest) {
}
template <typename T>
inline void pad6(T n, memory_buf_t &dest) {
void pad6(T n, memory_buf_t &dest) {
pad_uint(n, 6, dest);
}
template <typename T>
inline void pad9(T n, memory_buf_t &dest) {
void pad9(T n, memory_buf_t &dest) {
pad_uint(n, 9, dest);
}
@@ -103,7 +103,7 @@ inline void pad9(T n, memory_buf_t &dest) {
// e.g.
// fraction<std::milliseconds>(tp) -> will return the millis part of the second
template <typename ToDuration>
inline ToDuration time_fraction(log_clock::time_point tp) {
ToDuration time_fraction(log_clock::time_point tp) {
using std::chrono::duration_cast;
using std::chrono::seconds;
auto duration = tp.time_since_epoch();

View File

@@ -47,20 +47,15 @@ public:
}
void enqueue_if_have_room(T &&item) {
bool pushed = false;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
if (!q_.full()) {
q_.push_back(std::move(item));
pushed = true;
std::unique_lock lock(queue_mutex_);
if (q_.full()) {
++discard_counter_;
return;
}
q_.push_back(std::move(item));
}
if (pushed) {
push_cv_.notify_one();
} else {
++discard_counter_;
}
push_cv_.notify_one();
}
// dequeue with a timeout.

View File

@@ -7,7 +7,6 @@
#include <utility>
// null, no cost dummy "mutex" and dummy "atomic" log level
namespace spdlog {
namespace details {
struct null_mutex {

View File

@@ -42,7 +42,7 @@ public:
logger(std::string name, sinks_init_list sinks)
: logger(std::move(name), sinks.begin(), sinks.end()) {}
logger(const logger &other) ;
logger(const logger &other);
logger(logger &&other) noexcept;
~logger() = default;
@@ -200,7 +200,6 @@ private:
}
}
}
if (should_flush(msg)) {
flush_();
}

View File

@@ -70,14 +70,16 @@ public:
static constexpr std::string_view bold_on_red = "\033[1m\033[41m";
private:
void sink_it_(const details::log_msg &msg) override;
void flush_() override;
FILE *target_file_;
bool should_do_colors_;
std::array<std::string, levels_count> colors_;
void print_ccode_(const string_view_t color_code);
void print_range_(const memory_buf_t &formatted, size_t start, size_t end);
static std::string to_string_(const string_view_t sv);
void sink_it_(const details::log_msg &msg) override;
void flush_() override;
void set_color_mode_(color_mode mode);
void print_ccode_(string_view_t color_code) const;
void print_range_(const memory_buf_t &formatted, size_t start, size_t end) const;
static std::string to_string_(string_view_t sv);
};
template <typename Mutex>

View File

@@ -31,7 +31,8 @@ public:
discard_new // Discard the log message if the queue is full
};
enum { default_queue_size = 8192, max_queue_size = 10 * 1024 * 1024 };
static constexpr size_t default_queue_size = 8192;
static constexpr size_t max_queue_size = 250'000;
struct config {
size_t queue_size = default_queue_size;
@@ -43,8 +44,46 @@ public:
};
explicit async_sink(config async_config);
async_sink(const async_sink &) = delete;
async_sink &operator=(const async_sink &) = delete;
async_sink(async_sink &&) = delete;
async_sink &operator=(async_sink &&) = delete;
~async_sink() override;
// create an async_sink with one backend sink
// sink interface implementation
void log(const details::log_msg &msg) override;
void set_pattern(const std::string &pattern) override;
void set_formatter(std::unique_ptr<formatter> sink_formatter) override;
// enqueue flush request to the worker thread and return immediately(default)
// if you need to wait for the actual flush to finish, call wait_all() after flush() or destruct the sink
void flush() override;
// non sink interface methods
// wait until all logs were processed up to timeout millis and return false if timeout was reached
[[nodiscard]] bool wait_all(std::chrono::milliseconds timeout) const;
// wait until all logs were processed
void wait_all() const;
// return the number of overrun messages (effective only if policy is overrun_oldest)
[[nodiscard]] size_t get_overrun_counter() const;
// reset the overrun counter
void reset_overrun_counter() const;
// return the number of discarded messages (effective only if policy is discard_new)
[[nodiscard]] size_t get_discard_counter() const;
// reset the discard counter
void reset_discard_counter() const;
// return the current async_sink configuration
[[nodiscard]] const config &get_config() const;
// create an async_sink with one backend sink constructed with the given args.
// example:
// auto async_file = async_sink::with<spdlog::sinks::basic_file_sink_st>("mylog.txt");
template <typename Sink, typename... SinkArgs>
static std::shared_ptr<async_sink> with(SinkArgs &&...sink_args) {
config cfg{};
@@ -52,34 +91,20 @@ public:
return std::make_shared<async_sink>(cfg);
}
~async_sink() override;
// sink interface implementation
void log(const details::log_msg &msg) override;
void flush() override;
void set_pattern(const std::string &pattern) override;
void set_formatter(std::unique_ptr<formatter> sink_formatter) override;
// async sink specific methods
[[nodiscard]] size_t get_overrun_counter() const;
void reset_overrun_counter() const;
[[nodiscard]] size_t get_discard_counter() const;
void reset_discard_counter() const;
[[nodiscard]] const config &get_config() const;
private:
using async_log_msg = details::async_log_msg;
using queue_t = details::mpmc_blocking_queue<async_log_msg>;
void send_message_(async_log_msg::type msg_type, const details::log_msg &msg) const;
void enqueue_message_(details::async_log_msg &&msg) const;
void backend_loop_();
void backend_log_(const details::log_msg &msg) ;
void backend_log_(const details::log_msg &msg);
void backend_flush_();
config config_;
std::unique_ptr<queue_t> q_;
std::thread worker_thread_;
details::err_helper err_helper_;
std::atomic_bool terminate_worker_ = false;
};
} // namespace sinks

View File

@@ -36,7 +36,7 @@ public:
protected:
// sink formatter
std::unique_ptr<spdlog::formatter> formatter_;
Mutex mutex_;
mutable Mutex mutex_;
virtual void sink_it_(const details::log_msg &msg) = 0;
virtual void flush_() = 0;

View File

@@ -16,11 +16,11 @@
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
#include <mutex>
#include "../details/null_mutex.h"
#include "../common.h"
#include "../details/log_msg.h"
#include "../details/null_mutex.h"
#include "./base_sink.h"
namespace spdlog {

View File

@@ -4,6 +4,7 @@
#pragma once
#include <mutex>
#include "../details/null_mutex.h"
#include "./base_sink.h"

View File

@@ -46,7 +46,9 @@ public:
}
protected:
void sink_it_(const details::log_msg &msg) override { q_.push_back(details::async_log_msg{details::async_log_msg::type::log, msg}); }
void sink_it_(const details::log_msg &msg) override {
q_.push_back(details::async_log_msg{details::async_log_msg::type::log, msg});
}
void flush_() override {}
private:

View File

@@ -17,8 +17,8 @@ public:
virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0;
void set_level(level level) { level_.store(level, std::memory_order_relaxed); }
level log_level() const { return level_.load(std::memory_order_relaxed);}
bool should_log(level msg_level) const {return msg_level >= level_.load(std::memory_order_relaxed);}
level log_level() const { return level_.load(std::memory_order_relaxed); }
bool should_log(level msg_level) const { return msg_level >= level_.load(std::memory_order_relaxed); }
protected:
// sink log level - default is all

View File

@@ -8,8 +8,8 @@
#include <syslog.h>
#include <array>
#include <string>
#include <mutex>
#include <string>
namespace spdlog {
namespace sinks {
@@ -19,7 +19,7 @@ namespace sinks {
template <typename Mutex>
class syslog_sink final : public base_sink<Mutex> {
public:
syslog_sink(std::string ident = "", int syslog_option = 0, int syslog_facility = LOG_USER, bool enable_formatting=false)
syslog_sink(std::string ident = "", int syslog_option = 0, int syslog_facility = LOG_USER, bool enable_formatting = false)
: enable_formatting_{enable_formatting},
syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG,
/* spdlog::level::debug */ LOG_DEBUG,

View File

@@ -6,7 +6,6 @@
namespace spdlog {
namespace details {
async_log_msg::async_log_msg(const type type)
: msg_type_{type} {}
@@ -15,21 +14,25 @@ async_log_msg::async_log_msg(const type type)
// are compiler generated const chars* (__FILE__, __LINE__, __FUNCTION__)
// if you pass custom strings to source location, make sure they outlive the async_log_msg
async_log_msg::async_log_msg(const type type, const log_msg &orig_msg)
: log_msg{orig_msg}, msg_type_(type) {
: log_msg{orig_msg},
msg_type_(type) {
buffer_.append(logger_name);
buffer_.append(payload);
update_string_views();
}
async_log_msg::async_log_msg(const async_log_msg &other)
: log_msg{other}, msg_type_{other.msg_type_} {
: log_msg{other},
msg_type_{other.msg_type_} {
buffer_.append(logger_name);
buffer_.append(payload);
update_string_views();
}
async_log_msg::async_log_msg(async_log_msg &&other) noexcept
: log_msg{other}, msg_type_{other.msg_type_}, buffer_{std::move(other.buffer_)} {
: log_msg{other},
msg_type_{other.msg_type_},
buffer_{std::move(other.buffer_)} {
update_string_views();
}

View File

@@ -3,23 +3,42 @@
#include "spdlog/details/err_helper.h"
#include "iostream"
#include "spdlog/details/os.h"
namespace spdlog {
namespace details {
err_helper::err_helper(const err_helper &other) {
std::lock_guard lock(other.mutex_);
custom_err_handler_ = other.custom_err_handler_;
last_report_time_ = other.last_report_time_;
}
err_helper::err_helper(err_helper &&other) noexcept {
custom_err_handler_ = std::move(other.custom_err_handler_);
last_report_time_ = std::move(other.last_report_time_);
}
// Prints error to stderr with source location (if available). A stderr sink is not used because reaching
// this point might indicate a problem with the logging system itself so we use fputs() directly.
void err_helper::handle_ex(const std::string &origin, const source_loc &loc, const std::exception &ex) const noexcept {
void err_helper::handle_ex(const std::string &origin, const source_loc &loc, const std::exception &ex) noexcept {
std::lock_guard lock(mutex_);
try {
if (custom_err_handler_) {
custom_err_handler_(ex.what());
return;
}
const auto now = std::chrono::steady_clock::now();
if (now - last_report_time_ < std::chrono::seconds(1)) {
return;
}
last_report_time_ = now;
const auto tm_time = os::localtime();
char date_buf[32];
std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time);
char date_buf[64];
if (std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time) == 0) {
std::snprintf(date_buf, sizeof(date_buf), "unknown time");
}
std::string msg;
if (loc.empty()) {
msg = fmt_lib::format("[*** LOG ERROR ***] [{}] [{}] {}\n", date_buf, origin, ex.what());
@@ -29,17 +48,21 @@ void err_helper::handle_ex(const std::string &origin, const source_loc &loc, con
}
std::fputs(msg.c_str(), stderr);
} catch (const std::exception &handler_ex) {
std::fprintf(stderr, "[*** LOG ERROR ***] [%s] caught exception during error handler: %s\n", origin.c_str(), handler_ex.what());
std::fprintf(stderr, "[*** LOG ERROR ***] [%s] exception during %s handler: %s\n", origin.c_str(), custom_err_handler_ ? "custom" : "default",
handler_ex.what());
} catch (...) { // catch all exceptions
std::fprintf(stderr, "[*** LOG ERROR ***] [%s] caught unknown exception during error handler\n", origin.c_str());
std::fprintf(stderr, "[*** LOG ERROR ***] [%s] unknown exception during %s handler\n", origin.c_str(), custom_err_handler_ ? "custom" : "default");
}
}
void err_helper::handle_unknown_ex(const std::string &origin, const source_loc &loc) const noexcept {
void err_helper::handle_unknown_ex(const std::string &origin, const source_loc &loc) noexcept {
handle_ex(origin, loc, std::runtime_error("unknown exception"));
}
void err_helper::set_err_handler(err_handler handler) { custom_err_handler_ = std::move(handler); }
void err_helper::set_err_handler(err_handler handler) {
std::lock_guard lock(mutex_);
custom_err_handler_ = std::move(handler);
}
} // namespace details
} // namespace spdlog

View File

@@ -129,8 +129,10 @@ size_t filesize(FILE *f) {
// Return utc offset in minutes or throw spdlog_ex on failure
int utc_minutes_offset(const std::tm &tm) {
#if defined(sun) || defined(__sun) || defined(_AIX) || (defined(__NEWLIB__) && !defined(__TM_GMTOFF)) || \
(!defined(_BSD_SOURCE) && !defined(_GNU_SOURCE))
#if defined(sun) || defined(__sun) || defined(_AIX) || \
(defined(__NEWLIB__) && !defined(__TM_GMTOFF)) || \
(!defined(__APPLE__) && !defined(_BSD_SOURCE) && !defined(_GNU_SOURCE) && \
(!defined(_POSIX_VERSION) || (_POSIX_VERSION < 202405L)))
// 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris
struct helper {
static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(),

View File

@@ -60,6 +60,9 @@ public:
pad_it(remaining_pad_);
} else if (padinfo_.truncate_) {
long new_size = static_cast<long>(dest_.size()) + remaining_pad_;
if (new_size < 0) {
new_size = 0;
}
dest_.resize(static_cast<size_t>(new_size));
}
}
@@ -123,9 +126,8 @@ public:
};
///////////////////////////////////////////////////////////////////////
// Date time pattern appenders
// Date time pattern formatters
///////////////////////////////////////////////////////////////////////
static const char *ampm(const tm &t) { return t.tm_hour >= 12 ? "PM" : "AM"; }
static int to12h(const tm &t) { return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; }
@@ -248,7 +250,7 @@ public:
: flag_formatter(padinfo) {}
void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override {
constexpr size_t field_size = 10;
constexpr size_t field_size = 8;
ScopedPadder p(field_size, padinfo_, dest);
fmt_helper::pad2(tm_time.tm_mon + 1, dest);
@@ -742,12 +744,25 @@ private:
// Full info formatter
// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] [%s:%#] %v
class full_formatter final : public flag_formatter {
class default_format_formatter final : public flag_formatter {
public:
explicit full_formatter(padding_info padinfo)
// clang-format off
// aligned level names
static constexpr std::string_view trace_str = "[trace] ";
static constexpr std::string_view debug_str = "[debug] ";
static constexpr std::string_view info_str = "[info ] ";
static constexpr std::string_view warn_str = "[warn ] ";
static constexpr std::string_view error_str = "[error] ";
static constexpr std::string_view critical_str = "[crit ] ";
static constexpr std::string_view off_str = "[off ] ";
// clang-format on
static constexpr std::array<std::string_view, levels_count> padded_levels{
{trace_str, debug_str, info_str, warn_str, error_str, critical_str, off_str}};
explicit default_format_formatter(padding_info padinfo)
: flag_formatter(padinfo) {}
void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) override {
void format(const log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) override {
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::seconds;
@@ -780,13 +795,13 @@ public:
cache_timestamp_ = secs;
}
dest.append(cached_datetime_.begin(), cached_datetime_.end());
// append milliseconds
auto millis = fmt_helper::time_fraction<milliseconds>(msg.time);
fmt_helper::pad3(static_cast<uint32_t>(millis.count()), dest);
dest.push_back(']');
dest.push_back(' ');
// append logger name if exists
// append aligned logger name if exists
if (!msg.logger_name.empty()) {
dest.push_back('[');
fmt_helper::append_string_view(msg.logger_name, dest);
@@ -794,14 +809,10 @@ public:
dest.push_back(' ');
}
dest.push_back('[');
// wrap the level name with color
msg.color_range_start = dest.size();
// fmt_helper::append_string_view(level::to_c_str(msg.log_level), dest);
fmt_helper::append_string_view(to_string_view(msg.log_level), dest);
msg.color_range_end = dest.size();
dest.push_back(']');
dest.push_back(' ');
// wrap the level name inside the [..] with color
msg.color_range_start = dest.size() + 1; // +1 to start coloring after the '[';
fmt_helper::append_string_view(padded_levels.at(static_cast<size_t>(msg.log_level)), dest);
msg.color_range_end = dest.size() - 2; // -2 to end coloring before the "] "
// add source location if present
if (!msg.source.empty()) {
@@ -844,7 +855,7 @@ pattern_formatter::pattern_formatter(pattern_time_type time_type, std::string eo
need_localtime_{true},
cached_tm_{},
last_log_secs_{} {
formatters_.push_back(std::make_unique<details::full_formatter>(details::padding_info{}));
formatters_.push_back(std::make_unique<details::default_format_formatter>(details::padding_info{}));
}
std::unique_ptr<formatter> pattern_formatter::clone() const {
@@ -906,7 +917,7 @@ void pattern_formatter::handle_flag_(char flag, details::padding_info padding) {
// process built-in flags
switch (flag) {
case ('+'): // default formatter
formatters_.push_back(std::make_unique<details::full_formatter>(padding));
formatters_.push_back(std::make_unique<details::default_format_formatter>(padding));
need_localtime_ = true;
break;

View File

@@ -12,11 +12,8 @@ namespace spdlog {
namespace sinks {
template <typename Mutex>
ansicolor_sink<Mutex>::ansicolor_sink(FILE *target_file, color_mode mode)
: target_file_(target_file)
{
set_color_mode(mode);
ansicolor_sink<Mutex>::ansicolor_sink(FILE *target_file, color_mode mode) : target_file_(target_file) {
set_color_mode_(mode);
colors_.at(level_to_number(level::trace)) = to_string_(white);
colors_.at(level_to_number(level::debug)) = to_string_(cyan);
colors_.at(level_to_number(level::info)) = to_string_(green);
@@ -34,31 +31,37 @@ void ansicolor_sink<Mutex>::set_color(level color_level, string_view_t color) {
template <typename Mutex>
bool ansicolor_sink<Mutex>::should_color() const {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
return should_do_colors_;
}
template <typename Mutex>
void ansicolor_sink<Mutex>::set_color_mode(color_mode mode) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
set_color_mode_(mode);
}
template <typename Mutex>
void ansicolor_sink<Mutex>::set_color_mode_(color_mode mode) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
switch (mode) {
case color_mode::always:
should_do_colors_ = true;
return;
return;
case color_mode::automatic:
should_do_colors_ = details::os::in_terminal(target_file_) && details::os::is_color_terminal();
return;
return;
case color_mode::never:
should_do_colors_ = false;
return;
return;
default:
should_do_colors_ = false;
}
}
template <typename Mutex>
void ansicolor_sink<Mutex>::sink_it_(const details::log_msg &msg) {
// Wrap the originally formatted message in color codes.
// If color is not supported in the terminal, log as is instead.
msg.color_range_start = 0;
msg.color_range_end = 0;
memory_buf_t formatted;
@@ -85,12 +88,12 @@ void ansicolor_sink<Mutex>::flush_() {
}
template <typename Mutex>
void ansicolor_sink<Mutex>::print_ccode_(const string_view_t color_code) {
void ansicolor_sink<Mutex>::print_ccode_(const string_view_t color_code) const {
details::os::fwrite_bytes(color_code.data(), color_code.size(), target_file_);
}
template <typename Mutex>
void ansicolor_sink<Mutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end) {
void ansicolor_sink<Mutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end) const {
details::os::fwrite_bytes(formatted.data() + start, end - start, target_file_);
}
@@ -109,12 +112,13 @@ template <typename Mutex>
ansicolor_stderr_sink<Mutex>::ansicolor_stderr_sink(color_mode mode)
: ansicolor_sink<Mutex>(stderr, mode) {}
} // namespace sinks
} // namespace spdlog
// template instantiations
#include "spdlog/details/null_mutex.h"
template class SPDLOG_API spdlog::sinks::ansicolor_sink<std::mutex>;
template class SPDLOG_API spdlog::sinks::ansicolor_sink<spdlog::details::null_mutex>;
template class SPDLOG_API spdlog::sinks::ansicolor_stdout_sink<std::mutex>;
template class SPDLOG_API spdlog::sinks::ansicolor_stdout_sink<spdlog::details::null_mutex>;
template class SPDLOG_API spdlog::sinks::ansicolor_stderr_sink<std::mutex>;

View File

@@ -3,8 +3,10 @@
#include "spdlog/sinks/async_sink.h"
#include <algorithm>
#include <cassert>
#include <memory>
#include <utility>
#include "spdlog/common.h"
#include "spdlog/details/mpmc_blocking_q.h"
@@ -19,6 +21,10 @@ async_sink::async_sink(config async_config)
if (config_.queue_size == 0 || config_.queue_size > max_queue_size) {
throw spdlog_ex("async_sink: invalid queue size");
}
if (config_.custom_err_handler) {
err_helper_.set_err_handler(config_.custom_err_handler);
}
q_ = std::make_unique<queue_t>(config_.queue_size);
worker_thread_ = std::thread([this] {
if (config_.on_thread_start) config_.on_thread_start();
@@ -32,13 +38,16 @@ async_sink::~async_sink() {
q_->enqueue(async_log_msg(async_log_msg::type::terminate));
worker_thread_.join();
} catch (...) {
terminate_worker_ = true; // as last resort, stop the worker thread using terminate_worker_ flag.
#ifndef NDEBUG
printf("Exception in ~async_sink()\n");
#endif
}
}
void async_sink::log(const details::log_msg &msg) { send_message_(async_log_msg::type::log, msg); }
void async_sink::log(const details::log_msg &msg) { enqueue_message_(async_log_msg(async_log_msg::type::log, msg)); }
void async_sink::flush() { send_message_(async_log_msg::type::flush, details::log_msg()); }
void async_sink::flush() { enqueue_message_(details::async_log_msg(async_log_msg::type::flush)); }
void async_sink::set_pattern(const std::string &pattern) { set_formatter(std::make_unique<pattern_formatter>(pattern)); }
@@ -54,6 +63,25 @@ void async_sink::set_formatter(std::unique_ptr<formatter> formatter) {
}
}
bool async_sink::wait_all(const std::chrono::milliseconds timeout) const {
using std::chrono::steady_clock;
constexpr std::chrono::milliseconds sleep_duration(5);
const auto start_time = steady_clock::now();
while (q_->size() > 0) {
auto elapsed = steady_clock::now() - start_time;
if (elapsed > timeout) {
return false;
}
std::this_thread::sleep_for(std::min(sleep_duration, timeout));
}
return true;
}
void async_sink::wait_all() const {
while (!wait_all(std::chrono::milliseconds(10))) { /* empty */
}
}
size_t async_sink::get_overrun_counter() const { return q_->overrun_counter(); }
void async_sink::reset_overrun_counter() const { q_->reset_overrun_counter(); }
@@ -65,16 +93,16 @@ void async_sink::reset_discard_counter() const { q_->reset_discard_counter(); }
const async_sink::config &async_sink::get_config() const { return config_; }
// private methods
void async_sink::send_message_(async_log_msg::type msg_type, const details::log_msg &msg) const {
void async_sink::enqueue_message_(details::async_log_msg &&msg) const {
switch (config_.policy) {
case overflow_policy::block:
q_->enqueue(async_log_msg(msg_type, msg));
q_->enqueue(std::move(msg));
break;
case overflow_policy::overrun_oldest:
q_->enqueue_nowait(async_log_msg(msg_type, msg));
q_->enqueue_nowait(std::move(msg));
break;
case overflow_policy::discard_new:
q_->enqueue_if_have_room(async_log_msg(msg_type, msg));
q_->enqueue_if_have_room(std::move(msg));
break;
default:
assert(false);
@@ -84,7 +112,7 @@ void async_sink::send_message_(async_log_msg::type msg_type, const details::log_
void async_sink::backend_loop_() {
details::async_log_msg incoming_msg;
for (;;) {
while (!terminate_worker_) {
q_->dequeue(incoming_msg);
switch (incoming_msg.message_type()) {
case async_log_msg::type::log:
@@ -101,7 +129,7 @@ void async_sink::backend_loop_() {
}
}
void async_sink::backend_log_(const details::log_msg &msg) {
void async_sink::backend_log_(const details::log_msg &msg) {
for (const auto &sink : config_.sinks) {
if (sink->should_log(msg.log_level)) {
try {

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <mutex>
#include "spdlog/common.h"
#include "spdlog/pattern_formatter.h"
@@ -53,8 +54,8 @@ void base_sink<Mutex>::set_formatter_(std::unique_ptr<formatter> sink_formatter)
formatter_ = std::move(sink_formatter);
}
} // namespace sinks
} // namespace spdlog
} // namespace sinks
} // namespace spdlog
// template instantiations
#include "spdlog/details/null_mutex.h"

View File

@@ -2,9 +2,11 @@
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/common.h"
#include <mutex>
#include "spdlog/common.h"
namespace spdlog {
namespace sinks {
@@ -34,7 +36,6 @@ void basic_file_sink<Mutex>::flush_() {
} // namespace sinks
} // namespace spdlog
// template instantiations
#include "spdlog/details/null_mutex.h"
template class SPDLOG_API spdlog::sinks::basic_file_sink<std::mutex>;

View File

@@ -142,4 +142,4 @@ bool rotating_file_sink<Mutex>::rename_file_(const filename_t &src_filename, con
// template instantiations
#include "spdlog/details/null_mutex.h"
template class SPDLOG_API spdlog::sinks::rotating_file_sink<std::mutex>;
template class SPDLOG_API spdlog::sinks::rotating_file_sink<spdlog::details::null_mutex>;
template class SPDLOG_API spdlog::sinks::rotating_file_sink<spdlog::details::null_mutex>;

View File

@@ -8,6 +8,7 @@
// clang-format on
#include "spdlog/sinks/wincolor_sink.h"
#include "spdlog/common.h"
namespace spdlog {
@@ -131,7 +132,6 @@ template <typename Mutex>
wincolor_stderr_sink<Mutex>::wincolor_stderr_sink(color_mode mode)
: wincolor_sink<Mutex>(::GetStdHandle(STD_ERROR_HANDLE), mode) {}
} // namespace sinks
} // namespace spdlog

View File

@@ -13,21 +13,17 @@
namespace spdlog {
#ifndef SPDLOG_DISABLE_GLOBAL_LOGGER
static std::shared_ptr<logger> s_logger = std::make_shared<logger>("global", std::make_shared<sinks::stdout_color_sink_mt>());
static std::shared_ptr<logger> s_logger = std::make_shared<logger>("", std::make_shared<sinks::stdout_color_sink_mt>());
#else
static std::short_ptr<logger> s_logger = nullptr;
static std::short_ptr<logger> s_logger = nullptr;
#endif
std::shared_ptr<logger> global_logger() { return s_logger; }
void set_global_logger(std::shared_ptr<logger> global_logger) { s_logger = std::move(global_logger); }
logger *global_logger_raw() noexcept {
return s_logger.get();
}
logger *global_logger_raw() noexcept { return s_logger.get(); }
void set_formatter(std::unique_ptr<formatter> formatter) { global_logger()->set_formatter(std::move(formatter)); }

View File

@@ -10,6 +10,7 @@
using spdlog::sinks::async_sink;
using spdlog::sinks::sink;
using spdlog::sinks::test_sink_mt;
using namespace std::chrono_literals;
auto creat_async_logger(size_t queue_size, std::shared_ptr<sink> backend_sink) {
async_sink::config cfg;
@@ -41,7 +42,7 @@ TEST_CASE("basic async test ", "[async]") {
TEST_CASE("discard policy ", "[async]") {
auto test_sink = std::make_shared<test_sink_mt>();
test_sink->set_delay(std::chrono::milliseconds(1));
test_sink->set_delay(1ms);
async_sink::config config;
config.queue_size = 4;
config.policy = async_sink::overflow_policy::overrun_oldest;
@@ -62,7 +63,7 @@ TEST_CASE("discard policy ", "[async]") {
TEST_CASE("discard policy discard_new ", "[async]") {
auto test_sink = std::make_shared<test_sink_mt>();
test_sink->set_delay(std::chrono::milliseconds(1));
test_sink->set_delay(1ms);
async_sink::config config;
config.queue_size = 4;
config.policy = async_sink::overflow_policy::discard_new;
@@ -71,7 +72,6 @@ TEST_CASE("discard policy discard_new ", "[async]") {
auto as = std::make_shared<async_sink>(config);
auto logger = std::make_shared<spdlog::logger>("async_logger", as);
REQUIRE(as->get_config().policy == async_sink::overflow_policy::discard_new);
REQUIRE(as->get_discard_counter() == 0);
REQUIRE(as->get_overrun_counter() == 0);
@@ -95,14 +95,13 @@ TEST_CASE("flush", "[async]") {
}
logger->flush();
}
// std::this_thread::sleep_for(std::chrono::milliseconds(250));
REQUIRE(test_sink->msg_counter() == messages);
REQUIRE(test_sink->flush_counter() == 1);
}
TEST_CASE("wait_dtor ", "[async]") {
auto test_sink = std::make_shared<test_sink_mt>();
test_sink->set_delay(std::chrono::milliseconds(5));
test_sink->set_delay(5ms);
async_sink::config config;
config.sinks.push_back(test_sink);
config.queue_size = 4;
@@ -167,7 +166,6 @@ TEST_CASE("to_file", "[async]") {
REQUIRE(ends_with(contents, spdlog::fmt_lib::format("Hello message #1023{}", default_eol)));
}
TEST_CASE("bad_ctor", "[async]") {
async_sink::config cfg;
cfg.queue_size = 0;
@@ -295,3 +293,63 @@ TEST_CASE("backend_ex", "[async]") {
REQUIRE_NOTHROW(logger->info("Hello message"));
REQUIRE_NOTHROW(logger->flush());
}
// test async custom error handler. trigger it using a backend exception and make sure it's called
TEST_CASE("custom_err_handler", "[async]") {
bool error_called = false;
auto test_sink = std::make_shared<test_sink_mt>();
test_sink->set_exception(std::runtime_error("test backend exception"));
async_sink::config config;
config.sinks.push_back(std::move(test_sink));
config.custom_err_handler = [&error_called](const std::string &) { error_called = true; };
auto asink = std::make_shared<async_sink>(config);
spdlog::logger("async_logger", std::move(asink)).info("Test");
// lvalue logger so will be destructed here already so all messages were processed
REQUIRE(error_called);
}
// test wait_all
TEST_CASE("wait_all", "[async]") {
auto test_sink = std::make_shared<test_sink_mt>();
auto delay = 10ms;
test_sink->set_delay(delay);
async_sink::config config;
config.sinks.push_back(test_sink);
size_t messages = 10;
auto as = std::make_shared<async_sink>(config);
auto logger = std::make_shared<spdlog::logger>("async_logger", as);
for (size_t i = 0; i < messages; i++) {
logger->info("Hello message");
}
REQUIRE_FALSE(as->wait_all(-10ms));
REQUIRE_FALSE(as->wait_all(0ms));
auto start = std::chrono::steady_clock::now();
REQUIRE_FALSE(as->wait_all(delay));
// should have waited approx 10ms before giving up
auto elapsed = std::chrono::steady_clock::now() - start;
REQUIRE(elapsed >= delay);
REQUIRE(elapsed < delay * 6); // big tolerance, to pass tests in slow virtual machines
// wait enough time for all messages to be processed
REQUIRE(as->wait_all(messages * delay + 500ms));
REQUIRE(as->wait_all(-10ms)); // no more messages
REQUIRE(as->wait_all(0ms)); // no more messages
REQUIRE(as->wait_all(10ms)); // no more messages
}
// test wait_all without timeout
TEST_CASE("wait_all2", "[async]") {
auto test_sink = std::make_shared<test_sink_mt>();
auto delay = 10ms;
test_sink->set_delay(delay);
async_sink::config config;
config.sinks.push_back(test_sink);
size_t messages = 10;
auto as = std::make_shared<async_sink>(config);
auto logger = std::make_shared<spdlog::logger>("async_logger", as);
for (size_t i = 0; i < messages; i++) {
logger->info("Hello message");
}
as->wait_all();
REQUIRE(test_sink->msg_counter() == messages);
}

View File

@@ -18,11 +18,10 @@ protected:
};
struct custom_ex {};
using namespace spdlog::sinks;
TEST_CASE("default_error_handler", "[errors]") {
prepare_logdir();
auto logger = spdlog::create<basic_file_sink_mt>("test-error", log_filename);
auto logger = spdlog::create<basic_file_sink_mt>("test-bad-format", log_filename);
logger->set_pattern("%v");
logger->info(SPDLOG_FMT_RUNTIME("Test message {} {}"), 1);
logger->info("Test message {}", 2);
@@ -36,17 +35,43 @@ TEST_CASE("custom_error_handler", "[errors]") {
prepare_logdir();
auto logger = spdlog::create<basic_file_sink_mt>("test-format-error", log_filename);
logger->flush_on(spdlog::level::info);
logger->set_error_handler([=](const std::string & msg) {
REQUIRE(msg == "argument not found");
});
logger->set_error_handler([=](const std::string &msg) { REQUIRE(msg == "argument not found"); });
logger->info("Good message #1");
REQUIRE_NOTHROW(logger->info(SPDLOG_FMT_RUNTIME("Bad format msg {} {}"), "xxx"));
logger->info("Good message #2");
require_message_count(log_filename, 2);
}
TEST_CASE("default_error_handler2", "[errors]") {
auto logger = std::make_shared<spdlog::logger>("test-failing-sink", std::make_shared<failing_sink>());
TEST_CASE("throwing_sink", "[errors]") {
auto logger = std::make_shared<spdlog::logger>("test-throwing-sink", std::make_shared<failing_sink>());
REQUIRE_NOTHROW(logger->info("Some message"));
}
TEST_CASE("throwing_flush", "[errors]") {
auto logger = spdlog::create<failing_sink>("test-throwing-sink");
REQUIRE_NOTHROW(logger->flush());
}
TEST_CASE("throwing_error_handler", "[errors]") {
auto logger = std::make_shared<spdlog::logger>("test-throwing-error-handler", std::make_shared<failing_sink>());
logger->set_error_handler([=](const std::string &msg) {
REQUIRE(msg == log_err_msg);
throw std::runtime_error("test throw");
});
REQUIRE_NOTHROW(logger->info("Some message"));
}
TEST_CASE("throwing_flush_error_handler", "[errors]") {
auto logger = spdlog::create<failing_sink>("test-throwing-error-handler");
logger->set_error_handler([=](const std::string &msg) {
REQUIRE(msg == flush_err_msg);
throw std::runtime_error("test throw");
});
REQUIRE_NOTHROW(logger->flush());
}
TEST_CASE("unknown_ex_from_err_handler", "[errors]") {
auto logger = std::make_shared<spdlog::logger>("test-throwing-error-handler", std::make_shared<failing_sink>());
logger->set_error_handler([=](const std::string &msg) {
REQUIRE(msg == log_err_msg);
throw custom_ex();
@@ -54,8 +79,8 @@ TEST_CASE("default_error_handler2", "[errors]") {
REQUIRE_NOTHROW(logger->info("Some message"));
}
TEST_CASE("flush_error_handler", "[errors]") {
auto logger = spdlog::create<failing_sink>("test-failing-sink");
TEST_CASE("unknown_ex_from_flush_err_handler", "[errors]") {
auto logger = spdlog::create<failing_sink>("test-throwing-error-handler");
logger->set_error_handler([=](const std::string &msg) {
REQUIRE(msg == flush_err_msg);
throw custom_ex();

View File

@@ -7,8 +7,8 @@
#include "includes.h"
#include "spdlog/details/os.h"
#include "spdlog/sinks/ostream_sink.h"
#include "spdlog/sinks/async_sink.h"
#include "spdlog/sinks/ostream_sink.h"
#include "test_sink.h"
template <class T>
@@ -74,23 +74,93 @@ TEST_CASE("to_level_enum", "[convert_to_level_enum]") {
REQUIRE(spdlog::level_from_str("null") == spdlog::level::off);
}
TEST_CASE("copy_ctor", "[copy_ctor]") {
using spdlog::sinks::test_sink_mt;
auto test_sink = std::make_shared<test_sink_mt>();
auto logger = std::make_shared<spdlog::logger>("orig", test_sink);
logger->set_pattern("%v");
bool error_handled = false;
logger->set_error_handler([&error_handled](const std::string&) { error_handled = true; });
spdlog::logger copied = *logger;
REQUIRE(copied.name() == logger->name());
REQUIRE(logger->sinks() == copied.sinks());
REQUIRE(logger->log_level() == copied.log_level());
REQUIRE(logger->flush_level() == copied.flush_level());
logger->info("Some message 1");
copied.info("Some message 2");
REQUIRE(test_sink->lines().size() == 2);
REQUIRE(test_sink->lines()[0] == "Some message 1");
REQUIRE(test_sink->lines()[1] == "Some message 2");
// check that copied custom error handler was indeed copied
test_sink->set_exception(std::runtime_error("Some error"));
REQUIRE(error_handled == false);
copied.error("Some error");
REQUIRE(error_handled == true);
}
TEST_CASE("move_ctor", "[move_ctor]") {
auto log_level = spdlog::level::critical;
auto flush_level = spdlog::level::warn;
using spdlog::sinks::test_sink_mt;
auto test_sink = std::make_shared<test_sink_mt>();
auto logger = std::make_shared<spdlog::logger>("orig", test_sink);
logger->flush_on(flush_level);
logger->set_level(log_level);
logger->set_pattern("%v");
bool error_handled = false;
logger->set_error_handler([&error_handled](const std::string&) { error_handled = true; });
spdlog::logger moved = std::move(*logger);
REQUIRE(logger->name() == "");
REQUIRE(logger->sinks().empty());
REQUIRE(moved.name() == "orig");
REQUIRE(moved.sinks()[0].get() == test_sink.get());
REQUIRE(moved.log_level() == log_level);
REQUIRE(moved.flush_level() == flush_level);
logger->critical("Some message 1");
moved.critical("Some message 2");
REQUIRE(test_sink->lines().size() == 1);
REQUIRE(test_sink->lines()[0] == "Some message 2");
// check that copied custom error handler was indeed copied
test_sink->set_exception(std::runtime_error("Some error"));
REQUIRE(error_handled == false);
moved.critical("Some error");
REQUIRE(error_handled == true);
}
TEST_CASE("clone-logger", "[clone]") {
using spdlog::sinks::test_sink_mt;
auto test_sink = std::make_shared<test_sink_mt>();
auto logger = std::make_shared<spdlog::logger>("orig", test_sink);
logger->set_pattern("%v");
bool error_handled = false;
logger->set_error_handler([&error_handled](const std::string&) { error_handled = true; });
auto cloned = logger->clone("clone");
REQUIRE(cloned->name() == "clone");
REQUIRE(logger->sinks() == cloned->sinks());
REQUIRE(logger->log_level() == cloned->log_level());
REQUIRE(logger->flush_level() == cloned->flush_level());
logger->info("Some message 1");
cloned->info("Some message 2");
REQUIRE(test_sink->lines().size() == 2);
REQUIRE(test_sink->lines()[0] == "Some message 1");
REQUIRE(test_sink->lines()[1] == "Some message 2");
// check that cloned custom error handler was indeed cloned
test_sink->set_exception(std::runtime_error("Some error"));
REQUIRE(error_handled == false);
cloned->error("Some error");
REQUIRE(error_handled == true);
}
TEST_CASE("clone async", "[clone]") {

View File

@@ -2,6 +2,8 @@
#include "spdlog/sinks/ostream_sink.h"
#include "test_sink.h"
#include <chrono>
using spdlog::memory_buf_t;
// log to str and return it
@@ -18,6 +20,21 @@ static std::string log_to_str(const std::string &msg, const Args &...args) {
return oss.str();
}
// log to str and return it with time
template <typename... Args>
static std::string log_to_str_with_time(spdlog::log_clock::time_point log_time, const std::string &msg, const Args &...args) {
std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::logger oss_logger("pattern_tester", oss_sink);
oss_logger.set_level(spdlog::level::info);
oss_logger.set_formatter(
std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(args...)));
oss_logger.log(log_time, {}, spdlog::level::info, msg);
return oss.str();
}
TEST_CASE("custom eol", "[pattern_formatter]") {
std::string msg = "Hello custom eol test";
std::string eol = ";)";
@@ -52,6 +69,15 @@ TEST_CASE("date MM/DD/YY ", "[pattern_formatter]") {
REQUIRE(log_to_str("Some message", "%D %v", spdlog::pattern_time_type::local, "\n") == oss.str());
}
TEST_CASE("GMT offset ", "[pattern_formatter]") {
using namespace std::chrono_literals;
const auto now = std::chrono::system_clock::now();
const auto yesterday = now - 24h;
REQUIRE(log_to_str_with_time(yesterday, "Some message", "%z", spdlog::pattern_time_type::utc, "\n") ==
"+00:00\n");
}
TEST_CASE("color range test1", "[pattern_formatter]") {
auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n");

View File

@@ -6,9 +6,9 @@
#pragma once
#include <chrono>
#include <exception>
#include <mutex>
#include <thread>
#include <exception>
#include "spdlog/details/null_mutex.h"
#include "spdlog/details/os.h"
@@ -37,13 +37,9 @@ public:
delay_ = delay;
}
void set_exception(const std::runtime_error& ex) {
exception_ptr_ = std::make_exception_ptr(ex);
}
void set_exception(const std::runtime_error& ex) { exception_ptr_ = std::make_exception_ptr(ex); }
void clear_exception() {
exception_ptr_ = nullptr;
}
void clear_exception() { exception_ptr_ = nullptr; }
// return last output without the eol
std::vector<std::string> lines() {
@@ -52,7 +48,7 @@ public:
}
protected:
void sink_it_(const details::log_msg &msg) override {
void sink_it_(const details::log_msg& msg) override {
if (exception_ptr_) {
std::rethrow_exception(exception_ptr_);
}
@@ -78,7 +74,7 @@ protected:
size_t flush_counter_{0};
std::chrono::milliseconds delay_{std::chrono::milliseconds::zero()};
std::vector<std::string> lines_;
std::exception_ptr exception_ptr_; // will be thrown on next log or flush if not null
std::exception_ptr exception_ptr_; // will be thrown on next log or flush if not null
};
using test_sink_mt = test_sink<std::mutex>;