merge with v1.x

This commit is contained in:
gabime 2020-02-26 18:13:13 +02:00
commit c10be7eaec
37 changed files with 909 additions and 249 deletions

View File

@ -13,7 +13,7 @@ readability-*,\
clang-analyzer-*' clang-analyzer-*'
WarningsAsErrors: '' WarningsAsErrors: ''
HeaderFilterRegex: 'async.h|async_logger.h|common.h|details|formatter.h|logger.h|sinks|spdlog.h|tweakme.h|version.h' HeaderFilterRegex: '*spdlog/[^f].*'
AnalyzeTemporaryDtors: false AnalyzeTemporaryDtors: false
FormatStyle: none FormatStyle: none

View File

@ -3,15 +3,21 @@
cmake_minimum_required(VERSION 3.2) cmake_minimum_required(VERSION 3.2)
ENABLE_LANGUAGE(C)
#--------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------
# Start spdlog project # Start spdlog project
#--------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------
include(GNUInstallDirs)
include(cmake/utils.cmake) include(cmake/utils.cmake)
include(cmake/ide.cmake) include(cmake/ide.cmake)
spdlog_extract_version() spdlog_extract_version()
project(spdlog VERSION ${SPDLOG_VERSION} LANGUAGES CXX)
message(STATUS "Build spdlog: ${SPDLOG_VERSION}")
include(GNUInstallDirs)
#--------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------
# Set default build to release # Set default build to release
#--------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------
@ -19,8 +25,6 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
endif() endif()
project(spdlog VERSION ${SPDLOG_VERSION} LANGUAGES CXX)
message(STATUS "Build spdlog: ${SPDLOG_VERSION}")
#--------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------
# Compiler config # Compiler config
@ -92,6 +96,17 @@ option(SPDLOG_NO_THREAD_ID "prevent spdlog from querying the thread id on each l
option(SPDLOG_NO_TLS "prevent spdlog from using thread local storage" OFF) option(SPDLOG_NO_TLS "prevent spdlog from using thread local storage" OFF)
option(SPDLOG_NO_ATOMIC_LEVELS "prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently" OFF) option(SPDLOG_NO_ATOMIC_LEVELS "prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently" OFF)
# clang-tidy
if(${CMAKE_VERSION} VERSION_GREATER "3.5")
option(SPDLOG_TIDY "run clang-tidy" OFF)
endif()
if(SPDLOG_TIDY)
set(CMAKE_CXX_CLANG_TIDY "clang-tidy")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
message(STATUS "Enabled clang-tidy")
endif()
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
#--------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------
@ -286,6 +301,5 @@ if (SPDLOG_INSTALL)
# Support creation of installable packages # Support creation of installable packages
#--------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------
include(cmake/spdlogCPack.cmake) include(cmake/spdlogCPack.cmake)
endif () endif ()

View File

@ -20,3 +20,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
-- NOTE: Third party dependecy used by this sofware --
This software depends on the fmt lib (MIT License),
and users must comply to its license: https://github.com/fmtlib/fmt/blob/master/LICENSE.rst

View File

@ -25,12 +25,13 @@ $ cmake .. && make -j
## Package managers: ## Package managers:
* Homebrew: `brew install spdlog` * Homebrew: `brew install spdlog`
* MacPorts: `sudo port install spdlog`
* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean` * FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean`
* Fedora: `yum install spdlog` * Fedora: `yum install spdlog`
* Gentoo: `emerge dev-libs/spdlog` * Gentoo: `emerge dev-libs/spdlog`
* Arch Linux: `yaourt -S spdlog-git` * Arch Linux: `yaourt -S spdlog-git`
* vcpkg: `vcpkg install spdlog` * vcpkg: `vcpkg install spdlog`
* conan: `spdlog/[>=1.4.1]@bincrafters/stable` * conan: `spdlog/[>=1.4.1]`
## Features ## Features
@ -299,6 +300,15 @@ void android_example()
android_logger->critical("Use \"adb shell logcat\" to view this message."); android_logger->critical("Use \"adb shell logcat\" to view this message.");
} }
``` ```
---
#### Compile-time format string syntax checking
```C++
#include "spdlog/spdlog.h"
int main()
{
spdlog::info(FMT_STRING("{:d} is an invalid format tag"));
}
```
## Benchmarks ## Benchmarks

View File

@ -1,7 +1,4 @@
set(CPACK_GENERATOR set(CPACK_GENERATOR "TGZ;ZIP" CACHE STRING "Semicolon separated list of generators")
TGZ
ZIP
)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0) set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_INSTALL_CMAKE_PROJECTS set(CPACK_INSTALL_CMAKE_PROJECTS
@ -22,11 +19,32 @@ set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PR
if (PROJECT_VERSION_TWEAK) if (PROJECT_VERSION_TWEAK)
set(CPACK_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}.${PROJECT_VERSION_TWEAK}) set(CPACK_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}.${PROJECT_VERSION_TWEAK})
endif () endif ()
set(CPACK_PACKAGE_RELOCATABLE ON) set(CPACK_PACKAGE_RELOCATABLE ON CACHE BOOL "Build relocatable package")
set(CPACK_RPM_PACKAGE_LICENSE "MIT") set(CPACK_RPM_PACKAGE_LICENSE "MIT")
set(CPACK_RPM_PACKAGE_GROUP "System Environment/Libraries") set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries")
set(CPACK_RPM_PACKAGE_URL ${CPACK_PROJECT_URL}) set(CPACK_RPM_PACKAGE_URL ${CPACK_PROJECT_URL})
set(CPACK_RPM_PACKAGE_DESCRIPTION "Very fast, header-only/compiled, C++ logging library.") set(CPACK_RPM_PACKAGE_DESCRIPTION "Very fast, header-only/compiled, C++ logging library.")
if (CPACK_PACKAGE_NAME)
set(CPACK_RPM_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
else()
set(CPACK_RPM_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
endif()
if (CPACK_RPM_PACKAGE_RELEASE)
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}-${CPACK_RPM_PACKAGE_RELEASE}")
endif ()
if (CPACK_RPM_PACKAGE_ARCHITECTURE)
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.${CPACK_RPM_PACKAGE_ARCHITECTURE}")
endif ()
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.rpm")
if (NOT CPACK_PACKAGE_RELOCATABLE)
# Depend on pkgconfig rpm to create the system pkgconfig folder
set(CPACK_RPM_PACKAGE_REQUIRES pkgconfig)
set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CPACK_PACKAGING_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig")
endif ()
include(CPack) include(CPack)

View File

@ -4,6 +4,8 @@
cmake_minimum_required(VERSION 3.1) cmake_minimum_required(VERSION 3.1)
project(spdlog_examples CXX) project(spdlog_examples CXX)
include(../cmake/utils.cmake)
if(NOT TARGET spdlog) if(NOT TARGET spdlog)
# Stand-alone build # Stand-alone build
find_package(spdlog REQUIRED) find_package(spdlog REQUIRED)

View File

@ -15,18 +15,6 @@
#include <type_traits> #include <type_traits>
#include <functional> #include <functional>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX // prevent windows redefining min/max
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif //_WIN32
#ifdef SPDLOG_COMPILED_LIB #ifdef SPDLOG_COMPILED_LIB
#undef SPDLOG_HEADER_ONLY #undef SPDLOG_HEADER_ONLY
#define SPDLOG_INLINE #define SPDLOG_INLINE
@ -147,6 +135,7 @@ enum level_enum
err = SPDLOG_LEVEL_ERROR, err = SPDLOG_LEVEL_ERROR,
critical = SPDLOG_LEVEL_CRITICAL, critical = SPDLOG_LEVEL_CRITICAL,
off = SPDLOG_LEVEL_OFF, off = SPDLOG_LEVEL_OFF,
n_levels
}; };
#if !defined(SPDLOG_LEVEL_NAMES) #if !defined(SPDLOG_LEVEL_NAMES)

View File

@ -23,16 +23,9 @@
#ifdef _WIN32 #ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX // prevent windows redefining min/max
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <io.h> // _get_osfhandle and _isatty support #include <io.h> // _get_osfhandle and _isatty support
#include <process.h> // _get_pid support #include <process.h> // _get_pid support
#include <windows.h> #include <spdlog/details/windows_include.h>
#ifdef __MINGW32__ #ifdef __MINGW32__
#include <share.h> #include <share.h>
@ -126,23 +119,6 @@ SPDLOG_INLINE std::tm gmtime() SPDLOG_NOEXCEPT
return gmtime(now_t); return gmtime(now_t);
} }
#ifdef SPDLOG_PREVENT_CHILD_FD
SPDLOG_INLINE void prevent_child_fd(FILE *f)
{
#ifdef _WIN32
auto file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(::_fileno(f)));
if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0))
SPDLOG_THROW(spdlog_ex("SetHandleInformation failed", errno));
#else
auto fd = ::fileno(f);
if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
{
SPDLOG_THROW(spdlog_ex("fcntl with FD_CLOEXEC failed", errno));
}
#endif
}
#endif // SPDLOG_PREVENT_CHILD_FD
// fopen_s on non windows for writing // fopen_s on non windows for writing
SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode)
{ {
@ -152,17 +128,35 @@ SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename
#else #else
*fp = ::_fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); *fp = ::_fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO);
#endif #endif
#else // unix #if defined(SPDLOG_PREVENT_CHILD_FD)
*fp = ::fopen((filename.c_str()), mode.c_str());
#endif
#ifdef SPDLOG_PREVENT_CHILD_FD
// prevent child processes from inheriting log file descriptors
if (*fp != nullptr) if (*fp != nullptr)
{ {
prevent_child_fd(*fp); auto file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(::_fileno(*fp)));
if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0))
{
:fclose(*fp);
*fp = nullptr;
}
} }
#endif #endif
#else // unix
#if defined(SPDLOG_PREVENT_CHILD_FD)
const int mode_flag = mode == SPDLOG_FILENAME_T("ab") ? O_APPEND : O_TRUNC;
const int fd = ::open((filename.c_str()), O_CREAT | O_WRONLY | O_CLOEXEC | mode_flag, mode_t(0644));
if (fd == -1)
{
return false;
}
*fp = ::fdopen(fd, mode.c_str());
if (*fp == nullptr)
{
::close(fd);
}
#else
*fp = ::fopen((filename.c_str()), mode.c_str());
#endif
#endif
return *fp == nullptr; return *fp == nullptr;
} }

View File

@ -38,10 +38,6 @@ static const char folder_sep = '\\';
SPDLOG_CONSTEXPR static const char folder_sep = '/'; SPDLOG_CONSTEXPR static const char folder_sep = '/';
#endif #endif
#ifdef SPDLOG_PREVENT_CHILD_FD
void prevent_child_fd(FILE *f);
#endif
// fopen_s on non windows for writing // fopen_s on non windows for writing
bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode); bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode);

View File

@ -0,0 +1,149 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#ifdef _WIN32
#error tcp_client not supported under windows yet
#endif
// tcp client helper
#include <spdlog/common.h>
#include <spdlog/details/os.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <string>
namespace spdlog {
namespace details {
class tcp_client
{
int socket_ = -1;
public:
bool is_connected() const
{
return socket_ != -1;
}
void close()
{
if (is_connected())
{
::close(socket_);
socket_ = -1;
}
}
int fd() const
{
return socket_;
}
~tcp_client()
{
close();
}
// try to connect or throw on failure
void connect(const std::string &host, int port)
{
close();
spdlog::info("Connecting..");
struct addrinfo hints{};
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; // IPv4
hints.ai_socktype = SOCK_STREAM; // TCP
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0;
auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result;
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
if (rv != 0)
{
auto msg = fmt::format("::getaddrinfo failed: {}", gai_strerror(rv));
SPDLOG_THROW(spdlog::spdlog_ex(msg));
}
// Try each address until we successfully connect(2).
int last_errno = 0;
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next)
{
#ifdef SPDLOG_PREVENT_CHILD_FD
int const flags = SOCK_CLOEXEC;
#else
int const flags = 0;
#endif
socket_ = ::socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol);
if (socket_ == -1)
{
last_errno = errno;
continue;
}
rv = ::connect(socket_, rp->ai_addr, rp->ai_addrlen);
if (rv == 0)
{
break;
}
else
{
last_errno = errno;
::close(socket_);
socket_ = -1;
}
}
::freeaddrinfo(addrinfo_result);
if (socket_ == -1)
{
SPDLOG_THROW(spdlog::spdlog_ex("::connect failed", last_errno));
}
// set TCP_NODELAY
int enable_flag = 1;
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, (char *)&enable_flag, sizeof(enable_flag));
// prevent sigpipe on systems where MSG_NOSIGNAL is not available
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
::setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, (char *)&enable_flag, sizeof(enable_flag));
#endif
#if !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
#error "tcp_sink would raise SIGPIPE since niether SO_NOSIGPIPE nor MSG_NOSIGNAL are available"
#endif
}
// Send exactly n_bytes of the given data.
// On error close the connection and throw.
void send(const char *data, size_t n_bytes)
{
size_t bytes_sent = 0;
while (bytes_sent < n_bytes)
{
#if defined(MSG_NOSIGNAL)
const int send_flags = MSG_NOSIGNAL;
#else
const int send_flags = 0;
#endif
auto write_result = ::send(socket_, data + bytes_sent, n_bytes - bytes_sent, send_flags);
if (write_result < 0)
{
close();
SPDLOG_THROW(spdlog::spdlog_ex("write(2) failed", errno));
}
if (write_result == 0) // (probably should not happen but in any case..)
{
break;
}
bytes_sent += static_cast<size_t>(write_result);
}
}
};
} // namespace details
} // namespace spdlog

View File

@ -0,0 +1,11 @@
#pragma once
#ifndef NOMINMAX
#define NOMINMAX // prevent windows redefining min/max
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>

View File

@ -143,6 +143,11 @@ public:
// T can be statically converted to string_view // T can be statically converted to string_view
template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::string_view_t>::value, T>::type * = nullptr> template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::string_view_t>::value, T>::type * = nullptr>
void log(source_loc loc, level::level_enum lvl, const T &msg) void log(source_loc loc, level::level_enum lvl, const T &msg)
{
log(loc, lvl, string_view_t{msg});
}
void log(source_loc loc, level::level_enum lvl, string_view_t msg)
{ {
bool log_enabled = should_log(lvl); bool log_enabled = should_log(lvl);
bool traceback_enabled = tracer_.enabled(); bool traceback_enabled = tracer_.enabled();

View File

@ -43,7 +43,8 @@ SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::log(const details::log_msg &msg
// Wrap the originally formatted message in color codes. // Wrap the originally formatted message in color codes.
// If color is not supported in the terminal, log as is instead. // If color is not supported in the terminal, log as is instead.
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
msg.color_range_start = 0;
msg.color_range_end = 0;
memory_buf_t formatted; memory_buf_t formatted;
formatter_->format(msg, formatted); formatter_->format(msg, formatted);
if (should_do_colors_ && msg.color_range_end > msg.color_range_start) if (should_do_colors_ && msg.color_range_end > msg.color_range_start)
@ -111,7 +112,7 @@ SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode(color_mode mode)
template<typename ConsoleMutex> template<typename ConsoleMutex>
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(const string_view_t &color_code) SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(const string_view_t &color_code)
{ {
fwrite(color_code.data(), sizeof(string_view_t::char_type), color_code.size(), target_file_); fwrite(color_code.data(), sizeof(char), color_code.size(), target_file_);
} }
template<typename ConsoleMutex> template<typename ConsoleMutex>

View File

@ -9,7 +9,7 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <unordered_map> #include <array>
namespace spdlog { namespace spdlog {
namespace sinks { namespace sinks {
@ -80,7 +80,7 @@ private:
mutex_t &mutex_; mutex_t &mutex_;
bool should_do_colors_; bool should_do_colors_;
std::unique_ptr<spdlog::formatter> formatter_; std::unique_ptr<spdlog::formatter> formatter_;
std::unordered_map<level::level_enum, string_view_t, level::level_hasher> colors_; std::array<string_view_t, level::n_levels> colors_;
void print_ccode_(const string_view_t &color_code); void print_ccode_(const string_view_t &color_code);
void print_range_(const memory_buf_t &formatted, size_t start, size_t end); void print_range_(const memory_buf_t &formatted, size_t start, size_t end);
}; };

View File

@ -66,13 +66,13 @@ public:
if (max_files_ > 0) if (max_files_ > 0)
{ {
filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_)); init_filenames_q_();
filenames_q_.push_back(std::move(filename));
} }
} }
const filename_t &filename() const filename_t filename()
{ {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
return file_helper_.filename(); return file_helper_.filename();
} }
@ -104,6 +104,29 @@ protected:
} }
private: private:
void init_filenames_q_()
{
using details::os::path_exists;
filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_));
std::vector<filename_t> filenames;
auto now = log_clock::now();
while (filenames.size() < max_files_)
{
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now));
if (!path_exists(filename))
{
break;
}
filenames.emplace_back(filename);
now -= std::chrono::hours(24);
}
for (auto iter = filenames.rbegin(); iter != filenames.rend(); ++iter)
{
filenames_q_.push_back(std::move(*iter));
}
}
tm now_tm(log_clock::time_point tp) tm now_tm(log_clock::time_point tp)
{ {
time_t tnow = log_clock::to_time_t(tp); time_t tnow = log_clock::to_time_t(tp);
@ -167,15 +190,15 @@ using daily_file_sink_st = daily_file_sink<details::null_mutex>;
// //
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_mt( inline std::shared_ptr<logger> daily_logger_mt(
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0)
{ {
return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate); return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_st( inline std::shared_ptr<logger> daily_logger_st(
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0)
{ {
return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate); return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files);
} }
} // namespace spdlog } // namespace spdlog

View File

@ -8,6 +8,7 @@
#include <spdlog/details/null_mutex.h> #include <spdlog/details/null_mutex.h>
#include <spdlog/sinks/base_sink.h> #include <spdlog/sinks/base_sink.h>
#include <spdlog/details/windows_include.h>
#include <winbase.h> #include <winbase.h>
#include <mutex> #include <mutex>

View File

@ -54,8 +54,9 @@ SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename
} }
template<typename Mutex> template<typename Mutex>
SPDLOG_INLINE const filename_t &rotating_file_sink<Mutex>::filename() const SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::filename()
{ {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
return file_helper_.filename(); return file_helper_.filename();
} }
@ -99,13 +100,13 @@ SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_()
} }
filename_t target = calc_filename(base_filename_, i); filename_t target = calc_filename(base_filename_, i);
if (!rename_file(src, target)) if (!rename_file_(src, target))
{ {
// if failed try again after a small delay. // if failed try again after a small delay.
// this is a workaround to a windows issue, where very high rotation // this is a workaround to a windows issue, where very high rotation
// rates can cause the rename to fail with permission denied (because of antivirus?). // rates can cause the rename to fail with permission denied (because of antivirus?).
details::os::sleep_for_millis(100); details::os::sleep_for_millis(100);
if (!rename_file(src, target)) if (!rename_file_(src, target))
{ {
file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit!
current_size_ = 0; current_size_ = 0;
@ -120,7 +121,7 @@ SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_()
// delete the target if exists, and rename the src file to target // delete the target if exists, and rename the src file to target
// return true on success, false otherwise. // return true on success, false otherwise.
template<typename Mutex> template<typename Mutex>
SPDLOG_INLINE bool rotating_file_sink<Mutex>::rename_file(const filename_t &src_filename, const filename_t &target_filename) SPDLOG_INLINE bool rotating_file_sink<Mutex>::rename_file_(const filename_t &src_filename, const filename_t &target_filename)
{ {
// try to delete the target file in case it already exists. // try to delete the target file in case it already exists.
(void)details::os::remove(target_filename); (void)details::os::remove(target_filename);

View File

@ -24,7 +24,7 @@ class rotating_file_sink final : public base_sink<Mutex>
public: public:
rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false); rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false);
static filename_t calc_filename(const filename_t &filename, std::size_t index); static filename_t calc_filename(const filename_t &filename, std::size_t index);
const filename_t &filename() const; filename_t filename();
protected: protected:
void sink_it_(const details::log_msg &msg) override; void sink_it_(const details::log_msg &msg) override;
@ -40,7 +40,7 @@ private:
// delete the target if exists, and rename the src file to target // delete the target if exists, and rename the src file to target
// return true on success, false otherwise. // return true on success, false otherwise.
bool rename_file(const filename_t &src_filename, const filename_t &target_filename); bool rename_file_(const filename_t &src_filename, const filename_t &target_filename);
filename_t base_filename_; filename_t base_filename_;
std::size_t max_size_; std::size_t max_size_;

View File

@ -0,0 +1,75 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
#include <spdlog/common.h>
#include <spdlog/sinks/base_sink.h>
#include <spdlog/details/null_mutex.h>
#include <spdlog/details/tcp_client.h>
#include <mutex>
#include <string>
#include <chrono>
#include <functional>
#pragma once
// Simple tcp client sink
// Connects to remote address and send the formatted log.
// Will attempt to reconnect if connection drops.
// If more complicated behaviour is needed (i.e get responses), you can inherit it and override the sink_it_ method.
namespace spdlog {
namespace sinks {
struct tcp_sink_config
{
std::string server_host;
int server_port;
bool lazy_connect = false; // connect on first log call instead of in construction
tcp_sink_config(std::string host, int port)
: server_host{std::move(host)}
, server_port{port}
{}
};
template<typename Mutex>
class tcp_sink : public spdlog::sinks::base_sink<Mutex>
{
public:
// connect to tcp host/port or throw if failed
// host can be hostname or ip address
explicit tcp_sink(tcp_sink_config sink_config)
: config_{std::move(sink_config)}
{
if (!config_.lazy_connect)
{
this->client_.connect(config_.server_host, config_.server_port);
}
}
~tcp_sink() override = default;
protected:
void sink_it_(const spdlog::details::log_msg &msg) override
{
spdlog::memory_buf_t formatted;
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
if (!client_.is_connected())
{
client_.connect(config_.server_host, config_.server_port);
}
client_.send(formatted.data(), formatted.size());
}
void flush_() override {}
tcp_sink_config config_;
details::tcp_client client_;
};
using tcp_sink_mt = tcp_sink<std::mutex>;
using tcp_sink_st = tcp_sink<spdlog::details::null_mutex>;
} // namespace sinks
} // namespace spdlog

View File

@ -0,0 +1,267 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
// Writing to Windows Event Log requires the registry entries below to be present, with the following modifications:
// 1. <log_name> should be replaced with your log name (e.g. your application name)
// 2. <source_name> should be replaced with the specific source name and the key should be duplicated for
// each source used in the application
//
// Since typically modifications of this kind require elevation, it's better to do it as a part of setup procedure.
// The snippet below uses mscoree.dll as the message file as it exists on most of the Windows systems anyway and
// happens to contain the needed resource.
//
// You can also specify a custom message file if needed.
// Please refer to Event Log functions descriptions in MSDN for more details on custom message files.
/*---------------------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\<log_name>]
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\<log_name>\<source_name>]
"TypesSupported"=dword:00000007
"EventMessageFile"=hex(2):25,00,73,00,79,00,73,00,74,00,65,00,6d,00,72,00,6f,\
00,6f,00,74,00,25,00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,\
5c,00,6d,00,73,00,63,00,6f,00,72,00,65,00,65,00,2e,00,64,00,6c,00,6c,00,00,\
00
-----------------------------------------------------------------------------------------*/
#pragma once
#include <spdlog/details/null_mutex.h>
#include <spdlog/sinks/base_sink.h>
#include <spdlog/details/windows_include.h>
#include <winbase.h>
#include <mutex>
#include <string>
#include <vector>
namespace spdlog {
namespace sinks {
namespace win_eventlog {
namespace internal {
/** Windows error */
struct win32_error : public spdlog_ex
{
/** Formats an error report line: "user-message: error-code (system message)" */
static std::string format(std::string const &user_message, DWORD error_code = GetLastError())
{
std::string system_message;
LPSTR format_message_result{};
auto format_message_succeeded =
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&format_message_result, 0, nullptr);
if (format_message_succeeded && format_message_result)
{
system_message = fmt::format(" ({})", format_message_result);
}
if (format_message_result)
{
LocalFree((HLOCAL)format_message_result);
}
return fmt::format("{}: {}{}", user_message, error_code, system_message);
}
win32_error(std::string const &func_name, DWORD error = GetLastError())
: spdlog_ex(format(func_name, error))
{}
};
/** Wrapper for security identifiers (SID) on Windows */
struct sid_t
{
std::vector<char> buffer_;
public:
sid_t() {}
/** creates a wrapped SID copy */
static sid_t duplicate_sid(PSID psid)
{
if (!::IsValidSid(psid))
{
SPDLOG_THROW(spdlog_ex("sid_t::sid_t(): invalid SID received"));
}
auto const sid_length{::GetLengthSid(psid)};
sid_t result;
result.buffer_.resize(sid_length);
if (!::CopySid(sid_length, (PSID)result.as_sid(), psid))
{
SPDLOG_THROW(win32_error("CopySid"));
}
return result;
}
/** Retrieves pointer to the internal buffer contents as SID* */
SID *as_sid() const
{
return buffer_.empty() ? nullptr : (SID *)buffer_.data();
}
/** Get SID for the current user */
static sid_t get_current_user_sid()
{
/* create and init RAII holder for process token */
struct process_token_t
{
HANDLE token_handle_ = INVALID_HANDLE_VALUE;
explicit process_token_t(HANDLE process)
{
if (!::OpenProcessToken(process, TOKEN_QUERY, &token_handle_))
{
SPDLOG_THROW(win32_error("OpenProcessToken"));
}
}
~process_token_t()
{
::CloseHandle(token_handle_);
}
} current_process_token(::GetCurrentProcess()); // GetCurrentProcess returns pseudohandle, no leak here!
// Get the required size, this is expected to fail with ERROR_INSUFFICIENT_BUFFER and return the token size
DWORD tusize = 0;
if (::GetTokenInformation(current_process_token.token_handle_, TokenUser, NULL, 0, &tusize))
{
SPDLOG_THROW(win32_error("GetTokenInformation should fail"));
}
// get user token
std::vector<unsigned char> buffer(static_cast<size_t>(tusize));
if (!::GetTokenInformation(current_process_token.token_handle_, TokenUser, (LPVOID)buffer.data(), tusize, &tusize))
{
SPDLOG_THROW(win32_error("GetTokenInformation"));
}
// create a wrapper of the SID data as stored in the user token
return sid_t::duplicate_sid(((TOKEN_USER *)buffer.data())->User.Sid);
}
};
struct eventlog
{
static WORD get_event_type(details::log_msg const &msg)
{
switch (msg.level)
{
case level::trace:
case level::debug:
return EVENTLOG_SUCCESS;
case level::info:
return EVENTLOG_INFORMATION_TYPE;
case level::warn:
return EVENTLOG_WARNING_TYPE;
case level::err:
case level::critical:
case level::off:
return EVENTLOG_ERROR_TYPE;
default:
// should be unreachable
SPDLOG_THROW(std::logic_error(fmt::format("Unsupported log level {}", msg.level)));
}
}
static WORD get_event_category(details::log_msg const &msg)
{
return (WORD)msg.level;
}
};
} // namespace internal
/*
* Windows Event Log sink
*/
template<typename Mutex>
class win_eventlog_sink : public base_sink<Mutex>
{
private:
HANDLE hEventLog_{NULL};
internal::sid_t current_user_sid_;
std::string source_;
WORD event_id_;
HANDLE event_log_handle()
{
if (!hEventLog_)
{
hEventLog_ = ::RegisterEventSource(nullptr, source_.c_str());
if (!hEventLog_ || hEventLog_ == (HANDLE)ERROR_ACCESS_DENIED)
{
SPDLOG_THROW(internal::win32_error("RegisterEventSource"));
}
}
return hEventLog_;
}
protected:
void sink_it_(const details::log_msg &msg) override
{
using namespace internal;
memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted);
formatted.push_back('\0');
LPCSTR lp_str = static_cast<LPCSTR>(formatted.data());
auto succeeded = ::ReportEvent(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg), event_id_,
current_user_sid_.as_sid(), 1, 0, &lp_str, nullptr);
if (!succeeded)
{
SPDLOG_THROW(win32_error("ReportEvent"));
}
}
void flush_() override {}
public:
win_eventlog_sink(std::string const &source, WORD event_id = 1000 /* according to mscoree.dll */)
: source_(source)
, event_id_(event_id)
{
try
{
current_user_sid_ = internal::sid_t::get_current_user_sid();
}
catch (...)
{
// get_current_user_sid() is unlikely to fail and if it does, we can still proceed without
// current_user_sid but in the event log the record will have no user name
}
}
~win_eventlog_sink()
{
if (hEventLog_)
DeregisterEventSource(hEventLog_);
}
};
} // namespace win_eventlog
using win_eventlog_sink_mt = win_eventlog::win_eventlog_sink<std::mutex>;
using win_eventlog_sink_st = win_eventlog::win_eventlog_sink<details::null_mutex>;
} // namespace sinks
} // namespace spdlog

View File

@ -52,6 +52,8 @@ template<typename ConsoleMutex>
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg) void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg)
{ {
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
msg.color_range_start = 0;
msg.color_range_end = 0;
memory_buf_t formatted; memory_buf_t formatted;
formatter_->format(msg, formatted); formatter_->format(msg, formatted);
if (!in_console_) if (!in_console_)
@ -59,7 +61,6 @@ void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg)
write_to_file_(formatted); write_to_file_(formatted);
return; return;
} }
if (should_do_colors_ && msg.color_range_end > msg.color_range_start) if (should_do_colors_ && msg.color_range_end > msg.color_range_start)
{ {
// before color range // before color range

View File

@ -11,7 +11,9 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <unordered_map> #include <array>
#include <spdlog/details/windows_include.h>
#include <wincon.h> #include <wincon.h>
namespace spdlog { namespace spdlog {
@ -52,7 +54,7 @@ protected:
bool in_console_; bool in_console_;
bool should_do_colors_; bool should_do_colors_;
std::unique_ptr<spdlog::formatter> formatter_; std::unique_ptr<spdlog::formatter> formatter_;
std::unordered_map<level::level_enum, WORD, level::level_hasher> colors_; std::array<WORD, level::n_levels> colors_;
// set foreground color and return the orig console attributes (for resetting later) // set foreground color and return the orig console attributes (for resetting later)
WORD set_foreground_color_(WORD attribs); WORD set_foreground_color_(WORD attribs);

View File

@ -39,13 +39,11 @@ inline std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs
// Initialize and register a logger, // Initialize and register a logger,
// formatter and flush level will be set according the global settings. // formatter and flush level will be set according the global settings.
// //
// NOTE: // Useful for initializing manually created loggers with the global settings.
// Use this function when creating loggers manually.
// //
// Example: // Example:
// auto console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>(); // auto mylogger = std::make_shared<spdlog::logger>("mylogger", ...);
// auto console_logger = std::make_shared<spdlog::logger>("console_logger", console_sink); // spdlog::initialize_logger(mylogger);
// spdlog::initialize_logger(console_logger);
void initialize_logger(std::shared_ptr<logger> logger); void initialize_logger(std::shared_ptr<logger> logger);
// Return an existing logger or nullptr if a logger with such name doesn't // Return an existing logger or nullptr if a logger with such name doesn't

View File

@ -23,7 +23,7 @@ if get_option('external_fmt')
if not meson.version().version_compare('>=0.49.0') if not meson.version().version_compare('>=0.49.0')
warning('Finding fmt can fail with meson versions before 0.49.0') warning('Finding fmt can fail with meson versions before 0.49.0')
endif endif
dep_list += dependency('fmt') dep_list += dependency('fmt', fallback : ['fmt', 'fmt_dep'])
compile_args += '-DSPDLOG_FMT_EXTERNAL' compile_args += '-DSPDLOG_FMT_EXTERNAL'
endif endif
@ -149,7 +149,7 @@ endif
# --- Conditionally add subdirs --- # --- Conditionally add subdirs ---
# ------------------------------------- # -------------------------------------
if get_option('enable_tests') or get_option('enable_tests-ho') if get_option('enable_tests') or get_option('enable_tests_ho')
subdir('tests') subdir('tests')
endif endif

View File

@ -1,5 +0,0 @@
#!/bin/bash
cd "$(dirname "$0")"
clang-tidy ../example/example.cpp -- -I ../include

View File

@ -1,12 +1,12 @@
#!/bin/bash #!/bin/bash
cd "$(dirname "$0")" cd "$(dirname "$0")"/..
pwd
echo -n "Running dos2unix " echo -n "Running dos2unix "
find .. -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'" find . -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'"
echo echo
echo -n "Running clang-format " echo -n "Running clang-format "
find .. -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "clang-format -i {}; echo -n '.'" find . -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "clang-format -i {}; echo -n '.'"
echo echo

View File

@ -9,53 +9,63 @@
#if !defined(SPDLOG_FMT_EXTERNAL) #if !defined(SPDLOG_FMT_EXTERNAL)
#include "spdlog/fmt/bundled/format-inl.h" #include "spdlog/fmt/bundled/format-inl.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace internal { namespace internal {
template <typename T> template<typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision, int format_float(char *buf, std::size_t size, const char *format, int precision, T value)
T value) { {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (precision > 100000) if (precision > 100000)
throw std::runtime_error( throw std::runtime_error("fuzz mode - avoid large allocation inside snprintf");
"fuzz mode - avoid large allocation inside snprintf");
#endif #endif
// Suppress the warning about nonliteral format string. // Suppress the warning about nonliteral format string.
auto snprintf_ptr = FMT_SNPRINTF; auto snprintf_ptr = FMT_SNPRINTF;
return precision < 0 ? snprintf_ptr(buf, size, format, value) return precision < 0 ? snprintf_ptr(buf, size, format, value) : snprintf_ptr(buf, size, format, precision, value);
: snprintf_ptr(buf, size, format, precision, value); }
} struct sprintf_specs
struct sprintf_specs { {
int precision; int precision;
char type; char type;
bool alt : 1; bool alt : 1;
template <typename Char> template<typename Char>
constexpr sprintf_specs(basic_format_specs<Char> specs) constexpr sprintf_specs(basic_format_specs<Char> specs)
: precision(specs.precision), type(specs.type), alt(specs.alt) {} : precision(specs.precision)
, type(specs.type)
, alt(specs.alt)
{}
constexpr bool has_precision() const { return precision >= 0; } constexpr bool has_precision() const
}; {
return precision >= 0;
}
};
// This is deprecated and is kept only to preserve ABI compatibility. // This is deprecated and is kept only to preserve ABI compatibility.
template <typename Double> template<typename Double>
char* sprintf_format(Double value, internal::buffer<char>& buf, char *sprintf_format(Double value, internal::buffer<char> &buf, sprintf_specs specs)
sprintf_specs specs) { {
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
FMT_ASSERT(buf.capacity() != 0, "empty buffer"); FMT_ASSERT(buf.capacity() != 0, "empty buffer");
// Build format string. // Build format string.
enum { max_format_size = 10 }; // longest format: %#-*.*Lg enum
{
max_format_size = 10
}; // longest format: %#-*.*Lg
char format[max_format_size]; char format[max_format_size];
char* format_ptr = format; char *format_ptr = format;
*format_ptr++ = '%'; *format_ptr++ = '%';
if (specs.alt || !specs.type) *format_ptr++ = '#'; if (specs.alt || !specs.type)
if (specs.precision >= 0) { *format_ptr++ = '#';
if (specs.precision >= 0)
{
*format_ptr++ = '.'; *format_ptr++ = '.';
*format_ptr++ = '*'; *format_ptr++ = '*';
} }
if (std::is_same<Double, long double>::value) *format_ptr++ = 'L'; if (std::is_same<Double, long double>::value)
*format_ptr++ = 'L';
char type = specs.type; char type = specs.type;
@ -64,7 +74,8 @@ FMT_BEGIN_NAMESPACE
else if (type == 0 || type == 'n') else if (type == 0 || type == 'n')
type = 'g'; type = 'g';
#if FMT_MSC_VER #if FMT_MSC_VER
if (type == 'F') { if (type == 'F')
{
// MSVC's printf doesn't support 'F'. // MSVC's printf doesn't support 'F'.
type = 'f'; type = 'f';
} }
@ -73,32 +84,44 @@ FMT_BEGIN_NAMESPACE
*format_ptr = '\0'; *format_ptr = '\0';
// Format using snprintf. // Format using snprintf.
char* start = nullptr; char *start = nullptr;
char* decimal_point_pos = nullptr; char *decimal_point_pos = nullptr;
for (;;) { for (;;)
{
std::size_t buffer_size = buf.capacity(); std::size_t buffer_size = buf.capacity();
start = &buf[0]; start = &buf[0];
int result = int result = format_float(start, buffer_size, format, specs.precision, value);
format_float(start, buffer_size, format, specs.precision, value); if (result >= 0)
if (result >= 0) { {
unsigned n = internal::to_unsigned(result); unsigned n = internal::to_unsigned(result);
if (n < buf.capacity()) { if (n < buf.capacity())
{
// Find the decimal point. // Find the decimal point.
auto p = buf.data(), end = p + n; auto p = buf.data(), end = p + n;
if (*p == '+' || *p == '-') ++p; if (*p == '+' || *p == '-')
if (specs.type != 'a' && specs.type != 'A') { ++p;
while (p < end && *p >= '0' && *p <= '9') ++p; if (specs.type != 'a' && specs.type != 'A')
if (p < end && *p != 'e' && *p != 'E') { {
while (p < end && *p >= '0' && *p <= '9')
++p;
if (p < end && *p != 'e' && *p != 'E')
{
decimal_point_pos = p; decimal_point_pos = p;
if (!specs.type) { if (!specs.type)
{
// Keep only one trailing zero after the decimal point. // Keep only one trailing zero after the decimal point.
++p; ++p;
if (*p == '0') ++p; if (*p == '0')
while (p != end && *p >= '1' && *p <= '9') ++p; ++p;
char* where = p; while (p != end && *p >= '1' && *p <= '9')
while (p != end && *p == '0') ++p; ++p;
if (p == end || *p < '0' || *p > '9') { char *where = p;
if (p != end) std::memmove(where, p, to_unsigned(end - p)); while (p != end && *p == '0')
++p;
if (p == end || *p < '0' || *p > '9')
{
if (p != end)
std::memmove(where, p, to_unsigned(end - p));
n -= static_cast<unsigned>(p - where); n -= static_cast<unsigned>(p - where);
} }
} }
@ -108,75 +131,59 @@ FMT_BEGIN_NAMESPACE
break; // The buffer is large enough - continue with formatting. break; // The buffer is large enough - continue with formatting.
} }
buf.reserve(n + 1); buf.reserve(n + 1);
} else { }
else
{
// If result is negative we ask to increase the capacity by at least 1, // If result is negative we ask to increase the capacity by at least 1,
// but as std::vector, the buffer grows exponentially. // but as std::vector, the buffer grows exponentially.
buf.reserve(buf.capacity() + 1); buf.reserve(buf.capacity() + 1);
} }
} }
return decimal_point_pos; return decimal_point_pos;
} }
} // namespace internal } // namespace internal
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&, template FMT_API char *internal::sprintf_format(double, internal::buffer<char> &, sprintf_specs);
sprintf_specs); template FMT_API char *internal::sprintf_format(long double, internal::buffer<char> &, sprintf_specs);
template FMT_API char* internal::sprintf_format(long double,
internal::buffer<char>&,
sprintf_specs);
template struct FMT_API internal::basic_data<void>; template struct FMT_API internal::basic_data<void>;
// Workaround a bug in MSVC2013 that prevents instantiation of format_float. // Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, internal::float_specs, int (*instantiate_format_float)(double, int, internal::float_specs, internal::buffer<char> &) = internal::format_float;
internal::buffer<char>&) =
internal::format_float;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc); template FMT_API internal::locale_ref::locale_ref(const std::locale &loc);
template FMT_API std::locale internal::locale_ref::get<std::locale>() const; template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
#endif #endif
// Explicit instantiations for char. // Explicit instantiations for char.
template FMT_API std::string internal::grouping_impl<char>(locale_ref); template FMT_API std::string internal::grouping_impl<char>(locale_ref);
template FMT_API char internal::thousands_sep_impl(locale_ref); template FMT_API char internal::thousands_sep_impl(locale_ref);
template FMT_API char internal::decimal_point_impl(locale_ref); template FMT_API char internal::decimal_point_impl(locale_ref);
template FMT_API void internal::buffer<char>::append(const char*, const char*); template FMT_API void internal::buffer<char>::append(const char *, const char *);
template FMT_API void internal::arg_map<format_context>::init( template FMT_API void internal::arg_map<format_context>::init(const basic_format_args<format_context> &args);
const basic_format_args<format_context>& args);
template FMT_API std::string internal::vformat<char>( template FMT_API std::string internal::vformat<char>(string_view, basic_format_args<format_context>);
string_view, basic_format_args<format_context>);
template FMT_API format_context::iterator internal::vformat_to( template FMT_API format_context::iterator internal::vformat_to(internal::buffer<char> &, string_view, basic_format_args<format_context>);
internal::buffer<char>&, string_view, basic_format_args<format_context>);
template FMT_API int internal::snprintf_float(double, int, template FMT_API int internal::snprintf_float(double, int, internal::float_specs, internal::buffer<char> &);
internal::float_specs, template FMT_API int internal::snprintf_float(long double, int, internal::float_specs, internal::buffer<char> &);
internal::buffer<char>&); template FMT_API int internal::format_float(double, int, internal::float_specs, internal::buffer<char> &);
template FMT_API int internal::snprintf_float(long double, int, template FMT_API int internal::format_float(long double, int, internal::float_specs, internal::buffer<char> &);
internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::format_float(double, int, internal::float_specs,
internal::buffer<char>&);
template FMT_API int internal::format_float(long double, int,
internal::float_specs,
internal::buffer<char>&);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref); template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref); template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
template FMT_API wchar_t internal::decimal_point_impl(locale_ref); template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*, template FMT_API void internal::buffer<wchar_t>::append(const wchar_t *, const wchar_t *);
const wchar_t*);
template FMT_API std::wstring internal::vformat<wchar_t>( template FMT_API std::wstring internal::vformat<wchar_t>(wstring_view, basic_format_args<wformat_context>);
wstring_view, basic_format_args<wformat_context>);
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif #endif

View File

@ -1,5 +1,12 @@
cmake_minimum_required(VERSION 3.2)
project(spdlog_utests CXX) project(spdlog_utests CXX)
if(NOT TARGET spdlog)
# Stand-alone build
find_package(spdlog REQUIRED)
endif()
include(../cmake/utils.cmake) include(../cmake/utils.cmake)
find_package(PkgConfig) find_package(PkgConfig)
@ -12,6 +19,7 @@ set(SPDLOG_UTESTS_SOURCES
test_file_logging.cpp test_file_logging.cpp
test_daily_logger.cpp test_daily_logger.cpp
test_misc.cpp test_misc.cpp
test_eventlog.cpp
test_pattern_formatter.cpp test_pattern_formatter.cpp
test_async.cpp test_async.cpp
test_registry.cpp test_registry.cpp
@ -48,6 +56,7 @@ function(spdlog_prepare_test test_target spdlog_lib)
spdlog_enable_sanitizer(${test_target}) spdlog_enable_sanitizer(${test_target})
endif() endif()
add_test(NAME ${test_target} COMMAND ${test_target}) add_test(NAME ${test_target} COMMAND ${test_target})
set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON)
endfunction() endfunction()
# The compiled library tests # The compiled library tests

View File

@ -169,9 +169,10 @@ TEST_CASE("to_file", "[async]")
} }
} }
REQUIRE(count_lines(filename) == messages); require_message_count(filename, messages);
auto contents = file_contents(filename); auto contents = file_contents(filename);
REQUIRE(ends_with(contents, std::string("Hello message #1023\n"))); using spdlog::details::os::default_eol;
REQUIRE(ends_with(contents, fmt::format("Hello message #1023{}", default_eol)));
} }
TEST_CASE("to_file multi-workers", "[async]") TEST_CASE("to_file multi-workers", "[async]")
@ -191,5 +192,5 @@ TEST_CASE("to_file multi-workers", "[async]")
} }
} }
REQUIRE(count_lines(filename) == messages); require_message_count(filename, messages);
} }

View File

@ -24,7 +24,7 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
logger->flush(); logger->flush();
auto filename = fmt::to_string(w); auto filename = fmt::to_string(w);
REQUIRE(count_lines(filename) == 10); require_message_count(filename, 10);
} }
struct custom_daily_file_name_calculator struct custom_daily_file_name_calculator
@ -55,12 +55,10 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
logger->info("Test message {}", i); logger->info("Test message {}", i);
} }
logger-> logger->flush();
flush();
auto filename = fmt::to_string(w); auto filename = fmt::to_string(w);
REQUIRE(count_lines(filename) == 10); require_message_count(filename, 10);
} }
/* /*

View File

@ -34,7 +34,8 @@ TEST_CASE("default_error_handler", "[errors]]")
logger->info("Test message {}", 2); logger->info("Test message {}", 2);
logger->flush(); logger->flush();
REQUIRE(file_contents(filename) == std::string("Test message 2\n")); using spdlog::details::os::default_eol;
REQUIRE(file_contents(filename) == fmt::format("Test message 2{}", default_eol));
REQUIRE(count_lines(filename) == 1); REQUIRE(count_lines(filename) == 1);
} }
@ -51,7 +52,7 @@ TEST_CASE("custom_error_handler", "[errors]]")
REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex); REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex);
logger->info("Good message #2"); logger->info("Good message #2");
REQUIRE(count_lines(filename) == 2); require_message_count(filename, 2);
} }
TEST_CASE("default_error_handler2", "[errors]]") TEST_CASE("default_error_handler2", "[errors]]")
@ -93,7 +94,7 @@ TEST_CASE("async_error_handler", "[errors]]")
spdlog::drop("logger"); // force logger to drain the queue and shutdown spdlog::drop("logger"); // force logger to drain the queue and shutdown
} }
spdlog::init_thread_pool(128, 1); spdlog::init_thread_pool(128, 1);
REQUIRE(count_lines(filename) == 2); require_message_count(filename, 2);
REQUIRE(file_contents("test_logs/custom_err.txt") == err_msg); REQUIRE(file_contents("test_logs/custom_err.txt") == err_msg);
} }

71
tests/test_eventlog.cpp Normal file
View File

@ -0,0 +1,71 @@
#if _WIN32
#include "includes.h"
#include "test_sink.h"
#include "spdlog/sinks/win_eventlog_sink.h"
static const LPCSTR TEST_SOURCE = "spdlog_test";
static void test_single_print(std::function<void(std::string const &)> do_log, std::string const &expected_contents, WORD expected_ev_type)
{
using namespace std::chrono;
do_log(expected_contents);
const auto expected_time_generated = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
struct handle_t
{
HANDLE handle_;
~handle_t()
{
if (handle_)
{
REQUIRE(CloseEventLog(handle_));
}
}
} event_log{::OpenEventLog(nullptr, TEST_SOURCE)};
REQUIRE(event_log.handle_);
DWORD read_bytes{}, size_needed{};
auto ok =
::ReadEventLog(event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, &read_bytes, 0, &read_bytes, &size_needed);
REQUIRE(!ok);
REQUIRE(::GetLastError() == ERROR_INSUFFICIENT_BUFFER);
std::vector<char> record_buffer(size_needed);
PEVENTLOGRECORD record = (PEVENTLOGRECORD)record_buffer.data();
ok = ::ReadEventLog(
event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, record, size_needed, &read_bytes, &size_needed);
REQUIRE(ok);
REQUIRE(record->NumStrings == 1);
REQUIRE(record->EventType == expected_ev_type);
REQUIRE(record->TimeGenerated == expected_time_generated);
std::string message_in_log(((char *)record + record->StringOffset));
REQUIRE(message_in_log == expected_contents + spdlog::details::os::default_eol);
}
TEST_CASE("eventlog", "[eventlog]")
{
using namespace spdlog;
auto test_sink = std::make_shared<sinks::win_eventlog_sink_mt>(TEST_SOURCE);
spdlog::logger test_logger("eventlog", test_sink);
test_logger.set_level(level::trace);
test_sink->set_pattern("%v");
test_single_print([&test_logger](std::string const &msg) { test_logger.trace(msg); }, "my trace message", EVENTLOG_SUCCESS);
test_single_print([&test_logger](std::string const &msg) { test_logger.debug(msg); }, "my debug message", EVENTLOG_SUCCESS);
test_single_print([&test_logger](std::string const &msg) { test_logger.info(msg); }, "my info message", EVENTLOG_INFORMATION_TYPE);
test_single_print([&test_logger](std::string const &msg) { test_logger.warn(msg); }, "my warn message", EVENTLOG_WARNING_TYPE);
test_single_print([&test_logger](std::string const &msg) { test_logger.error(msg); }, "my error message", EVENTLOG_ERROR_TYPE);
test_single_print([&test_logger](std::string const &msg) { test_logger.critical(msg); }, "my critical message", EVENTLOG_ERROR_TYPE);
}
#endif //_WIN32

View File

@ -15,8 +15,9 @@ TEST_CASE("simple_file_logger", "[simple_logger]]")
logger->info("Test message {}", 2); logger->info("Test message {}", 2);
logger->flush(); logger->flush();
REQUIRE(file_contents(filename) == std::string("Test message 1\nTest message 2\n")); require_message_count(filename, 2);
REQUIRE(count_lines(filename) == 2); using spdlog::details::os::default_eol;
REQUIRE(file_contents(filename) == fmt::format("Test message 1{}Test message 2{}", default_eol, default_eol));
} }
TEST_CASE("flush_on", "[flush_on]]") TEST_CASE("flush_on", "[flush_on]]")
@ -34,8 +35,10 @@ TEST_CASE("flush_on", "[flush_on]]")
logger->info("Test message {}", 1); logger->info("Test message {}", 1);
logger->info("Test message {}", 2); logger->info("Test message {}", 2);
REQUIRE(file_contents(filename) == std::string("Should not be flushed\nTest message 1\nTest message 2\n")); require_message_count(filename, 3);
REQUIRE(count_lines(filename) == 3); using spdlog::details::os::default_eol;
REQUIRE(file_contents(filename) ==
fmt::format("Should not be flushed{}Test message 1{}Test message 2{}", default_eol, default_eol, default_eol));
} }
TEST_CASE("rotating_file_logger1", "[rotating_logger]]") TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
@ -52,7 +55,7 @@ TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
logger->flush(); logger->flush();
auto filename = basename; auto filename = basename;
REQUIRE(count_lines(filename) == 10); require_message_count(filename, 10);
} }
TEST_CASE("rotating_file_logger2", "[rotating_logger]]") TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
@ -81,7 +84,8 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
logger->flush(); logger->flush();
auto filename = basename; auto filename = basename;
REQUIRE(count_lines(filename) == 10); require_message_count(filename, 10);
for (int i = 0; i < 1000; i++) for (int i = 0; i < 1000; i++)
{ {

View File

@ -22,7 +22,8 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
SPDLOG_LOGGER_DEBUG(logger, "Test message 2"); SPDLOG_LOGGER_DEBUG(logger, "Test message 2");
logger->flush(); logger->flush();
REQUIRE(ends_with(file_contents(filename), "Test message 2\n")); using spdlog::details::os::default_eol;
REQUIRE(ends_with(file_contents(filename), fmt::format("Test message 2{}", default_eol)));
REQUIRE(count_lines(filename) == 1); REQUIRE(count_lines(filename) == 1);
spdlog::set_default_logger(logger); spdlog::set_default_logger(logger);
@ -31,8 +32,8 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
SPDLOG_DEBUG("Test message {}", 4); SPDLOG_DEBUG("Test message {}", 4);
logger->flush(); logger->flush();
REQUIRE(ends_with(file_contents(filename), "Test message 4\n")); require_message_count(filename, 2);
REQUIRE(count_lines(filename) == 2); REQUIRE(ends_with(file_contents(filename), fmt::format("Test message 4{}", default_eol)));
} }
TEST_CASE("disable param evaluation", "[macros]") TEST_CASE("disable param evaluation", "[macros]")

View File

@ -21,7 +21,7 @@ void prepare_logdir()
std::string file_contents(const std::string &filename) std::string file_contents(const std::string &filename)
{ {
std::ifstream ifs(filename); std::ifstream ifs(filename, std::ios_base::binary);
if (!ifs) if (!ifs)
{ {
throw std::runtime_error("Failed open file "); throw std::runtime_error("Failed open file ");
@ -44,6 +44,18 @@ std::size_t count_lines(const std::string &filename)
return counter; return counter;
} }
void require_message_count(const std::string &filename, const std::size_t messages)
{
if (strlen(spdlog::details::os::default_eol) == 0)
{
REQUIRE(count_lines(filename) == 1);
}
else
{
REQUIRE(count_lines(filename) == messages);
}
}
std::size_t get_filesize(const std::string &filename) std::size_t get_filesize(const std::string &filename)
{ {
std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary); std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary);

View File

@ -3,8 +3,6 @@
#include <cstddef> #include <cstddef>
#include <string> #include <string>
std::size_t count_lines(const std::string &filename);
std::size_t count_files(const std::string &folder); std::size_t count_files(const std::string &folder);
void prepare_logdir(); void prepare_logdir();
@ -13,6 +11,8 @@ std::string file_contents(const std::string &filename);
std::size_t count_lines(const std::string &filename); std::size_t count_lines(const std::string &filename);
void require_message_count(const std::string &filename, const std::size_t messages);
std::size_t get_filesize(const std::string &filename); std::size_t get_filesize(const std::string &filename);
bool ends_with(std::string const &value, std::string const &ending); bool ends_with(std::string const &value, std::string const &ending);