diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..0a9050b3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "vendor/zf_log"] + path = vendor/zf_log + url = https://github.com/wonder-mice/zf_log.git +[submodule "vendor/glog"] + path = vendor/glog + url = https://github.com/google/glog.git +[submodule "vendor/easyloggingpp"] + path = vendor/easyloggingpp + url = https://github.com/easylogging/easyloggingpp.git +[submodule "vendor/g3log"] + path = vendor/g3log + url = https://github.com/KjellKod/g3log.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f11d7327..4c85f7ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(spdlog INTERFACE) option(SPDLOG_BUILD_EXAMPLES "Build examples" OFF) +option(SPDLOG_BUILD_TESTS "Build tests" OFF) +option(SPDLOG_BUILD_BENCHMARKS "Build comparison benchmarks for various logging libraries" OFF) target_include_directories( spdlog @@ -20,9 +22,20 @@ target_include_directories( "$" ) +set(HEADER_BASE "${CMAKE_CURRENT_SOURCE_DIR}/include") + +include(CTest) if(SPDLOG_BUILD_EXAMPLES) - enable_testing() - add_subdirectory(example) + add_subdirectory(example) +endif() + +if(SPDLOG_BUILD_TESTS) + add_subdirectory(tests) +endif() + +if(SPDLOG_BUILD_BENCHMARKS) + add_subdirectory(vendor) + add_subdirectory(bench) endif() ### Install ### diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt new file mode 100644 index 00000000..563f5b65 --- /dev/null +++ b/bench/CMakeLists.txt @@ -0,0 +1,73 @@ +# +# Benchmarks against various logging systems +# + +# +# Dependencies +# + +find_package(Threads) + +enable_testing() + +# Helper function for building benchmark programs +function(add_benchmark _target) + set(options "") # no options + set(singleValueArgs "") # no single-value arguments + set(multiValueArgs LIBS SOURCES INCLUDES DEFINITIONS) # lists of additional libraries, source files, and include directories + cmake_parse_arguments(_benchmark "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) + + add_executable(${_target} ${_target}.cpp ${_benchmark_SOURCES}) + target_include_directories( + ${_target} + PUBLIC + ${HEADER_BASE} + ${_benchmark_INCLUDES} + ) + + target_link_libraries( + ${_target} + ${CMAKE_THREAD_LIBS_INIT} + ${_benchmark_LIBS} + ) + + if(_benchmark_DEFINITIONS) + target_compile_definitions(${_target} PUBLIC ${_benchmark_DEFINITIONS}) + endif() + + add_test(NAME test_benchmark_${_target} COMMAND ${_target}) +endfunction() + +# Benchmark programs +add_benchmark(spdlog-bench) +add_benchmark(spdlog-bench-mt) +add_benchmark(spdlog-async) + +if(TARGET zf_log) + add_benchmark(zf_log-bench LIBS zf_log) + add_benchmark(zf_log-bench-mt LIBS zf_log) +endif() + +find_package(Boost QUIET COMPONENTS log) +if(Boost_FOUND) + set(BOOST_DEFS "-DBOOST_LOG_DYN_LINK=1") + add_benchmark(boost-bench LIBS ${Boost_LIBRARIES} INCLUDES ${Boost_INCLUDE_DIRS} DEFINITIONS ${BOOST_DEFS}) + add_benchmark(boost-bench-mt LIBS ${Boost_LIBRARIES} INCLUDES ${Boost_INCLUDE_DIRS} DEFINITIONS ${BOOST_DEFS}) +endif() + +if(TARGET glog) + add_benchmark(glog-bench LIBS glog) + add_benchmark(glog-bench-mt LIBS glog) +endif() + +if(TARGET g3logger) + add_benchmark(g3log-async LIBS g3logger INCLUDES "${g3log_SOURCE_DIR}/src") +endif() + +if(TARGET easylogging) + add_benchmark(easylogging-bench LIBS easylogging) + add_benchmark(easylogging-bench-mt LIBS easylogging) +endif() + +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") + diff --git a/bench/easylogging-bench-mt.cpp b/bench/easylogging-bench-mt.cpp index 98d1ae35..18d81618 100644 --- a/bench/easylogging-bench-mt.cpp +++ b/bench/easylogging-bench-mt.cpp @@ -9,7 +9,7 @@ #define _ELPP_THREAD_SAFE #include "easylogging++.h" -_INITIALIZE_EASYLOGGINGPP +INITIALIZE_EASYLOGGINGPP using namespace std; diff --git a/bench/easylogging-bench.cpp b/bench/easylogging-bench.cpp index a952cbd5..fa20032e 100644 --- a/bench/easylogging-bench.cpp +++ b/bench/easylogging-bench.cpp @@ -6,7 +6,7 @@ #include "easylogging++.h" -_INITIALIZE_EASYLOGGINGPP +INITIALIZE_EASYLOGGINGPP int main(int, char* []) { diff --git a/bench/g3log-async.cpp b/bench/g3log-async.cpp new file mode 100644 index 00000000..f1e5c17f --- /dev/null +++ b/bench/g3log-async.cpp @@ -0,0 +1,63 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +template std::string format(const T& value); + +int main(int argc, char* argv[]) +{ + using namespace std::chrono; + using clock=steady_clock; + int thread_count = 10; + + if(argc > 1) + thread_count = atoi(argv[1]); + int howmany = 1000000; + + auto g3log = g3::LogWorker::createLogWorker(); + auto defaultHandler = g3log->addDefaultLogger(argv[0], "logs"); + g3::initializeLogging(g3log.get()); + + + std::atomic msg_counter {0}; + vector threads; + auto start = clock::now(); + for (int t = 0; t < thread_count; ++t) + { + threads.push_back(std::thread([&]() + { + while (true) + { + int counter = ++msg_counter; + if (counter > howmany) break; + LOG(INFO) << "g3log message #" << counter << ": This is some text for your pleasure"; + } + })); + } + + + for(auto &t:threads) + { + t.join(); + }; + + duration delta = clock::now() - start; + float deltaf = delta.count(); + auto rate = howmany/deltaf; + + cout << "Total: " << howmany << std::endl; + cout << "Threads: " << thread_count << std::endl; + std::cout << "Delta = " << deltaf << " seconds" << std::endl; + std::cout << "Rate = " << rate << "/sec" << std::endl; +} diff --git a/bench/zf_log-bench-mt.cpp b/bench/zf_log-bench-mt.cpp index aace2770..12cb48ac 100644 --- a/bench/zf_log-bench-mt.cpp +++ b/bench/zf_log-bench-mt.cpp @@ -9,7 +9,7 @@ const char g_path[] = "logs/zf_log.txt"; int g_fd; -static void output_callback(zf_log_message *msg) +static void output_callback(const zf_log_message* msg, void* arg) { *msg->p = '\n'; write(g_fd, msg->buf, msg->p - msg->buf + 1); @@ -25,7 +25,7 @@ int main(int argc, char* argv[]) ZF_LOGE_AUX(ZF_LOG_STDERR, "Failed to open log file: %s", g_path); return -1; } - zf_log_set_output_callback(ZF_LOG_PUT_STD, output_callback); + ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_PUT_STD, nullptr, &output_callback}; int thread_count = 10; if(argc > 1) diff --git a/bench/zf_log-bench.cpp b/bench/zf_log-bench.cpp index a6e3e1ff..d6024cf5 100644 --- a/bench/zf_log-bench.cpp +++ b/bench/zf_log-bench.cpp @@ -4,7 +4,7 @@ const char g_path[] = "logs/zf_log.txt"; static FILE *g_f; -static void output_callback(zf_log_message *msg) +static void output_callback(const zf_log_message* msg, void* arg) { *msg->p = '\n'; fwrite(msg->buf, msg->p - msg->buf + 1, 1, g_f); @@ -18,7 +18,7 @@ int main(int, char* []) ZF_LOGE_AUX(ZF_LOG_STDERR, "Failed to open log file: %s", g_path); return -1; } - zf_log_set_output_callback(ZF_LOG_PUT_STD, output_callback); + ZF_LOG_DEFINE_GLOBAL_OUTPUT = {ZF_LOG_PUT_STD, nullptr, &output_callback}; const int howmany = 1000000; for(int i = 0 ; i < howmany; ++i) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 6ef158e1..5abefefb 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -21,29 +21,16 @@ # * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ # *************************************************************************/ -cmake_minimum_required(VERSION 3.0) -project(SpdlogExamples) - -if(TARGET spdlog) - # Part of the main project - add_library(spdlog::spdlog ALIAS spdlog) -else() - # Stand-alone build - find_package(spdlog CONFIG REQUIRED) -endif() - -if (CMAKE_COMPILER_IS_GNUCXX) - set ( CMAKE_CXX_FLAGS "--std=c++11 -pthread") - set ( CMAKE_EXE_LIKKER_FLAGS "-pthread") -endif () +find_package(Threads) add_executable(example example.cpp) -target_link_libraries(example spdlog::spdlog) +target_link_libraries(example spdlog ${CMAKE_THREAD_LIBS_INIT}) add_executable(benchmark bench.cpp) -target_link_libraries(benchmark spdlog::spdlog) +target_link_libraries(benchmark spdlog ${CMAKE_THREAD_LIBS_INIT}) enable_testing() file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") add_test(NAME RunExample COMMAND example) add_test(NAME RunBenchmark COMMAND benchmark) + diff --git a/include/spdlog/sinks/stdout_sinks.h b/include/spdlog/sinks/stdout_sinks.h index ca4c55ac..1921bce2 100644 --- a/include/spdlog/sinks/stdout_sinks.h +++ b/include/spdlog/sinks/stdout_sinks.h @@ -5,6 +5,7 @@ #pragma once +#include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..307ddeb6 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Tests +# + +enable_testing() + +# Build Catch unit tests +add_library(catch INTERFACE) +target_include_directories(catch INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +file(GLOB catch_tests LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp) +add_executable(catch_tests ${catch_tests}) +target_link_libraries(catch_tests spdlog) +add_test(NAME catch_tests COMMAND catch_tests) +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") + +# Ensure headers include their own dependencies +add_subdirectory(header_dependencies) + diff --git a/tests/header_dependencies/CMakeLists.txt b/tests/header_dependencies/CMakeLists.txt new file mode 100644 index 00000000..81779694 --- /dev/null +++ b/tests/header_dependencies/CMakeLists.txt @@ -0,0 +1,58 @@ +# +# Ensure all headers include all dependencies +# + +set(IGNORED_HEADERS "") + +set(COMMON_TEST_LIBRARIES spdlog) + +add_custom_target(header_dependencies) + +file(GLOB_RECURSE headers RELATIVE "${HEADER_BASE}" ${HEADER_BASE}/*.h) +set(test_index 0) +foreach(HEADER ${headers}) + # Sample of relevant variables computed here + # HEADER: details/line_logger_impl.h + # symbolname: spdlog_details_line_logger_impl + + # Compute symbolname + string(REPLACE ".h" "" symbolname "${HEADER}") + string(MAKE_C_IDENTIFIER "${symbolname}" symbolname) + + list(FIND IGNORED_HEADERS "${HEADER}" _index) + # If we didn't explicitly ignore this and if we built this target + if(${_index} EQUAL -1) + #message(STATUS "${HEADER}: '${symbolname}'") + + set(extension cpp) + + # Name the test and output file with a number, to dodge Windows path length limits. + # Call it header, instead of test, to avoid polluting the 'executable namespace' + set(test_name "header_${extension}_${test_index}") + + set(source_file "${CMAKE_CURRENT_SOURCE_DIR}/main.${extension}") + + add_executable(${test_name} "${source_file}") + target_compile_definitions(${test_name} PRIVATE HEADER_TO_TEST="${HEADER}") + target_include_directories(${test_name} + PRIVATE + ${BUILDTREE_HEADER_BASE} + ${HEADER_BASE}) + + set_target_properties(${test_name} PROPERTIES + FOLDER "Header dependency tests") + + target_link_libraries(${test_name} + PRIVATE + ${COMMON_TEST_LIBRARIES} + ${LIBRARIES_${symbolname}} + ${LIBRARIES_${libname}}) + + add_test(NAME ${test_name}_builds COMMAND ${test_name}) + add_dependencies(header_dependencies ${test_name}) + + math(EXPR test_index "${test_index} + 1") + endif() +endforeach() + + diff --git a/tests/header_dependencies/main.c b/tests/header_dependencies/main.c new file mode 100644 index 00000000..d2b5af77 --- /dev/null +++ b/tests/header_dependencies/main.c @@ -0,0 +1,7 @@ + +#include HEADER_TO_TEST + +int main(int argc, char** argv) +{ + return 0; +} diff --git a/tests/header_dependencies/main.cpp b/tests/header_dependencies/main.cpp new file mode 100644 index 00000000..7716c88b --- /dev/null +++ b/tests/header_dependencies/main.cpp @@ -0,0 +1,4 @@ + +#include HEADER_TO_TEST + +int main(int argc, char *argv[]) { return 0; } diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt new file mode 100644 index 00000000..a9fed11e --- /dev/null +++ b/vendor/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# External libraries +# +# +# Most of these libraries are used for running comparison benchmarks against +# other logging libraries. + +if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/zf_log") + add_subdirectory(zf_log) +endif() + +if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/glog") + add_subdirectory(glog) +endif() + +if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/easyloggingpp") + add_library(easylogging INTERFACE) + set(SPDLOG_VENDORED_EASYLOGGING_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/easyloggingpp" CACHE INTERNAL "" FORCE) + target_include_directories(easylogging INTERFACE "${SPDLOG_VENDORED_EASYLOGGING_ROOT}/src") +endif() + +if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/g3log") + add_subdirectory(g3log) +endif() + diff --git a/vendor/easyloggingpp b/vendor/easyloggingpp new file mode 160000 index 00000000..f926802d --- /dev/null +++ b/vendor/easyloggingpp @@ -0,0 +1 @@ +Subproject commit f926802dfbde716d82b64b8ef3c25b7f0fcfec65 diff --git a/vendor/g3log b/vendor/g3log new file mode 160000 index 00000000..6c1698c4 --- /dev/null +++ b/vendor/g3log @@ -0,0 +1 @@ +Subproject commit 6c1698c4f7db6b9e4246ead38051f9866ea3ac06 diff --git a/vendor/glog b/vendor/glog new file mode 160000 index 00000000..de6149ef --- /dev/null +++ b/vendor/glog @@ -0,0 +1 @@ +Subproject commit de6149ef8e67b064a433a8b88924fa9f606ad5d5 diff --git a/vendor/zf_log b/vendor/zf_log new file mode 160000 index 00000000..4c15e670 --- /dev/null +++ b/vendor/zf_log @@ -0,0 +1 @@ +Subproject commit 4c15e6704edffdafe289d4b84c2db89009368626