Compare commits

...

285 Commits

Author SHA1 Message Date
gabime
61ed2a670e bump version number to 1.9.0 2021-07-20 23:01:34 +03:00
gabime
db1bc035f7 clang-format 2021-07-20 22:55:47 +03:00
gabime
8de6cdaa82 bump version number to 1.8.6 2021-07-20 22:52:37 +03:00
gabime
fe1a4f5fb6 Added SPDLOG_FMT_RUNTIME macro for compatibilty with fmt prior 8 2021-07-20 17:18:40 +03:00
gabime
d38f89cae8 Fixed daily_file_sink compilation under c++20 2021-07-20 15:02:39 +03:00
gabime
9c90fe8264 Fixed wchar support 2021-07-20 12:53:50 +03:00
gabime
b85a666f72 Enabled parallel build under msvc 2021-07-19 16:33:31 +03:00
Gabi Melman
5ba95f6816 Update logger.h 2021-07-19 03:46:01 +03:00
Gabi Melman
dc38b7c3c4 Update logger.h 2021-07-19 03:20:34 +03:00
Gabi Melman
6484b03dd9 Update logger.h 2021-07-19 03:09:37 +03:00
gabime
29235d9b4b minor string_view change and comment 2021-07-19 01:15:53 +03:00
gabime
4b3687f1a6 Removed unneeded macro definition 2021-07-19 01:11:45 +03:00
gabime
e7e8b75a4c clang-format 2021-07-19 00:50:51 +03:00
gabime
e98265a49b cosmetic reorder of logger funcs definitions 2021-07-19 00:48:01 +03:00
gabime
e87f69bdb6 Removed check if format string can be converted to fmt::is_compile_string 2021-07-19 00:12:17 +03:00
Gabi Melman
70d2832c0d Update README.md 2021-07-14 16:30:54 +03:00
gabime
7636f1f659 revert some changes made by mistake 2021-07-14 14:33:45 +03:00
gabime
1523c83650 Added fmt/compile.h bundled file 2021-07-14 14:28:34 +03:00
Gabi Melman
a6987efaec Update README.md 2021-07-14 13:51:23 +03:00
Gabi Melman
6491abb519 Update .travis.yml 2021-07-10 19:14:47 +03:00
gabime
8faabb4e3a Fix msvc compile 2021-07-10 17:33:08 +03:00
gabime
2838c2c8a5 use vformat_to instead for format_to for better performance 2021-07-10 17:00:13 +03:00
gabime
3315bad009 Treat wall warnings as errors if SPDLOG_BUILD_WARNINGS is ON 2021-07-10 15:22:44 +03:00
gabime
3eeced78b5 Removed some cmake wdev warnings 2021-07-10 15:15:39 +03:00
gabime
c23430b438 Fixed cast warning 2021-07-10 14:31:56 +03:00
Gabi Melman
0e49bfff51 Update .travis.yml 2021-07-10 14:20:06 +03:00
Gabi Melman
3ed40d04a9 Update .travis.yml 2021-07-10 14:14:09 +03:00
gabime
70b36aa55d Remove fmt::runtime() wrapper in logger.h 2021-07-10 14:07:32 +03:00
gabime
0f83b33d4f backward compatibility with fmt version < 8 2021-07-10 13:48:06 +03:00
gabime
b83106bed4 Update bundled fmt to v8.0.1 2021-07-03 23:10:57 +03:00
Gabi Melman
21413e599a Update qt_sinks.h 2021-06-29 02:20:48 +03:00
Gabi Melman
5f4cc7b036 Merge pull request #1986 from mguludag/v1.x
Added QTextEdit and QPlainTextEdit sink
2021-06-29 02:18:00 +03:00
Muhammed Galib Uludag
9aa26fb969 Added Qt sinks 2021-06-29 01:04:25 +03:00
Muhammed Galib Uludag
7f74012a0d Delete qtextedit_sink.h 2021-06-29 01:02:16 +03:00
Muhammed Galib Uludag
96ebef093f Delete qtextedit_sink_p.h 2021-06-29 01:01:48 +03:00
Muhammed Galib Uludag
a19f4bba0c Delete qplaintextedit_sink_p.h 2021-06-29 01:01:40 +03:00
Muhammed Galib Uludag
c24b957e17 Delete qplaintextedit_sink.h 2021-06-29 01:01:18 +03:00
Muhammed Galib Uludag
5ba2f77230 Added QPlainTextEdit sink
QPlainTextEdit performs better than QTextEdit and its derivatives and also it has rich features
2021-06-28 23:16:23 +03:00
Muhammed Galib Uludag
a09f490804 Implemented QTextEdit and, QTextBrowser sink 2021-06-28 23:13:37 +03:00
Gabi Melman
082d6fbea9 Merge pull request #1984 from hctym1995/v1.x
Add a color-terminal type
2021-06-28 15:10:01 +03:00
zyw1995ted@163.com
37372960a8 add a color-terminal type 2021-06-28 18:12:12 +08:00
gabime
0035a0c98d Fixed dup sink compile warnings in older compilers with back_inserter 2021-06-28 12:09:39 +03:00
Gabi Melman
036cc5d575 Merge pull request #1982 from mguludag/v1.x
ignore pattern formatting
2021-06-27 23:11:39 +03:00
Muhammed Galib Uludag
14950926ed ignore pattern formatting for message section in mongodb 2021-06-27 22:50:31 +03:00
Muhammed Galib Uludag
baa3b1a07e Merge branch 'gabime:v1.x' into v1.x 2021-06-27 20:32:52 +03:00
Gabi Melman
2a09f66a44 Remove un needed functions and added override keyword 2021-06-27 20:32:01 +03:00
Muhammed Galib Uludag
e50b62c770 suppressed unused var 2021-06-27 20:30:41 +03:00
Gabi Melman
13d8b0f17f Merge pull request #1981 from mguludag/v1.x
added mongodb sink
2021-06-27 20:30:08 +03:00
Muhammed Galib Uludag
9e0c658b29 factory functions namespace fix 2021-06-27 20:17:19 +03:00
Muhammed Galib Uludag
74fec56927 Changed base class to base_sink and added factory functions 2021-06-27 19:59:07 +03:00
Muhammed Galib Uludag
514f304a47 changed license template for compability to other spdlog headers 2021-06-27 18:06:44 +03:00
Muhammed Galib Uludag
7f85a5c988 change license to mit 2021-06-27 17:53:19 +03:00
Muhammed Galib Uludag
14d626d961 added mongodb sink 2021-06-27 16:16:39 +03:00
Gabi Melman
7560cacb3f Update .travis.yml 2021-06-27 00:31:34 +03:00
Gabi Melman
3cd9bcdab9 Update dup_filter_sink.h 2021-06-27 00:21:03 +03:00
Gabi Melman
32f1efdc99 Update dup_filter_sink.h 2021-06-27 00:11:31 +03:00
Gabi Melman
bcc9f03457 Update .travis.yml 2021-06-27 00:01:44 +03:00
Gabi Melman
4c845bf02b Update dup_filter_sink.h 2021-06-26 23:40:11 +03:00
Gabi Melman
c727864393 Update .travis.yml 2021-06-26 23:25:27 +03:00
Gabi Melman
4548573a75 Update .travis.yml 2021-06-26 23:18:22 +03:00
Gabi Melman
385246730d Update .travis.yml 2021-06-26 23:14:26 +03:00
Gabi Melman
e06d21a4c0 Update .travis.yml 2021-06-26 22:53:48 +03:00
Gabi Melman
c132d2ae8c Update .travis.yml 2021-06-26 22:51:28 +03:00
Gabi Melman
ece31100e0 Update appveyor.yml 2021-06-26 22:47:32 +03:00
Gabi Melman
ce4e1ac54b Update appveyor.yml 2021-06-26 22:44:02 +03:00
Gabi Melman
ef61fb11f0 Update README.md 2021-06-26 22:21:55 +03:00
Gabi Melman
42f2b11ec8 Update .travis.yml 2021-06-26 21:46:49 +03:00
Gabi Melman
ac87cbb0d1 Update appveyor.yml 2021-06-26 21:44:03 +03:00
Gabi Melman
c65de3d689 Update appveyor.yml 2021-06-26 21:41:15 +03:00
Gabi Melman
1433fa4209 Update appveyor.yml 2021-06-26 21:40:42 +03:00
Gabi Melman
d51149e5ac Update appveyor.yml 2021-06-26 20:45:29 +03:00
Gabi Melman
ffd813435a fix compile error again with wchar formatting 2021-06-26 20:10:21 +03:00
gabime
d75fd2c7f9 Fixed wchar support under msvc 2021-06-26 19:43:37 +03:00
gabime
cdad84aa46 merge 2021-06-26 18:02:09 +03:00
gabime
0fdb545d8c Fixed clang c++20 compile 2021-06-26 17:59:08 +03:00
gabime
a5f5ff70e0 Fixed clang c++20 compile 2021-06-26 17:58:45 +03:00
gabime
4f0e320236 Fixed format string in bench 2021-06-26 17:52:55 +03:00
gabime
68aed6a5eb Fixed building under c++17 2021-06-26 17:36:57 +03:00
Gabi Melman
6811112208 Update logger.h 2021-06-24 19:42:12 +03:00
gabime
9ebc4b24d9 Added missing args.h file 2021-06-24 17:26:02 +03:00
gabime
b990080a52 Fixed fmt locale.h deprecation warning 2021-06-24 17:22:06 +03:00
gabime
efbe3e4d57 Added missing fmt 8 headers 2021-06-24 17:17:27 +03:00
gabime
7b14a65b2b Fixed format_to deprecated warning by wrapping the buffer with std::back_inserter 2021-06-24 17:07:14 +03:00
gabime
5887744d8b Fixed bin2hex to work with fmt v8 2021-06-24 15:58:25 +03:00
gabime
8bf718671a Update fmt version 8.0 2021-06-24 13:22:02 +03:00
Gabi Melman
c858b14c03 Update clang format to format cpp macros 2021-06-20 23:20:36 +03:00
Gabi Melman
12df172575 Merge pull request #1971 from SpriteOvO/v1.x
Use std::function for the global error handler
2021-06-16 01:25:09 +03:00
Sprite
7fa751d36e Use std::function for the global error handler 2021-06-16 05:04:17 +08:00
Gabi Melman
7a7611e977 Merge pull request #1970 from bansan85/v1.x
Fix signed/unsigned mismatch in VS
2021-06-13 21:49:08 +03:00
LE GARREC Vincent
ec8763adf2 Fix signed/unsigned mismatch in VS 2021-06-13 19:35:02 +02:00
Gabi Melman
f2d1d573f5 Fix #1967 2021-06-07 22:39:42 +03:00
Gabi Melman
a530b87fd0 Merge pull request #1961 from jafj/FixCxx20Wdeprecated-copy
Fix C++20 build resulting in deprecated implicit copy assignment operator warning
2021-06-02 12:15:38 +03:00
JB_12
6c21789aed Fix C++20 build resulting in deprecated implicit copy assignment operator warning 2021-06-02 08:20:29 +01:00
Gabi Melman
616866fcf4 Merge pull request #1958 from mlund/v1.x
Allow compilation with nvc++ (and possibly PGI)
2021-05-30 22:14:02 +03:00
Mikael Lund
faf06bcfe5 Add newlin to color_sinks.cpp 2021-05-30 13:07:16 +01:00
Mikael Lund
cd376a5c43 Allow compilation with nvc++ 2021-05-30 13:02:56 +01:00
Gabi Melman
6ba5ab6d67 Merge pull request #1948 from stevenlunt/v1.x
add macros for overriding the individual level names
2021-05-20 01:09:09 +03:00
steven lunt
1bee3218b4 cleanup thanks to gabime 2021-05-19 17:51:03 -04:00
steven lunt
802eaadd2d add macros for overriding the individual level names 2021-05-19 10:45:33 -04:00
steven lunt
ee22eed23d add macros for overriding the individual level names 2021-05-18 19:36:45 -04:00
steven lunt
ab72de5f7a Revert "added spdlog::level::set_string_view to enable alternate log level names without changing the build via SPDLOG_LEVEL_NAMES"
This reverts commit 2a16d1d230.
2021-05-18 19:25:07 -04:00
steven lunt
a32cea24fd Revert "remove constexpr on level_string_views to fix compilation on C++17 from addition of set_string_view"
This reverts commit ac3e26b0ff.
2021-05-18 19:24:44 -04:00
Gabi Melman
af0d805be4 Merge pull request #1946 from jafj/EnableCMakePolicyCMP0077
Add support for CMake policy CMP0077
2021-05-17 01:54:23 +03:00
JB_12
181c22f798 Add support for CMake policy CMP0077 2021-05-16 22:49:02 +01:00
Gabi Melman
87133ef6b7 Merge pull request #1933 from neheb/s
small std::find conversion
2021-05-11 02:12:46 +03:00
Rosen Penev
1ef2f014ee small std::find conversion
Signed-off-by: Rosen Penev <rosenp@gmail.com>
2021-05-10 14:42:28 -07:00
Gabi Melman
0a92d1d684 Merge pull request #1931 from vadz/msvc-undef-warning-fix
Fix warning about testing _WIN64 which might be undefined
2021-05-07 01:05:41 +03:00
Vadim Zeitlin
ff5221b693 Fix warning about testing _WIN64 which might be undefined
This warning is disabled by default, but is pretty useful and worth
enabling for MSVC, just as -Wundef for gcc, so fix it in Win32 build.
2021-05-06 22:33:41 +01:00
Gabi Melman
db484cc4b8 Merge pull request #1930 from vadz/msvc-unreachable-warning-fix-alt
Avoid harmless warning about unreachable statement in MSVS build
2021-05-07 00:27:45 +03:00
Vadim Zeitlin
6442963f49 Avoid harmless warning about unreachable statement in MSVS build
All MSVS versions >= 2015 warn about "return 0" after throw_spdlog_ex()
being unreachable in filesize(), so disable this warning in this
function (note that it can't be disabled inside it).
2021-05-06 22:02:25 +01:00
Gabi Melman
0f7b95ce47 Merge pull request #1924 from lnovey/remove-c-style-casts
Change c-style casts to reinterpret casts in tcp_client
2021-05-03 20:12:03 +03:00
Luke Novey
632a2e0894 Change c-style casts to reinterpret casts 2021-05-03 12:33:58 -04:00
gabime
e9635c7b2d rethrnow non std exceptions to fix #533 2021-05-01 00:29:36 +03:00
Gabi Melman
8e3b1338a5 Merge pull request #1918 from matt77hias/v1.x
Made mutex member variable mutable
2021-04-22 22:48:29 +03:00
Matthias Moulin
9d3dde0900 Made mutex member variable mutable
Classes inheriting from `base_sink` can now lock the base mutex inside their `const` member methods (e.g., basic accessors).
2021-04-22 21:19:54 +02:00
Gabi Melman
c5abaeddca Merge pull request #1916 from haifengkao/FixNoNewLineAtEndOfFile
fix Xcode compiler warning "no new line at the end of file"
2021-04-20 22:31:56 +03:00
Hai Feng Kao
ca2cd6f3e7 fix Xcode compiler warning "no new line at the end of file" 2021-04-20 12:43:56 +08:00
Gabi Melman
7d07e0312a Update README.md 2021-04-10 13:07:36 +03:00
Gabi Melman
e1c73fd8f4 Update README.md 2021-04-10 13:05:39 +03:00
Gabi Melman
b83ab21283 Update README.md 2021-04-10 13:02:16 +03:00
Gabi Melman
8001156ca8 Update stdout_sinks-inl.h 2021-04-08 23:20:36 +03:00
Gabi Melman
57e31f0a58 Merge pull request #1906 from LonghronShen/v1.x
fix bug #1790
2021-04-08 23:18:20 +03:00
Gabi Melman
51fadf6b7e Merge pull request #1912 from SUPERustam/v1.x
Minor update, PEP8 format and f-string instead of format method
2021-04-08 23:13:33 +03:00
Gabi Melman
2a6a8aa0a0 Merge pull request #1913 from bsergean/patch-1
Update pattern_formatter-inl.h full_formatter comment to describe the default logging pattern expression accurately
2021-04-08 21:11:53 +03:00
Benjamin Sergeant
aa264a7fb2 Update pattern_formatter-inl.h
Comment describing the default logging pattern is missing [%s:%#], which is the abbreviated source file + the line number.

I tried to customize our own logger by copy pasting this info, and then I noticed we had lost that information.
2021-04-08 09:05:55 -07:00
SUPERustam
5e35c2b6ab Update extract_version.py 2021-04-08 09:56:01 +03:00
Steven Hangger
0385372314 fix bug #1790 2021-04-05 20:00:22 +08:00
Gabi Melman
efbff95ec7 Merge pull request #1900 from nandanvasudevan/patch-1
Minor typo
2021-04-03 16:16:04 +03:00
Nandan V
2a9edb2153 Minor typo 2021-04-03 11:42:35 +00:00
Gabi Melman
be14e60d9e Merge pull request #1891 from Hugoto69/patch-1
Update .clang-tidy
2021-03-25 22:15:12 +02:00
Hugo Bonnet
ef4641cad7 Update .clang-tidy 2021-03-25 20:43:51 +01:00
Gabi Melman
100f30043f Update version.h 2021-03-25 21:00:48 +02:00
Gabi Melman
1574b5b0a2 Merge pull request #1889 from stevenlunt/set_string_view
remove constexpr on level_string_views to fix compilation on C++17 fr…
2021-03-25 20:59:37 +02:00
Gabi Melman
012fe99ab1 Update version.h 2021-03-25 20:34:53 +02:00
Gabi Melman
8ff5a3e096 Merge pull request #1890 from prince-chrismc/patch-4
Remove version requirement from fmt find_package
2021-03-25 19:22:51 +02:00
Chris Mc
65317eb019 Remove version requirement from fmt find_package
By introducing 'no module' support this inadvertently triggered the find_package to use the "full version" version selection.

From https://cmake.org/cmake/help/latest/command/find_package.html#version-selection:

> When the [version] argument is given, Config mode will only find a version of the package that claims compatibility with the requested version (see format specification).

FMT does not set this configuration https://github.com/fmtlib/fmt/blob/7.1.3/support/cmake/fmt-config.cmake.in... but regardless it would not be "any compatibility" based on version semantics.

Which causes this error. v1.8.2 builds perfectly fine.

```
CMake Error at CMakeLists.txt:181 (find_package):
  Could not find a configuration file for package "fmt" that is compatible
  with requested version "5.3.0".

  The following configuration files were considered but not accepted:

    /home/proj/build/e98598522b7c484d9220bffc7ec84474515facbc/fmt-config.cmake, version: 7.1.3
```
2021-03-24 21:54:36 -04:00
steven lunt
ac3e26b0ff remove constexpr on level_string_views to fix compilation on C++17 from addition of set_string_view 2021-03-24 18:20:08 -04:00
Gabi Melman
e86f450428 Merge pull request #1885 from ahmedyarub/fix_android_build
Add required libraries for Android CMake build
2021-03-25 00:14:41 +02:00
Gabi Melman
7b2776fdc7 Merge pull request #1888 from stevenlunt/set_string_view
added spdlog::level::set_string_view to enable alternate log level na…
2021-03-24 23:10:11 +02:00
steven lunt
2a16d1d230 added spdlog::level::set_string_view to enable alternate log level names without changing the build via SPDLOG_LEVEL_NAMES 2021-03-24 16:22:54 -04:00
Gabi Melman
53e1c9ab11 Update version number to 1.8.3 2021-03-24 21:49:10 +02:00
Gabi Melman
410abc4626 Added the Windows Event logger to readme features 2021-03-24 01:45:26 +02:00
Ahmed Yarub Hani Al Nuaimi
a2e28443f0 Add required libraries for Android CMake build 2021-03-22 00:08:17 -03:00
Gabi Melman
c1af0a3f21 Merge pull request #1882 from imsherlock/v1.x
add default cases
2021-03-20 21:02:57 +02:00
Ryan Sherlock
bb5e1ee2f9 Removing changes to bundled fmt
Removing changes to the bundled fmt library. The default case
statements will be changed in the upstream library.

Signed-off-by: Ryan Sherlock <sherlock@loftorbital.com>
2021-03-20 09:22:40 -07:00
Ryan Sherlock
3aee89c8fd add default cases
Adding default case for cases where the compilation flag
-Wswitch-default is present on the command line when spdlog is
included in external projects.

Signed-off-by: Ryan Sherlock <ryan.m.sherlock@gmail.com>
2021-03-19 10:06:09 -07:00
gabime
44e1f9f682 Added nonreturn sepcifier to fix #1748 2021-03-17 00:25:26 +02:00
gabime
37d76b961c Fix #1876 2021-03-16 23:39:55 +02:00
gabime
1305663d99 make sure __cplusplus is defined under msvc 2021-03-12 15:10:15 +02:00
gabime
8f4efe57a2 make sure __cplusplus is defined under msvc 2021-03-12 15:09:35 +02:00
gabime
0613dbc4a2 Revert pr #1860 2021-03-04 23:52:50 +02:00
Gabi Melman
0ed0d69368 Update CMakeLists.txt 2021-03-04 23:24:02 +02:00
Gabi Melman
2ffbbee1f6 Merge pull request #1860 from rofferom/msvc-allow-static-runtime
CMake: Add SPDLOG_STATIC_VCRT to choose static MSVC runtime
2021-03-04 23:17:32 +02:00
Romain Roffé
b9d2f2537b CMake: Add SPDLOG_STATIC_VCRT to choose static MSVC runtime 2021-03-04 20:14:18 +01:00
Gabi Melman
69dc173979 Update README.md 2021-03-03 00:17:33 +02:00
Gabi Melman
ded8b5ebd4 Added build2 to package managers section in readme 2021-03-03 00:16:59 +02:00
Gabi Melman
ed58ae9f98 Revert PR #1851 2021-03-02 22:49:11 +02:00
Gabi Melman
f7f790b4b3 Merge pull request #1851 from Klaim/patch-1
Add build2 package support
2021-03-02 22:37:43 +02:00
Klaim (Joël Lamotte)
fe74c80992 Add build2 package support
WARNING: Please do not merge yet! See below.

We are currently in the process of packaging `spdlog` for `build2`, we have it working (even on unsupported Windows, see for example: https://ci.stage.build2.org/@a993b64e-8ba2-422e-97d7-250cdb5828e0?builds=&pv=&tc=*&cf=&mn=&tg=&rs=*)

This change simply adds the necessary information to use this package when using `build2`. Some notes:
 - not sure how to give the info succintly because `build2` allows packages to come from different sources, including the git repository of the package - in doubt I just linked to the future community repository address providing all the info, but the other package manager didn't do it like that so not sure if it's ok for you?; 
 - `build2` distinguish the package (`depends: spdlog <some-version-scheme>`, added in the `manifest` file of a `build2` project) and the target (`spdlog%lib{spdlog}` imported in a `buildfile`) because packages can contain several targets. I was not sure how to formulate that here, so feel free to tell me if I should just provide the name of the package and not more info?

The package isn't available yet so this have to be merged only once it is made available (probably in a few days).
2021-02-27 14:45:55 +01:00
Gabi Melman
fa659bf7ad Update daily_file_sink.h 2021-02-27 11:18:12 +02:00
Gabi Melman
9b41649601 Merge pull request #1849 from concatime/cmake-library-type-option-flag
CMake: Replace custom SPDLOG_BUILD_SHARED by standard BUILD_SHARED_LIBS
2021-02-26 23:35:23 +02:00
Issam E. Maghni
1b3438f5a5 AppVeyor: Use standard BUILD_SHARED_LIBS 2021-02-26 16:26:53 -05:00
Issam E. Maghni
3eed64e5c4 CMake: Replace custom SPDLOG_BUILD_SHARED by standard BUILD_SHARED_LIBS 2021-02-26 16:26:50 -05:00
Gabi Melman
0fac33781d Update daily_file_sink.h 2021-02-26 12:38:49 +02:00
Gabi Melman
3135b6a33d Update comment 2021-02-26 12:33:43 +02:00
Gabi Melman
2686ae2322 Merge pull request #1847 from fawdlstty/v1.x
add daily sink filename format
2021-02-26 12:17:29 +02:00
fawdlstty
a709e29586 fix unique mode compile 2021-02-26 10:18:33 +08:00
fawdlstty
dd46579cb4 fix name 2021-02-26 10:17:43 +08:00
fawdlstty
f4b7210e7b remove externs 2021-02-26 09:28:12 +08:00
fawdlstty
05a0b0d7b0 use fmt::chrono and remove is_fmt flags 2021-02-26 09:21:13 +08:00
fawdlstty
c1f4d7506a replace tab 2021-02-25 23:57:37 +08:00
fawdlstty
b6ba0be550 add daily_logger_format_mt and daily_logger_format_st sink 2021-02-25 23:55:19 +08:00
Gabi Melman
23dfb4e2f9 Merge pull request #1846 from MathiasMagnus/fix-bench
Avoid relying on function name decay mechanics
2021-02-24 11:26:25 +02:00
Máté Ferenc Nagy-Egri
7a10e31982 Avoid relying on function name decay mechanics 2021-02-24 09:45:35 +01:00
Gabi Melman
de89c4fd01 Merge pull request #1835 from jneruda/v1.x
Set default value to registry::err_handler_
2021-02-18 19:49:39 +02:00
Jakub Neruda
5d4956d34b Set default value to registry::err_handler_ 2021-02-17 14:02:20 +01:00
Gabi Melman
42c5eb59c9 Update wincolor_sink-inl.h 2021-02-14 01:49:29 +02:00
Gabi Melman
09cc6e7754 Simplify wincolor_sink::set_foreground_color_ 2021-02-14 01:03:02 +02:00
Gabi Melman
4a5bc41e89 Validate range in wincolor_sink::print_range_ 2021-02-14 00:20:44 +02:00
Gabi Melman
0ade18828d Remove switch statement from wincolor_sink::set_color_mode_impl 2021-02-13 23:53:36 +02:00
Gabi Melman
91046e6ca4 Simplify wincolor sink color mode management 2021-02-13 20:59:10 +02:00
Gabi Melman
17e1ba8ae2 simplifiy and mutex protect set_color_mode in wincolor sink 2021-02-13 20:16:15 +02:00
Gabi Melman
c47c854f15 Merge branch 'v1.x' of https://github.com/gabime/spdlog into v1.x 2021-02-13 20:06:51 +02:00
Gabi Melman
e931866b35 Removed public color constants from wincolor sink and intensify red and yellow 2021-02-13 20:06:40 +02:00
Gabi Melman
7828a065bf Update stdout_sinks-inl.h 2021-02-13 17:45:00 +02:00
Gabi Melman
3e689e700e Handle return values of win32 color functions in wincolor sink 2021-02-13 17:26:55 +02:00
Gabi Melman
a9964afcf7 Don't throw wincolor redirection failed, to be consistent with the non-redirect behaviour 2021-02-13 16:58:04 +02:00
Gabi Melman
95c19876c6 Skip log in wincolor sink if out_handle_ is null 2021-02-13 16:55:40 +02:00
Gabi Melman
5efccfa5e2 Fix #1828 2021-02-13 16:11:07 +02:00
Gabi Melman
89e737a258 Handle invalid handle in wincolor sink 2021-02-13 15:00:35 +02:00
Gabi Melman
8fbc853b0d Merge pull request #1829 from prateek9623/fix-prevent-msvc-warning-in-cuda
Fix: prevents MSVC warning flags to propagate in CUDA
2021-02-12 10:57:33 +02:00
Prateek Chokse
2e008b319c Fix: prevents MSVC warning flags to propagate in CUDA 2021-02-12 14:13:51 +05:30
Gabi Melman
ff6e3c95f2 Merge pull request #1819 from NukeULater/v1.x
better numeric_limits<>::max/min fix when using SPDLOG_WCHAR_TO_UTF8_SUPPORT
2021-02-06 11:32:14 +02:00
Unknown
7e9385405f better numeric_limits<>::max/min fix when using SPDLOG_WCHAR_TO_UTF8_SUPPORT
NOMINMAX macro doesn't work everytime for some reason
2021-02-06 10:50:29 +02:00
Gabi Melman
592ea36a86 Fix #1811 2021-01-21 21:12:42 +02:00
Gabi Melman
e059ebf99d Merge pull request #1807 from ChristianPanov/patch-1
Initializer list style consistency
2021-01-21 03:02:26 +02:00
Gabi Melman
609480ed78 Merge pull request #1798 from gv-me/constexpr-log-levels
add constexpr to SPDLOG_LEVEL_NAMES declaration
2021-01-21 03:00:49 +02:00
Gabi Melman
4271185936 Merge pull request #1805 from sillykelvin/patch-1
fix compiling error and typo
2021-01-21 02:56:53 +02:00
Christian Panov
aacae62591 Initializer list style consistency 2021-01-20 20:11:34 +02:00
Kelvin Hu
47cbf3828d fix compiling error and typo 2021-01-19 22:03:47 +08:00
Gabi Melman
46d418164d Merge pull request #1787 from sylveon/windows-separator-filenames
Better support for / separators on Windows, improve wchar filename coverage
2021-01-12 00:29:08 +02:00
Gaurav Vaidya
ede8d84884 fix constexpr declaration for c++11 2021-01-11 11:25:27 +01:00
Gaurav Vaidya
53d223b45f add constexpr to SPDLOG_LEVEL_NAMES declaration
for #1791
2021-01-11 11:16:06 +01:00
Charles Milette
ac35dd5a6f Prevent integer overflows in wstr_to_utf8buf and utf8_to_wstrbuf 2021-01-10 18:26:24 -05:00
Charles Milette
9e19012cb0 Remove filename_memory_buf_t from headers 2021-01-10 18:19:28 -05:00
Charles Milette
1234cda3b3 Don't build the example with wide filenames 2021-01-10 18:19:00 -05:00
Gabi Melman
710a0e3a45 Merge pull request #1795 from graydon/truncate-but-still-append
Open files with "ab" mode even if truncating.
2021-01-09 12:20:26 +02:00
Graydon Hoare
b7f24b2456 Open files with "ab" mode even if truncating. 2021-01-09 00:35:32 -08:00
Charles Milette
fc594b551a Prevent win_eventlog_sink from silently discarding errors when wide support is enabled 2021-01-06 22:17:09 -05:00
Charles Milette
f39ccccc0c Fix linker error 2021-01-06 21:52:59 -05:00
Charles Milette
f0a4ddd78b Fix character issues in test_errors.cpp 2021-01-06 20:00:59 -05:00
Charles Milette
c691769e46 Fix other build errors and unit tests 2021-01-06 19:39:47 -05:00
Charles Milette
19dc30567e Fix build errors 2021-01-06 18:55:57 -05:00
Charles Milette
a453bccff0 Better support for / separators on Windows, improve wchar filename coverage 2021-01-06 04:15:14 -05:00
Gabi Melman
aa2053a575 Merge pull request #1774 from Ryan-rsm-McKenzie/v1.x
skip module mode when finding fmt
2020-12-27 14:19:08 +02:00
ryan-rsm-mckenzie
3d8f71c4d2 skip module mode when finding fmt 2020-12-27 01:37:12 -08:00
Gabi Melman
6aaaabbc4d Merge pull request #1773 from Ryan-rsm-McKenzie/v1.x
ensure SPDLOG_FMT_EXTERNAL is honored throughout whole codebase
2020-12-27 11:17:30 +02:00
ryan-rsm-mckenzie
42c36f48ed ensure SPDLOG_FMT_EXTERNAL is honored throughout whole codebase 2020-12-27 00:47:57 -08:00
Gabi Melman
a5f4139102 Merge pull request #1771 from Ryan-rsm-McKenzie/v1.x
ensure stdout color sinks do not leak windows headers with SPDLOG_COMPILED_LIB
2020-12-26 15:23:37 +02:00
ryan-rsm-mckenzie
030d85a9b3 ensure stdout color sinks do not leak windows headers with SPDLOG_COMPILED_LIB 2020-12-26 03:45:12 -08:00
Gabi Melman
adcfb7fb55 Merge pull request #1768 from dominicpoeschko/colorterminal_detection
Changed is_color_terminal to be more generic.
2020-12-20 03:01:42 +02:00
dominic
cec365888a Added check for COLORTERM environment variable to detect if terminal
supports color.
Added alacritty to supported color terminals.
2020-12-19 23:22:31 +01:00
Gabi Melman
55bfa8dd11 Merge pull request #1766 from shimaowo/v1.x
Fix #1765 - Add SPDLOG_DISABLE_DEFAULT_LOGGER as a cmake option
2020-12-19 07:40:39 +02:00
shimaowo
e99759fe45 Fix #1765 - Add SPDLOG_DISABLE_DEFAULT_LOGGER as a cmake option 2020-12-18 14:31:34 -08:00
Gabi Melman
17c6e6ee3f Merge pull request #1760 from iko1/v1.x
fix windows event sink log compilation error with UNICODE preprocessor
2020-12-16 11:41:44 +02:00
Amir Alperin
7fff900a1a Fix error message in exception 2020-12-16 06:57:24 +02:00
Amir Alperin
c67974e4c8 replace deprected wchar string conversion 2020-12-16 01:14:55 +02:00
Gabi Melman
a36696e02e Merge pull request #1763 from BVonk/v1.x
Add Hourly file sink
2020-12-16 01:11:51 +02:00
BVonk
9b80ca6c41 Add files via upload 2020-12-15 17:21:09 +01:00
Amir Alperin
22f514aabf restore ident format 2020-12-13 23:11:25 +02:00
Amir Alperin
211478e13e convert message back to wchar 2020-12-13 10:45:55 +02:00
Amir Alperin
5e33a7e58b fix compilation errors 2020-12-13 10:11:00 +02:00
gabime
b2e31721e8 Update example 2020-12-11 16:58:49 +02:00
gabime
de0dbfa359 version 1.8.2 2020-12-11 16:40:52 +02:00
gabime
f93459579f version 1.9.0 2020-12-11 16:37:37 +02:00
gabime
2b81c40b90 Bump fmt to version 7.1.3 2020-12-11 16:32:39 +02:00
Gabi Melman
233e97c5e4 Merge pull request #1749 from bluescarni/pr/async_test_fix
Increase sleep time on a test case to avoid spurious failures
2020-11-26 11:03:46 +02:00
Francesco Biscani
fc1ce48dc7 Increase sleep time on a test case to avoid spurious failures. 2020-11-26 09:45:39 +01:00
Gabi Melman
fd5562eebe Merge pull request #1744 from ArnaudBienner/ArnaudBienner-readme-patch
📝 README example: Add {:a} format flag to bin_to_hex example
2020-11-23 02:01:34 +02:00
ArnaudBienner
0695d9cb5f 📝 README example: Add {:a} format flag to bin_to_hex example
Add {:a} format flag to bin_to_hex example
2020-11-22 20:54:28 +01:00
Gabi Melman
456b24134d Merge pull request #1742 from jwittbrodt/v1.x
Add missing include
2020-11-20 18:13:49 +02:00
Jonas Wittbrodt
f8ba24afee add missing <algorithm> include 2020-11-19 14:09:33 +01:00
Gabi Melman
eebb921c9f Merge pull request #1735 from o2gy84/v1.x
Ability to get size of messages queue of async thread pool
2020-11-13 12:59:52 +02:00
Могилин Виктор
e17ee87f38 Ability to get size of messages queue of async thread pool 2020-11-13 13:12:32 +03:00
Gabi Melman
18e3f07f7d Fix #1710 2020-11-05 18:27:31 +02:00
Gabi Melman
9ce39a470f Merge pull request #1726 from dkavolis/v1.x
Perfect forwarding for arguments
2020-11-02 04:02:38 +02:00
dkavolis
23572369fc Perfect forwarding for arguments 2020-11-02 00:37:03 +00:00
Gabi Melman
01b350de96 Merge pull request #1712 from ChristofKaufmann/deb-package
Add CPack debian package settings
2020-10-20 22:23:45 +03:00
Christof Kaufmann
365e470a32 Add CPack debian package settings 2020-10-20 19:57:56 +02:00
Gabi Melman
a42b40656c Merge pull request #1711 from ChristofKaufmann/fix-typo
Fix typo in comment
2020-10-20 10:00:43 +03:00
Christof Kaufmann
40160f2a57 Fix typo in comment 2020-10-20 02:49:09 +02:00
Gabi Melman
90b33b1552 Merge pull request #1709 from kitattyor/v1.x
Download googlebenchmark if not found
2020-10-17 17:56:59 +03:00
kitattyor
5567ed01e5 Download googlebenchmark if not found; requires cmake 3.11 2020-10-17 21:08:36 +08:00
gabime
cbe9448650 version 1.8.1 2020-09-30 17:30:06 +03:00
gabime
5b345534dc Minor change in ifdef for clarity 2020-09-30 17:29:44 +03:00
Gabi Melman
c8dc318fb3 Update logger-inl.h 2020-09-29 02:23:03 +03:00
Gabi Melman
23cb1a1080 Update logger-inl.h 2020-09-29 02:20:43 +03:00
Gabi Melman
21cf8d7d3c Merge pull request #1685 from eddelbuettel/feature/r_use_of_reprintf
let R header defines switch to REprintf over fprintf(stdderr)
2020-09-29 02:18:35 +03:00
Dirk Eddelbuettel
3cf4d34094 let R header defines switch to REprintf over fprintf(stdderr) 2020-09-28 17:13:09 -05:00
Gabi Melman
16d78ae5db Update stdout_sinks-inl.h 2020-09-28 17:08:13 +03:00
gabime
62b4b7af83 Fix #1667 2020-09-28 13:39:31 +03:00
Gabi Melman
9799ecac6a Remove redundant size check before calling WriteFile 2020-09-27 21:27:58 +03:00
Gabi Melman
dccb766095 Fix warning about enum usage 2020-09-27 19:08:24 +03:00
gabime
c97983a91c Fix linux build 2020-09-27 18:42:27 +03:00
gabime
680fb07fd5 Updatd WriteFile usage 2020-09-27 18:34:01 +03:00
gabime
cfd0ea197c Simplify WriteFile() usage under windows 2020-09-27 18:32:08 +03:00
gabime
48d4ed9bc0 Fix #1675 2020-09-27 18:27:41 +03:00
gabime
3bed78356e Added cfg tests 2020-09-27 02:10:52 +03:00
gabime
8923922f30 Cleaned level loading from env var 2020-09-27 02:08:24 +03:00
gabime
7542e42e4f removed extra parentheses 2020-09-27 01:05:55 +03:00
Gabi Melman
7a9b23e4f4 Update registry-inl.h 2020-09-26 16:09:43 +03:00
gabime
47253ba2a1 Updated comment 2020-09-26 15:56:57 +03:00
gabime
69b54dd9e4 Merge branch 'v1.x' of https://github.com/gabime/spdlog into v1.x 2020-09-26 15:49:21 +03:00
gabime
36138617fc small readablilty update in registry 2020-09-26 15:36:57 +03:00
gabime
231ca50700 clang-format 2020-09-26 15:34:05 +03:00
gabime
c7613f3e91 Fixed #1680 2020-09-26 15:32:44 +03:00
gabime
05d5546eb1 Restore example 2020-09-26 15:30:45 +03:00
gabime
cefe67726e wip fix #1680 again 2020-09-26 15:06:53 +03:00
gabime
1ac2dcc537 wip fix #1680 again 2020-09-26 14:41:33 +03:00
gabime
3a68eecb28 Fix issue #1680 2020-09-25 18:19:50 +03:00
gabime
54a8259b42 Fix #1680 2020-09-25 15:03:13 +03:00
gabime
32b6f1619f Added tests for issue #1680 2020-09-25 14:44:01 +03:00
Gabi Melman
99b8c5d379 Update bin_to_hex.h 2020-09-24 10:04:07 +03:00
Gabi Melman
5deb7c55e1 Fix #1676 2020-09-21 03:31:19 +03:00
gabime
9cd25dd216 Remove un needed hasher declaration 2020-09-01 23:49:54 +03:00
124 changed files with 9009 additions and 6004 deletions

View File

@@ -104,5 +104,6 @@ SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
IndentPPDirectives: AfterHash
...

View File

@@ -2,7 +2,7 @@ Checks: 'cppcoreguidelines-*,
performance-*,
modernize-*,
google-*,
misc-*,
misc-*
cert-*,
readability-*,
clang-analyzer-*,

View File

@@ -5,7 +5,7 @@
sudo: required
language: cpp
# gcc 4.8
# gcc t
addons: &gcc48
apt:
packages:
@@ -21,6 +21,16 @@ addons: &gcc7
sources:
- ubuntu-toolchain-r-test
# gcc 9.0
addons: &gcc9
apt:
packages:
- g++-9
sources:
- ubuntu-toolchain-r-test
# Clang 3.5
addons: &clang35
apt:
@@ -44,43 +54,43 @@ addons: &clang10
matrix:
include:
# Test gcc-4.8: C++11, Build=Debug/Release
- env: GCC_VERSION=4.8 BUILD_TYPE=Debug CPP=11
os: linux
addons: *gcc48
# Test gcc-4.8: C++11, Build=Release
- env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11
os: linux
addons: *gcc48
# Test gcc-7: C++11, Build=Release
- env: GCC_VERSION=7 BUILD_TYPE=Release CPP=11
os: linux
addons: *gcc7
# Test clang-3.5: C++11, Build=Debug/Release
- env: CLANG_VERSION=3.5 BUILD_TYPE=Debug CPP=11
# Test gcc-9: C++17, Build=Release
- env: GCC_VERSION=9 BUILD_TYPE=Release CPP=17
os: linux
addons: *clang35
addons: *gcc9
# Test clang-3.5: C++11, Build=Release
- env: CLANG_VERSION=3.5 BUILD_TYPE=Release CPP=11
os: linux
addons: *clang35
# osx
# Text osx
- env: BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=Off
os: osx
# Test clang-10.0: C++11, Build=Debug/Release
- env: CLANG_VERSION=10 BUILD_TYPE=Debug CPP=11
os: linux
dist: bionic
addons: *clang10
# Test clang-10.0: C++11, Build=Release
- env: CLANG_VERSION=10 BUILD_TYPE=Release CPP=11 ASAN=On
os: linux
dist: bionic
addons: *clang10
# Test clang-10.0: C++17, Build=Debug
- env: CLANG_VERSION=10 BUILD_TYPE=Debug CPP=17 ASAN=Off
os: linux
dist: bionic
addons: *clang10
before_script:
- if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi

View File

@@ -15,6 +15,13 @@ message(STATUS "Build spdlog: ${SPDLOG_VERSION}")
include(GNUInstallDirs)
# ---------------------------------------------------------------------------------------
# Set CMake policies to support later version behaviour
# ---------------------------------------------------------------------------------------
if(POLICY CMP0077)
cmake_policy(SET CMP0077 NEW) # option() honors variables already set
endif()
# ---------------------------------------------------------------------------------------
# Set default build to release
# ---------------------------------------------------------------------------------------
@@ -30,6 +37,11 @@ if (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
# make sure __cplusplus is defined when using msvc and enable parallel build
if(MSVC)
string(APPEND CMAKE_CXX_FLAGS " /Zc:__cplusplus /MP")
endif()
set(CMAKE_CXX_EXTENSIONS OFF)
if(CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS")
@@ -87,10 +99,15 @@ endif ()
if(WIN32)
option(SPDLOG_WCHAR_SUPPORT "Support wchar api" OFF)
option(SPDLOG_WCHAR_FILENAMES "Support wchar filenames" OFF)
else()
set(SPDLOG_WCHAR_SUPPORT OFF CACHE BOOL "non supported option" FORCE)
set(SPDLOG_WCHAR_FILENAMES OFF CACHE BOOL "non supported option" FORCE)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
option(SPDLOG_CLOCK_COARSE
"Use the much faster (but much less accurate) CLOCK_REALTIME_COARSE instead of the regular clock," OFF)
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)
@@ -100,6 +117,7 @@ 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_DISABLE_DEFAULT_LOGGER "Disable default logger creation" OFF)
# clang-tidy
if(${CMAKE_VERSION} VERSION_GREATER "3.5")
@@ -123,7 +141,7 @@ if (NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
list(APPEND SPDLOG_SRCS src/fmt.cpp)
endif()
if (SPDLOG_BUILD_SHARED)
if(SPDLOG_BUILD_SHARED OR BUILD_SHARED_LIBS)
if(WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY)
list(APPEND SPDLOG_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
@@ -131,7 +149,8 @@ if (SPDLOG_BUILD_SHARED)
add_library(spdlog SHARED ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS})
target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB)
if(MSVC)
target_compile_options(spdlog PUBLIC /wd4251 /wd4275)
target_compile_options(spdlog PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251
/wd4275>)
endif()
if(NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
target_compile_definitions(spdlog PRIVATE FMT_EXPORT PUBLIC FMT_SHARED)
@@ -171,7 +190,7 @@ target_link_libraries(spdlog_header_only INTERFACE Threads::Threads)
# ---------------------------------------------------------------------------------------
if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO)
if(NOT TARGET fmt::fmt)
find_package(fmt 5.3.0 REQUIRED)
find_package(fmt CONFIG REQUIRED)
endif()
target_compile_definitions(spdlog PUBLIC SPDLOG_FMT_EXTERNAL)
target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FMT_EXTERNAL)
@@ -188,6 +207,14 @@ if (SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO)
set(PKG_CONFIG_REQUIRES fmt) # add dependency to pkg-config
endif()
# ---------------------------------------------------------------------------------------
# Add required libraries for Android CMake build
# ---------------------------------------------------------------------------------------
if(ANDROID)
target_link_libraries(spdlog PUBLIC log)
target_link_libraries(spdlog_header_only INTERFACE log)
endif()
# ---------------------------------------------------------------------------------------
# Misc definitions according to tweak options
# ---------------------------------------------------------------------------------------
@@ -201,7 +228,8 @@ foreach (
SPDLOG_PREVENT_CHILD_FD
SPDLOG_NO_THREAD_ID
SPDLOG_NO_TLS
SPDLOG_NO_ATOMIC_LEVELS)
SPDLOG_NO_ATOMIC_LEVELS
SPDLOG_DISABLE_DEFAULT_LOGGER)
if(${SPDLOG_OPTION})
target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION})
target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION})

View File

@@ -1,6 +1,6 @@
# spdlog
Very fast, header-only/compiled, C++ logging library. [![Build Status](https://travis-ci.org/gabime/spdlog.svg?branch=v1.x)](https://travis-ci.org/gabime/spdlog)&nbsp; [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog) [![Release](https://img.shields.io/github/release/gabime/spdlog.svg)](https://github.com/gabime/spdlog/releases/latest)
Very fast, header-only/compiled, C++ logging library. [![Build Status](https://travis-ci.com/gabime/spdlog.svg?branch=v1.x)](https://travis-ci.com/gabime/spdlog)&nbsp; [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog) [![Release](https://img.shields.io/github/release/gabime/spdlog.svg)](https://github.com/gabime/spdlog/releases/latest)
## Install
#### Header only version
@@ -31,6 +31,8 @@ $ cmake .. && make -j
* vcpkg: `vcpkg install spdlog`
* conan: `spdlog/[>=1.4.1]`
* conda: `conda install -c conda-forge spdlog`
* build2: ```depends: spdlog ^1.8.2```
## Features
@@ -45,8 +47,9 @@ $ cmake .. && make -j
* Daily log files.
* Console logging (colors supported).
* syslog.
* Windows debugger (```OutputDebugString(..)```)
* Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface).
* Windows event log.
* Windows debugger (```OutputDebugString(..)```).
* 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 in runtime as well as in compile time.
* Support for loading log levels from argv or from environment var.
* [Backtrace](#backtrace-support) support - store debug messages in a ring buffer and display later on demand.
@@ -140,8 +143,9 @@ void daily_example()
---
#### Backtrace support
```c++
// Loggers can store in a ring buffer all messages (including debug/trace) and display later on demand.
// When needed, call dump_backtrace() to see them
// Debug messages can be stored in a ring buffer instead of being logged immediately.
// This is useful in order to display debug logs only when really nededed (e.g. when error happens).
// When needed, call dump_backtrace() to see them.
spdlog::enable_backtrace(32); // Store the latest 32 messages in a buffer. Older messages will be dropped.
// or my_logger->enable_backtrace(32)..
@@ -188,6 +192,7 @@ void stopwatch_example()
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.
// {:a} - show ASCII if :n is not set.
#include "spdlog/fmt/bin_to_hex.h"

View File

@@ -4,32 +4,59 @@ environment:
matrix:
- GENERATOR: '"Visual Studio 14 2015"'
BUILD_TYPE: Debug
WCHAR: 'OFF'
BUILD_SHARED: 'OFF'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
- GENERATOR: '"Visual Studio 14 2015"'
BUILD_TYPE: Release
WCHAR: 'ON'
BUILD_SHARED: 'OFF'
WCHAR: 'ON'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
- GENERATOR: '"Visual Studio 14 2015 Win64"'
BUILD_TYPE: Debug
WCHAR: 'ON'
BUILD_SHARED: 'OFF'
WCHAR: 'ON'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
- GENERATOR: '"Visual Studio 14 2015 Win64"'
BUILD_TYPE: Release
WCHAR: 'ON'
BUILD_SHARED: 'OFF'
WCHAR: 'ON'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
- GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Debug
BUILD_SHARED: 'OFF'
WCHAR: 'ON'
BUILD_SHARED: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
- GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release
WCHAR: 'OFF'
BUILD_SHARED: 'OFF'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
- GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release
WCHAR: 'OFF'
BUILD_SHARED: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
- GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
WCHAR: 'ON'
WCHAR_FILES: 'ON'
BUILD_EXAMPLE: 'OFF'
- GENERATOR: '"Visual Studio 16 2019" -A x64'
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'OFF'
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
build_script:
- cmd: >-
set
@@ -40,7 +67,7 @@ build_script:
set PATH=%PATH%;C:\Program Files\Git\usr\bin
cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DSPDLOG_WCHAR_SUPPORT=%WCHAR% -DSPDLOG_BUILD_SHARED=%BUILD_SHARED% -DSPDLOG_BUILD_EXAMPLE=ON -DSPDLOG_BUILD_EXAMPLE_HO=ON -DSPDLOG_BUILD_TESTS=ON -DSPDLOG_BUILD_TESTS_HO=OFF -DSPDLOG_BUILD_WARNINGS=ON
cmake -G %GENERATOR% -D CMAKE_BUILD_TYPE=%BUILD_TYPE% -D BUILD_SHARED_LIBS=%BUILD_SHARED% -D SPDLOG_WCHAR_SUPPORT=%WCHAR% -D SPDLOG_WCHAR_FILENAMES=%WCHAR_FILES% -D SPDLOG_BUILD_EXAMPLE=%BUILD_EXAMPLE% -D SPDLOG_BUILD_EXAMPLE_HO=%BUILD_EXAMPLE% -D SPDLOG_BUILD_TESTS=ON -D SPDLOG_BUILD_TESTS_HO=OFF -D SPDLOG_BUILD_WARNINGS=ON ..
cmake --build . --config %BUILD_TYPE%

View File

@@ -9,7 +9,22 @@ if(NOT TARGET spdlog)
endif()
find_package(Threads REQUIRED)
find_package(benchmark CONFIG REQUIRED)
find_package(benchmark CONFIG)
if(NOT benchmark_FOUND)
message(STATUS "Using CMake Version ${CMAKE_VERSION}")
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.11.0")
# User can fetch googlebenchmark
message(STATUS "Downloading GoogleBenchmark")
include(FetchContent)
set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE INTERNAL "")
# Do not build and run googlebenchmark tests
FetchContent_Declare(googlebenchmark GIT_REPOSITORY https://github.com/google/benchmark.git GIT_TAG v1.5.2)
FetchContent_MakeAvailable(googlebenchmark)
else()
message(FATAL_ERROR "GoogleBenchmark is missing. Use CMake >= 3.11 or download it")
endif()
endif()
add_executable(bench bench.cpp)
spdlog_enable_warnings(bench)

View File

@@ -9,7 +9,12 @@
#include "spdlog/spdlog.h"
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/fmt/bundled/locale.h"
#ifdef SPDLOG_FMT_EXTERNAL
# include <fmt/format.h>
#else
# include "spdlog/fmt/bundled/format.h"
#endif
#include "utils.h"
#include <atomic>
@@ -98,12 +103,11 @@ int main(int argc, char *argv[])
auto slot_size = sizeof(spdlog::details::async_msg);
spdlog::info("-------------------------------------------------");
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Messages : {:L}", howmany));
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Threads : {:L}", threads));
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Queue : {:L} slots", queue_size));
spdlog::info(fmt::format(
std::locale("en_US.UTF-8"), "Queue memory : {:L} x {:L} = {:L} KB ", queue_size, slot_size, (queue_size * slot_size) / 1024));
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Total iters : {:L}", iters));
spdlog::info("Messages : {:L}", howmany);
spdlog::info("Threads : {:L}", threads);
spdlog::info("Queue : {:L} slots", queue_size);
spdlog::info("Queue memory : {:L} x {:L} = {:L} KB ", queue_size, slot_size, (queue_size * slot_size) / 1024);
spdlog::info("Total iters : {:L}", iters);
spdlog::info("-------------------------------------------------");
const char *filename = "logs/basic_async.log";
@@ -176,5 +180,5 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> logger, int thread_co
auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count();
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Elapsed: {} secs\t {:L}/sec", delta_d, int(howmany / delta_d)));
spdlog::info("Elapsed: {} secs\t {:L}/sec", delta_d, int(howmany / delta_d));
}

View File

@@ -11,7 +11,12 @@
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/null_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/fmt/bundled/locale.h"
#ifdef SPDLOG_FMT_EXTERNAL
# include <fmt/locale.h>
#else
# include "spdlog/fmt/bundled/format.h"
#endif
#include "utils.h"
#include <atomic>
@@ -123,7 +128,7 @@ int main(int argc, char *argv[])
if (threads > max_threads)
{
throw std::runtime_error(fmt::format("Number of threads exceeds maximum({}})", max_threads));
throw std::runtime_error(fmt::format("Number of threads exceeds maximum({})", max_threads));
}
bench_single_threaded(iters);

View File

@@ -34,14 +34,14 @@ void bench_formatters()
for (auto &flag : all_flags)
{
auto pattern = std::string("%") + flag;
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern);
// pattern = std::string("%16") + flag;
// benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
// benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern);
//
// // bench center padding
// pattern = std::string("%=16") + flag;
// benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
// benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern);
}
// complex patterns
@@ -52,7 +52,7 @@ void bench_formatters()
};
for (auto &pattern : patterns)
{
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern)->Iterations(2500000);
benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern)->Iterations(2500000);
}
}
@@ -73,7 +73,7 @@ int main(int argc, char *argv[])
}
else
{
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern);
}
benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();

View File

@@ -18,23 +18,37 @@ set(CPACK_PACKAGE_RELOCATABLE ON CACHE BOOL "Build relocatable package")
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries")
set(CPACK_DEBIAN_PACKAGE_SECTION "libs")
set(CPACK_RPM_PACKAGE_URL ${CPACK_PROJECT_URL})
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE ${CPACK_PROJECT_URL})
set(CPACK_RPM_PACKAGE_DESCRIPTION "Very fast, header-only/compiled, C++ logging library.")
set(CPACK_DEBIAN_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}")
set(CPACK_DEBIAN_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
else()
set(CPACK_RPM_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
set(CPACK_DEBIAN_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
set(CPACK_RPM_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
endif()
if(CPACK_RPM_PACKAGE_RELEASE)
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}-${CPACK_RPM_PACKAGE_RELEASE}")
endif()
if(CPACK_DEBIAN_PACKAGE_RELEASE)
set(CPACK_DEBIAN_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}-${CPACK_DEBIAN_PACKAGE_RELEASE}")
endif()
if(CPACK_RPM_PACKAGE_ARCHITECTURE)
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.${CPACK_RPM_PACKAGE_ARCHITECTURE}")
endif()
if(CPACK_DEBIAN_PACKAGE_ARCHITECTURE)
set(CPACK_DEBIAN_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}.${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
endif()
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.rpm")
set(CPACK_DEBIAN_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}.deb")
if(NOT CPACK_PACKAGE_RELOCATABLE)
# Depend on pkgconfig rpm to create the system pkgconfig folder

View File

@@ -42,6 +42,7 @@ function(spdlog_enable_warnings target_name)
-Wextra
-Wconversion
-pedantic
-Werror
-Wfatal-errors>
$<$<CXX_COMPILER_ID:MSVC>:${MSVC_OPTIONS}>)
endif()

View File

@@ -22,7 +22,7 @@ void syslog_example();
void custom_flags_example();
#include "spdlog/spdlog.h"
#include "spdlog/cfg/env.h" // for loading levels from the environment variable
#include "spdlog/cfg/env.h" // support for loading levels from the environment variable
int main(int, char *[])
{
@@ -129,7 +129,7 @@ void daily_example()
#include "spdlog/cfg/env.h"
void load_levels_example()
{
// Set the log level to "info" and mylogger to to "trace":
// Set the log level to "info" and mylogger to "trace":
// SPDLOG_LEVEL=info,mylogger=trace && ./example
spdlog::cfg::load_env_levels();
// or from command line:

View File

@@ -46,7 +46,7 @@ struct async_factory_impl
auto tp = registry_inst.get_tp();
if (tp == nullptr)
{
tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1);
tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1U);
registry_inst.set_tp(tp);
}

View File

@@ -30,8 +30,7 @@ inline void load_argv_levels(int argc, const char **argv)
if (arg.find(spdlog_level_prefix) == 0)
{
auto levels_string = arg.substr(spdlog_level_prefix.size());
auto levels = helpers::extract_levels(levels_string);
details::registry::instance().update_levels(std::move(levels));
helpers::load_levels(levels_string);
}
}
}

View File

@@ -17,7 +17,7 @@
// export SPDLOG_LEVEL=debug
//
// turn off all logging except for logger1:
// export SPDLOG_LEVEL="off,logger1=debug"
// export SPDLOG_LEVEL="*=off,logger1=debug"
//
// turn off all logging except for logger1 and logger2:
@@ -28,8 +28,10 @@ namespace cfg {
inline void load_env_levels()
{
auto env_val = details::os::getenv("SPDLOG_LEVEL");
auto levels = helpers::extract_levels(env_val);
details::registry::instance().update_levels(std::move(levels));
if (!env_val.empty())
{
helpers::load_levels(env_val);
}
}
} // namespace cfg

View File

@@ -11,6 +11,7 @@
#include <spdlog/details/os.h>
#include <spdlog/details/registry.h>
#include <algorithm>
#include <string>
#include <utility>
#include <sstream>
@@ -78,24 +79,40 @@ inline std::unordered_map<std::string, std::string> extract_key_vals_(const std:
return rv;
}
SPDLOG_INLINE log_levels extract_levels(const std::string &input)
SPDLOG_INLINE void load_levels(const std::string &input)
{
if (input.empty() || input.size() > 512)
{
return;
}
auto key_vals = extract_key_vals_(input);
log_levels rv;
std::unordered_map<std::string, level::level_enum> levels;
level::level_enum global_level = level::info;
bool global_level_found = false;
for (auto &name_level : key_vals)
{
auto &logger_name = name_level.first;
auto level_name = to_lower_(name_level.second);
auto level = level::from_str(level_name);
// fallback to "info" if unrecognized level name
// ignore unrecognized level names
if (level == level::off && level_name != "off")
{
level = level::info;
continue;
}
rv.set(logger_name, level);
if (logger_name.empty()) // no logger name indicate global level
{
global_level_found = true;
global_level = level;
}
return rv;
else
{
levels[logger_name] = level;
}
}
details::registry::instance().set_levels(std::move(levels), global_level_found ? &global_level : nullptr);
}
} // namespace helpers

View File

@@ -3,7 +3,8 @@
#pragma once
#include <spdlog/cfg/log_levels.h>
#include <spdlog/common.h>
#include <unordered_map>
namespace spdlog {
namespace cfg {
@@ -17,7 +18,7 @@ namespace helpers {
// turn off all logging except for logger1: "off,logger1=debug"
// turn off all logging except for logger1 and logger2: "off,logger1=debug,logger2=info"
//
SPDLOG_API log_levels extract_levels(const std::string &txt);
SPDLOG_API void load_levels(const std::string &txt);
} // namespace helpers
} // namespace cfg

View File

@@ -1,47 +0,0 @@
// 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 <string>
#include <unordered_map>
namespace spdlog {
namespace cfg {
class log_levels
{
std::unordered_map<std::string, spdlog::level::level_enum> levels_;
spdlog::level::level_enum default_level_ = level::info;
public:
void set(const std::string &logger_name, level::level_enum lvl)
{
if (logger_name.empty())
{
default_level_ = lvl;
}
else
{
levels_[logger_name] = lvl;
}
}
void set_default(level::level_enum lvl)
{
default_level_ = lvl;
}
level::level_enum get(const std::string &logger_name)
{
auto it = levels_.find(logger_name);
return it != levels_.end() ? it->second : default_level_;
}
level::level_enum default_level()
{
return default_level_;
}
};
} // namespace cfg
} // namespace spdlog

View File

@@ -7,13 +7,20 @@
# include <spdlog/common.h>
#endif
#include <algorithm>
#include <iterator>
namespace spdlog {
namespace level {
#if __cplusplus >= 201703L
constexpr
#endif
static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES;
static const char *short_level_names[] SPDLOG_SHORT_LEVEL_NAMES;
SPDLOG_INLINE string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT
SPDLOG_INLINE const string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT
{
return level_string_views[l];
}
@@ -25,15 +32,10 @@ SPDLOG_INLINE const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOE
SPDLOG_INLINE spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT
{
int level = 0;
for (const auto &level_str : level_string_views)
{
if (level_str == name)
{
return static_cast<level::level_enum>(level);
}
level++;
}
auto it = std::find(std::begin(level_string_views), std::end(level_string_views), name);
if (it != std::end(level_string_views))
return static_cast<level::level_enum>(std::distance(std::begin(level_string_views), it));
// check also for "warn" and "err" before giving up..
if (name == "warn")
{
@@ -54,7 +56,7 @@ SPDLOG_INLINE spdlog_ex::spdlog_ex(std::string msg)
SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string &msg, int last_errno)
{
memory_buf_t outbuf;
fmt::format_system_error(outbuf, last_errno, msg);
fmt::format_system_error(outbuf, last_errno, msg.c_str());
msg_ = fmt::to_string(outbuf);
}

View File

@@ -35,6 +35,16 @@
#include <spdlog/fmt/fmt.h>
// backward compatibility with fmt versions older than 8
#if FMT_VERSION >= 80000
# define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string)
# if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
# include <spdlog/fmt/xchar.h>
# endif
#else
# define SPDLOG_FMT_RUNTIME(format_string) format_string
#endif
// visual studio upto 2013 does not support noexcept nor constexpr
#if defined(_MSC_VER) && (_MSC_VER < 1900)
# define SPDLOG_NOEXCEPT _NOEXCEPT
@@ -71,11 +81,12 @@
printf("spdlog fatal error: %s\n", ex.what()); \
std::abort(); \
} while (0)
#define SPDLOG_CATCH_ALL()
# define SPDLOG_CATCH_STD
#else
# define SPDLOG_TRY try
# define SPDLOG_THROW(ex) throw(ex)
#define SPDLOG_CATCH_ALL() catch (...)
# define SPDLOG_CATCH_STD \
catch (const std::exception &) {}
#endif
namespace spdlog {
@@ -88,7 +99,9 @@ class sink;
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
using filename_t = std::wstring;
#define SPDLOG_FILENAME_T(s) L##s
// allow macro expansion to occur in SPDLOG_FILENAME_T
# define SPDLOG_FILENAME_T_INNER(s) L##s
# define SPDLOG_FILENAME_T(s) SPDLOG_FILENAME_T_INNER(s)
#else
using filename_t = std::string;
# define SPDLOG_FILENAME_T(s) s
@@ -101,6 +114,7 @@ using err_handler = std::function<void(const std::string &err_msg)>;
using string_view_t = fmt::basic_string_view<char>;
using wstring_view_t = fmt::basic_string_view<wchar_t>;
using memory_buf_t = fmt::basic_memory_buffer<char, 250>;
using wmemory_buf_t = fmt::basic_memory_buffer<wchar_t, 250>;
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
# ifndef _WIN32
@@ -148,10 +162,19 @@ enum level_enum
n_levels
};
#define SPDLOG_LEVEL_NAME_TRACE string_view_t("trace", 5)
#define SPDLOG_LEVEL_NAME_DEBUG string_view_t("debug", 5)
#define SPDLOG_LEVEL_NAME_INFO string_view_t("info", 4)
#define SPDLOG_LEVEL_NAME_WARNING string_view_t("warning", 7)
#define SPDLOG_LEVEL_NAME_ERROR string_view_t("error", 5)
#define SPDLOG_LEVEL_NAME_CRITICAL string_view_t("critical", 8)
#define SPDLOG_LEVEL_NAME_OFF string_view_t("off", 3)
#if !defined(SPDLOG_LEVEL_NAMES)
# define SPDLOG_LEVEL_NAMES \
{ \
"trace", "debug", "info", "warning", "error", "critical", "off" \
SPDLOG_LEVEL_NAME_TRACE, SPDLOG_LEVEL_NAME_DEBUG, SPDLOG_LEVEL_NAME_INFO, SPDLOG_LEVEL_NAME_WARNING, SPDLOG_LEVEL_NAME_ERROR, \
SPDLOG_LEVEL_NAME_CRITICAL, SPDLOG_LEVEL_NAME_OFF \
}
#endif
@@ -163,11 +186,10 @@ enum level_enum
}
#endif
SPDLOG_API string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
SPDLOG_API const string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
SPDLOG_API const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
SPDLOG_API spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT;
using level_hasher = std::hash<int>;
} // namespace level
//
@@ -204,8 +226,8 @@ private:
std::string msg_;
};
SPDLOG_API void throw_spdlog_ex(const std::string &msg, int last_errno);
SPDLOG_API void throw_spdlog_ex(std::string msg);
[[noreturn]] SPDLOG_API void throw_spdlog_ex(const std::string &msg, int last_errno);
[[noreturn]] SPDLOG_API void throw_spdlog_ex(std::string msg);
struct source_loc
{

View File

@@ -29,12 +29,27 @@ SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate)
{
close();
filename_ = fname;
auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab");
auto *mode = SPDLOG_FILENAME_T("ab");
auto *trunc_mode = SPDLOG_FILENAME_T("wb");
for (int tries = 0; tries < open_tries_; ++tries)
{
// create containing folder if not exists already.
os::create_dir(os::dir_name(fname));
if (truncate)
{
// Truncate by opening-and-closing a tmp file in "wb" mode, always
// opening the actual log-we-write-to in "ab" mode, since that
// interacts more politely with eternal processes that might
// rotate/truncate the file underneath us.
std::FILE *tmp;
if (os::fopen_s(&tmp, fname, trunc_mode))
{
continue;
}
std::fclose(tmp);
}
if (!os::fopen_s(&fd_, fname, mode))
{
return;
@@ -118,7 +133,7 @@ SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension
}
// treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile"
auto folder_index = fname.rfind(details::os::folder_sep);
auto folder_index = fname.find_last_of(details::os::folder_seps_filename);
if (folder_index != filename_t::npos && folder_index >= ext_index - 1)
{
return std::make_tuple(fname, filename_t());

View File

@@ -47,7 +47,7 @@ public:
private:
const int open_tries_ = 5;
const int open_interval_ = 10;
const unsigned int open_interval_ = 10;
std::FILE *fd_{nullptr};
filename_t filename_;
};

View File

@@ -4,6 +4,7 @@
#include <chrono>
#include <type_traits>
#include <iterator>
#include <spdlog/fmt/fmt.h>
#include <spdlog/common.h>
@@ -54,7 +55,7 @@ inline void pad2(int n, memory_buf_t &dest)
}
else // unlikely, but just in case, let fmt deal with it
{
fmt::format_to(dest, "{:02}", n);
fmt::format_to(std::back_inserter(dest), "{:02}", n);
}
}

View File

@@ -15,6 +15,7 @@ struct SPDLOG_API log_msg
log_msg(source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg);
log_msg(string_view_t logger_name, level::level_enum lvl, string_view_t msg);
log_msg(const log_msg &other) = default;
log_msg &operator=(const log_msg &other) = default;
string_view_t logger_name;
level::level_enum level{level::off};

View File

@@ -110,6 +110,12 @@ public:
return q_.overrun_counter();
}
size_t size()
{
std::unique_lock<std::mutex> lock(queue_mutex_);
return q_.size();
}
private:
std::mutex queue_mutex_;
std::condition_variable push_cv_;

View File

@@ -199,6 +199,12 @@ SPDLOG_INLINE bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT
#endif
}
#ifdef _MSC_VER
// avoid warning about unreachable statement at the end of filesize()
# pragma warning(push)
# pragma warning(disable : 4702)
#endif
// Return file size according to open FILE* object
SPDLOG_INLINE size_t filesize(FILE *f)
{
@@ -208,7 +214,7 @@ SPDLOG_INLINE size_t filesize(FILE *f)
}
#if defined(_WIN32) && !defined(__CYGWIN__)
int fd = ::_fileno(f);
#if _WIN64 // 64 bits
# if defined(_WIN64) // 64 bits
__int64 ret = ::_filelengthi64(fd);
if (ret >= 0)
{
@@ -249,6 +255,10 @@ SPDLOG_INLINE size_t filesize(FILE *f)
return 0; // will not be reached.
}
#ifdef _MSC_VER
# pragma warning(pop)
#endif
// Return utc offset in minutes or throw spdlog_ex on failure
SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm)
{
@@ -356,7 +366,7 @@ SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT
// This is avoid msvc issue in sleep_for that happens if the clock changes.
// See https://github.com/gabime/spdlog/issues/609
SPDLOG_INLINE void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT
SPDLOG_INLINE void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT
{
#if defined(_WIN32)
::Sleep(milliseconds);
@@ -397,17 +407,26 @@ SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT
#ifdef _WIN32
return true;
#else
static constexpr std::array<const char *, 14> terms = {
{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}};
const char *env_p = std::getenv("TERM");
if (env_p == nullptr)
static const bool result = []() {
const char *env_colorterm_p = std::getenv("COLORTERM");
if (env_colorterm_p != nullptr)
{
return true;
}
static constexpr std::array<const char *, 16> terms = {{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux",
"msys", "putty", "rxvt", "screen", "vt100", "xterm", "alacritty", "vt102"}};
const char *env_term_p = std::getenv("TERM");
if (env_term_p == nullptr)
{
return false;
}
static const bool result =
std::any_of(terms.begin(), terms.end(), [&](const char *term) { return std::strstr(env_p, term) != nullptr; });
return std::any_of(terms.begin(), terms.end(), [&](const char *term) { return std::strstr(env_term_p, term) != nullptr; });
}();
return result;
#endif
}
@@ -427,7 +446,7 @@ SPDLOG_INLINE bool in_terminal(FILE *file) SPDLOG_NOEXCEPT
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target)
{
if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)()))
if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) / 2 - 1)
{
throw_spdlog_ex("UTF-16 string is too big to be converted to UTF-8");
}
@@ -459,6 +478,41 @@ SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target)
throw_spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()));
}
SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target)
{
if (str.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) - 1)
{
throw_spdlog_ex("UTF-8 string is too big to be converted to UTF-16");
}
int str_size = static_cast<int>(str.size());
if (str_size == 0)
{
target.resize(0);
return;
}
int result_size = static_cast<int>(target.capacity());
if (str_size + 1 > result_size)
{
result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, NULL, 0);
}
if (result_size > 0)
{
target.resize(result_size);
result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, target.data(), result_size);
if (result_size > 0)
{
target.resize(result_size);
return;
}
}
throw_spdlog_ex(fmt::format("MultiByteToWideChar failed. Last error: {}", ::GetLastError()));
}
#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
// return true on success
@@ -489,15 +543,10 @@ SPDLOG_INLINE bool create_dir(filename_t path)
return false;
}
#ifdef _WIN32
// support forward slash in windows
std::replace(path.begin(), path.end(), '/', folder_sep);
#endif
size_t search_offset = 0;
do
{
auto token_pos = path.find(folder_sep, search_offset);
auto token_pos = path.find_first_of(folder_seps_filename, search_offset);
// treat the entire path as a folder if no folder separator not found
if (token_pos == filename_t::npos)
{
@@ -523,11 +572,7 @@ SPDLOG_INLINE bool create_dir(filename_t path)
// "abc///" => "abc//"
SPDLOG_INLINE filename_t dir_name(filename_t path)
{
#ifdef _WIN32
// support forward slash in windows
std::replace(path.begin(), path.end(), '/', folder_sep);
#endif
auto pos = path.find_last_of(folder_sep);
auto pos = path.find_last_of(folder_seps_filename);
return pos != filename_t::npos ? path.substr(0, pos) : filename_t{};
}

View File

@@ -32,11 +32,16 @@ SPDLOG_API std::tm gmtime() SPDLOG_NOEXCEPT;
SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL;
// folder separator
#if !defined(SPDLOG_FOLDER_SEPS)
# ifdef _WIN32
static const char folder_sep = '\\';
# define SPDLOG_FOLDER_SEPS "\\/"
# else
SPDLOG_CONSTEXPR static const char folder_sep = '/';
# define SPDLOG_FOLDER_SEPS "/"
# endif
#endif
SPDLOG_CONSTEXPR static const char folder_seps[] = SPDLOG_FOLDER_SEPS;
SPDLOG_CONSTEXPR static const filename_t::value_type folder_seps_filename[] = SPDLOG_FILENAME_T(SPDLOG_FOLDER_SEPS);
// fopen_s on non windows for writing
SPDLOG_API bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode);
@@ -69,7 +74,7 @@ SPDLOG_API size_t thread_id() SPDLOG_NOEXCEPT;
// This is avoid msvc issue in sleep_for that happens if the clock changes.
// See https://github.com/gabime/spdlog/issues/609
SPDLOG_API void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT;
SPDLOG_API void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT;
SPDLOG_API std::string filename_to_str(const filename_t &filename);
@@ -85,6 +90,8 @@ SPDLOG_API bool in_terminal(FILE *file) SPDLOG_NOEXCEPT;
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
SPDLOG_API void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target);
SPDLOG_API void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target);
#endif
// Return directory name from given path or empty string

View File

@@ -67,7 +67,11 @@ SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logge
new_logger->set_error_handler(err_handler_);
}
new_logger->set_level(levels_.get(new_logger->name()));
// set new level according to previously configured level or default level
auto it = log_levels_.find(new_logger->name());
auto new_level = it != log_levels_.end() ? it->second : global_log_level_;
new_logger->set_level(new_level);
new_logger->flush_on(flush_level_);
if (backtrace_n_messages_ > 0)
@@ -171,7 +175,7 @@ SPDLOG_INLINE void registry::set_level(level::level_enum log_level)
{
l.second->set_level(log_level);
}
levels_.set_default(log_level);
global_log_level_ = log_level;
}
SPDLOG_INLINE void registry::flush_on(level::level_enum log_level)
@@ -191,14 +195,14 @@ SPDLOG_INLINE void registry::flush_every(std::chrono::seconds interval)
periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);
}
SPDLOG_INLINE void registry::set_error_handler(void (*handler)(const std::string &msg))
SPDLOG_INLINE void registry::set_error_handler(err_handler handler)
{
std::lock_guard<std::mutex> lock(logger_map_mutex_);
for (auto &l : loggers_)
{
l.second->set_error_handler(handler);
}
err_handler_ = handler;
err_handler_ = std::move(handler);
}
SPDLOG_INLINE void registry::apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun)
@@ -263,14 +267,24 @@ SPDLOG_INLINE void registry::set_automatic_registration(bool automatic_registrat
automatic_registration_ = automatic_registration;
}
SPDLOG_INLINE void registry::update_levels(cfg::log_levels levels)
SPDLOG_INLINE void registry::set_levels(log_levels levels, level::level_enum *global_level)
{
std::lock_guard<std::mutex> lock(logger_map_mutex_);
levels_ = std::move(levels);
for (auto &l : loggers_)
log_levels_ = std::move(levels);
auto global_level_requested = global_level != nullptr;
global_log_level_ = global_level_requested ? *global_level : global_log_level_;
for (auto &logger : loggers_)
{
auto &logger = l.second;
logger->set_level(levels_.get(logger->name()));
auto logger_entry = log_levels_.find(logger.first);
if (logger_entry != log_levels_.end())
{
logger.second->set_level(logger_entry->second);
}
else if (global_level_requested)
{
logger.second->set_level(*global_level);
}
}
}

View File

@@ -9,7 +9,6 @@
// This class is thread safe
#include <spdlog/common.h>
#include <spdlog/cfg/log_levels.h>
#include <chrono>
#include <functional>
@@ -28,6 +27,7 @@ class periodic_worker;
class SPDLOG_API registry
{
public:
using log_levels = std::unordered_map<std::string, level::level_enum>;
registry(const registry &) = delete;
registry &operator=(const registry &) = delete;
@@ -63,7 +63,7 @@ public:
void flush_every(std::chrono::seconds interval);
void set_error_handler(void (*handler)(const std::string &msg));
void set_error_handler(err_handler handler);
void apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun);
@@ -80,7 +80,8 @@ public:
void set_automatic_registration(bool automatic_registration);
void update_levels(cfg::log_levels levels);
// set levels for all existing/future loggers. global_level can be null if should not set.
void set_levels(log_levels levels, level::level_enum *global_level);
static registry &instance();
@@ -90,13 +91,15 @@ private:
void throw_if_exists_(const std::string &logger_name);
void register_logger_(std::shared_ptr<logger> new_logger);
bool set_level_from_cfg_(logger *logger);
std::mutex logger_map_mutex_, flusher_mutex_;
std::recursive_mutex tp_mutex_;
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;
cfg::log_levels levels_;
log_levels log_levels_;
std::unique_ptr<formatter> formatter_;
spdlog::level::level_enum global_log_level_ = level::info;
level::level_enum flush_level_ = level::off;
void (*err_handler_)(const std::string &msg);
err_handler err_handler_;
std::shared_ptr<thread_pool> tp_;
std::unique_ptr<periodic_worker> periodic_flusher_;
std::shared_ptr<logger> default_logger_;

View File

@@ -144,7 +144,7 @@ public:
// set TCP_NODELAY
int enable_flag = 1;
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, (char *)&enable_flag, sizeof(enable_flag));
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag), sizeof(enable_flag));
}
// Send exactly n_bytes of the given data.

View File

@@ -103,11 +103,11 @@ public:
// set TCP_NODELAY
int enable_flag = 1;
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, (char *)&enable_flag, sizeof(enable_flag));
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<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));
::setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char *>(&enable_flag), sizeof(enable_flag));
#endif
#if !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)

View File

@@ -49,7 +49,7 @@ SPDLOG_INLINE thread_pool::~thread_pool()
t.join();
}
}
SPDLOG_CATCH_ALL() {}
SPDLOG_CATCH_STD
}
void SPDLOG_INLINE thread_pool::post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy)
@@ -68,6 +68,11 @@ size_t SPDLOG_INLINE thread_pool::overrun_counter()
return q_.overrun_counter();
}
size_t SPDLOG_INLINE thread_pool::queue_size()
{
return q_.size();
}
void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy)
{
if (overflow_policy == async_overflow_policy::block)

View File

@@ -97,6 +97,7 @@ public:
void post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy);
void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy);
size_t overrun_counter();
size_t queue_size();
private:
q_type q_;

View File

@@ -9,7 +9,7 @@
//
// Support for logging binary data as hex
// format flags:
// format flags, any combination of the followng:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
@@ -209,7 +209,7 @@ struct formatter<spdlog::details::dump_info<T>>
if (put_positions)
{
fmt::format_to(inserter, "{:<04X}: ", pos);
fmt::format_to(inserter, "{:04X}: ", pos);
}
}
};

View File

@@ -1,27 +0,0 @@
Copyright (c) 2012 - present, Victor Zverovich
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- Optional exception to the license ---
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into a machine-executable object form of such
source code, you may redistribute such embedded portions in such object form
without including the above copyright and permission notices.

View File

@@ -0,0 +1,232 @@
// Formatting library for C++ - dynamic format arguments
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_ARGS_H_
#define FMT_ARGS_H_
#include <functional> // std::reference_wrapper
#include <memory> // std::unique_ptr
#include <vector>
#include "core.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
template <typename T> const T& unwrap(const T& v) { return v; }
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
return static_cast<const T&>(v);
}
class dynamic_arg_list {
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
// templates it doesn't complain about inability to deduce single translation
// unit for placing vtable. So storage_node_base is made a fake template.
template <typename = void> struct node {
virtual ~node() = default;
std::unique_ptr<node<>> next;
};
template <typename T> struct typed_node : node<> {
T value;
template <typename Arg>
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
template <typename Char>
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
: value(arg.data(), arg.size()) {}
};
std::unique_ptr<node<>> head_;
public:
template <typename T, typename Arg> const T& push(const Arg& arg) {
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
auto& value = new_node->value;
new_node->next = std::move(head_);
head_ = std::move(new_node);
return value;
}
};
} // namespace detail
/**
\rst
A dynamic version of `fmt::format_arg_store`.
It's equipped with a storage to potentially temporary objects which lifetimes
could be shorter than the format arguments object.
It can be implicitly converted into `~fmt::basic_format_args` for passing
into type-erased formatting functions such as `~fmt::vformat`.
\endrst
*/
template <typename Context>
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private:
using char_type = typename Context::char_type;
template <typename T> struct need_copy {
static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value;
enum {
value = !(detail::is_reference_wrapper<T>::value ||
std::is_same<T, basic_string_view<char_type>>::value ||
std::is_same<T, detail::std_string_view<char_type>>::value ||
(mapped_type != detail::type::cstring_type &&
mapped_type != detail::type::string_type &&
mapped_type != detail::type::custom_type))
};
};
template <typename T>
using stored_type = conditional_t<detail::is_string<T>::value &&
!has_formatter<T, Context>::value &&
!detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>;
// Storage of basic_format_arg must be contiguous.
std::vector<basic_format_arg<Context>> data_;
std::vector<detail::named_arg_info<char_type>> named_info_;
// Storage of arguments not fitting into basic_format_arg must grow
// without relocation because items in data_ refer to it.
detail::dynamic_arg_list dynamic_args_;
friend class basic_format_args<Context>;
unsigned long long get_types() const {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
const basic_format_arg<Context>* data() const {
return named_info_.empty() ? data_.data() : data_.data() + 1;
}
template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg));
}
template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) {
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
data_.insert(data_.begin(), {zero_ptr, 0});
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back();
};
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
guard.release();
}
public:
/**
\rst
Adds an argument into the dynamic store for later passing to a formatting
function.
Note that custom types and string types (but not string views) are copied
into the store dynamically allocating memory if necessary.
**Example**::
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(42);
store.push_back("abc");
store.push_back(1.5f);
std::string result = fmt::vformat("{} and {} and {}", store);
\endrst
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
else
emplace_arg(detail::unwrap(arg));
}
/**
\rst
Adds a reference to the argument into the dynamic store for later passing to
a formatting function.
**Example**::
fmt::dynamic_format_arg_store<fmt::format_context> store;
char band[] = "Rolling Stones";
store.push_back(std::cref(band));
band[9] = 'c'; // Changing str affects the output.
std::string result = fmt::vformat("{}", store);
// result == "Rolling Scones"
\endrst
*/
template <typename T> void push_back(std::reference_wrapper<T> arg) {
static_assert(
need_copy<T>::value,
"objects of built-in types and string views are always copied");
emplace_arg(arg.get());
}
/**
Adds named argument into the dynamic store for later passing to a formatting
function. ``std::reference_wrapper`` is supported to avoid copying of the
argument. The name is always copied into the store.
*/
template <typename T>
void push_back(const detail::named_arg<char_type, T>& arg) {
const char_type* arg_name =
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
} else {
emplace_arg(fmt::arg(arg_name, arg.value));
}
}
/** Erase all elements from the store */
void clear() {
data_.clear();
named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list();
}
/**
\rst
Reserves space to store at least *new_cap* arguments including
*new_cap_named* named arguments.
\endrst
*/
void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
};
FMT_END_NAMESPACE
#endif // FMT_ARGS_H_

View File

@@ -8,13 +8,13 @@
#ifndef FMT_CHRONO_H_
#define FMT_CHRONO_H_
#include <algorithm>
#include <chrono>
#include <ctime>
#include <locale>
#include <sstream>
#include "format.h"
#include "locale.h"
FMT_BEGIN_NAMESPACE
@@ -72,43 +72,27 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
static_assert(F::is_integer, "From must be integral");
static_assert(T::is_integer, "To must be integral");
if (F::is_signed && !T::is_signed) {
if (detail::const_check(F::is_signed && !T::is_signed)) {
// From may be negative, not allowed!
if (fmt::detail::is_negative(from)) {
ec = 1;
return {};
}
// From is positive. Can it always fit in To?
if (F::digits <= T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>((T::max)())) {
if (F::digits > T::digits &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
}
}
}
if (!F::is_signed && T::is_signed) {
// can from be held in To?
if (F::digits < T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>((T::max)())) {
// outside range.
if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
}
return static_cast<To>(from); // Lossless conversion.
}
}
// reaching here means all is ok for lossless conversion.
return static_cast<To>(from);
} // function
template <typename To, typename From,
FMT_ENABLE_IF(std::is_same<From, To>::value)>
@@ -190,11 +174,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// safe conversion to IntermediateRep
IntermediateRep count =
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
if (ec) {
return {};
}
if (ec) return {};
// multiply with Factor::num without overflow or underflow
if (Factor::num != 1) {
if (detail::const_check(Factor::num != 1)) {
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
if (count > max1) {
ec = 1;
@@ -209,17 +191,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
count *= Factor::num;
}
// this can't go wrong, right? den>0 is checked earlier.
if (Factor::den != 1) {
count /= Factor::den;
}
// convert to the to type, safely
using ToRep = typename To::rep;
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
if (ec) {
return {};
}
return To{tocount};
if (detail::const_check(Factor::den != 1)) count /= Factor::den;
auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
return ec ? To() : To(tocount);
}
/**
@@ -308,13 +282,89 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
#define FMT_NOMACRO
namespace detail {
template <typename T = void> struct null {};
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
inline null<> localtime_s(...) { return null<>(); }
inline null<> gmtime_r(...) { return null<>(); }
inline null<> gmtime_s(...) { return null<>(); }
inline auto do_write(const std::tm& time, const std::locale& loc, char format,
char modifier) -> std::string {
auto&& os = std::ostringstream();
os.imbue(loc);
using iterator = std::ostreambuf_iterator<char>;
const auto& facet = std::use_facet<std::time_put<char, iterator>>(loc);
auto end = facet.put(os, os, ' ', &time, format, modifier);
if (end.failed()) FMT_THROW(format_error("failed to format time"));
auto str = os.str();
if (!detail::is_utf8() || loc == std::locale::classic()) return str;
// char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
// gcc-4.
#if FMT_MSC_VER != 0 || \
(defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
// The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
// and newer.
using code_unit = wchar_t;
#else
using code_unit = char32_t;
#endif
auto& f = std::use_facet<std::codecvt<code_unit, char, std::mbstate_t>>(loc);
auto mb = std::mbstate_t();
const char* from_next = nullptr;
code_unit* to_next = nullptr;
constexpr size_t buf_size = 32;
code_unit buf[buf_size] = {};
auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf,
buf + buf_size, to_next);
if (result != std::codecvt_base::ok)
FMT_THROW(format_error("failed to format time"));
str.clear();
for (code_unit* p = buf; p != to_next; ++p) {
uint32_t c = static_cast<uint32_t>(*p);
if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
// surrogate pair
++p;
if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
FMT_THROW(format_error("failed to format time"));
}
c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
}
if (c < 0x80) {
str.push_back(static_cast<char>(c));
} else if (c < 0x800) {
str.push_back(static_cast<char>(0xc0 | (c >> 6)));
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
str.push_back(static_cast<char>(0xe0 | (c >> 12)));
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if (c >= 0x10000 && c <= 0x10ffff) {
str.push_back(static_cast<char>(0xf0 | (c >> 18)));
str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else {
FMT_THROW(format_error("failed to format time"));
}
}
return str;
}
template <typename OutputIt>
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
char format, char modifier = 0) -> OutputIt {
auto str = do_write(time, loc, format, modifier);
return std::copy(str.begin(), str.end(), out);
}
} // namespace detail
// Thread-safe replacement for std::localtime
FMT_MODULE_EXPORT_BEGIN
/**
Converts given time since epoch as ``std::time_t`` value into calendar time,
expressed in local time. Unlike ``std::localtime``, this function is
thread-safe on most platforms.
*/
inline std::tm localtime(std::time_t time) {
struct dispatcher {
std::time_t time_;
@@ -351,7 +401,16 @@ inline std::tm localtime(std::time_t time) {
return lt.tm_;
}
// Thread-safe replacement for std::gmtime
inline std::tm localtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return localtime(std::chrono::system_clock::to_time_t(time_point));
}
/**
Converts given time since epoch as ``std::time_t`` value into calendar time,
expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
function is thread-safe on most platforms.
*/
inline std::tm gmtime(std::time_t time) {
struct dispatcher {
std::time_t time_;
@@ -387,33 +446,89 @@ inline std::tm gmtime(std::time_t time) {
return gt.tm_;
}
namespace detail {
inline std::tm gmtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return gmtime(std::chrono::system_clock::to_time_t(time_point));
}
FMT_BEGIN_DETAIL_NAMESPACE
inline size_t strftime(char* str, size_t count, const char* format,
const std::tm* time) {
return std::strftime(str, count, format, time);
// Assign to a pointer to suppress GCCs -Wformat-nonliteral
// First assign the nullptr to suppress -Wsuggest-attribute=format
std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) =
nullptr;
strftime = std::strftime;
return strftime(str, count, format, time);
}
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
const std::tm* time) {
return std::wcsftime(str, count, format, time);
// See above
std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*,
const std::tm*) = nullptr;
wcsftime = std::wcsftime;
return wcsftime(str, count, format, time);
}
FMT_END_DETAIL_NAMESPACE
template <typename Char, typename Duration>
struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
Char> : formatter<std::tm, Char> {
FMT_CONSTEXPR formatter() {
this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)};
}
} // namespace detail
template <typename Char> struct formatter<std::tm, Char> {
template <typename ParseContext>
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it == ':') ++it;
auto end = it;
while (end != ctx.end() && *end != '}') ++end;
tm_format.reserve(detail::to_unsigned(end - it + 1));
tm_format.append(it, end);
tm_format.push_back('\0');
if (end != it) this->specs = {it, detail::to_unsigned(end - it)};
return end;
}
template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
auto format(std::chrono::time_point<std::chrono::system_clock> val,
FormatContext& ctx) -> decltype(ctx.out()) {
std::tm time = localtime(val);
return formatter<std::tm, Char>::format(time, ctx);
}
static constexpr Char default_specs[] = {'%', 'Y', '-', '%', 'm', '-',
'%', 'd', ' ', '%', 'H', ':',
'%', 'M', ':', '%', 'S'};
};
template <typename Char, typename Duration>
constexpr Char
formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
Char>::default_specs[];
template <typename Char> struct formatter<std::tm, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it == ':') ++it;
auto end = it;
while (end != ctx.end() && *end != '}') ++end;
specs = {it, detail::to_unsigned(end - it)};
return end;
}
template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) const
-> decltype(ctx.out()) {
basic_memory_buffer<Char> tm_format;
tm_format.append(specs.begin(), specs.end());
// By appending an extra space we can distinguish an empty result that
// indicates insufficient buffer size from a guaranteed non-empty result
// https://github.com/fmtlib/fmt/issues/2238
tm_format.push_back(' ');
tm_format.push_back('\0');
basic_memory_buffer<Char> buf;
size_t start = buf.size();
for (;;) {
@@ -423,49 +538,40 @@ template <typename Char> struct formatter<std::tm, Char> {
buf.resize(start + count);
break;
}
if (size >= tm_format.size() * 256) {
// If the buffer is 256 times larger than the format string, assume
// that `strftime` gives an empty result. There doesn't seem to be a
// better way to distinguish the two cases:
// https://github.com/fmtlib/fmt/issues/367
break;
}
const size_t MIN_GROWTH = 10;
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
}
return std::copy(buf.begin(), buf.end(), ctx.out());
// Remove the extra space.
return std::copy(buf.begin(), buf.end() - 1, ctx.out());
}
basic_memory_buffer<Char> tm_format;
basic_string_view<Char> specs;
};
namespace detail {
template <typename Period> FMT_CONSTEXPR const char* get_units() {
FMT_BEGIN_DETAIL_NAMESPACE
template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
if (std::is_same<Period, std::atto>::value) return "as";
if (std::is_same<Period, std::femto>::value) return "fs";
if (std::is_same<Period, std::pico>::value) return "ps";
if (std::is_same<Period, std::nano>::value) return "ns";
if (std::is_same<Period, std::micro>::value) return "µs";
if (std::is_same<Period, std::milli>::value) return "ms";
if (std::is_same<Period, std::centi>::value) return "cs";
if (std::is_same<Period, std::deci>::value) return "ds";
if (std::is_same<Period, std::ratio<1>>::value) return "s";
if (std::is_same<Period, std::deca>::value) return "das";
if (std::is_same<Period, std::hecto>::value) return "hs";
if (std::is_same<Period, std::kilo>::value) return "ks";
if (std::is_same<Period, std::mega>::value) return "Ms";
if (std::is_same<Period, std::giga>::value) return "Gs";
if (std::is_same<Period, std::tera>::value) return "Ts";
if (std::is_same<Period, std::peta>::value) return "Ps";
if (std::is_same<Period, std::exa>::value) return "Es";
if (std::is_same<Period, std::ratio<60>>::value) return "m";
if (std::is_same<Period, std::ratio<3600>>::value) return "h";
return nullptr;
}
template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; }
template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; }
template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; }
template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; }
template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; }
template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; }
template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; }
template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; }
template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; }
template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; }
template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; }
template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; }
template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; }
template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; }
template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; }
template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; }
template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; }
template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() {
return "m";
}
template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() {
return "h";
}
enum class numeric_system {
standard,
@@ -631,33 +737,50 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
return ptr;
}
struct chrono_format_checker {
FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); }
template <typename Derived> struct null_chrono_spec_handler {
FMT_CONSTEXPR void unsupported() {
static_cast<Derived*>(this)->unsupported();
}
FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
FMT_CONSTEXPR void on_full_month() { unsupported(); }
FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_us_date() { unsupported(); }
FMT_CONSTEXPR void on_iso_date() { unsupported(); }
FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
FMT_CONSTEXPR void on_iso_time() { unsupported(); }
FMT_CONSTEXPR void on_am_pm() { unsupported(); }
FMT_CONSTEXPR void on_duration_value() { unsupported(); }
FMT_CONSTEXPR void on_duration_unit() { unsupported(); }
FMT_CONSTEXPR void on_utc_offset() { unsupported(); }
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
};
template <typename Char> void on_text(const Char*, const Char*) {}
FMT_NORETURN void on_abbr_weekday() { report_no_date(); }
FMT_NORETURN void on_full_weekday() { report_no_date(); }
FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); }
FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); }
FMT_NORETURN void on_abbr_month() { report_no_date(); }
FMT_NORETURN void on_full_month() { report_no_date(); }
void on_24_hour(numeric_system) {}
void on_12_hour(numeric_system) {}
void on_minute(numeric_system) {}
void on_second(numeric_system) {}
FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); }
FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); }
FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); }
FMT_NORETURN void on_us_date() { report_no_date(); }
FMT_NORETURN void on_iso_date() { report_no_date(); }
void on_12_hour_time() {}
void on_24_hour_time() {}
void on_iso_time() {}
void on_am_pm() {}
void on_duration_value() {}
void on_duration_unit() {}
FMT_NORETURN void on_utc_offset() { report_no_date(); }
FMT_NORETURN void on_tz_name() { report_no_date(); }
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
template <typename Char>
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
FMT_CONSTEXPR void on_24_hour(numeric_system) {}
FMT_CONSTEXPR void on_12_hour(numeric_system) {}
FMT_CONSTEXPR void on_minute(numeric_system) {}
FMT_CONSTEXPR void on_second(numeric_system) {}
FMT_CONSTEXPR void on_12_hour_time() {}
FMT_CONSTEXPR void on_24_hour_time() {}
FMT_CONSTEXPR void on_iso_time() {}
FMT_CONSTEXPR void on_am_pm() {}
FMT_CONSTEXPR void on_duration_value() {}
FMT_CONSTEXPR void on_duration_unit() {}
};
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
@@ -681,7 +804,8 @@ inline bool isfinite(T value) {
// Converts value to int and checks that it's in the range [0, upper).
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline int to_nonnegative_int(T value, int upper) {
FMT_ASSERT(value >= 0 && value <= upper, "invalid value");
FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
"invalid value");
(void)upper;
return static_cast<int>(value);
}
@@ -759,15 +883,21 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
}
template <typename Char, typename Rep, typename OutputIt>
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
const Char pr_f[] = {'{', ':', '.', '{', '}', 'f', '}', 0};
if (precision >= 0) return format_to(out, pr_f, val, precision);
const Char fp_f[] = {'{', ':', 'g', '}', 0};
const Char format[] = {'{', '}', 0};
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
val);
template <typename Char, typename Rep, typename OutputIt,
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
OutputIt format_duration_value(OutputIt out, Rep val, int) {
return write<Char>(out, val);
}
template <typename Char, typename Rep, typename OutputIt,
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
auto specs = basic_format_specs<Char>();
specs.precision = precision;
specs.type = precision > 0 ? 'f' : 'g';
return write<Char>(out, val, specs);
}
template <typename Char, typename OutputIt>
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
return std::copy(unit.begin(), unit.end(), out);
@@ -785,10 +915,15 @@ template <typename Char, typename Period, typename OutputIt>
OutputIt format_duration_unit(OutputIt out) {
if (const char* unit = get_units<Period>())
return copy_unit(string_view(unit), out, Char());
const Char num_f[] = {'[', '{', '}', ']', 's', 0};
if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num);
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};
return format_to(out, num_def_f, Period::num, Period::den);
*out++ = '[';
out = write<Char>(out, Period::num);
if (const_check(Period::den != 1)) {
*out++ = '/';
out = write<Char>(out, Period::den);
}
*out++ = ']';
*out++ = 's';
return out;
}
template <typename FormatContext, typename OutputIt, typename Rep,
@@ -797,6 +932,7 @@ struct chrono_formatter {
FormatContext& context;
OutputIt out;
int precision;
bool localized = false;
// rep is unsigned to avoid overflow.
using rep =
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
@@ -891,13 +1027,9 @@ struct chrono_formatter {
void format_localized(const tm& time, char format, char modifier = 0) {
if (isnan(val)) return write_nan();
auto locale = context.locale().template get<std::locale>();
auto& facet = std::use_facet<std::time_put<char_type>>(locale);
std::basic_ostringstream<char_type> os;
os.imbue(locale);
facet.put(os, os, ' ', &time, format, modifier);
auto str = os.str();
std::copy(str.begin(), str.end(), out);
const auto& loc = localized ? context.locale().template get<std::locale>()
: std::locale::classic();
out = detail::write(out, time, loc, format, modifier);
}
void on_text(const char_type* begin, const char_type* end) {
@@ -1010,17 +1142,59 @@ struct chrono_formatter {
out = format_duration_unit<char_type, Period>(out);
}
};
} // namespace detail
FMT_END_DETAIL_NAMESPACE
#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907
using weekday = std::chrono::weekday;
#else
// A fallback version of weekday.
class weekday {
private:
unsigned char value;
public:
weekday() = default;
explicit constexpr weekday(unsigned wd) noexcept
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
constexpr unsigned c_encoding() const noexcept { return value; }
};
#endif
// A rudimentary weekday formatter.
template <> struct formatter<weekday> {
private:
bool localized = false;
public:
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
auto begin = ctx.begin(), end = ctx.end();
if (begin != end && *begin == 'L') {
++begin;
localized = true;
}
return begin;
}
auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
auto time = std::tm();
time.tm_wday = static_cast<int>(wd.c_encoding());
const auto& loc = localized ? ctx.locale().template get<std::locale>()
: std::locale::classic();
return detail::write(ctx.out(), time, loc, 'a');
}
};
template <typename Rep, typename Period, typename Char>
struct formatter<std::chrono::duration<Rep, Period>, Char> {
private:
basic_format_specs<Char> specs;
int precision;
int precision = -1;
using arg_ref_type = detail::arg_ref<Char>;
arg_ref_type width_ref;
arg_ref_type precision_ref;
mutable basic_string_view<Char> format_str;
bool localized = false;
basic_string_view<Char> format_str;
using duration = std::chrono::duration<Rep, Period>;
struct spec_handler {
@@ -1043,17 +1217,21 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
}
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
void on_fill(basic_string_view<Char> fill) { f.specs.fill = fill; }
void on_align(align_t align) { f.specs.align = align; }
void on_width(int width) { f.specs.width = width; }
void on_precision(int _precision) { f.precision = _precision; }
void end_precision() {}
FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
f.specs.fill = fill;
}
FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; }
FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; }
FMT_CONSTEXPR void on_precision(int _precision) {
f.precision = _precision;
}
FMT_CONSTEXPR void end_precision() {}
template <typename Id> void on_dynamic_width(Id arg_id) {
template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
f.width_ref = make_arg_ref(arg_id);
}
template <typename Id> void on_dynamic_precision(Id arg_id) {
template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
f.precision_ref = make_arg_ref(arg_id);
}
};
@@ -1078,13 +1256,15 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
else
handler.on_error("precision not allowed for this argument type");
}
if (begin != end && *begin == 'L') {
++begin;
localized = true;
}
end = parse_chrono_format(begin, end, detail::chrono_format_checker());
return {begin, end};
}
public:
formatter() : precision(-1) {}
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto range = do_parse(ctx);
@@ -1094,30 +1274,35 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
}
template <typename FormatContext>
auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) {
auto format(const duration& d, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs_copy = specs;
auto precision_copy = precision;
auto begin = format_str.begin(), end = format_str.end();
// As a possible future optimization, we could avoid extra copying if width
// is not specified.
basic_memory_buffer<Char> buf;
auto out = std::back_inserter(buf);
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref,
ctx);
detail::handle_dynamic_spec<detail::precision_checker>(precision,
detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width,
width_ref, ctx);
detail::handle_dynamic_spec<detail::precision_checker>(precision_copy,
precision_ref, ctx);
if (begin == end || *begin == '}') {
out = detail::format_duration_value<Char>(out, d.count(), precision);
out = detail::format_duration_value<Char>(out, d.count(), precision_copy);
detail::format_duration_unit<Char, Period>(out);
} else {
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
ctx, out, d);
f.precision = precision;
parse_chrono_format(begin, end, f);
f.precision = precision_copy;
f.localized = localized;
detail::parse_chrono_format(begin, end, f);
}
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy);
}
};
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
#endif // FMT_CHRONO_H_

View File

@@ -10,7 +10,15 @@
#include "format.h"
// __declspec(deprecated) is broken in some MSVC versions.
#if FMT_MSC_VER
# define FMT_DEPRECATED_NONMSVC
#else
# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
#endif
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
@@ -198,7 +206,7 @@ struct rgb {
uint8_t b;
};
namespace detail {
FMT_BEGIN_DETAIL_NAMESPACE
// color is a struct of either a rgb color or a terminal color.
struct color_type {
@@ -221,9 +229,10 @@ struct color_type {
uint32_t rgb_color;
} value;
};
} // namespace detail
// Experimental text formatting support.
FMT_END_DETAIL_NAMESPACE
/** A text style consisting of foreground and background colors and emphasis. */
class text_style {
public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
@@ -260,33 +269,14 @@ class text_style {
return lhs |= rhs;
}
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
const text_style& rhs) {
return lhs &= rhs;
return and_assign(rhs);
}
FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
operator&(text_style lhs, const text_style& rhs) {
return lhs.and_assign(rhs);
}
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
@@ -326,8 +316,34 @@ class text_style {
}
}
// DEPRECATED!
FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
FMT_NOEXCEPT;
@@ -338,19 +354,22 @@ class text_style {
emphasis ems;
};
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/true, foreground);
/** Creates a text style from the foreground (text) color. */
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
return text_style(true, foreground);
}
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/false, background);
/** Creates a text style from the background color. */
FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
return text_style(false, background);
}
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
emphasis rhs) FMT_NOEXCEPT {
return text_style(lhs) | rhs;
}
namespace detail {
FMT_BEGIN_DETAIL_NAMESPACE
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
@@ -358,7 +377,7 @@ template <typename Char> struct ansi_color_escape {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == detail::data::background_color;
bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
@@ -411,7 +430,7 @@ template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
return buffer + std::char_traits<Char>::length(buffer);
}
@@ -430,13 +449,13 @@ template <typename Char> struct ansi_color_escape {
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
detail::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, detail::data::foreground_color);
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
detail::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, detail::data::background_color);
return ansi_color_escape<Char>(background, "\x1b[48;2;");
}
template <typename Char>
@@ -455,24 +474,23 @@ inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
}
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
fputs(detail::data::reset_color, stream);
fputs("\x1b[0m", stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
fputs(detail::data::wreset_color, stream);
fputs(L"\x1b[0m", stream);
}
template <typename Char>
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
const char* begin = data::reset_color;
const char* end = begin + sizeof(data::reset_color) - 1;
buffer.append(begin, end);
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end());
}
template <typename Char>
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) {
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
@@ -489,14 +507,15 @@ void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
detail::vformat_to(buf, format_str, args);
detail::vformat_to(buf, format_str, args, {});
if (has_style) detail::reset_color<Char>(buf);
}
} // namespace detail
FMT_END_DETAIL_NAMESPACE
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<Char>> args) {
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0));
@@ -504,28 +523,34 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
}
/**
\rst
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
Example:
**Example**::
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
detail::check_format_string<Args...>(format_str);
using context = buffer_context<char_t<S>>;
format_arg_store<context, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context>(as));
vprint(f, ts, format_str,
fmt::make_args_checked<Args...>(format_str, args...));
}
/**
\rst
Formats a string and prints it to stdout using ANSI escape sequences to
specify text formatting.
Example:
**Example**::
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
@@ -557,10 +582,46 @@ inline std::basic_string<Char> vformat(
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return vformat(ts, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
return fmt::vformat(ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename OutputIt, typename Char,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
OutputIt vformat_to(
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
detail::is_string<S>::value>
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_

View File

@@ -8,13 +8,135 @@
#ifndef FMT_COMPILE_H_
#define FMT_COMPILE_H_
#include <vector>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
// An output iterator that counts the number of objects written to it and
// discards them.
class counting_iterator {
private:
size_t count_;
public:
using iterator_category = std::output_iterator_tag;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
using _Unchecked_type = counting_iterator; // Mark iterator as checked.
struct value_type {
template <typename T> void operator=(const T&) {}
};
counting_iterator() : count_(0) {}
size_t count() const { return count_; }
counting_iterator& operator++() {
++count_;
return *this;
}
counting_iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
friend counting_iterator operator+(counting_iterator it, difference_type n) {
it.count_ += static_cast<size_t>(n);
return it;
}
value_type operator*() const { return {}; }
};
template <typename Char, typename InputIt>
inline counting_iterator copy_str(InputIt begin, InputIt end,
counting_iterator it) {
return it + (end - begin);
}
template <typename OutputIt> class truncating_iterator_base {
protected:
OutputIt out_;
size_t limit_;
size_t count_ = 0;
truncating_iterator_base() : out_(), limit_(0) {}
truncating_iterator_base(OutputIt out, size_t limit)
: out_(out), limit_(limit) {}
public:
using iterator_category = std::output_iterator_tag;
using value_type = typename std::iterator_traits<OutputIt>::value_type;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
using _Unchecked_type =
truncating_iterator_base; // Mark iterator as checked.
OutputIt base() const { return out_; }
size_t count() const { return count_; }
};
// An output iterator that truncates the output and counts the number of objects
// written to it.
template <typename OutputIt,
typename Enable = typename std::is_void<
typename std::iterator_traits<OutputIt>::value_type>::type>
class truncating_iterator;
template <typename OutputIt>
class truncating_iterator<OutputIt, std::false_type>
: public truncating_iterator_base<OutputIt> {
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
public:
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
truncating_iterator& operator++() {
if (this->count_++ < this->limit_) ++this->out_;
return *this;
}
truncating_iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
value_type& operator*() const {
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
}
};
template <typename OutputIt>
class truncating_iterator<OutputIt, std::true_type>
: public truncating_iterator_base<OutputIt> {
public:
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
template <typename T> truncating_iterator& operator=(T val) {
if (this->count_++ < this->limit_) *this->out_++ = val;
return *this;
}
truncating_iterator& operator++() { return *this; }
truncating_iterator& operator++(int) { return *this; }
truncating_iterator& operator*() { return *this; }
};
// A compile-time string which is compiled into fast formatting code.
class compiled_string {};
@@ -34,341 +156,36 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst
*/
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)
#ifdef __cpp_if_constexpr
# define FMT_COMPILE(s) \
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
#else
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
template <typename Char, size_t N,
fmt::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
constexpr operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#endif
template <typename T, typename... Tail>
const T& first(const T& value, const Tail&...) {
return value;
}
// Part of a compiled format string. It can be either literal text or a
// replacement field.
template <typename Char> struct format_part {
enum class kind { arg_index, arg_name, text, replacement };
struct replacement {
arg_ref<Char> arg_id;
dynamic_format_specs<Char> specs;
};
kind part_kind;
union value {
int arg_index;
basic_string_view<Char> str;
replacement repl;
FMT_CONSTEXPR value(int index = 0) : arg_index(index) {}
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
FMT_CONSTEXPR value(replacement r) : repl(r) {}
} val;
// Position past the end of the argument id.
const Char* arg_id_end = nullptr;
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
: part_kind(k), val(v) {}
static FMT_CONSTEXPR format_part make_arg_index(int index) {
return format_part(kind::arg_index, index);
}
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
return format_part(kind::arg_name, name);
}
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
return format_part(kind::text, text);
}
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
return format_part(kind::replacement, repl);
}
};
template <typename Char> struct part_counter {
unsigned num_parts = 0;
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
if (begin != end) ++num_parts;
}
FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
return ++num_parts, 0;
}
FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
const Char* end) {
// Find the matching brace.
unsigned brace_counter = 0;
for (; begin != end; ++begin) {
if (*begin == '{') {
++brace_counter;
} else if (*begin == '}') {
if (brace_counter == 0u) break;
--brace_counter;
}
}
return begin;
}
FMT_CONSTEXPR void on_error(const char*) {}
};
// Counts the number of parts in a format string.
template <typename Char>
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
part_counter<Char> counter;
parse_format_string<true>(format_str, counter);
return counter.num_parts;
}
template <typename Char, typename PartHandler>
class format_string_compiler : public error_handler {
private:
using part = format_part<Char>;
PartHandler handler_;
part part_;
basic_string_view<Char> format_str_;
basic_format_parse_context<Char> parse_context_;
public:
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
PartHandler handler)
: handler_(handler),
format_str_(format_str),
parse_context_(format_str) {}
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
if (begin != end)
handler_(part::make_text({begin, to_unsigned(end - begin)}));
}
FMT_CONSTEXPR int on_arg_id() {
part_ = part::make_arg_index(parse_context_.next_arg_id());
return 0;
}
FMT_CONSTEXPR int on_arg_id(int id) {
parse_context_.check_arg_id(id);
part_ = part::make_arg_index(id);
return 0;
}
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
part_ = part::make_arg_name(id);
return 0;
}
FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) {
part_.arg_id_end = ptr;
handler_(part_);
}
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
const Char* end) {
auto repl = typename part::replacement();
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
repl.specs, parse_context_);
auto it = parse_format_specs(begin, end, handler);
if (*it != '}') on_error("missing '}' in format string");
repl.arg_id = part_.part_kind == part::kind::arg_index
? arg_ref<Char>(part_.val.arg_index)
: arg_ref<Char>(part_.val.str);
auto part = part::make_replacement(repl);
part.arg_id_end = begin;
handler_(part);
return it;
}
};
// Compiles a format string and invokes handler(part) for each parsed part.
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
PartHandler handler) {
parse_format_string<IS_CONSTEXPR>(
format_str,
format_string_compiler<Char, PartHandler>(format_str, handler));
}
template <typename OutputIt, typename Context, typename Id>
void format_arg(
basic_format_parse_context<typename Context::char_type>& parse_ctx,
Context& ctx, Id arg_id) {
ctx.advance_to(visit_format_arg(
arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
ctx.arg(arg_id)));
}
// vformat_to is defined in a subnamespace to prevent ADL.
namespace cf {
template <typename Context, typename OutputIt, typename CompiledFormat>
auto vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args<Context> args) -> typename Context::iterator {
using char_type = typename Context::char_type;
basic_format_parse_context<char_type> parse_ctx(
to_string_view(cf.format_str_));
Context ctx(out, args);
const auto& parts = cf.parts();
for (auto part_it = std::begin(parts); part_it != std::end(parts);
++part_it) {
const auto& part = *part_it;
const auto& value = part.val;
using format_part_t = format_part<char_type>;
switch (part.part_kind) {
case format_part_t::kind::text: {
const auto text = value.str;
auto output = ctx.out();
auto&& it = reserve(output, text.size());
it = std::copy_n(text.begin(), text.size(), it);
ctx.advance_to(output);
break;
}
case format_part_t::kind::arg_index:
advance_to(parse_ctx, part.arg_id_end);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
break;
case format_part_t::kind::arg_name:
advance_to(parse_ctx, part.arg_id_end);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.str);
break;
case format_part_t::kind::replacement: {
const auto& arg_id_value = value.repl.arg_id.val;
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
? ctx.arg(arg_id_value.index)
: ctx.arg(arg_id_value.name);
auto specs = value.repl.specs;
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
handle_dynamic_spec<precision_checker>(specs.precision,
specs.precision_ref, ctx);
error_handler h;
numeric_specs_checker<error_handler> checker(h, arg.type());
if (specs.align == align::numeric) checker.require_numeric_argument();
if (specs.sign != sign::none) checker.check_sign();
if (specs.alt) checker.require_numeric_argument();
if (specs.precision >= 0) checker.check_precision();
advance_to(parse_ctx, part.arg_id_end);
ctx.advance_to(
visit_format_arg(arg_formatter<OutputIt, typename Context::char_type>(
ctx, nullptr, &specs),
arg));
break;
}
}
}
return ctx.out();
}
} // namespace cf
struct basic_compiled_format {};
template <typename S, typename = void>
struct compiled_format_base : basic_compiled_format {
using char_type = char_t<S>;
using parts_container = std::vector<detail::format_part<char_type>>;
parts_container compiled_parts;
explicit compiled_format_base(basic_string_view<char_type> format_str) {
compile_format_string<false>(format_str,
[this](const format_part<char_type>& part) {
compiled_parts.push_back(part);
});
}
const parts_container& parts() const { return compiled_parts; }
};
template <typename Char, unsigned N> struct format_part_array {
format_part<Char> data[N] = {};
FMT_CONSTEXPR format_part_array() = default;
};
template <typename Char, unsigned N>
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
basic_string_view<Char> format_str) {
format_part_array<Char, N> parts;
unsigned counter = 0;
// This is not a lambda for compatibility with older compilers.
struct {
format_part<Char>* parts;
unsigned* counter;
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
parts[(*counter)++] = part;
}
} collector{parts.data, &counter};
compile_format_string<true>(format_str, collector);
if (counter < N) {
parts.data[counter] =
format_part<Char>::make_text(basic_string_view<Char>());
}
return parts;
}
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
template <typename S>
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
: basic_compiled_format {
using char_type = char_t<S>;
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
// Workaround for old compilers. Format string compilation will not be
// performed there anyway.
#if FMT_USE_CONSTEXPR
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
constexpr_max(count_parts(to_string_view(S())), 1u);
#else
static const unsigned num_format_parts = 1;
#endif
using parts_container = format_part<char_type>[num_format_parts];
const parts_container& parts() const {
static FMT_CONSTEXPR_DECL const auto compiled_parts =
compile_to_parts<char_type, num_format_parts>(
detail::to_string_view(S()));
return compiled_parts.data;
}
};
template <typename S, typename... Args>
class compiled_format : private compiled_format_base<S> {
public:
using typename compiled_format_base<S>::char_type;
private:
basic_string_view<char_type> format_str_;
template <typename Context, typename OutputIt, typename CompiledFormat>
friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args<Context> args) ->
typename Context::iterator;
public:
compiled_format() = delete;
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
: compiled_format_base<S>(format_str), format_str_(format_str) {}
};
#ifdef __cpp_if_constexpr
template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get(const T& first, const Args&... rest) {
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
@@ -376,6 +193,12 @@ constexpr const auto& get(const T& first, const Args&... rest) {
return get<N - 1>(rest...);
}
template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
return get_arg_index_by_name<Args...>(name);
}
template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
@@ -392,7 +215,7 @@ template <typename Char> struct text {
using char_type = Char;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, data);
}
};
@@ -406,32 +229,87 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
return {{&s[pos], size}};
}
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, value);
}
};
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
} else {
return arg;
}
}
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
using char_type = Char;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
return write<Char>(out, arg);
constexpr OutputIt format(OutputIt out, const Args&... args) const {
return write<Char>(out, get_arg_checked<T, N>(args...));
}
};
template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument with name.
template <typename Char> struct runtime_named_field {
using char_type = Char;
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static bool try_format_argument(
OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) {
out = write<Char>(out, arg.value);
return true;
}
}
return false;
}
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
throw format_error("argument with specified name is not found");
}
return out;
}
};
template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
mutable formatter<T, Char> fmt;
formatter<T, Char> fmt;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
basic_format_context<OutputIt, Char> ctx(out, {});
return fmt.format(arg, ctx);
constexpr FMT_INLINE OutputIt format(OutputIt out,
const Args&... args) const {
const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx);
}
};
@@ -444,7 +322,7 @@ template <typename L, typename R> struct concat {
using char_type = typename L::char_type;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
constexpr OutputIt format(OutputIt out, const Args&... args) const {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
@@ -489,16 +367,80 @@ constexpr auto parse_tail(T head, S format_str) {
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
constexpr int manual_indexing_id = -1;
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos) {
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx = basic_format_parse_context<Char>(str);
auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + (end - str.data()) + 1};
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
}
template <typename Char> struct arg_id_handler {
arg_ref<Char> arg_id;
constexpr int operator()() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int operator()(int id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int operator()(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr void on_error(const char* message) { throw format_error(message); }
};
template <typename Char> struct parse_arg_id_result {
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> struct field_type {
using type = remove_cvref_t<T>;
};
template <typename T>
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
using type = remove_cvref_t<decltype(T::value)>;
};
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
typename S>
constexpr auto parse_replacement_field_then_tail(S format_str) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(format_str);
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
if constexpr (c == '}') {
return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
format_str);
} else if constexpr (c == ':') {
constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
return parse_tail<Args, result.end, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt},
format_str);
}
}
// Compiles a non-empty format string and returns the compiled representation
@@ -506,79 +448,90 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str) {
using char_type = typename S::char_type;
constexpr basic_string_view<char_type> str = format_str;
constexpr auto str = basic_string_view<char_type>(format_str);
if constexpr (str[POS] == '{') {
if (POS + 1 == str.size())
if constexpr (POS + 1 == str.size())
throw format_error("unmatched '{' in format string");
if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}') {
using type = get_type<ID, Args>;
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
static_assert(ID != manual_indexing_id,
"cannot switch from manual to automatic argument indexing");
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
POS + 1, ID, next_id>(
format_str);
} else if constexpr (str[POS + 1] == ':') {
using type = get_type<ID, Args>;
constexpr auto result = parse_specs<type>(str, POS + 2);
return parse_tail<Args, result.end, ID + 1>(
spec_field<char_type, type, ID>{result.fmt}, format_str);
} else {
return unknown_format();
constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
static_assert(
ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos,
arg_index, manual_indexing_id>(
format_str);
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
if constexpr (arg_index != invalid_arg_index) {
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(format_str);
} else {
if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
format_str);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
}
}
}
} else if constexpr (str[POS] == '}') {
if (POS + 1 == str.size())
if constexpr (POS + 1 == str.size())
throw format_error("unmatched '}' in format string");
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else {
constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
format_str);
}
}
}
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value ||
detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
constexpr auto compile(S format_str) {
constexpr basic_string_view<typename S::char_type> str = format_str;
constexpr auto str = basic_string_view<typename S::char_type>(format_str);
if constexpr (str.size() == 0) {
return detail::make_text(str, 0, 0);
} else {
constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
detail::unknown_format>()) {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
} else {
return result;
}
}
}
#else
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) -> detail::compiled_format<S, Args...> {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
}
#endif // __cpp_if_constexpr
// Compiles the format string which must be a string literal.
template <typename... Args, typename Char, size_t N>
auto compile(const Char (&format_str)[N])
-> detail::compiled_format<const Char*, Args...> {
return detail::compiled_format<const Char*, Args...>(
basic_string_view<Char>(format_str, N - 1));
}
} // namespace detail
// DEPRECATED! use FMT_COMPILE instead.
template <typename... Args>
FMT_DEPRECATED auto compile(const Args&... args)
-> decltype(detail::compile(args...)) {
return detail::compile(args...);
}
FMT_MODULE_EXPORT_BEGIN
#if FMT_USE_CONSTEXPR
#ifdef __cpp_if_constexpr
template <typename CompiledFormat, typename... Args,
@@ -586,80 +539,101 @@ template <typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
basic_memory_buffer<Char> buffer;
detail::buffer<Char>& base = buffer;
cf.format(std::back_inserter(base), args...);
return to_string(buffer);
auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...);
return s;
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
# endif // __cpp_if_constexpr
#endif // FMT_USE_CONSTEXPR
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
using context = buffer_context<Char>;
detail::buffer<Char>& base = buffer;
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
make_format_args<context>(args...));
return to_string(buffer);
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
constexpr basic_string_view<typename S::char_type> str = S();
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
return fmt::to_string(detail::first(args...));
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
const auto& first = detail::first(args...);
if constexpr (detail::is_named_arg<
remove_cvref_t<decltype(first)>>::value) {
return fmt::to_string(first.value);
} else {
return fmt::to_string(first);
}
}
}
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return format(static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return format(compiled, std::forward<Args>(args)...);
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
using char_type = typename CompiledFormat::char_type;
using context = format_context_t<OutputIt, char_type>;
return detail::cf::vformat_to<context>(out, cf,
make_format_args<context>(args...));
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
OutputIt format_to(OutputIt out, const S&, const Args&... args) {
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
return format_to(out, compiled, args...);
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return format_to(out,
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return format_to(out, compiled, std::forward<Args>(args)...);
}
}
#endif
template <
typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
detail::basic_compiled_format, CompiledFormat>::value)>
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf,
const Args&... args) {
auto it =
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
const S& format_str, Args&&... args) {
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str,
std::forward<Args>(args)...);
return {it.base(), it.count()};
}
template <typename CompiledFormat, typename... Args>
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(detail::counting_iterator(), cf, args...).count();
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
size_t formatted_size(const S& format_str, const Args&... args) {
return format_to(detail::counting_iterator(), format_str, args...).count();
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(std::FILE* f, const S& format_str, const Args&... args) {
memory_buffer buffer;
format_to(std::back_inserter(buffer), format_str, args...);
detail::print(f, {buffer.data(), buffer.size()});
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(const S& format_str, const Args&... args) {
print(stdout, format_str, args...);
}
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
inline namespace literals {
template <detail_exported::fixed_string Str>
constexpr detail::udl_compiled_string<
remove_cvref_t<decltype(Str.data[0])>,
sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
operator""_cf() {
return {};
}
} // namespace literals
#endif
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +1,2 @@
// Formatting library for C++ - std::locale support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_LOCALE_H_
#define FMT_LOCALE_H_
#include <locale>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char>
typename buffer_context<Char>::iterator vformat_to(
const std::locale& loc, buffer<Char>& buf,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
args, detail::locale_ref(loc));
}
template <typename Char>
std::basic_string<Char> vformat(
const std::locale& loc, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
detail::vformat_to(loc, buffer, format_str, args);
return fmt::to_string(buffer);
}
} // namespace detail
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vformat(
const std::locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
return detail::vformat(loc, to_string_view(format_str), args);
}
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const std::locale& loc,
const S& format_str, Args&&... args) {
return detail::vformat(
loc, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
}
template <typename S, typename OutputIt, typename... Args,
typename Char = enable_if_t<
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
inline OutputIt vformat_to(
OutputIt out, const std::locale& loc, const S& format_str,
format_args_t<type_identity_t<OutputIt>, Char> args) {
using af = detail::arg_formatter<OutputIt, Char>;
return vformat_to<af>(out, to_string_view(format_str), args,
detail::locale_ref(loc));
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
detail::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) {
detail::check_format_string<Args...>(format_str);
using context = format_context_t<OutputIt, char_t<S>>;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, loc, to_string_view(format_str),
basic_format_args<context>(as));
}
FMT_END_NAMESPACE
#endif // FMT_LOCALE_H_
#include "xchar.h"
#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead

View File

@@ -8,16 +8,12 @@
#ifndef FMT_OS_H_
#define FMT_OS_H_
#if defined(__MINGW32__) || defined(__CYGWIN__)
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
# undef __STRICT_ANSI__
#endif
#include <cerrno>
#include <clocale> // for locale_t
#include <clocale> // locale_t
#include <cstddef>
#include <cstdio>
#include <cstdlib> // for strtod_l
#include <cstdlib> // strtod_l
#include <system_error> // std::system_error
#if defined __APPLE__ || defined(__FreeBSD__)
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
@@ -29,7 +25,8 @@
#if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h>
#endif
#if FMT_HAS_INCLUDE("fcntl.h") && \
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
@@ -73,6 +70,7 @@
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
/**
\rst
@@ -121,19 +119,28 @@ template <typename Char> class basic_cstring_view {
using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>;
// An error code.
class error_code {
private:
int value_;
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
public:
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
int get() const FMT_NOEXCEPT { return value_; }
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(),
basic_format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
#ifdef _WIN32
namespace detail {
FMT_API const std::error_category& system_category() FMT_NOEXCEPT;
FMT_BEGIN_DETAIL_NAMESPACE
// A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 {
@@ -142,7 +149,7 @@ class utf16_to_utf8 {
public:
utf16_to_utf8() {}
FMT_API explicit utf16_to_utf8(wstring_view s);
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
operator string_view() const { return string_view(&buffer_[0], size()); }
size_t size() const { return buffer_.size() - 1; }
const char* c_str() const { return &buffer_[0]; }
@@ -151,22 +158,19 @@ class utf16_to_utf8 {
// Performs conversion returning a system error code instead of
// throwing exception on conversion error. This method may still throw
// in case of memory allocation error.
FMT_API int convert(wstring_view s);
FMT_API int convert(basic_string_view<wchar_t> s);
};
FMT_API void format_windows_error(buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT;
} // namespace detail
const char* message) FMT_NOEXCEPT;
FMT_END_DETAIL_NAMESPACE
/** A Windows error. */
class windows_error : public system_error {
private:
FMT_API void init(int error_code, string_view format_str, format_args args);
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
format_args args);
public:
/**
\rst
Constructs a :class:`fmt::windows_error` object with the description
Constructs a :class:`std::system_error` object with the description
of the form
.. parsed-literal::
@@ -180,7 +184,7 @@ class windows_error : public system_error {
**Example**::
// This throws a windows_error with the description
// This throws a system_error with the description
// cannot open file 'madeup': The system cannot find the file specified.
// or similar (system message may vary).
const char *filename = "madeup";
@@ -193,17 +197,29 @@ class windows_error : public system_error {
\endrst
*/
template <typename... Args>
windows_error(int error_code, string_view message, const Args&... args) {
init(error_code, message, make_format_args(args...));
std::system_error windows_error(int error_code, string_view message,
const Args&... args) {
return vwindows_error(error_code, message, fmt::make_format_args(args...));
}
};
// Reports a Windows error without throwing an exception.
// Can be used to report errors from destructors.
FMT_API void report_windows_error(int error_code,
string_view message) FMT_NOEXCEPT;
const char* message) FMT_NOEXCEPT;
#else
inline const std::error_category& system_category() FMT_NOEXCEPT {
return std::system_category();
}
#endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
}
#endif
// A buffered file.
class buffered_file {
private:
@@ -254,7 +270,7 @@ class buffered_file {
template <typename... Args>
inline void print(string_view format_str, const Args&... args) {
vprint(format_str, make_format_args(args...));
vprint(format_str, fmt::make_format_args(args...));
}
};
@@ -278,7 +294,9 @@ class file {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
};
// Constructs a file object which doesn't represent any file.
@@ -293,7 +311,8 @@ class file {
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
file& operator=(file&& other) FMT_NOEXCEPT {
// Move assignment is not noexcept because close may throw.
file& operator=(file&& other) {
close();
fd_ = other.fd_;
other.fd_ = -1;
@@ -329,7 +348,7 @@ class file {
// Makes fd be the copy of this file descriptor, closing fd first if
// necessary.
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT;
// Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively.
@@ -343,62 +362,107 @@ class file {
// Returns the memory page size.
long getpagesize();
class direct_buffered_file;
FMT_BEGIN_DETAIL_NAMESPACE
template <typename S, typename... Args>
void print(direct_buffered_file& f, const S& format_str,
const Args&... args);
struct buffer_size {
buffer_size() = default;
size_t value = 0;
buffer_size operator=(size_t val) const {
auto bs = buffer_size();
bs.value = val;
return bs;
}
};
// A buffered file with a direct buffer access and no synchronization.
class direct_buffered_file {
struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
oflag = new_oflag;
}
template <typename... T>
ostream_params(T... params, detail::buffer_size bs)
: ostream_params(params...) {
this->buffer_size = bs.value;
}
};
FMT_END_DETAIL_NAMESPACE
constexpr detail::buffer_size buffer_size;
/** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> {
private:
file file_;
enum { buffer_size = 4096 };
char buffer_[buffer_size];
int pos_;
void flush() {
if (pos_ == 0) return;
file_.write(buffer_, pos_);
pos_ = 0;
if (size() == 0) return;
file_.write(data(), size());
clear();
}
int free_capacity() const { return buffer_size - pos_; }
void grow(size_t) override;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
public:
direct_buffered_file(cstring_view path, int oflag)
: file_(path, oflag), pos_(0) {}
~direct_buffered_file() {
flush();
ostream(ostream&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
void close() {
flush();
file_.close();
}
template <typename S, typename... Args>
friend void print(direct_buffered_file& f, const S& format_str,
const Args&... args) {
// We could avoid double buffering.
auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf), format_str, args...);
auto remaining_pos = 0;
auto remaining_size = buf.size();
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
auto size = f.free_capacity();
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
f.pos_ += size;
f.flush();
remaining_pos += size;
remaining_size -= size;
}
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
f.pos_ += static_cast<int>(remaining_size);
/**
Formats ``args`` according to specifications in ``fmt`` and writes the
output to the file.
*/
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(detail::buffer_appender<char>(*this), fmt,
fmt::make_format_args(args...));
}
};
/**
\rst
Opens a file for writing. Supported parameters passed in *params*:
* ``<integer>``: Flags passed to `open
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
(``file::WRONLY | file::CREATE`` by default)
* ``buffer_size=<integer>``: Output buffer size
**Example**::
auto out = fmt::output_file("guide.txt");
out.print("Don't {}", "Panic");
\endrst
*/
template <typename... T>
inline ostream output_file(cstring_view path, T... params) {
return {path, detail::ostream_params(params...)};
}
#endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE
@@ -445,6 +509,7 @@ class locale {
};
using Locale FMT_DEPRECATED_ALIAS = locale;
#endif // FMT_LOCALE
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
#endif // FMT_OS_H_

View File

@@ -49,17 +49,27 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
}
};
struct converter {
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
};
template <typename Char> struct test_stream : std::basic_ostream<Char> {
private:
// Hide all operator<< from std::basic_ostream<Char>.
void_t<> operator<<(null<>);
void_t<> operator<<(const Char*);
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
!std::is_enum<T>::value)>
void_t<> operator<<(T);
void_t<> operator<<(converter);
};
// Hide insertion operators for built-in types.
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
// Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream).
template <typename T, typename Char> class is_streamable {
@@ -75,6 +85,8 @@ template <typename T, typename Char> class is_streamable {
using result = decltype(test<T>(0));
public:
is_streamable() = default;
static const bool value = result::value;
};
@@ -103,7 +115,7 @@ void format_value(buffer<Char>& buf, const T& value,
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
buf.resize(buf.size());
buf.try_resize(buf.size());
}
// Formats an object of type T that has an overloaded ostream operator<<.
@@ -139,6 +151,7 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
};
} // namespace detail
FMT_MODULE_EXPORT
template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
@@ -156,11 +169,12 @@ void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
fmt::print(cerr, "Don't {}!", "panic");
\endrst
*/
FMT_MODULE_EXPORT
template <typename S, typename... Args,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
vprint(os, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
fmt::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

View File

@@ -1,2 +0,0 @@
#include "os.h"
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"

View File

@@ -10,11 +10,54 @@
#include <algorithm> // std::max
#include <limits> // std::numeric_limits
#include <ostream>
#include "ostream.h"
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
FMT_MODULE_EXPORT_BEGIN
template <typename T> struct printf_formatter { printf_formatter() = delete; };
template <typename Char>
class basic_printf_parse_context : public basic_format_parse_context<Char> {
using basic_format_parse_context<Char>::basic_format_parse_context;
};
template <typename OutputIt, typename Char> class basic_printf_context {
private:
OutputIt out_;
basic_format_args<basic_printf_context> args_;
public:
using char_type = Char;
using format_arg = basic_format_arg<basic_printf_context>;
using parse_context_type = basic_printf_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
/**
\rst
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args) {}
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
detail::locale_ref locale() { return {}; }
format_arg arg(int id) const { return args_.get(id); }
FMT_CONSTEXPR void on_error(const char* message) {
detail::error_handler().on_error(message);
}
};
FMT_BEGIN_DETAIL_NAMESPACE
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
@@ -178,79 +221,34 @@ template <typename Char> class printf_width_handler {
}
};
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(std::back_inserter(buf), format, args).format();
}
} // namespace detail
// For printing into memory_buffer.
template <typename Char, typename Context>
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
basic_string_view<Char> format,
basic_format_args<Context> args) {
return detail::vprintf(buf, format, args);
}
using detail::vprintf;
template <typename Char>
class basic_printf_parse_context : public basic_format_parse_context<Char> {
using basic_format_parse_context<Char>::basic_format_parse_context;
};
template <typename OutputIt, typename Char> class basic_printf_context;
/**
\rst
The ``printf`` argument formatter.
\endrst
*/
// The ``printf`` argument formatter.
template <typename OutputIt, typename Char>
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
public:
using iterator = OutputIt;
class printf_arg_formatter : public arg_formatter<Char> {
private:
using char_type = Char;
using base = detail::arg_formatter_base<OutputIt, Char>;
using base = arg_formatter<Char>;
using context_type = basic_printf_context<OutputIt, Char>;
using format_specs = basic_format_specs<Char>;
context_type& context_;
void write_null_pointer(char) {
this->specs()->type = 0;
this->write("(nil)");
}
void write_null_pointer(wchar_t) {
this->specs()->type = 0;
this->write(L"(nil)");
OutputIt write_null_pointer(bool is_string = false) {
auto s = this->specs;
s.type = 0;
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
}
public:
using format_specs = typename base::format_specs;
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
: base{iter, s, locale_ref()}, context_(ctx) {}
/**
\rst
Constructs an argument formatter object.
*buffer* is a reference to the output buffer and *specs* contains format
specifier information for standard argument types.
\endrst
*/
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
: base(iter, &specs, detail::locale_ref()), context_(ctx) {}
OutputIt operator()(monostate value) { return base::operator()(value); }
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
iterator operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and char_type so
// use std::is_same instead.
if (std::is_same<T, bool>::value) {
format_specs& fmt_specs = *this->specs();
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0);
fmt_specs.type = 0;
this->write(value != 0);
} else if (std::is_same<T, char_type>::value) {
format_specs& fmt_specs = *this->specs();
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
OutputIt operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs;
if (fmt_specs.type && fmt_specs.type != 'c')
return (*this)(static_cast<int>(value));
fmt_specs.sign = sign::none;
@@ -260,137 +258,48 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
return base::operator()(value);
} else {
return base::operator()(value);
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
}
return this->out();
return base::operator()(value);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
iterator operator()(T value) {
OutputIt operator()(T value) {
return base::operator()(value);
}
/** Formats a null-terminated C string. */
iterator operator()(const char* value) {
if (value)
base::operator()(value);
else if (this->specs()->type == 'p')
write_null_pointer(char_type());
else
this->write("(null)");
return this->out();
OutputIt operator()(const char* value) {
if (value) return base::operator()(value);
return write_null_pointer(this->specs.type != 'p');
}
/** Formats a null-terminated wide C string. */
iterator operator()(const wchar_t* value) {
if (value)
base::operator()(value);
else if (this->specs()->type == 'p')
write_null_pointer(char_type());
else
this->write(L"(null)");
return this->out();
OutputIt operator()(const wchar_t* value) {
if (value) return base::operator()(value);
return write_null_pointer(this->specs.type != 'p');
}
iterator operator()(basic_string_view<char_type> value) {
OutputIt operator()(basic_string_view<Char> value) {
return base::operator()(value);
}
iterator operator()(monostate value) { return base::operator()(value); }
/** Formats a pointer. */
iterator operator()(const void* value) {
if (value) return base::operator()(value);
this->specs()->type = 0;
write_null_pointer(char_type());
return this->out();
OutputIt operator()(const void* value) {
return value ? base::operator()(value) : write_null_pointer();
}
/** Formats an argument of a custom (user-defined) type. */
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
handle.format(context_.parse_context(), context_);
return this->out();
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx =
basic_printf_parse_context<Char>(basic_string_view<Char>());
handle.format(parse_ctx, context_);
return this->out;
}
};
template <typename T> struct printf_formatter {
printf_formatter() = delete;
template <typename ParseContext>
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
detail::format_value(detail::get_container(ctx.out()), value);
return ctx.out();
}
};
/**
This template formats data and writes the output through an output iterator.
*/
template <typename OutputIt, typename Char> class basic_printf_context {
public:
/** The character type for the output. */
using char_type = Char;
using iterator = OutputIt;
using format_arg = basic_format_arg<basic_printf_context>;
using parse_context_type = basic_printf_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
private:
using format_specs = basic_format_specs<char_type>;
OutputIt out_;
basic_format_args<basic_printf_context> args_;
parse_context_type parse_ctx_;
static void parse_flags(format_specs& specs, const Char*& it,
const Char* end);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
format_arg get_arg(int arg_index = -1);
// Parses argument index, flags and width and returns the argument index.
int parse_header(const Char*& it, const Char* end, format_specs& specs);
public:
/**
\rst
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args), parse_ctx_(format_str) {}
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
detail::locale_ref locale() { return {}; }
format_arg arg(int id) const { return args_.get(id); }
parse_context_type& parse_context() { return parse_ctx_; }
FMT_CONSTEXPR void on_error(const char* message) {
parse_ctx_.on_error(message);
}
/** Formats stored arguments and writes the output to the range. */
template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
OutputIt format();
};
template <typename OutputIt, typename Char>
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
const Char*& it,
template <typename Char>
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
const Char* end) {
for (; it != end; ++it) {
switch (*it) {
@@ -417,35 +326,24 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
}
}
template <typename OutputIt, typename Char>
typename basic_printf_context<OutputIt, Char>::format_arg
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
if (arg_index < 0)
arg_index = parse_ctx_.next_arg_id();
else
parse_ctx_.check_arg_id(--arg_index);
return detail::get_arg(*this, arg_index);
}
template <typename OutputIt, typename Char>
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
const Char* end,
format_specs& specs) {
template <typename Char, typename GetArg>
int parse_header(const Char*& it, const Char* end,
basic_format_specs<Char>& specs, GetArg get_arg) {
int arg_index = -1;
char_type c = *it;
Char c = *it;
if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s).
detail::error_handler eh;
int value = parse_nonnegative_int(it, end, eh);
int value = parse_nonnegative_int(it, end, -1);
if (it != end && *it == '$') { // value is an argument index
++it;
arg_index = value;
arg_index = value != -1 ? value : max_value<int>();
} else {
if (c == '0') specs.fill[0] = '0';
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
if (value == -1) FMT_THROW(format_error("number is too big"));
specs.width = value;
return arg_index;
}
@@ -455,58 +353,76 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
// Parse width.
if (it != end) {
if (*it >= '0' && *it <= '9') {
detail::error_handler eh;
specs.width = parse_nonnegative_int(it, end, eh);
specs.width = parse_nonnegative_int(it, end, -1);
if (specs.width == -1) FMT_THROW(format_error("number is too big"));
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(visit_format_arg(
detail::printf_width_handler<char_type>(specs), get_arg()));
detail::printf_width_handler<Char>(specs), get_arg(-1)));
}
}
return arg_index;
}
template <typename OutputIt, typename Char>
template <typename ArgFormatter>
OutputIt basic_printf_context<OutputIt, Char>::format() {
auto out = this->out();
const Char* start = parse_ctx_.begin();
const Char* end = parse_ctx_.end();
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
using OutputIt = buffer_appender<Char>;
auto out = OutputIt(buf);
auto context = basic_printf_context<OutputIt, Char>(out, args);
auto parse_ctx = basic_printf_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
auto get_arg = [&](int arg_index) {
if (arg_index < 0)
arg_index = parse_ctx.next_arg_id();
else
parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index);
};
const Char* start = parse_ctx.begin();
const Char* end = parse_ctx.end();
auto it = start;
while (it != end) {
char_type c = *it++;
if (c != '%') continue;
if (!detail::find<false, Char>(it, end, '%', it)) {
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
break;
}
Char c = *it++;
if (it != end && *it == c) {
out = std::copy(start, it, out);
out = detail::write(
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
start = ++it;
continue;
}
out = std::copy(start, it - 1, out);
out = detail::write(out, basic_string_view<Char>(
start, detail::to_unsigned(it - 1 - start)));
format_specs specs;
basic_format_specs<Char> specs;
specs.align = align::right;
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs);
if (arg_index == 0) on_error("argument not found");
int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) parse_ctx.on_error("argument not found");
// Parse precision.
if (it != end && *it == '.') {
++it;
c = it != end ? *it : 0;
if ('0' <= c && c <= '9') {
detail::error_handler eh;
specs.precision = parse_nonnegative_int(it, end, eh);
specs.precision = parse_nonnegative_int(it, end, 0);
} else if (c == '*') {
++it;
specs.precision = static_cast<int>(
visit_format_arg(detail::printf_precision_handler(), get_arg()));
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
} else {
specs.precision = 0;
}
}
format_arg arg = get_arg(arg_index);
auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral())
@@ -516,9 +432,10 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>(
str,
detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
basic_string_view<Char>(
str, detail::to_unsigned(nul != str_end ? nul - str
: specs.precision)));
}
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
specs.alt = false;
@@ -532,7 +449,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
char_type t = it != end ? *it : 0;
Char t = it != end ? *it : 0;
using detail::convert_arg;
switch (c) {
case 'h':
@@ -582,7 +499,8 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
specs.type = 'd';
break;
case 'c':
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
visit_format_arg(
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
arg);
break;
}
@@ -591,14 +509,16 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
start = it;
// Format argument.
out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
out = visit_format_arg(
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
}
return std::copy(start, it, out);
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
}
FMT_END_DETAIL_NAMESPACE
template <typename Char>
using basic_printf_context_t =
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
basic_printf_context<detail::buffer_appender<Char>, Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;
@@ -612,9 +532,9 @@ using wprintf_args = basic_format_args<wprintf_context>;
arguments and can be implicitly converted to `~fmt::printf_args`.
\endrst
*/
template <typename... Args>
inline format_arg_store<printf_context, Args...> make_printf_args(
const Args&... args) {
template <typename... T>
inline auto make_printf_args(const T&... args)
-> format_arg_store<printf_context, T...> {
return {args...};
}
@@ -624,18 +544,19 @@ inline format_arg_store<printf_context, Args...> make_printf_args(
arguments and can be implicitly converted to `~fmt::wprintf_args`.
\endrst
*/
template <typename... Args>
inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
const Args&... args) {
template <typename... T>
inline auto make_wprintf_args(const T&... args)
-> format_arg_store<wprintf_context, T...> {
return {args...};
}
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vsprintf(
const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
inline auto vsprintf(
const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args);
vprintf(buffer, to_string_view(fmt), args);
return to_string(buffer);
}
@@ -648,19 +569,20 @@ inline std::basic_string<Char> vsprintf(
std::string message = fmt::sprintf("The answer is %d", 42);
\endrst
*/
template <typename S, typename... Args,
template <typename S, typename... T,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
using context = basic_printf_context_t<Char>;
return vsprintf(to_string_view(format), make_format_args<context>(args...));
return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...));
}
template <typename S, typename Char = char_t<S>>
inline int vfprintf(
std::FILE* f, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
inline auto vfprintf(
std::FILE* f, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args);
vprintf(buffer, to_string_view(fmt), args);
size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
? -1
@@ -676,19 +598,19 @@ inline int vfprintf(
fmt::fprintf(stderr, "Don't %s!", "panic");
\endrst
*/
template <typename S, typename... Args,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
template <typename S, typename... T, typename Char = char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
using context = basic_printf_context_t<Char>;
return vfprintf(f, to_string_view(format),
make_format_args<context>(args...));
return vfprintf(f, to_string_view(fmt),
fmt::make_format_args<context>(args...));
}
template <typename S, typename Char = char_t<S>>
inline int vprintf(
const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
return vfprintf(stdout, to_string_view(format), args);
inline auto vprintf(
const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
return vfprintf(stdout, to_string_view(fmt), args);
}
/**
@@ -700,52 +622,31 @@ inline int vprintf(
fmt::printf("Elapsed time: %.2f seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
inline int printf(const S& format_str, const Args&... args) {
using context = basic_printf_context_t<char_t<S>>;
return vprintf(to_string_view(format_str),
make_format_args<context>(args...));
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
inline auto printf(const S& fmt, const T&... args) -> int {
return vprintf(
to_string_view(fmt),
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
}
template <typename S, typename Char = char_t<S>>
inline int vfprintf(
std::basic_ostream<Char>& os, const S& format,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
FMT_DEPRECATED auto vfprintf(
std::basic_ostream<Char>& os, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args);
detail::write_buffer(os, buffer);
vprintf(buffer, to_string_view(fmt), args);
os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
return static_cast<int>(buffer.size());
}
/** Formats arguments and writes the output to the range. */
template <typename ArgFormatter, typename Char,
typename Context =
basic_printf_context<typename ArgFormatter::iterator, Char>>
typename ArgFormatter::iterator vprintf(
detail::buffer<Char>& out, basic_string_view<Char> format_str,
basic_format_args<type_identity_t<Context>> args) {
typename ArgFormatter::iterator iter(out);
Context(iter, format_str, args).template format<ArgFormatter>();
return iter;
template <typename S, typename... T, typename Char = char_t<S>>
FMT_DEPRECATED auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
const T&... args) -> int {
return vfprintf(os, to_string_view(fmt),
fmt::make_format_args<basic_printf_context_t<Char>>(args...));
}
/**
\rst
Prints formatted data to the stream *os*.
**Example**::
fmt::fprintf(cerr, "Don't %s!", "panic");
\endrst
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
const Args&... args) {
using context = basic_printf_context_t<Char>;
return vfprintf(os, to_string_view(format_str),
make_format_args<context>(args...));
}
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_

View File

@@ -17,41 +17,31 @@
#include "format.h"
// output only up to N items from the range.
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
#endif
FMT_BEGIN_NAMESPACE
template <typename Char> struct formatting_base {
template <typename Char, typename Enable = void> struct formatting_range {
#ifdef FMT_DEPRECATED_BRACED_RANGES
Char prefix = '{';
Char postfix = '}';
#else
Char prefix = '[';
Char postfix = ']';
#endif
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
};
template <typename Char, typename Enable = void>
struct formatting_range : formatting_base<Char> {
static FMT_CONSTEXPR_DECL const size_t range_length_limit =
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
// range.
Char prefix;
Char delimiter;
Char postfix;
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
};
template <typename Char, typename Enable = void> struct formatting_tuple {
Char prefix = '(';
Char postfix = ')';
template <typename Char, typename Enable = void>
struct formatting_tuple : formatting_base<Char> {
Char prefix;
Char delimiter;
Char postfix;
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
};
namespace detail {
@@ -75,8 +65,14 @@ OutputIterator copy(char ch, OutputIterator out) {
return out;
}
template <typename OutputIterator>
OutputIterator copy(wchar_t ch, OutputIterator out) {
*out++ = ch;
return out;
}
/// Return true value if T has std::string interface, like std::string_view.
template <typename T> class is_like_std_string {
template <typename T> class is_std_string_like {
template <typename U>
static auto check(U* p)
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
@@ -88,19 +84,107 @@ template <typename T> class is_like_std_string {
};
template <typename Char>
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload
template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}
template <typename T, typename Enable = void>
struct has_member_fn_begin_end_t : std::false_type {};
template <typename T>
struct is_range_<
T, conditional_t<false,
conditional_helper<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>,
void>> : std::true_type {};
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>>
: std::true_type {};
// Member function overload
template <typename T>
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
template <typename T>
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
// ADL overload. Only participates in overload resolution if member functions
// are not found.
template <typename T>
auto range_begin(T&& rng)
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(begin(static_cast<T&&>(rng)))> {
return begin(static_cast<T&&>(rng));
}
template <typename T>
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(end(static_cast<T&&>(rng)))> {
return end(static_cast<T&&>(rng));
}
template <typename T, typename Enable = void>
struct has_const_begin_end : std::false_type {};
template <typename T, typename Enable = void>
struct has_mutable_begin_end : std::false_type {};
template <typename T>
struct has_const_begin_end<
T, void_t<decltype(detail::range_begin(
std::declval<const remove_cvref_t<T>&>())),
decltype(detail::range_begin(
std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {};
template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_begin(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>>
: std::true_type {};
template <typename T>
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};
template <typename T, typename Enable = void> struct range_to_view;
template <typename T>
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
struct view_t {
const T* m_range_ptr;
auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
};
static auto view(const T& range) -> view_t { return {&range}; }
};
template <typename T>
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
has_mutable_begin_end<T>::value>> {
struct view_t {
T m_range_copy;
auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
};
static auto view(const T& range) -> view_t { return {range}; }
};
# undef FMT_DECLTYPE_RETURN
#endif
/// tuple_size and tuple_element check.
@@ -157,30 +241,41 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
}
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
typename std::decay<Arg>::type>::value)>
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
return add_space ? " {}" : "{}";
template <typename Range>
using value_type =
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
*out++ = ',';
*out++ = ' ';
return out;
}
template <typename Arg, FMT_ENABLE_IF(is_like_std_string<
typename std::decay<Arg>::type>::value)>
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
return add_space ? " \"{}\"" : "\"{}\"";
template <
typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
OutputIt write_range_entry(OutputIt out, const Arg& v) {
*out++ = '"';
out = write<Char>(out, v);
*out++ = '"';
return out;
}
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
return add_space ? " \"{}\"" : "\"{}\"";
}
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
return add_space ? L" \"{}\"" : L"\"{}\"";
template <typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg v) {
*out++ = '\'';
*out++ = v;
*out++ = '\'';
return out;
}
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
return add_space ? " '{}'" : "'{}'";
}
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
return add_space ? L" '{}'" : L"'{}'";
template <
typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
!std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg& v) {
return write<Char>(out, v);
}
} // namespace detail
@@ -196,23 +291,14 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
// C++11 generic lambda for format()
template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) {
if (i > 0) {
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
out = detail::copy(formatting.delimiter, out);
}
out = format_to(out,
detail::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), v),
v);
if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, v);
++i;
}
formatting_tuple<Char>& formatting;
size_t& i;
typename std::add_lvalue_reference<decltype(
std::declval<FormatContext>().out())>::type out;
typename std::add_lvalue_reference<
decltype(std::declval<FormatContext>().out())>::type out;
};
public:
@@ -227,12 +313,9 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out();
size_t i = 0;
detail::copy(formatting.prefix, out);
detail::copy(formatting.prefix, out);
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
detail::copy(formatting.postfix, out);
return ctx.out();
@@ -241,14 +324,22 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value =
detail::is_range_<T>::value && !detail::is_like_std_string<T>::value &&
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<detail::std_string_view<Char>, T>::value;
};
template <typename RangeT, typename Char>
struct formatter<RangeT, Char,
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<
fmt::is_range<T, Char>::value
// Workaround a bug in MSVC 2017 and earlier.
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
&& (has_formatter<detail::value_type<T>, format_context>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
#endif
>> {
formatting_range<Char> formatting;
template <typename ParseContext>
@@ -257,75 +348,68 @@ struct formatter<RangeT, Char,
}
template <typename FormatContext>
typename FormatContext::iterator format(const RangeT& values,
FormatContext& ctx) {
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
auto out = detail::copy(formatting.prefix, ctx.out());
size_t i = 0;
auto it = values.begin();
auto end = values.end();
auto view = detail::range_to_view<T>::view(values);
auto it = view.begin();
auto end = view.end();
for (; it != end; ++it) {
if (i > 0) {
if (formatting.add_prepostfix_space) *out++ = ' ';
out = detail::copy(formatting.delimiter, out);
if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, *it);
++i;
}
out = format_to(out,
detail::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), *it),
*it);
if (++i > formatting.range_length_limit) {
out = format_to(out, " ... <other elements>");
break;
}
}
if (formatting.add_prepostfix_space) *out++ = ' ';
return detail::copy(formatting.postfix, out);
}
};
template <typename Char, typename... T> struct tuple_arg_join : detail::view {
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep;
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple{t}, sep{s} {}
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
template <typename Char, typename... T>
struct formatter<tuple_arg_join<Char, T...>, Char> {
using tuple_arg_join = tuple_join_view<Char, T...>;
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
typename FormatContext::iterator format(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) ->
typename FormatContext::iterator {
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
}
private:
template <typename FormatContext, size_t... N>
typename FormatContext::iterator format(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
detail::index_sequence<N...>) {
return format_args(value, ctx, std::get<N>(value.tuple)...);
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
detail::index_sequence<N...>) ->
typename FormatContext::iterator {
using std::get;
return format_args(value, ctx, get<N>(value.tuple)...);
}
template <typename FormatContext>
typename FormatContext::iterator format_args(
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) ->
typename FormatContext::iterator {
// NOTE: for compilers that support C++17, this empty function instantiation
// can be replaced with a constexpr branch in the variadic overload.
return ctx.out();
}
template <typename FormatContext, typename Arg, typename... Args>
typename FormatContext::iterator format_args(
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
const Arg& arg, const Args&... args) {
auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
const Arg& arg, const Args&... args) ->
typename FormatContext::iterator {
using base = formatter<typename std::decay<Arg>::type, Char>;
auto out = ctx.out();
out = base{}.format(arg, ctx);
auto out = base().format(arg, ctx);
if (sizeof...(Args) > 0) {
out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
@@ -335,6 +419,8 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
}
};
FMT_MODULE_EXPORT_BEGIN
/**
\rst
Returns an object that formats `tuple` with elements separated by `sep`.
@@ -347,14 +433,15 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
\endrst
*/
template <typename... T>
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
string_view sep) {
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
-> tuple_join_view<char, T...> {
return {tuple, sep};
}
template <typename... T>
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
wstring_view sep) {
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> {
return {tuple, sep};
}
@@ -370,17 +457,12 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
\endrst
*/
template <typename T>
arg_join<const T*, const T*, char> join(std::initializer_list<T> list,
string_view sep) {
return join(std::begin(list), std::end(list), sep);
}
template <typename T>
arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
wstring_view sep) {
auto join(std::initializer_list<T> list, string_view sep)
-> join_view<const T*, const T*> {
return join(std::begin(list), std::end(list), sep);
}
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
#endif // FMT_RANGES_H_

View File

@@ -0,0 +1,236 @@
// Formatting library for C++ - optional wchar_t and exotic character support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_WCHAR_H_
#define FMT_WCHAR_H_
#include <cwchar>
#include <tuple>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
}
FMT_MODULE_EXPORT_BEGIN
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
using wformat_context = buffer_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename... Args> using wformat_string = wstring_view;
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
#endif
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<detail::char8_type> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
template <typename... Args>
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
const Args&... args) {
return {args...};
}
inline namespace literals {
constexpr auto operator"" _format(const wchar_t* s, size_t n)
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
return {s};
}
#endif
} // namespace literals
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep)
-> join_view<It, Sentinel, wchar_t> {
return {begin, end, sep};
}
template <typename Range>
auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
wchar_t> {
return join(std::begin(range), std::end(range), sep);
}
template <typename T>
auto join(std::initializer_list<T> list, wstring_view sep)
-> join_view<const T*, const T*, wchar_t> {
return join(std::begin(list), std::end(list), sep);
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
basic_memory_buffer<Char> buffer;
detail::vformat_to(buffer, format_str, args);
return to_string(buffer);
}
// Pass char_t as a default template parameter instead of using
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... Args, typename Char = char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat(to_string_view(format_str), vargs);
}
template <typename Locale, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat(
const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
return detail::vformat(loc, to_string_view(format_str), args);
}
template <typename Locale, typename S, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& format_str, Args&&... args)
-> std::basic_string<Char> {
return detail::vformat(loc, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
template <typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, to_string_view(format_str), args);
return detail::get_iterator(buf);
}
template <typename OutputIt, typename S, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
return vformat_to(out, to_string_view(fmt), vargs);
}
template <typename S, typename... Args, typename Char, size_t SIZE,
typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
const S& format_str, Args&&... args) ->
typename buffer_context<Char>::iterator {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
detail::vformat_to(buf, to_string_view(format_str), vargs, {});
return detail::buffer_appender<Char>(buf);
}
template <typename Locale, typename S, typename OutputIt, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(
OutputIt out, const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
return detail::get_iterator(buf);
}
template <
typename OutputIt, typename Locale, typename S, typename... Args,
typename Char = char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, loc, to_string_view(format_str), vargs);
}
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> format_to_n_result<OutputIt> {
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
n);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
}
template <typename OutputIt, typename S, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
const Args&... args) -> format_to_n_result<OutputIt> {
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
return vformat_to_n(out, n, to_string_view(fmt), vargs);
}
template <typename S, typename... Args, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
detail::counting_buffer<Char> buf;
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
detail::vformat_to(buf, to_string_view(fmt), vargs);
return buf.count();
}
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
wmemory_buffer buffer;
detail::vformat_to(buffer, fmt, args);
buffer.push_back(L'\0');
if (std::fputws(buffer.data(), f) == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
inline void vprint(wstring_view fmt, wformat_args args) {
vprint(stdout, fmt, args);
}
template <typename... T>
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return vprint(f, wstring_view(fmt), make_wformat_args(args...));
}
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), make_wformat_args(args...));
}
/**
Converts *value* to ``std::wstring`` using the default format for type *T*.
*/
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value);
}
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
#endif // FMT_WCHAR_H_

View File

@@ -0,0 +1,20 @@
//
// Copyright(c) 2016 Gabi Melman.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//
#pragma once
//
// include bundled or external copy of fmtlib's ostream support
//
#if !defined(SPDLOG_FMT_EXTERNAL)
# ifdef SPDLOG_HEADER_ONLY
# ifndef FMT_HEADER_ONLY
# define FMT_HEADER_ONLY
# endif
# endif
# include <spdlog/fmt/bundled/compile.h>
#else
# include <fmt/compile.h>
#endif

View File

@@ -0,0 +1,20 @@
//
// Copyright(c) 2016 Gabi Melman.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//
#pragma once
//
// include bundled or external copy of fmtlib's ostream support
//
#if !defined(SPDLOG_FMT_EXTERNAL)
# ifdef SPDLOG_HEADER_ONLY
# ifndef FMT_HEADER_ONLY
# define FMT_HEADER_ONLY
# endif
# endif
# include <spdlog/fmt/bundled/xchar.h>
#else
# include <fmt/xchar.h>
#endif

View File

@@ -247,7 +247,11 @@ SPDLOG_INLINE void logger::err_handler_(const std::string &msg)
auto tm_time = details::os::localtime(system_clock::to_time_t(now));
char date_buf[64];
std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time);
fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str());
#if defined(USING_R) && defined(R_R_H) // if in R environment
REprintf("[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str());
#else
std::fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str());
#endif
}
}
} // namespace spdlog

View File

@@ -19,10 +19,14 @@
#include <spdlog/details/backtracer.h>
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
# ifndef _WIN32
# error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows
# endif
# include <spdlog/details/os.h>
#endif
#include <vector>
#ifndef SPDLOG_NO_EXCEPTIONS
# define SPDLOG_LOGGER_CATCH() \
catch (const std::exception &ex) \
@@ -31,7 +35,8 @@
} \
catch (...) \
{ \
err_handler_("Unknown exception in logger"); \
err_handler_("Rethrowing unknown exception in logger"); \
throw; \
}
#else
# define SPDLOG_LOGGER_CATCH()
@@ -70,63 +75,18 @@ public:
logger(const logger &other);
logger(logger &&other) SPDLOG_NOEXCEPT;
logger &operator=(logger other) SPDLOG_NOEXCEPT;
void swap(spdlog::logger &other) SPDLOG_NOEXCEPT;
// FormatString is a type derived from fmt::compile_string
template<typename FormatString, typename std::enable_if<fmt::is_compile_string<FormatString>::value, int>::type = 0, typename... Args>
void log(source_loc loc, level::level_enum lvl, const FormatString &fmt, const Args &... args)
template<typename FormatString, typename... Args>
void log(source_loc loc, level::level_enum lvl, const FormatString &fmt, Args &&...args)
{
log_(loc, lvl, fmt, args...);
}
// FormatString is NOT a type derived from fmt::compile_string but is a string_view_t or can be implicitly converted to one
template<typename... Args>
void log(source_loc loc, level::level_enum lvl, string_view_t fmt, const Args &... args)
{
log_(loc, lvl, fmt, args...);
log_(loc, lvl, fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
void log(level::level_enum lvl, const FormatString &fmt, const Args &... args)
void log(level::level_enum lvl, const FormatString &fmt, Args &&...args)
{
log(source_loc{}, lvl, fmt, args...);
}
template<typename FormatString, typename... Args>
void trace(const FormatString &fmt, const Args &... args)
{
log(level::trace, fmt, args...);
}
template<typename FormatString, typename... Args>
void debug(const FormatString &fmt, const Args &... args)
{
log(level::debug, fmt, args...);
}
template<typename FormatString, typename... Args>
void info(const FormatString &fmt, const Args &... args)
{
log(level::info, fmt, args...);
}
template<typename FormatString, typename... Args>
void warn(const FormatString &fmt, const Args &... args)
{
log(level::warn, fmt, args...);
}
template<typename FormatString, typename... Args>
void error(const FormatString &fmt, const Args &... args)
{
log(level::err, fmt, args...);
}
template<typename FormatString, typename... Args>
void critical(const FormatString &fmt, const Args &... args)
{
log(level::critical, fmt, args...);
log(source_loc{}, lvl, fmt, std::forward<Args>(args)...);
}
template<typename T>
@@ -135,9 +95,8 @@ public:
log(source_loc{}, lvl, msg);
}
// T can be statically converted to string_view and isn't a fmt::compile_string
template<class T, typename std::enable_if<
std::is_convertible<const T &, spdlog::string_view_t>::value && !fmt::is_compile_string<T>::value, int>::type = 0>
// 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, int>::type = 0>
void log(source_loc loc, level::level_enum lvl, const T &msg)
{
log(loc, lvl, string_view_t{msg});
@@ -183,6 +142,42 @@ public:
log(loc, lvl, "{}", msg);
}
template<typename FormatString, typename... Args>
void trace(const FormatString &fmt, Args &&...args)
{
log(level::trace, fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
void debug(const FormatString &fmt, Args &&...args)
{
log(level::debug, fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
void info(const FormatString &fmt, Args &&...args)
{
log(level::info, fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
void warn(const FormatString &fmt, Args &&...args)
{
log(level::warn, fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
void error(const FormatString &fmt, Args &&...args)
{
log(level::err, fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
void critical(const FormatString &fmt, Args &&...args)
{
log(level::critical, fmt, std::forward<Args>(args)...);
}
template<typename T>
void trace(const T &msg)
{
@@ -219,57 +214,6 @@ public:
log(level::critical, msg);
}
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
#ifndef _WIN32
#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows
#else
template<typename... Args>
void log(source_loc loc, level::level_enum lvl, wstring_view_t fmt, const Args &... args)
{
bool log_enabled = should_log(lvl);
bool traceback_enabled = tracer_.enabled();
if (!log_enabled && !traceback_enabled)
{
return;
}
SPDLOG_TRY
{
// format to wmemory_buffer and convert to utf8
fmt::wmemory_buffer wbuf;
fmt::format_to(wbuf, fmt, args...);
memory_buf_t buf;
details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf);
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
log_it_(log_msg, log_enabled, traceback_enabled);
}
SPDLOG_LOGGER_CATCH()
}
// T can be statically converted to wstring_view
template<class T, typename std::enable_if<is_convertible_to_wstring_view<const T &>::value, int>::type = 0>
void log(source_loc loc, level::level_enum lvl, const T &msg)
{
bool log_enabled = should_log(lvl);
bool traceback_enabled = tracer_.enabled();
if (!log_enabled && !traceback_enabled)
{
return;
}
SPDLOG_TRY
{
memory_buf_t buf;
details::os::wstr_to_utf8buf(msg, buf);
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
log_it_(log_msg, log_enabled, traceback_enabled);
}
SPDLOG_LOGGER_CATCH()
}
#endif // _WIN32
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT
// return true logging is enabled for the given level.
bool should_log(level::level_enum msg_level) const
{
@@ -325,8 +269,9 @@ protected:
details::backtracer tracer_;
// common implementation for after templated public api has been resolved
template<typename FormatString, typename... Args>
void log_(source_loc loc, level::level_enum lvl, const FormatString &fmt, const Args &... args)
template<typename FormatString, typename... Args, typename Char = fmt::char_t<FormatString>,
typename std::enable_if<!std::is_same<Char, wchar_t>::value, Char>::type * = nullptr>
void log_(source_loc loc, level::level_enum lvl, const FormatString &fmt, Args &&...args)
{
bool log_enabled = should_log(lvl);
bool traceback_enabled = tracer_.enabled();
@@ -337,13 +282,58 @@ protected:
SPDLOG_TRY
{
memory_buf_t buf;
fmt::format_to(buf, fmt, args...);
fmt::detail::vformat_to(buf, fmt::to_string_view(fmt), fmt::make_format_args(args...));
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
log_it_(log_msg, log_enabled, traceback_enabled);
}
SPDLOG_LOGGER_CATCH()
}
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
template<typename... Args>
void log_(source_loc loc, level::level_enum lvl, const wstring_view_t &fmt, Args &&...args)
{
bool log_enabled = should_log(lvl);
bool traceback_enabled = tracer_.enabled();
if (!log_enabled && !traceback_enabled)
{
return;
}
SPDLOG_TRY
{
// format to wmemory_buffer and convert to utf8
fmt::wmemory_buffer wbuf;
fmt::format_to(std::back_inserter(wbuf), fmt, std::forward<Args>(args)...);
memory_buf_t buf;
details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf);
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
log_it_(log_msg, log_enabled, traceback_enabled);
}
SPDLOG_LOGGER_CATCH()
}
// T can be statically converted to wstring_view, and no formatting needed.
template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::wstring_view_t>::value, int>::type = 0>
void log_(source_loc loc, level::level_enum lvl, const T &msg)
{
bool log_enabled = should_log(lvl);
bool traceback_enabled = tracer_.enabled();
if (!log_enabled && !traceback_enabled)
{
return;
}
SPDLOG_TRY
{
memory_buf_t buf;
details::os::wstr_to_utf8buf(msg, buf);
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
log_it_(log_msg, log_enabled, traceback_enabled);
}
SPDLOG_LOGGER_CATCH()
}
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT
// log the given message (if the given log level is high enough),
// and save backtrace (if backtrace is enabled).
void log_it_(const details::log_msg &log_msg, bool log_enabled, bool traceback_enabled);

View File

@@ -13,11 +13,13 @@
#include <spdlog/fmt/fmt.h>
#include <spdlog/formatter.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <ctime>
#include <cctype>
#include <cstring>
#include <iterator>
#include <memory>
#include <mutex>
#include <string>
@@ -45,12 +47,12 @@ public:
return;
}
if (padinfo_.side_ == padding_info::left)
if (padinfo_.side_ == padding_info::pad_side::left)
{
pad_it(remaining_pad_);
remaining_pad_ = 0;
}
else if (padinfo_.side_ == padding_info::center)
else if (padinfo_.side_ == padding_info::pad_side::center)
{
auto half_pad = remaining_pad_ / 2;
auto reminder = remaining_pad_ & 1;
@@ -127,7 +129,7 @@ public:
void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
{
string_view_t &level_name = level::to_string_view(msg.level);
const string_view_t &level_name = level::to_string_view(msg.level);
ScopedPadder p(level_name.size(), padinfo_, dest);
fmt_helper::append_string_view(level_name, dest);
}
@@ -814,11 +816,31 @@ public:
: flag_formatter(padinfo)
{}
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable : 4127) // consider using 'if constexpr' instead
#endif // _MSC_VER
static const char *basename(const char *filename)
{
const char *rv = std::strrchr(filename, os::folder_sep);
// if the size is 2 (1 character + null terminator) we can use the more efficient strrchr
// the branch will be elided by optimizations
if (sizeof(os::folder_seps) == 2)
{
const char *rv = std::strrchr(filename, os::folder_seps[0]);
return rv != nullptr ? rv + 1 : filename;
}
else
{
const std::reverse_iterator<const char *> begin(filename + std::strlen(filename));
const std::reverse_iterator<const char *> end(filename);
const auto it = std::find_first_of(begin, end, std::begin(os::folder_seps), std::end(os::folder_seps) - 1);
return it != end ? it.base() : filename;
}
}
#ifdef _MSC_VER
# pragma warning(pop)
#endif // _MSC_VER
void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
{
@@ -903,7 +925,7 @@ private:
};
// Full info formatter
// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v
// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] [%s:%#] %v
class full_formatter final : public flag_formatter
{
public:
@@ -1285,15 +1307,15 @@ SPDLOG_INLINE details::padding_info pattern_formatter::handle_padspec_(std::stri
switch (*it)
{
case '-':
side = padding_info::right;
side = padding_info::pad_side::right;
++it;
break;
case '=':
side = padding_info::center;
side = padding_info::pad_side::center;
++it;
break;
default:
side = details::padding_info::left;
side = details::padding_info::pad_side::left;
break;
}

View File

@@ -22,7 +22,7 @@ namespace details {
// padding information.
struct padding_info
{
enum pad_side
enum class pad_side
{
left,
right,
@@ -42,7 +42,7 @@ struct padding_info
return enabled_;
}
size_t width_ = 0;
pad_side side_ = left;
pad_side side_ = pad_side::left;
bool truncate_ = false;
bool enabled_ = false;
};
@@ -92,9 +92,9 @@ public:
void format(const details::log_msg &msg, memory_buf_t &dest) override;
template<typename T, typename... Args>
pattern_formatter &add_flag(char flag, const Args &... args)
pattern_formatter &add_flag(char flag, Args &&...args)
{
custom_handlers_[flag] = details::make_unique<T>(args...);
custom_handlers_[flag] = details::make_unique<T>(std::forward<Args>(args)...);
return *this;
}
void set_pattern(std::string pattern);

View File

@@ -106,6 +106,8 @@ SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode(color_mode mode)
case color_mode::never:
should_do_colors_ = false;
return;
default:
should_do_colors_ = false;
}
}

View File

@@ -37,7 +37,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

@@ -7,6 +7,7 @@
#include <spdlog/details/file_helper.h>
#include <spdlog/details/null_mutex.h>
#include <spdlog/fmt/fmt.h>
#include <spdlog/fmt/chrono.h>
#include <spdlog/sinks/base_sink.h>
#include <spdlog/details/os.h>
#include <spdlog/details/circular_q.h>
@@ -36,6 +37,27 @@ struct daily_filename_calculator
}
};
/*
* Generator of daily log file names with strftime format.
* Usages:
* auto sink = std::make_shared<spdlog::sinks::daily_file_format_sink_mt>("myapp-%Y-%m-%d:%H:%M:%S.log", hour, minute);"
* auto logger = spdlog::daily_logger_format_mt("loggername, "myapp-%Y-%m-%d:%X.log", hour, minute)"
*
*/
struct daily_filename_format_calculator
{
static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
{
// generate fmt datetime format string, e.g. {:%Y-%m-%d}.
filename_t fmt_filename = fmt::format(SPDLOG_FILENAME_T("{{:{}}}"), filename);
#if defined(_MSC_VER) && defined(SPDLOG_WCHAR_FILENAMES) // for some reason msvc doesnt allow fmt::runtime(..) with wchar here
return fmt::format(fmt_filename, now_tm);
#else
return fmt::format(SPDLOG_FMT_RUNTIME(fmt_filename), now_tm);
#endif
}
};
/*
* Rotating file sink based on date.
* If truncate != false , the created file will be truncated.
@@ -182,6 +204,8 @@ private:
using daily_file_sink_mt = daily_file_sink<std::mutex>;
using daily_file_sink_st = daily_file_sink<details::null_mutex>;
using daily_file_format_sink_mt = daily_file_sink<std::mutex, daily_filename_format_calculator>;
using daily_file_format_sink_st = daily_file_sink<details::null_mutex, daily_filename_format_calculator>;
} // namespace sinks
@@ -195,10 +219,24 @@ inline std::shared_ptr<logger> daily_logger_mt(
return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files);
}
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_format_mt(
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_format_sink_mt>(logger_name, filename, hour, minute, truncate, max_files);
}
template<typename Factory = spdlog::synchronous_factory>
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, uint16_t max_files = 0)
{
return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files);
}
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_format_st(
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_format_sink_st>(logger_name, filename, hour, minute, truncate, max_files);
}
} // namespace spdlog

View File

@@ -7,6 +7,7 @@
#include <spdlog/details/null_mutex.h>
#include <spdlog/details/log_msg.h>
#include <cstdio>
#include <mutex>
#include <string>
#include <chrono>
@@ -62,11 +63,14 @@ protected:
// log the "skipped.." message
if (skip_counter_ > 0)
{
memory_buf_t buf;
fmt::format_to(buf, "Skipped {} duplicate messages..", skip_counter_);
details::log_msg skipped_msg{msg.logger_name, msg.level, string_view_t{buf.data(), buf.size()}};
char buf[64];
auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..", static_cast<unsigned>(skip_counter_));
if (msg_size > 0 && static_cast<size_t>(msg_size) < sizeof(buf))
{
details::log_msg skipped_msg{msg.logger_name, level::info, string_view_t{buf, static_cast<size_t>(msg_size)}};
dist_sink<Mutex>::sink_it_(skipped_msg);
}
}
// log current message
dist_sink<Mutex>::sink_it_(msg);

View File

@@ -0,0 +1,194 @@
// 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/details/file_helper.h>
#include <spdlog/details/null_mutex.h>
#include <spdlog/fmt/fmt.h>
#include <spdlog/sinks/base_sink.h>
#include <spdlog/details/os.h>
#include <spdlog/details/circular_q.h>
#include <spdlog/details/synchronous_factory.h>
#include <chrono>
#include <cstdio>
#include <ctime>
#include <mutex>
#include <string>
namespace spdlog {
namespace sinks {
/*
* Generator of Hourly log file names in format basename.YYYY-MM-DD-HH.ext
*/
struct hourly_filename_calculator
{
// Create filename for the form basename.YYYY-MM-DD-H
static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
{
filename_t basename, ext;
std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
return fmt::format(SPDLOG_FILENAME_T("{}_{:04d}{:02d}{:02d}_{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1,
now_tm.tm_mday, now_tm.tm_hour, ext);
}
};
/*
* Rotating file sink based on time.
* If truncate != false , the created file will be truncated.
* If max_files > 0, retain only the last max_files and delete previous.
*/
template<typename Mutex, typename FileNameCalc = hourly_filename_calculator>
class hourly_file_sink final : public base_sink<Mutex>
{
public:
// create hourly file sink which rotates on given time
hourly_file_sink(filename_t base_filename, bool truncate = false, uint16_t max_files = 0)
: base_filename_(std::move(base_filename))
, truncate_(truncate)
, max_files_(max_files)
, filenames_q_()
{
auto now = log_clock::now();
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now));
file_helper_.open(filename, truncate_);
rotation_tp_ = next_rotation_tp_();
if (max_files_ > 0)
{
init_filenames_q_();
}
}
filename_t filename()
{
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
return file_helper_.filename();
}
protected:
void sink_it_(const details::log_msg &msg) override
{
auto time = msg.time;
bool should_rotate = time >= rotation_tp_;
if (should_rotate)
{
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time));
file_helper_.open(filename, truncate_);
rotation_tp_ = next_rotation_tp_();
}
memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted);
file_helper_.write(formatted);
// Do the cleaning only at the end because it might throw on failure.
if (should_rotate && max_files_ > 0)
{
delete_old_();
}
}
void flush_() override
{
file_helper_.flush();
}
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(1);
}
for (auto iter = filenames.rbegin(); iter != filenames.rend(); ++iter)
{
filenames_q_.push_back(std::move(*iter));
}
}
tm now_tm(log_clock::time_point tp)
{
time_t tnow = log_clock::to_time_t(tp);
return spdlog::details::os::localtime(tnow);
}
log_clock::time_point next_rotation_tp_()
{
auto now = log_clock::now();
tm date = now_tm(now);
date.tm_min = 0;
date.tm_sec = 0;
auto rotation_time = log_clock::from_time_t(std::mktime(&date));
if (rotation_time > now)
{
return rotation_time;
}
return {rotation_time + std::chrono::hours(1)};
}
// Delete the file N rotations ago.
// Throw spdlog_ex on failure to delete the old file.
void delete_old_()
{
using details::os::filename_to_str;
using details::os::remove_if_exists;
filename_t current_file = file_helper_.filename();
if (filenames_q_.full())
{
auto old_filename = std::move(filenames_q_.front());
filenames_q_.pop_front();
bool ok = remove_if_exists(old_filename) == 0;
if (!ok)
{
filenames_q_.push_back(std::move(current_file));
SPDLOG_THROW(spdlog_ex("Failed removing hourly file " + filename_to_str(old_filename), errno));
}
}
filenames_q_.push_back(std::move(current_file));
}
filename_t base_filename_;
log_clock::time_point rotation_tp_;
details::file_helper file_helper_;
bool truncate_;
uint16_t max_files_;
details::circular_q<filename_t> filenames_q_;
};
using hourly_file_sink_mt = hourly_file_sink<std::mutex>;
using hourly_file_sink_st = hourly_file_sink<details::null_mutex>;
} // namespace sinks
//
// factory functions
//
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> hourly_logger_mt(
const std::string &logger_name, const filename_t &filename, bool truncate = false, uint16_t max_files = 0)
{
return Factory::template create<sinks::hourly_file_sink_mt>(logger_name, filename, truncate, max_files);
}
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> hourly_logger_st(
const std::string &logger_name, const filename_t &filename, bool truncate = false, uint16_t max_files = 0)
{
return Factory::template create<sinks::hourly_file_sink_st>(logger_name, filename, truncate, max_files);
}
} // namespace spdlog

View File

@@ -0,0 +1,98 @@
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
//
// Custom sink for mongodb
// Building and using requires mongocxx library.
// For building mongocxx library check the url below
// http://mongocxx.org/mongocxx-v3/installation/
//
#include "spdlog/common.h"
#include "spdlog/details/log_msg.h"
#include "spdlog/sinks/base_sink.h"
#include <spdlog/details/synchronous_factory.h>
#include <bsoncxx/builder/stream/document.hpp>
#include <bsoncxx/types.hpp>
#include <bsoncxx/view_or_value.hpp>
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
namespace spdlog {
namespace sinks {
template<typename Mutex>
class mongo_sink : public base_sink<Mutex>
{
public:
mongo_sink(const std::string &db_name, const std::string &collection_name, const std::string &uri = "mongodb://localhost:27017")
{
try
{
client_ = std::make_unique<mongocxx::client>(mongocxx::uri{uri});
db_name_ = db_name;
coll_name_ = collection_name;
}
catch (const std::exception)
{
throw spdlog_ex("Error opening database");
}
}
~mongo_sink()
{
flush_();
}
protected:
void sink_it_(const details::log_msg &msg) override
{
using bsoncxx::builder::stream::document;
using bsoncxx::builder::stream::finalize;
if (client_ != nullptr)
{
auto doc = document{} << "timestamp" << bsoncxx::types::b_date(msg.time) << "level" << level::to_string_view(msg.level).data()
<< "message" << std::string(msg.payload.begin(), msg.payload.end()) << "logger_name"
<< std::string(msg.logger_name.begin(), msg.logger_name.end()) << "thread_id"
<< static_cast<int>(msg.thread_id) << finalize;
client_->database(db_name_).collection(coll_name_).insert_one(doc.view());
}
}
void flush_() override {}
private:
static mongocxx::instance instance_;
std::string db_name_;
std::string coll_name_;
std::unique_ptr<mongocxx::client> client_ = nullptr;
};
mongocxx::instance mongo_sink<std::mutex>::instance_{};
#include "spdlog/details/null_mutex.h"
#include <mutex>
using mongo_sink_mt = mongo_sink<std::mutex>;
using mongo_sink_st = mongo_sink<spdlog::details::null_mutex>;
} // namespace sinks
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> mongo_logger_mt(const std::string &logger_name, const std::string &db_name,
const std::string &collection_name, const std::string &uri = "mongodb://localhost:27017")
{
return Factory::template create<sinks::mongo_sink_mt>(logger_name, db_name, collection_name, uri);
}
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> mongo_logger_st(const std::string &logger_name, const std::string &db_name,
const std::string &collection_name, const std::string &uri = "mongodb://localhost:27017")
{
return Factory::template create<sinks::mongo_sink_st>(logger_name, db_name, collection_name, uri);
}
} // namespace spdlog

View File

@@ -8,12 +8,12 @@
# 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>
// Avoid including windows.h (https://stackoverflow.com/a/30741042)
extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char *lpOutputString);
namespace spdlog {
namespace sinks {
/*
@@ -23,12 +23,11 @@ template<typename Mutex>
class msvc_sink : public base_sink<Mutex>
{
public:
explicit msvc_sink() {}
msvc_sink() = default;
protected:
void sink_it_(const details::log_msg &msg) override
{
memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted);
OutputDebugStringA(fmt::to_string(formatted).c_str());

View File

@@ -0,0 +1,200 @@
// Copyright(c) 2015-present, Gabi Melman, mguludag and spdlog contributors.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
#pragma once
//
// Custom sink for QPlainTextEdit or QTextEdit and its children(QTextBrowser...
// etc) Building and using requires Qt library.
//
#include "spdlog/common.h"
#include "spdlog/details/log_msg.h"
#include "spdlog/details/synchronous_factory.h"
#include "spdlog/sinks/base_sink.h"
#include <QObject>
#include <QPlainTextEdit>
#include <QTextEdit>
namespace _spdlog_p {
namespace _sinks_p {
//
// Private class for QTextEdit and its derivatives
//
class qtextedit_sink_p : public QObject
{
Q_OBJECT
public:
qtextedit_sink_p(QTextEdit *textedit = nullptr)
{
if (textedit != nullptr)
{
textedit_ = textedit;
connect(this, &qtextedit_sink_p::append_text, textedit_, &QTextEdit::append);
}
}
~qtextedit_sink_p() {}
void append(const spdlog::string_view_t &str)
{
emit append_text(QString::fromUtf8(str.data(), static_cast<int>(str.size() - 2)));
}
signals:
void append_text(const QString &);
private:
QTextEdit *textedit_ = nullptr;
};
//
// Private class for QPlainTextEdit
//
class qplaintextedit_sink_p : public QObject
{
Q_OBJECT
public:
qplaintextedit_sink_p(QPlainTextEdit *textedit = nullptr)
{
if (textedit != nullptr)
{
textedit_ = textedit;
connect(this, &qplaintextedit_sink_p::append_text, textedit_, &QPlainTextEdit::appendPlainText);
}
}
~qplaintextedit_sink_p() {}
void append(const spdlog::string_view_t &str)
{
emit append_text(QString::fromUtf8(str.data(), static_cast<int>(str.size() - 2)));
}
signals:
void append_text(const QString &);
private:
QPlainTextEdit *textedit_ = nullptr;
};
} // namespace _sinks_p
} // namespace _spdlog_p
//
// qtextedit_sink class
//
namespace spdlog {
namespace sinks {
template<typename Mutex>
class qtextedit_sink : public base_sink<Mutex>
{
public:
qtextedit_sink(QTextEdit *textedit = nullptr)
{
if (textedit != nullptr)
{
textedit_p = std::make_shared<_spdlog_p::_sinks_p::qtextedit_sink_p>(textedit);
}
else
{
throw spdlog_ex("Error opening QTextEdit");
}
}
~qtextedit_sink()
{
flush_();
}
protected:
void sink_it_(const details::log_msg &msg) override
{
memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted);
string_view_t str_v = string_view_t(formatted.data(), formatted.size());
textedit_p->append(str_v);
}
void flush_() override {}
private:
std::shared_ptr<_spdlog_p::_sinks_p::qtextedit_sink_p> textedit_p = nullptr;
};
//
// qplaintextedit_sink class
//
template<typename Mutex>
class qplaintextedit_sink : public base_sink<Mutex>
{
public:
qplaintextedit_sink(QPlainTextEdit *textedit = nullptr)
{
if (textedit != nullptr)
{
textedit_p = std::make_shared<_spdlog_p::_sinks_p::qplaintextedit_sink_p>(textedit);
}
else
{
throw spdlog_ex("Error opening QPlainTextEdit");
}
}
~qplaintextedit_sink()
{
flush_();
}
protected:
void sink_it_(const details::log_msg &msg) override
{
memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted);
string_view_t str_v = string_view_t(formatted.data(), formatted.size());
textedit_p->append(str_v);
}
void flush_() override {}
private:
std::shared_ptr<_spdlog_p::_sinks_p::qplaintextedit_sink_p> textedit_p = nullptr;
};
#include "spdlog/details/null_mutex.h"
#include <mutex>
using qtextedit_sink_mt = qtextedit_sink<std::mutex>;
using qtextedit_sink_st = qtextedit_sink<spdlog::details::null_mutex>;
using qplaintextedit_sink_mt = qplaintextedit_sink<std::mutex>;
using qplaintextedit_sink_st = qplaintextedit_sink<spdlog::details::null_mutex>;
} // namespace sinks
//
// Factory functions
//
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> qtextedit_logger_mt(const std::string &logger_name, QTextEdit *qtextedit = nullptr)
{
return Factory::template create<sinks::qtextedit_sink_mt>(logger_name, qtextedit);
}
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> qtextedit_logger_st(const std::string &logger_name, QTextEdit *qtextedit = nullptr)
{
return Factory::template create<sinks::qtextedit_sink_st>(logger_name, qtextedit);
}
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> qplaintextedit_logger_mt(const std::string &logger_name, QPlainTextEdit *qplaintextedit = nullptr)
{
return Factory::template create<sinks::qplaintextedit_sink_mt>(logger_name, qplaintextedit);
}
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> qplaintextedit_logger_st(const std::string &logger_name, QPlainTextEdit *qplaintextedit = nullptr)
{
return Factory::template create<sinks::qplaintextedit_sink_st>(logger_name, qplaintextedit);
}
} // namespace spdlog

View File

@@ -11,6 +11,19 @@
#include <spdlog/pattern_formatter.h>
#include <memory>
#ifdef _WIN32
// under windows using fwrite to non-binary stream results in \r\r\n (see issue #1675)
// so instead we use ::FileWrite
# include <spdlog/details/windows_include.h>
# ifndef _USING_V110_SDK71_ // fileapi.h doesnt exist in winxp
# include <fileapi.h> // WriteFile (..)
# endif
# include <io.h> // _get_osfhandle(..)
# include <stdio.h> // _fileno(..)
#endif // WIN32
namespace spdlog {
namespace sinks {
@@ -20,16 +33,48 @@ SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE *file)
: mutex_(ConsoleMutex::mutex())
, file_(file)
, formatter_(details::make_unique<spdlog::pattern_formatter>())
{}
{
#ifdef _WIN32
// get windows handle from the FILE* object
handle_ = (HANDLE)::_get_osfhandle(::_fileno(file_));
// don't throw to support cases where no console is attached,
// and let the log method to do nothing if (handle_ == INVALID_HANDLE_VALUE).
// throw only if non stdout/stderr target is requested (probably regular file and not console).
if (handle_ == INVALID_HANDLE_VALUE && file != stdout && file != stderr)
{
throw_spdlog_ex("spdlog::stdout_sink_base: _get_osfhandle() failed", errno);
}
#endif // WIN32
}
template<typename ConsoleMutex>
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &msg)
{
#ifdef _WIN32
if (handle_ == INVALID_HANDLE_VALUE)
{
return;
}
std::lock_guard<mutex_t> lock(mutex_);
memory_buf_t formatted;
formatter_->format(msg, formatted);
fwrite(formatted.data(), sizeof(char), formatted.size(), file_);
fflush(file_); // flush every line to terminal
::fflush(file_); // flush in case there is somthing in this file_ already
auto size = static_cast<DWORD>(formatted.size());
DWORD bytes_written = 0;
bool ok = ::WriteFile(handle_, formatted.data(), size, &bytes_written, nullptr) != 0;
if (!ok)
{
throw_spdlog_ex("stdout_sink_base: WriteFile() failed. GetLastError(): " + std::to_string(::GetLastError()));
}
#else
std::lock_guard<mutex_t> lock(mutex_);
memory_buf_t formatted;
formatter_->format(msg, formatted);
::fwrite(formatted.data(), sizeof(char), formatted.size(), file_);
::fflush(file_); // flush every line to terminal
#endif // WIN32
}
template<typename ConsoleMutex>

View File

@@ -8,6 +8,10 @@
#include <spdlog/sinks/sink.h>
#include <cstdio>
#ifdef _WIN32
# include <spdlog/details/windows_include.h>
#endif
namespace spdlog {
namespace sinks {
@@ -36,6 +40,9 @@ protected:
mutex_t &mutex_;
FILE *file_;
std::unique_ptr<spdlog::formatter> formatter_;
#ifdef _WIN32
HANDLE handle_;
#endif // WIN32
};
template<typename ConsoleMutex>

View File

@@ -57,7 +57,7 @@ struct win32_error : public spdlog_ex
LPSTR format_message_result{};
auto format_message_succeeded =
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
::FormatMessageA(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)
@@ -203,7 +203,7 @@ private:
{
if (!hEventLog_)
{
hEventLog_ = ::RegisterEventSource(nullptr, source_.c_str());
hEventLog_ = ::RegisterEventSourceA(nullptr, source_.c_str());
if (!hEventLog_ || hEventLog_ == (HANDLE)ERROR_ACCESS_DENIED)
{
SPDLOG_THROW(internal::win32_error("RegisterEventSource"));
@@ -218,13 +218,23 @@ protected:
{
using namespace internal;
bool succeeded;
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_,
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
wmemory_buf_t buf;
details::os::utf8_to_wstrbuf(string_view_t(formatted.data(), formatted.size()), buf);
LPCWSTR lp_wstr = buf.data();
succeeded = ::ReportEventW(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg), event_id_,
current_user_sid_.as_sid(), 1, 0, &lp_wstr, nullptr);
#else
LPCSTR lp_str = formatted.data();
succeeded = ::ReportEventA(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);
#endif
if (!succeeded)
{

View File

@@ -7,30 +7,30 @@
# include <spdlog/sinks/wincolor_sink.h>
#endif
#include <spdlog/details/windows_include.h>
#include <wincon.h>
#include <spdlog/common.h>
#include <spdlog/pattern_formatter.h>
namespace spdlog {
namespace sinks {
template<typename ConsoleMutex>
SPDLOG_INLINE wincolor_sink<ConsoleMutex>::wincolor_sink(HANDLE out_handle, color_mode mode)
SPDLOG_INLINE wincolor_sink<ConsoleMutex>::wincolor_sink(void *out_handle, color_mode mode)
: out_handle_(out_handle)
, mutex_(ConsoleMutex::mutex())
, formatter_(details::make_unique<spdlog::pattern_formatter>())
{
// check if out_handle is points to the actual console.
// ::GetConsoleMode() should return 0 if it is redirected or not valid console handle.
DWORD console_mode;
in_console_ = ::GetConsoleMode(out_handle, &console_mode) != 0;
set_color_mode(mode);
colors_[level::trace] = WHITE;
colors_[level::debug] = CYAN;
colors_[level::info] = GREEN;
colors_[level::warn] = YELLOW | BOLD;
colors_[level::err] = RED | BOLD; // red bold
colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background
set_color_mode_impl(mode);
// set level colors
colors_[level::trace] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; // white
colors_[level::debug] = FOREGROUND_GREEN | FOREGROUND_BLUE; // cyan
colors_[level::info] = FOREGROUND_GREEN; // green
colors_[level::warn] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; // intense yellow
colors_[level::err] = FOREGROUND_RED | FOREGROUND_INTENSITY; // intense red
colors_[level::critical] =
BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; // intense white on red background
colors_[level::off] = 0;
}
@@ -42,7 +42,7 @@ SPDLOG_INLINE wincolor_sink<ConsoleMutex>::~wincolor_sink()
// change the color for the given level
template<typename ConsoleMutex>
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum level, WORD color)
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum level, std::uint16_t color)
{
std::lock_guard<mutex_t> lock(mutex_);
colors_[level] = color;
@@ -51,31 +51,30 @@ void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum leve
template<typename ConsoleMutex>
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg)
{
if (out_handle_ == nullptr || out_handle_ == INVALID_HANDLE_VALUE)
{
return;
}
std::lock_guard<mutex_t> lock(mutex_);
msg.color_range_start = 0;
msg.color_range_end = 0;
memory_buf_t formatted;
formatter_->format(msg, formatted);
if (!in_console_)
{
write_to_file_(formatted);
return;
}
if (should_do_colors_ && msg.color_range_end > msg.color_range_start)
{
// before color range
print_range_(formatted, 0, msg.color_range_start);
// in color range
auto orig_attribs = set_foreground_color_(colors_[msg.level]);
auto orig_attribs = static_cast<WORD>(set_foreground_color_(colors_[msg.level]));
print_range_(formatted, msg.color_range_start, msg.color_range_end);
// reset to orig colors
::SetConsoleTextAttribute(out_handle_, orig_attribs);
::SetConsoleTextAttribute(static_cast<HANDLE>(out_handle_), orig_attribs);
print_range_(formatted, msg.color_range_end, formatted.size());
}
else // print without colors if color range is invalid (or color is disabled)
{
print_range_(formatted, 0, formatted.size());
write_to_file_(formatted);
}
}
@@ -102,66 +101,63 @@ void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<sp
template<typename ConsoleMutex>
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color_mode(color_mode mode)
{
switch (mode)
std::lock_guard<mutex_t> lock(mutex_);
set_color_mode_impl(mode);
}
template<typename ConsoleMutex>
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color_mode_impl(color_mode mode)
{
case color_mode::always:
case color_mode::automatic:
should_do_colors_ = true;
break;
case color_mode::never:
should_do_colors_ = false;
break;
default:
should_do_colors_ = true;
if (mode == color_mode::automatic)
{
// should do colors only if out_handle_ points to actual console.
DWORD console_mode;
bool in_console = ::GetConsoleMode(static_cast<HANDLE>(out_handle_), &console_mode) != 0;
should_do_colors_ = in_console;
}
else
{
should_do_colors_ = mode == color_mode::always ? true : false;
}
}
// set foreground color and return the orig console attributes (for resetting later)
template<typename ConsoleMutex>
WORD SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_foreground_color_(WORD attribs)
std::uint16_t SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_foreground_color_(std::uint16_t attribs)
{
CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info;
::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info);
WORD back_color = orig_buffer_info.wAttributes;
// retrieve the current background color
back_color &= static_cast<WORD>(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY));
// keep the background color unchanged
::SetConsoleTextAttribute(out_handle_, attribs | back_color);
return orig_buffer_info.wAttributes; // return orig attribs
if (!::GetConsoleScreenBufferInfo(static_cast<HANDLE>(out_handle_), &orig_buffer_info))
{
// just return white if failed getting console info
return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
}
// change only the foreground bits (lowest 4 bits)
auto new_attribs = static_cast<WORD>(attribs) | (orig_buffer_info.wAttributes & 0xfff0);
auto ignored = ::SetConsoleTextAttribute(static_cast<HANDLE>(out_handle_), static_cast<WORD>(new_attribs));
(void)(ignored);
return static_cast<std::uint16_t>(orig_buffer_info.wAttributes); // return orig attribs
}
// print a range of formatted message to console
template<typename ConsoleMutex>
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end)
{
if (end > start)
{
auto size = static_cast<DWORD>(end - start);
::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr);
auto ignored = ::WriteConsoleA(static_cast<HANDLE>(out_handle_), formatted.data() + start, size, nullptr, nullptr);
(void)(ignored);
}
}
template<typename ConsoleMutex>
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::write_to_file_(const memory_buf_t &formatted)
{
if (out_handle_ == nullptr) // no console and no file redirect
{
return;
}
auto size = static_cast<DWORD>(formatted.size());
if (size == 0)
{
return;
}
DWORD total_written = 0;
do
{
DWORD bytes_written = 0;
bool ok = ::WriteFile(out_handle_, formatted.data() + total_written, size - total_written, &bytes_written, nullptr) != 0;
if (!ok || bytes_written == 0)
{
throw_spdlog_ex("wincolor_sink: write_to_file_ failed. GetLastError(): " + std::to_string(::GetLastError()));
}
total_written += bytes_written;
} while (total_written < size);
auto ignored = ::WriteFile(static_cast<HANDLE>(out_handle_), formatted.data(), size, &bytes_written, nullptr);
(void)(ignored);
}
// wincolor_stdout_sink
@@ -175,6 +171,5 @@ template<typename ConsoleMutex>
SPDLOG_INLINE wincolor_stderr_sink<ConsoleMutex>::wincolor_stderr_sink(color_mode mode)
: wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_ERROR_HANDLE), mode)
{}
} // namespace sinks
} // namespace spdlog

View File

@@ -12,9 +12,7 @@
#include <mutex>
#include <string>
#include <array>
#include <spdlog/details/windows_include.h>
#include <wincon.h>
#include <cstdint>
namespace spdlog {
namespace sinks {
@@ -26,21 +24,14 @@ template<typename ConsoleMutex>
class wincolor_sink : public sink
{
public:
const WORD BOLD = FOREGROUND_INTENSITY;
const WORD RED = FOREGROUND_RED;
const WORD GREEN = FOREGROUND_GREEN;
const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE;
const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN;
wincolor_sink(HANDLE out_handle, color_mode mode);
wincolor_sink(void *out_handle, color_mode mode);
~wincolor_sink() override;
wincolor_sink(const wincolor_sink &other) = delete;
wincolor_sink &operator=(const wincolor_sink &other) = delete;
// change the color for the given level
void set_color(level::level_enum level, WORD color);
void set_color(level::level_enum level, std::uint16_t color);
void log(const details::log_msg &msg) final override;
void flush() final override;
void set_pattern(const std::string &pattern) override final;
@@ -49,21 +40,22 @@ public:
protected:
using mutex_t = typename ConsoleMutex::mutex_t;
HANDLE out_handle_;
void *out_handle_;
mutex_t &mutex_;
bool in_console_;
bool should_do_colors_;
std::unique_ptr<spdlog::formatter> formatter_;
std::array<WORD, level::n_levels> colors_;
std::array<std::uint16_t, level::n_levels> colors_;
// set foreground color and return the orig console attributes (for resetting later)
WORD set_foreground_color_(WORD attribs);
std::uint16_t set_foreground_color_(std::uint16_t attribs);
// print a range of formatted message to console
void print_range_(const memory_buf_t &formatted, size_t start, size_t end);
// in case we are redirected to file (not in console mode)
void write_to_file_(const memory_buf_t &formatted);
void set_color_mode_impl(color_mode mode);
};
template<typename ConsoleMutex>
@@ -85,7 +77,6 @@ using wincolor_stdout_sink_st = wincolor_stdout_sink<details::console_nullmutex>
using wincolor_stderr_sink_mt = wincolor_stderr_sink<details::console_mutex>;
using wincolor_stderr_sink_st = wincolor_stderr_sink<details::console_nullmutex>;
} // namespace sinks
} // namespace spdlog

View File

@@ -111,7 +111,7 @@ SPDLOG_API void set_automatic_registration(bool automatic_registration);
//
// The default logger object can be accessed using the spdlog::default_logger():
// For example, to add another sink to it:
// spdlog::default_logger()->sinks()->push_back(some_sink);
// spdlog::default_logger()->sinks().push_back(some_sink);
//
// The default logger can replaced using spdlog::set_default_logger(new_logger).
// For example, to replace it with a file logger.
@@ -128,51 +128,51 @@ SPDLOG_API spdlog::logger *default_logger_raw();
SPDLOG_API void set_default_logger(std::shared_ptr<spdlog::logger> default_logger);
template<typename FormatString, typename... Args>
inline void log(source_loc source, level::level_enum lvl, const FormatString &fmt, const Args &... args)
inline void log(source_loc source, level::level_enum lvl, const FormatString &fmt, Args &&...args)
{
default_logger_raw()->log(source, lvl, fmt, args...);
default_logger_raw()->log(source, lvl, fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
inline void log(level::level_enum lvl, const FormatString &fmt, const Args &... args)
inline void log(level::level_enum lvl, const FormatString &fmt, Args &&...args)
{
default_logger_raw()->log(source_loc{}, lvl, fmt, args...);
default_logger_raw()->log(source_loc{}, lvl, fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
inline void trace(const FormatString &fmt, const Args &... args)
inline void trace(const FormatString &fmt, Args &&...args)
{
default_logger_raw()->trace(fmt, args...);
default_logger_raw()->trace(fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
inline void debug(const FormatString &fmt, const Args &... args)
inline void debug(const FormatString &fmt, Args &&...args)
{
default_logger_raw()->debug(fmt, args...);
default_logger_raw()->debug(fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
inline void info(const FormatString &fmt, const Args &... args)
inline void info(const FormatString &fmt, Args &&...args)
{
default_logger_raw()->info(fmt, args...);
default_logger_raw()->info(fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
inline void warn(const FormatString &fmt, const Args &... args)
inline void warn(const FormatString &fmt, Args &&...args)
{
default_logger_raw()->warn(fmt, args...);
default_logger_raw()->warn(fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
inline void error(const FormatString &fmt, const Args &... args)
inline void error(const FormatString &fmt, Args &&...args)
{
default_logger_raw()->error(fmt, args...);
default_logger_raw()->error(fmt, std::forward<Args>(args)...);
}
template<typename FormatString, typename... Args>
inline void critical(const FormatString &fmt, const Args &... args)
inline void critical(const FormatString &fmt, Args &&...args)
{
default_logger_raw()->critical(fmt, args...);
default_logger_raw()->critical(fmt, std::forward<Args>(args)...);
}
template<typename T>

View File

@@ -5,7 +5,7 @@
#include <spdlog/fmt/fmt.h>
// Stopwatch support for spdlog (using std::chrono::high_resolution_clock).
// Stopwatch support for spdlog (using std::chrono::steady_clock).
// Displays elapsed seconds since construction as double.
//
// Usage:
@@ -27,7 +27,7 @@
namespace spdlog {
class stopwatch
{
using clock = std::chrono::high_resolution_clock;
using clock = std::chrono::steady_clock;
std::chrono::time_point<clock> start_tp_;
public:

View File

@@ -58,6 +58,14 @@
// #define SPDLOG_EOL ";-)\n"
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment to override default folder separators ("/" or "\\/" under
// Linux/Windows). Each character in the string is treated as a different
// separator.
//
// #define SPDLOG_FOLDER_SEPS "\\"
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment to use your own copy of the fmt library instead of spdlog's copy.
// In this case spdlog will try to include <fmt/format.h> so set your -I flag
@@ -79,7 +87,7 @@
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment to customize level names (e.g. "MT TRACE")
// Uncomment to customize level names (e.g. "MY TRACE")
//
// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING",
// "MY ERROR", "MY CRITICAL", "OFF" }

View File

@@ -4,7 +4,7 @@
#pragma once
#define SPDLOG_VER_MAJOR 1
#define SPDLOG_VER_MINOR 8
#define SPDLOG_VER_MINOR 9
#define SPDLOG_VER_PATCH 0
#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH)

View File

@@ -14,4 +14,4 @@ with open(config_h, 'r') as fp:
if m:
data[m.group(1)] = int(m.group(2))
print('{}.{}.{}'.format(data['MAJOR'], data['MINOR'], data['PATCH']))
print(f"{data['MAJOR']}.{data['MINOR']}.{data['PATCH']}")

View File

@@ -23,9 +23,10 @@ int format_float(char *buf, std::size_t size, const char *format, int precision,
int (*snprintf_ptr)(char *, size_t, const char *, ...) = FMT_SNPRINTF;
return precision < 0 ? snprintf_ptr(buf, size, format, value) : snprintf_ptr(buf, size, format, precision, value);
}
} // namespace detail
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x) FMT_NOEXCEPT;
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x) FMT_NOEXCEPT;
} // namespace detail
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, detail::float_specs, detail::buffer<char> &) = detail::format_float;
@@ -37,14 +38,16 @@ template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
// Explicit instantiations for char.
template FMT_API std::string detail::grouping_impl<char>(locale_ref);
template FMT_API char detail::thousands_sep_impl(locale_ref);
template FMT_API auto detail::thousands_sep_impl(locale_ref) -> thousands_sep_result<char>;
template FMT_API char detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<char>::append(const char *, const char *);
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
detail::buffer<char> &, string_view, basic_format_args<FMT_BUFFER_CONTEXT(char)>);
// DEPRECATED!
// There is no correspondent extern template in format.h because of
// incompatibility between clang and gcc (#2377).
template FMT_API void detail::vformat_to(
detail::buffer<char> &, string_view, basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
template FMT_API int detail::snprintf_float(double, int, detail::float_specs, detail::buffer<char> &);
template FMT_API int detail::snprintf_float(long double, int, detail::float_specs, detail::buffer<char> &);
@@ -53,11 +56,13 @@ template FMT_API int detail::format_float(long double, int, detail::float_specs,
// Explicit instantiations for wchar_t.
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
template FMT_API auto detail::thousands_sep_impl(locale_ref) -> thousands_sep_result<wchar_t>;
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t *, const wchar_t *);
template struct detail::basic_data<void>;
FMT_END_NAMESPACE
#endif // !SPDLOG_FMT_EXTERNAL

View File

@@ -3,6 +3,8 @@
#include "spdlog/sinks/basic_file_sink.h"
#include "test_sink.h"
#define TEST_FILENAME "test_logs/async_test.log"
TEST_CASE("basic async test ", "[async]")
{
auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>();
@@ -87,7 +89,7 @@ TEST_CASE("async periodic flush", "[async]")
auto test_sink = std::static_pointer_cast<spdlog::sinks::test_sink_mt>(logger->sinks()[0]);
spdlog::flush_every(std::chrono::seconds(1));
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
std::this_thread::sleep_for(std::chrono::milliseconds(1700));
REQUIRE(test_sink->flush_counter() == 1);
spdlog::flush_every(std::chrono::seconds(0));
spdlog::drop_all();
@@ -149,7 +151,7 @@ TEST_CASE("to_file", "[async]")
prepare_logdir();
size_t messages = 1024;
size_t tp_threads = 1;
std::string filename = "test_logs/async_test.log";
spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME);
{
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true);
auto tp = std::make_shared<spdlog::details::thread_pool>(messages, tp_threads);
@@ -161,8 +163,8 @@ TEST_CASE("to_file", "[async]")
}
}
require_message_count(filename, messages);
auto contents = file_contents(filename);
require_message_count(TEST_FILENAME, messages);
auto contents = file_contents(TEST_FILENAME);
using spdlog::details::os::default_eol;
REQUIRE(ends_with(contents, fmt::format("Hello message #1023{}", default_eol)));
}
@@ -172,7 +174,7 @@ TEST_CASE("to_file multi-workers", "[async]")
prepare_logdir();
size_t messages = 1024 * 10;
size_t tp_threads = 10;
std::string filename = "test_logs/async_test.log";
spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME);
{
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true);
auto tp = std::make_shared<spdlog::details::thread_pool>(messages, tp_threads);
@@ -184,5 +186,5 @@ TEST_CASE("to_file multi-workers", "[async]")
}
}
require_message_count(filename, messages);
require_message_count(TEST_FILENAME, messages);
}

View File

@@ -48,7 +48,7 @@ TEST_CASE("bactrace-async", "[bactrace]")
for (int i = 0; i < 100; i++)
logger->debug("debug message {}", i);
sleep_for_millis(10);
sleep_for_millis(100);
REQUIRE(test_sink->lines().size() == 1);
REQUIRE(test_sink->lines()[0] == "info message");

View File

@@ -1,3 +1,4 @@
#include "includes.h"
#include "test_sink.h"
@@ -41,21 +42,23 @@ TEST_CASE("argv2", "[cfg]")
auto l1 = spdlog::create<test_sink_st>("l1");
REQUIRE(l1->level() == spdlog::level::warn);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace);
spdlog::set_level(spdlog::level::info);
}
TEST_CASE("argv3", "[cfg]")
{
spdlog::set_level(spdlog::level::trace);
spdlog::drop("l1");
const char *argv[] = {"ignore", "SPDLOG_LEVEL="};
const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk_name=warn"};
load_argv_levels(2, argv);
auto l1 = spdlog::create<test_sink_st>("l1");
REQUIRE(l1->level() == spdlog::level::info);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
REQUIRE(l1->level() == spdlog::level::trace);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace);
}
TEST_CASE("argv4", "[cfg]")
{
spdlog::set_level(spdlog::level::info);
spdlog::drop("l1");
const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk"};
load_argv_levels(2, argv);
@@ -65,6 +68,7 @@ TEST_CASE("argv4", "[cfg]")
TEST_CASE("argv5", "[cfg]")
{
spdlog::set_level(spdlog::level::info);
spdlog::drop("l1");
const char *argv[] = {"ignore", "ignore", "SPDLOG_LEVEL=l1=warn,trace"};
load_argv_levels(3, argv);
@@ -91,3 +95,89 @@ TEST_CASE("argv7", "[cfg]")
REQUIRE(spdlog::default_logger()->level() == spdlog::level::err);
spdlog::set_level(spdlog::level::info);
}
TEST_CASE("level-not-set-test1", "[cfg]")
{
spdlog::drop("l1");
const char *argv[] = {"ignore", ""};
load_argv_levels(2, argv);
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
l1->set_level(spdlog::level::trace);
REQUIRE(l1->level() == spdlog::level::trace);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
}
TEST_CASE("level-not-set-test2", "[cfg]")
{
spdlog::drop("l1");
spdlog::drop("l2");
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace"};
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
l1->set_level(spdlog::level::warn);
auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2");
l2->set_level(spdlog::level::warn);
load_argv_levels(2, argv);
REQUIRE(l1->level() == spdlog::level::trace);
REQUIRE(l2->level() == spdlog::level::warn);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
}
TEST_CASE("level-not-set-test3", "[cfg]")
{
spdlog::drop("l1");
spdlog::drop("l2");
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace"};
load_argv_levels(2, argv);
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2");
REQUIRE(l1->level() == spdlog::level::trace);
REQUIRE(l2->level() == spdlog::level::info);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
}
TEST_CASE("level-not-set-test4", "[cfg]")
{
spdlog::drop("l1");
spdlog::drop("l2");
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace,warn"};
load_argv_levels(2, argv);
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2");
REQUIRE(l1->level() == spdlog::level::trace);
REQUIRE(l2->level() == spdlog::level::warn);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::warn);
}
TEST_CASE("level-not-set-test5", "[cfg]")
{
spdlog::drop("l1");
spdlog::drop("l2");
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=junk,warn"};
load_argv_levels(2, argv);
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2");
REQUIRE(l1->level() == spdlog::level::warn);
REQUIRE(l2->level() == spdlog::level::warn);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::warn);
}
TEST_CASE("restore-to-default", "[cfg]")
{
spdlog::drop("l1");
spdlog::drop("l2");
const char *argv[] = {"ignore", "SPDLOG_LEVEL=info"};
load_argv_levels(2, argv);
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
}

View File

@@ -6,7 +6,7 @@
using spdlog::details::os::create_dir;
using spdlog::details::os::path_exists;
bool try_create_dir(const char *path, const char *normalized_path)
bool try_create_dir(const spdlog::filename_t &path, const spdlog::filename_t &normalized_path)
{
auto rv = create_dir(path);
REQUIRE(rv == true);
@@ -17,24 +17,25 @@ TEST_CASE("create_dir", "[create_dir]")
{
prepare_logdir();
REQUIRE(try_create_dir("test_logs/dir1/dir1", "test_logs/dir1/dir1"));
REQUIRE(try_create_dir("test_logs/dir1/dir1", "test_logs/dir1/dir1")); // test existing
REQUIRE(try_create_dir("test_logs/dir1///dir2//", "test_logs/dir1/dir2"));
REQUIRE(try_create_dir("./test_logs/dir1/dir3", "test_logs/dir1/dir3"));
REQUIRE(try_create_dir("test_logs/../test_logs/dir1/dir4", "test_logs/dir1/dir4"));
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir1"), SPDLOG_FILENAME_T("test_logs/dir1/dir1")));
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir1"), SPDLOG_FILENAME_T("test_logs/dir1/dir1"))); // test existing
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1///dir2//"), SPDLOG_FILENAME_T("test_logs/dir1/dir2")));
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("./test_logs/dir1/dir3"), SPDLOG_FILENAME_T("test_logs/dir1/dir3")));
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/../test_logs/dir1/dir4"), SPDLOG_FILENAME_T("test_logs/dir1/dir4")));
#ifdef WIN32
// test backslash folder separator
REQUIRE(try_create_dir("test_logs\\dir1\\dir222", "test_logs\\dir1\\dir222"));
REQUIRE(try_create_dir("test_logs\\dir1\\dir223\\", "test_logs\\dir1\\dir223\\"));
REQUIRE(try_create_dir(".\\test_logs\\dir1\\dir2\\dir99\\..\\dir23", "test_logs\\dir1\\dir2\\dir23"));
REQUIRE(try_create_dir("test_logs\\..\\test_logs\\dir1\\dir5", "test_logs\\dir1\\dir5"));
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\dir1\\dir222"), SPDLOG_FILENAME_T("test_logs\\dir1\\dir222")));
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\dir1\\dir223\\"), SPDLOG_FILENAME_T("test_logs\\dir1\\dir223\\")));
REQUIRE(
try_create_dir(SPDLOG_FILENAME_T(".\\test_logs\\dir1\\dir2\\dir99\\..\\dir23"), SPDLOG_FILENAME_T("test_logs\\dir1\\dir2\\dir23")));
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\..\\test_logs\\dir1\\dir5"), SPDLOG_FILENAME_T("test_logs\\dir1\\dir5")));
#endif
}
TEST_CASE("create_invalid_dir", "[create_dir]")
{
REQUIRE(create_dir("") == false);
REQUIRE(create_dir(SPDLOG_FILENAME_T("")) == false);
REQUIRE(create_dir(spdlog::filename_t{}) == false);
#ifdef __linux__
REQUIRE(create_dir("/proc/spdlog-utest") == false);
@@ -44,37 +45,31 @@ TEST_CASE("create_invalid_dir", "[create_dir]")
TEST_CASE("dir_name", "[create_dir]")
{
using spdlog::details::os::dir_name;
REQUIRE(dir_name("").empty());
REQUIRE(dir_name("dir").empty());
REQUIRE(dir_name(SPDLOG_FILENAME_T("")).empty());
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir")).empty());
#ifdef WIN32
REQUIRE(dir_name(R"(dir\)") == "dir");
REQUIRE(dir_name(R"(dir\\\)") == R"(dir\\)");
REQUIRE(dir_name(R"(dir\file)") == "dir");
REQUIRE(dir_name(R"(dir/file)") == "dir");
REQUIRE(dir_name(R"(dir\file.txt)") == "dir");
REQUIRE(dir_name(R"(dir/file)") == "dir");
REQUIRE(dir_name(R"(dir\file.txt\)") == R"(dir\file.txt)");
REQUIRE(dir_name(R"(dir/file.txt/)") == R"(dir\file.txt)");
REQUIRE(dir_name(R"(\dir\file.txt)") == R"(\dir)");
REQUIRE(dir_name(R"(/dir/file.txt)") == R"(\dir)");
REQUIRE(dir_name(R"(\\dir\file.txt)") == R"(\\dir)");
REQUIRE(dir_name(R"(//dir/file.txt)") == R"(\\dir)");
REQUIRE(dir_name(R"(..\file.txt)") == "..");
REQUIRE(dir_name(R"(../file.txt)") == "..");
REQUIRE(dir_name(R"(.\file.txt)") == ".");
REQUIRE(dir_name(R"(./file.txt)") == ".");
REQUIRE(dir_name(R"(c:\\a\b\c\d\file.txt)") == R"(c:\\a\b\c\d)");
REQUIRE(dir_name(R"(c://a/b/c/d/file.txt)") == R"(c:\\a\b\c\d)");
#else
REQUIRE(dir_name("dir/") == "dir");
REQUIRE(dir_name("dir///") == "dir//");
REQUIRE(dir_name("dir/file") == "dir");
REQUIRE(dir_name("dir/file.txt") == "dir");
REQUIRE(dir_name("dir/file.txt/") == "dir/file.txt");
REQUIRE(dir_name("/dir/file.txt") == "/dir");
REQUIRE(dir_name("//dir/file.txt") == "//dir");
REQUIRE(dir_name("../file.txt") == "..");
REQUIRE(dir_name("./file.txt") == ".");
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\)")) == SPDLOG_FILENAME_T("dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\\\)")) == SPDLOG_FILENAME_T(R"(dir\\)"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file)")) == SPDLOG_FILENAME_T("dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir/file)")) == SPDLOG_FILENAME_T("dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file.txt)")) == SPDLOG_FILENAME_T("dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir/file)")) == SPDLOG_FILENAME_T("dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file.txt\)")) == SPDLOG_FILENAME_T(R"(dir\file.txt)"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(\dir\file.txt)")) == SPDLOG_FILENAME_T(R"(\dir)"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(\\dir\file.txt)")) == SPDLOG_FILENAME_T(R"(\\dir)"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(..\file.txt)")) == SPDLOG_FILENAME_T(".."));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(.\file.txt)")) == SPDLOG_FILENAME_T("."));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(c:\\a\b\c\d\file.txt)")) == SPDLOG_FILENAME_T(R"(c:\\a\b\c\d)"));
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(c://a/b/c/d/file.txt)")) == SPDLOG_FILENAME_T(R"(c://a/b/c/d)"));
#endif
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/")) == SPDLOG_FILENAME_T("dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir///")) == SPDLOG_FILENAME_T("dir//"));
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file")) == SPDLOG_FILENAME_T("dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file.txt")) == SPDLOG_FILENAME_T("dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file.txt/")) == SPDLOG_FILENAME_T("dir/file.txt"));
REQUIRE(dir_name(SPDLOG_FILENAME_T("/dir/file.txt")) == SPDLOG_FILENAME_T("/dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T("//dir/file.txt")) == SPDLOG_FILENAME_T("//dir"));
REQUIRE(dir_name(SPDLOG_FILENAME_T("../file.txt")) == SPDLOG_FILENAME_T(".."));
REQUIRE(dir_name(SPDLOG_FILENAME_T("./file.txt")) == SPDLOG_FILENAME_T("."));
}

View File

@@ -3,6 +3,8 @@
*/
#include "includes.h"
using filename_memory_buf_t = fmt::basic_memory_buffer<spdlog::filename_t::value_type, 250>;
TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
{
using sink_type = spdlog::sinks::daily_file_sink<std::mutex, spdlog::sinks::daily_filename_calculator>;
@@ -10,10 +12,11 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
prepare_logdir();
// calculate filename (time based)
std::string basename = "test_logs/daily_dateonly";
spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly");
std::tm tm = spdlog::details::os::localtime();
spdlog::memory_buf_t w;
fmt::format_to(w, "{}_{:04d}-{:02d}-{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
filename_memory_buf_t w;
fmt::format_to(
std::back_inserter(w), SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
auto logger = spdlog::create<sink_type>("logger", basename, 0, 0);
for (int i = 0; i < 10; ++i)
@@ -23,7 +26,13 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
}
logger->flush();
#ifdef SPDLOG_WCHAR_FILENAMES
spdlog::memory_buf_t buf;
spdlog::details::os::wstr_to_utf8buf(fmt::to_string(w), buf);
auto filename = fmt::to_string(buf);
#else
auto filename = fmt::to_string(w);
#endif
require_message_count(filename, 10);
}
@@ -31,8 +40,9 @@ struct custom_daily_file_name_calculator
{
static spdlog::filename_t calc_filename(const spdlog::filename_t &basename, const tm &now_tm)
{
spdlog::memory_buf_t w;
fmt::format_to(w, "{}{:04d}{:02d}{:02d}", basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday);
filename_memory_buf_t w;
fmt::format_to(std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1,
now_tm.tm_mday);
return fmt::to_string(w);
}
};
@@ -44,10 +54,11 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
prepare_logdir();
// calculate filename (time based)
std::string basename = "test_logs/daily_dateonly";
spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly");
std::tm tm = spdlog::details::os::localtime();
spdlog::memory_buf_t w;
fmt::format_to(w, "{}{:04d}{:02d}{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
filename_memory_buf_t w;
fmt::format_to(
std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
auto logger = spdlog::create<sink_type>("logger", basename, 0, 0);
for (int i = 0; i < 10; ++i)
@@ -57,7 +68,13 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
logger->flush();
#ifdef SPDLOG_WCHAR_FILENAMES
spdlog::memory_buf_t buf;
spdlog::details::os::wstr_to_utf8buf(fmt::to_string(w), buf);
auto filename = fmt::to_string(buf);
#else
auto filename = fmt::to_string(w);
#endif
require_message_count(filename, 10);
}
@@ -67,20 +84,20 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
TEST_CASE("rotating_file_sink::calc_filename1", "[rotating_file_sink]]")
{
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 3);
REQUIRE(filename == "rotated.3.txt");
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated.txt"), 3);
REQUIRE(filename == SPDLOG_FILENAME_T("rotated.3.txt"));
}
TEST_CASE("rotating_file_sink::calc_filename2", "[rotating_file_sink]]")
{
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated", 3);
REQUIRE(filename == "rotated.3");
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated"), 3);
REQUIRE(filename == SPDLOG_FILENAME_T("rotated.3"));
}
TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]]")
{
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 0);
REQUIRE(filename == "rotated.txt");
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated.txt"), 0);
REQUIRE(filename == SPDLOG_FILENAME_T("rotated.txt"));
}
// regex supported only from gcc 4.9 and above
@@ -91,10 +108,12 @@ TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]]")
TEST_CASE("daily_file_sink::daily_filename_calculator", "[daily_file_sink]]")
{
// daily_YYYY-MM-DD_hh-mm.txt
auto filename = spdlog::sinks::daily_filename_calculator::calc_filename("daily.txt", spdlog::details::os::localtime());
auto filename =
spdlog::sinks::daily_filename_calculator::calc_filename(SPDLOG_FILENAME_T("daily.txt"), spdlog::details::os::localtime());
// date regex based on https://www.regular-expressions.info/dates.html
std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\.txt$)");
std::smatch match;
std::basic_regex<spdlog::filename_t::value_type> re(
SPDLOG_FILENAME_T(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\.txt$)"));
std::match_results<spdlog::filename_t::const_iterator> match;
REQUIRE(std::regex_match(filename, match, re));
}
#endif
@@ -116,7 +135,7 @@ static void test_rotate(int days_to_run, uint16_t max_days, uint16_t expected_n_
prepare_logdir();
std::string basename = "test_logs/daily_rotate.txt";
spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_rotate.txt");
daily_file_sink_st sink{basename, 2, 30, true, max_days};
// simulate messages with 24 intervals

View File

@@ -77,6 +77,7 @@ TEST_CASE("dup_filter_test5", "[dup_filter_sink]")
dup_filter_sink_mt dup_sink{std::chrono::seconds{5}};
auto test_sink = std::make_shared<test_sink_mt>();
test_sink->set_pattern("%v");
dup_sink.add_sink(test_sink);
dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"});
@@ -85,4 +86,5 @@ TEST_CASE("dup_filter_test5", "[dup_filter_sink]")
dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message2"});
REQUIRE(test_sink->msg_counter() == 3); // skip 2 messages but log the "skipped.." message before message2
REQUIRE(test_sink->lines()[1] == "Skipped 2 duplicate messages..");
}

View File

@@ -5,6 +5,9 @@
#include <iostream>
#define SIMPLE_LOG "test_logs/simple_log.txt"
#define SIMPLE_ASYNC_LOG "test_logs/simple_async_log.txt"
class failing_sink : public spdlog::sinks::base_sink<std::mutex>
{
protected:
@@ -22,7 +25,7 @@ protected:
TEST_CASE("default_error_handler", "[errors]]")
{
prepare_logdir();
std::string filename = "test_logs/simple_log.txt";
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("test-error", filename, true);
logger->set_pattern("%v");
@@ -31,8 +34,8 @@ TEST_CASE("default_error_handler", "[errors]]")
logger->flush();
using spdlog::details::os::default_eol;
REQUIRE(file_contents(filename) == fmt::format("Test message 2{}", default_eol));
REQUIRE(count_lines(filename) == 1);
REQUIRE(file_contents(SIMPLE_LOG) == fmt::format("Test message 2{}", default_eol));
REQUIRE(count_lines(SIMPLE_LOG) == 1);
}
struct custom_ex
@@ -40,7 +43,7 @@ struct custom_ex
TEST_CASE("custom_error_handler", "[errors]]")
{
prepare_logdir();
std::string filename = "test_logs/simple_log.txt";
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename, true);
logger->flush_on(spdlog::level::info);
logger->set_error_handler([=](const std::string &) { throw custom_ex(); });
@@ -48,7 +51,7 @@ TEST_CASE("custom_error_handler", "[errors]]")
REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex);
logger->info("Good message #2");
require_message_count(filename, 2);
require_message_count(SIMPLE_LOG, 2);
}
TEST_CASE("default_error_handler2", "[errors]]")
@@ -72,7 +75,7 @@ TEST_CASE("async_error_handler", "[errors]]")
prepare_logdir();
std::string err_msg("log failed with some msg");
std::string filename = "test_logs/simple_async_log.txt";
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_ASYNC_LOG);
{
spdlog::init_thread_pool(128, 1);
auto logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("logger", filename, true);
@@ -90,7 +93,7 @@ TEST_CASE("async_error_handler", "[errors]]")
spdlog::drop("logger"); // force logger to drain the queue and shutdown
}
spdlog::init_thread_pool(128, 1);
require_message_count(filename, 2);
require_message_count(SIMPLE_ASYNC_LOG, 2);
REQUIRE(file_contents("test_logs/custom_err.txt") == err_msg);
}
@@ -100,7 +103,7 @@ TEST_CASE("async_error_handler2", "[errors]]")
prepare_logdir();
std::string err_msg("This is async handler error message");
{
spdlog::details::os::create_dir("test_logs");
spdlog::details::os::create_dir(SPDLOG_FILENAME_T("test_logs"));
spdlog::init_thread_pool(128, 1);
auto logger = spdlog::create_async<failing_sink>("failed_logger");
logger->set_error_handler([=](const std::string &) {

View File

@@ -24,20 +24,20 @@ static void test_single_print(std::function<void(std::string const &)> do_log, s
REQUIRE(CloseEventLog(handle_));
}
}
} event_log{::OpenEventLog(nullptr, TEST_SOURCE)};
} event_log{::OpenEventLogA(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);
auto ok = ::ReadEventLogA(
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(
ok = ::ReadEventLogA(
event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, record, size_needed, &read_bytes, &size_needed);
REQUIRE(ok);

View File

@@ -3,12 +3,14 @@
*/
#include "includes.h"
#define TEST_FILENAME "test_logs/file_helper_test.txt"
using spdlog::details::file_helper;
static void write_with_helper(file_helper &helper, size_t howmany)
{
spdlog::memory_buf_t formatted;
fmt::format_to(formatted, "{}", std::string(howmany, '1'));
fmt::format_to(std::back_inserter(formatted), "{}", std::string(howmany, '1'));
helper.write(formatted);
helper.flush();
}
@@ -18,7 +20,7 @@ TEST_CASE("file_helper_filename", "[file_helper::filename()]]")
prepare_logdir();
file_helper helper;
std::string target_filename = "test_logs/file_helper_test.txt";
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
helper.open(target_filename);
REQUIRE(helper.filename() == target_filename);
}
@@ -26,7 +28,7 @@ TEST_CASE("file_helper_filename", "[file_helper::filename()]]")
TEST_CASE("file_helper_size", "[file_helper::size()]]")
{
prepare_logdir();
std::string target_filename = "test_logs/file_helper_test.txt";
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
size_t expected_size = 123;
{
file_helper helper;
@@ -34,13 +36,13 @@ TEST_CASE("file_helper_size", "[file_helper::size()]]")
write_with_helper(helper, expected_size);
REQUIRE(static_cast<size_t>(helper.size()) == expected_size);
}
REQUIRE(get_filesize(target_filename) == expected_size);
REQUIRE(get_filesize(TEST_FILENAME) == expected_size);
}
TEST_CASE("file_helper_reopen", "[file_helper::reopen()]]")
{
prepare_logdir();
std::string target_filename = "test_logs/file_helper_test.txt";
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
file_helper helper;
helper.open(target_filename);
write_with_helper(helper, 12);
@@ -52,7 +54,7 @@ TEST_CASE("file_helper_reopen", "[file_helper::reopen()]]")
TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]")
{
prepare_logdir();
std::string target_filename = "test_logs/file_helper_test.txt";
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
size_t expected_size = 14;
file_helper helper;
helper.open(target_filename);
@@ -62,16 +64,13 @@ TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]")
REQUIRE(helper.size() == expected_size);
}
static void test_split_ext(const char *fname, const char *expect_base, const char *expect_ext)
static void test_split_ext(const spdlog::filename_t::value_type *fname, const spdlog::filename_t::value_type *expect_base,
const spdlog::filename_t::value_type *expect_ext)
{
spdlog::filename_t filename(fname);
spdlog::filename_t expected_base(expect_base);
spdlog::filename_t expected_ext(expect_ext);
#ifdef _WIN32 // replace folder sep
std::replace(filename.begin(), filename.end(), '/', '\\');
std::replace(expected_base.begin(), expected_base.end(), '/', '\\');
#endif
spdlog::filename_t basename;
spdlog::filename_t ext;
std::tie(basename, ext) = file_helper::split_by_extension(filename);
@@ -81,22 +80,22 @@ static void test_split_ext(const char *fname, const char *expect_base, const cha
TEST_CASE("file_helper_split_by_extension", "[file_helper::split_by_extension()]]")
{
test_split_ext("mylog.txt", "mylog", ".txt");
test_split_ext(".mylog.txt", ".mylog", ".txt");
test_split_ext(".mylog", ".mylog", "");
test_split_ext("/aaa/bb.d/mylog", "/aaa/bb.d/mylog", "");
test_split_ext("/aaa/bb.d/mylog.txt", "/aaa/bb.d/mylog", ".txt");
test_split_ext("aaa/bbb/ccc/mylog.txt", "aaa/bbb/ccc/mylog", ".txt");
test_split_ext("aaa/bbb/ccc/mylog.", "aaa/bbb/ccc/mylog.", "");
test_split_ext("aaa/bbb/ccc/.mylog.txt", "aaa/bbb/ccc/.mylog", ".txt");
test_split_ext("/aaa/bbb/ccc/mylog.txt", "/aaa/bbb/ccc/mylog", ".txt");
test_split_ext("/aaa/bbb/ccc/.mylog", "/aaa/bbb/ccc/.mylog", "");
test_split_ext("../mylog.txt", "../mylog", ".txt");
test_split_ext(".././mylog.txt", ".././mylog", ".txt");
test_split_ext(".././mylog.txt/xxx", ".././mylog.txt/xxx", "");
test_split_ext("/mylog.txt", "/mylog", ".txt");
test_split_ext("//mylog.txt", "//mylog", ".txt");
test_split_ext("", "", "");
test_split_ext(".", ".", "");
test_split_ext("..txt", ".", ".txt");
test_split_ext(SPDLOG_FILENAME_T("mylog.txt"), SPDLOG_FILENAME_T("mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T(".mylog.txt"), SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T(""));
test_split_ext(SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), SPDLOG_FILENAME_T(""));
test_split_ext(SPDLOG_FILENAME_T("/aaa/bb.d/mylog.txt"), SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog.txt"), SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog."), SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog."), SPDLOG_FILENAME_T(""));
test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/.mylog.txt"), SPDLOG_FILENAME_T("aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T("/aaa/bbb/ccc/mylog.txt"), SPDLOG_FILENAME_T("/aaa/bbb/ccc/mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T("/aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T("/aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T(""));
test_split_ext(SPDLOG_FILENAME_T("../mylog.txt"), SPDLOG_FILENAME_T("../mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T(".././mylog.txt"), SPDLOG_FILENAME_T(".././mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T(".././mylog.txt/xxx"), SPDLOG_FILENAME_T(".././mylog.txt/xxx"), SPDLOG_FILENAME_T(""));
test_split_ext(SPDLOG_FILENAME_T("/mylog.txt"), SPDLOG_FILENAME_T("/mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T("//mylog.txt"), SPDLOG_FILENAME_T("//mylog"), SPDLOG_FILENAME_T(".txt"));
test_split_ext(SPDLOG_FILENAME_T(""), SPDLOG_FILENAME_T(""), SPDLOG_FILENAME_T(""));
test_split_ext(SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(""));
test_split_ext(SPDLOG_FILENAME_T("..txt"), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(".txt"));
}

View File

@@ -3,10 +3,13 @@
*/
#include "includes.h"
#define SIMPLE_LOG "test_logs/simple_log"
#define ROTATING_LOG "test_logs/rotating_log"
TEST_CASE("simple_file_logger", "[simple_logger]]")
{
prepare_logdir();
std::string filename = "test_logs/simple_log";
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
logger->set_pattern("%v");
@@ -15,29 +18,29 @@ TEST_CASE("simple_file_logger", "[simple_logger]]")
logger->info("Test message {}", 2);
logger->flush();
require_message_count(filename, 2);
require_message_count(SIMPLE_LOG, 2);
using spdlog::details::os::default_eol;
REQUIRE(file_contents(filename) == fmt::format("Test message 1{}Test message 2{}", default_eol, default_eol));
REQUIRE(file_contents(SIMPLE_LOG) == fmt::format("Test message 1{}Test message 2{}", default_eol, default_eol));
}
TEST_CASE("flush_on", "[flush_on]]")
{
prepare_logdir();
std::string filename = "test_logs/simple_log";
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
logger->set_pattern("%v");
logger->set_level(spdlog::level::trace);
logger->flush_on(spdlog::level::info);
logger->trace("Should not be flushed");
REQUIRE(count_lines(filename) == 0);
REQUIRE(count_lines(SIMPLE_LOG) == 0);
logger->info("Test message {}", 1);
logger->info("Test message {}", 2);
require_message_count(filename, 3);
require_message_count(SIMPLE_LOG, 3);
using spdlog::details::os::default_eol;
REQUIRE(file_contents(filename) ==
REQUIRE(file_contents(SIMPLE_LOG) ==
fmt::format("Should not be flushed{}Test message 1{}Test message 2{}", default_eol, default_eol, default_eol));
}
@@ -45,7 +48,7 @@ TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
{
prepare_logdir();
size_t max_size = 1024 * 10;
std::string basename = "test_logs/rotating_log";
spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG);
auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 0);
for (int i = 0; i < 10; ++i)
@@ -54,14 +57,14 @@ TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
}
logger->flush();
require_message_count(basename, 10);
require_message_count(ROTATING_LOG, 10);
}
TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
{
prepare_logdir();
size_t max_size = 1024 * 10;
std::string basename = "test_logs/rotating_log";
spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG);
{
// make an initial logger to create the first output file
@@ -83,7 +86,7 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
logger->flush();
require_message_count(basename, 10);
require_message_count(ROTATING_LOG, 10);
for (int i = 0; i < 1000; i++)
{
@@ -92,7 +95,6 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
}
logger->flush();
REQUIRE(get_filesize(basename) <= max_size);
auto filename1 = basename + ".1";
REQUIRE(get_filesize(filename1) <= max_size);
REQUIRE(get_filesize(ROTATING_LOG) <= max_size);
REQUIRE(get_filesize(ROTATING_LOG ".1") <= max_size);
}

View File

@@ -8,11 +8,13 @@
# error "Invalid SPDLOG_ACTIVE_LEVEL in test. Should be SPDLOG_LEVEL_DEBUG"
#endif
#define TEST_FILENAME "test_logs/simple_log"
TEST_CASE("debug and trace w/o format string", "[macros]]")
{
prepare_logdir();
std::string filename = "test_logs/simple_log";
spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME);
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
logger->set_pattern("%v");
@@ -23,8 +25,8 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
logger->flush();
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(ends_with(file_contents(TEST_FILENAME), fmt::format("Test message 2{}", default_eol)));
REQUIRE(count_lines(TEST_FILENAME) == 1);
spdlog::set_default_logger(logger);
@@ -32,8 +34,8 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
SPDLOG_DEBUG("Test message {}", 4);
logger->flush();
require_message_count(filename, 2);
REQUIRE(ends_with(file_contents(filename), fmt::format("Test message 4{}", default_eol)));
require_message_count(TEST_FILENAME, 2);
REQUIRE(ends_with(file_contents(TEST_FILENAME), fmt::format("Test message 4{}", default_eol)));
}
TEST_CASE("disable param evaluation", "[macros]")

View File

@@ -64,7 +64,7 @@ TEST_CASE("color range test1", "[pattern_formatter]")
auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n");
memory_buf_t buf;
fmt::format_to(buf, "Hello");
fmt::format_to(std::back_inserter(buf), "Hello");
memory_buf_t formatted;
std::string logger_name = "test";
spdlog::details::log_msg msg(logger_name, spdlog::level::info, spdlog::string_view_t(buf.data(), buf.size()));
@@ -372,7 +372,7 @@ TEST_CASE("clone-custom_formatter", "[pattern_formatter]")
//
#ifdef _WIN32
static const char *const test_path = "\\a\\b\\myfile.cpp";
static const char *const test_path = "\\a\\b\\c/myfile.cpp";
#else
static const char *const test_path = "/a/b//myfile.cpp";
#endif

View File

@@ -103,7 +103,7 @@ TEST_CASE("disable automatic registration", "[registry]")
spdlog::set_level(log_level);
// but disable automatic registration
spdlog::set_automatic_registration(false);
auto logger1 = spdlog::create<spdlog::sinks::daily_file_sink_st>(tested_logger_name, "filename", 11, 59);
auto logger1 = spdlog::create<spdlog::sinks::daily_file_sink_st>(tested_logger_name, SPDLOG_FILENAME_T("filename"), 11, 59);
auto logger2 = spdlog::create_async<spdlog::sinks::stdout_color_sink_mt>(tested_logger_name2);
// loggers should not be part of the registry
REQUIRE_FALSE(spdlog::get(tested_logger_name));

View File

@@ -1,6 +1,8 @@
#include "includes.h"
#ifndef _WIN32
#ifdef _WIN32
# include <Windows.h>
#else
# include <sys/types.h>
# include <dirent.h>
#endif
@@ -82,7 +84,7 @@ bool ends_with(std::string const &value, std::string const &ending)
std::size_t count_files(const std::string &folder)
{
size_t counter = 0;
WIN32_FIND_DATA ffd;
WIN32_FIND_DATAA ffd;
// Start iterating over the files in the folder directory.
HANDLE hFind = ::FindFirstFileA((folder + "\\*").c_str(), &ffd);
@@ -92,7 +94,7 @@ std::size_t count_files(const std::string &folder)
{
if (ffd.cFileName[0] != '.')
counter++;
} while (::FindNextFile(hFind, &ffd) != 0);
} while (::FindNextFileA(hFind, &ffd) != 0);
::FindClose(hFind);
}
else