Git Product home page Git Product logo

fmtlog's Introduction

fmtlog

fmtlog is a performant asynchronous logging library using fmt library format.

Features

  • Faster - lower runtime latency than NanoLog and higher throughput than spdlog (see Performance below).
  • Headers only or compiled
  • Feature rich formatting on top of excellent fmt library.
  • Asynchronous multi-threaded logging in time order and can also be used synchronously in single thread.
  • Custom formatting
  • Custom handling - user can set a callback function to handle log msgs in addition to writing into file.
  • Log filtering - log levels can be modified in runtime as well as in compile time.
  • Log frequency limitation - specific logs can be set a minimum logging interval.

Platforms

  • Linux (GCC 10.2 tested)
  • Windows (MSVC 2019 tested)

Install

C++17 is required, and fmtlog is dependent on fmtlib, you need to install fmtlib first if you haven't.

Header only version

Just copy fmtlog.h and fmtlog-inl.h to your project, and:

  • Either define macro FMTLOG_HEADER_ONLY before including fmtlog.h.
  • Or include fmtlog-inl.h in one of your source files.

Static/Shared lib version built by CMake

$ git clone https://github.com/MengRao/fmtlog.git
$ cd fmtlog
$ git submodule init
$ git submodule update
$ ./build.sh

Then copy fmtlog.h and libfmtlog-static.a/libfmtlog-shared.so generated in .build dir.

Usage

#include "fmtlog/fmtlog.h"
int main() 
{
  FMTLOG(fmtlog::INF, "The answer is {}.", 42);
}

There're also shortcut macros logd, logi, logw and loge defined for logging DBG, INF, WRN and ERR msgs respectively:

logi("A info msg");
logd("This msg will not be logged as the default log level is INF");
fmtlog::setLogLevel(fmtlog::DBG);
logd("Now debug msg is shown");

Note that fmtlog is asynchronous in nature, msgs are not written into file/console immediately after the log statements: they are simply pushed into a queue. You need to call fmtlog::poll() to collect data from log queues, format and write it out:

fmtlog::setThreadName("aaa");
logi("Thread name is bbb in this msg");
fmtlog::setThreadName("bbb");
fmtlog::poll();
fmtlog::setThreadName("ccc");
logi("Thread name is ccc in this msg");
fmtlog::poll();
fmtlog::setThreadName("ddd");

fmtlog supports multi-threaded logging, but can only have one thread calling fmtlog::poll(). By default, fmtlog doesn't create a polling thread internally, it requires the user to poll it periodically. The idea is that this allows users to manage the threads in their own way, and have full control of polling/flushing behavior. However, you can ask fmtlog to create a background polling thread for you by fmtlog::startPollingThread(interval) with a polling interval, but you can't call fmtlog::poll() yourself when the thread is running.

Format

fmtlog is based on fmtlib, almost all fmtlib features are supported(except for color):

#include "fmt/ranges.h"
using namespace fmt::literals;

logi("I'd rather be {1} than {0}.", "right", "happy");
logi("Hello, {name}! The answer is {number}. Goodbye, {name}.", "name"_a = "World", "number"_a = 42);

std::vector<int> v = {1, 2, 3};
logi("ranges: {}", v);

logi("std::move can be used for objects with non-trivial destructors: {}", std::move(v));
assert(v.size() == 0);

std::tuple<int, char> t = {1, 'a'};
logi("tuples: {}", fmt::join(t, ", "));

enum class color {red, green, blue};
template <> struct fmt::formatter<color>: formatter<string_view> {
  // parse is inherited from formatter<string_view>.
  template <typename FormatContext>
  auto format(color c, FormatContext& ctx) {
    string_view name = "unknown";
    switch (c) {
    case color::red:   name = "red"; break;
    case color::green: name = "green"; break;
    case color::blue:  name = "blue"; break;
    }
    return formatter<string_view>::format(name, ctx);
  }
};
logi("user defined type: {:>10}", color::blue);
logi("{:*^30}", "centered");
logi("int: {0:d};  hex: {0:#x};  oct: {0:#o};  bin: {0:#b}", 42);
logi("dynamic precision: {:.{}f}", 3.14, 1);

// This gives a compile-time error because d is an invalid format specifier for a string.
// FMT_STRING() is not needed from C++20 onward
logi(FMT_STRING("{:d}"), "I am not a number");

As an asynchronous logging library, fmtlog provides additional support for passing arguments by pointers(which is seldom needed for fmtlib and it only supports void and char pointers). User can pass a pointer of any type as argument to avoid copy overhead if the lifetime of referred object is assured(otherwise the polling thread will refer to a dangling pointer!). For string arg as an example, fmtlog copies string content for type std::string by default, but only a pointer for type std::string*:

  std::string str = "aaa";
  logi("str: {}, pstr: {}", str, &str);
  str = "bbb";
  fmtlog::poll();
  // output: str: aaa, pstr: bbb

In addition to raw pointers, fmtlog supports std::shared_ptr and std::unique_ptr as well, which makes object lifetime management much easier:

  int a = 4;
  auto sptr = std::make_shared<int>(5);
  auto uptr = std::make_unique<int>(6);
  logi("void ptr: {}, ptr: {}, sptr: {}, uptr: {}", (void*)&a, &a, sptr, std::move(uptr));
  a = 7;
  *sptr = 8;
  fmtlog::poll();
  // output: void ptr: 0x7ffd08ac53ac, ptr: 7, sptr: 8, uptr: 6

Log header pattern can also be customized with fmtlog::setHeaderPattern() and the argument is a fmtlib format string with named arguments. The default header pattern is "{HMSf} {s:<16} {l}[{t:<6}] " (example: "15:46:19.149844 log_test.cc:43 INF[448050] "). All supported named arguments in header are as below:

Name Meaning Example
l Log level INF
s File base name and line num log_test.cc:48
g File path and line num /home/raomeng/fmtlog/log_test.cc:48
t Thread id by default, can be reset by fmt::setThreadName() main
a Weekday Mon
b Month name May
Y Year 2021
C Short year 21
m Month 05
d Day 03
H Hour 16
M Minute 08
S Second 09
e Millisecond 796
f Microsecond 796341
F Nanosecond 796341126
Ymd Year-Month-Day 2021-05-03
HMS Hour:Minute:Second 16:08:09
HMSe Hour:Minute:Second.Millisecond 16:08:09.796
HMSf Hour:Minute:Second.Microsecond 16:08:09.796341
HMSF Hour:Minute:Second.Nanosecond 16:08:09.796341126
YmdHMS Year-Month-Day Hour:Minute:Second 2021-05-03 16:08:09
YmdHMSe Year-Month-Day Hour:Minute:Second.Millisecond 2021-05-03 16:08:09.796
YmdHMSf Year-Month-Day Hour:Minute:Second.Microsecond 2021-05-03 16:08:09.796341
YmdHMSF Year-Month-Day Hour:Minute:Second.Nanosecond 2021-05-03 16:08:09.796341126

Note that using concatenated named args is more efficient than seperated ones, e.g. {YmdHMS} is faster than {Y}-{m}-{d} {H}:{M}:{S}.

Output

By default, fmtlog output to stdout. Normally users want to write to a log file instead, this is accomplished by fmtlog::setLogFile(filename,truncate). For performance, fmtlog internally buffer data, and under certain conditions will the buffer be flushed into the underlying file. The flushing conditions are:

  • The underlying FILE* is not managed by fmtlog, then fmtlog will not buffer at all. For example, the default stdout FILE* will not be buffered. User can also pass an existing FILE* and indicate whether fmtlog should manage it by fmtlog::setLogFile(fp, manageFp), e.g. fmtlog::setLogFile(stderr, false), then fmtlog will log into stderr without buffering.
  • The buffer size is larger than 8 KB, this number can be reset by fmtlog::setFlushBufSize(bytes).
  • The oldest data in the buffer has passed a specified duration. The duration is by default 3 seconds, and can be set by fmtlog::setFlushDelay(ns).
  • The new log has at least a specified flush log level. The default flush log level can't be reached by any log, but it can be set by fmtlog::flushOn(logLevel).
  • User can actively ask fmtlog to flush by fmtlog::poll(true).

Optionally, user can ask fmtlog to close the log file by fmtlog::closeLogFile(), and subsequent log msgs will not be output.

In addition to writing to a FILE*, user can register a callback function to handle log msgs by fmtlog::setLogCB(cb, minCBLogLevel). This can be useful in circumstances where warning/error msgs need to be published out in real time for alerting purposes. Log callback will not be buffered as log file, and can be triggered even when the file is closed. The signature of callback function is:

  // callback signature user can register
  // ns: nanosecond timestamp
  // level: logLevel
  // location: full file path with line num, e.g: /home/raomeng/fmtlog/fmtlog.h:45
  // basePos: file base index in the location
  // threadName: thread id or the name user set with setThreadName
  // msg: full log msg with header
  // bodyPos: log body index in the msg
  // logFilePos: log file position of this msg
  typedef void (*LogCBFn)(int64_t ns, LogLevel level, fmt::string_view location, size_t basePos,
                          fmt::string_view threadName, fmt::string_view msg, size_t bodyPos, size_t logFilePos);

Performance

Benchmark is done in terms of both front-end latency and throughput, with comparisons to Nanolog and spdlog basic_logger_st. Test log messages use NanoLog benchmark Log-Messages-Map, and header pattern uses spdlog default pattern(e.g. "[2021-05-04 10:36:38.098] [spdlog] [info] [bench.cc:111] "), check bench.cc for details.

The results on a linux server with "Intel(R) Xeon(R) Gold 6144 CPU @ 3.50GHz" is:

Message fmtlog Nanolog spdlog
staticString 6.4 ns, 7.08 M/s 6.5 ns, 33.10 M/s 156.4 ns, 6.37 M/s
stringConcat 6.4 ns, 6.05 M/s 7.5 ns, 14.20 M/s 209.4 ns, 4.77 M/s
singleInteger 6.3 ns, 6.22 M/s 6.5 ns, 50.29 M/s 202.3 ns, 4.94 M/s
twoIntegers 6.4 ns, 4.87 M/s 6.6 ns, 39.25 M/s 257.2 ns, 3.89 M/s
singleDouble 6.2 ns, 5.37 M/s 6.5 ns, 39.62 M/s 225.0 ns, 4.44 M/s
complexFormat 6.4 ns, 2.95 M/s 6.7 ns, 24.30 M/s 390.9 ns, 2.56 M/s

Note that the throughput of Nanolog is not comparable here because it outputs to binary log file instead of human-readable text format, e.g. it saves an int64 timestamp instead of a long formatted date time string.

How can fmtlog achieve such low and stable latency? Two key optimization techniques are employed inspired by Nanolog:

One is allocating a single producer single consumer queue for each logging thread, and have the background thread polling for all these queues. This avoids threads contention and performance will not deteriorate when thread number increases. The queue is automatically created on the first log msg of a thread, so queue is not created for threads that don't use fmtlog. The thread queue has a default size of 1 MB(can be changed by macro FMTLOG_QUEUE_SIZE), and it takes a little time to allocate the queue. It's recommended that user actively calls fmt::preallocate() once the thread is created, so even the first log can have low latency.

What happens when the queue is full? By default, fmtlog simply dump addtional log msgs and return. Alternatively, front-end logging can be blocked while the queue is full by defining macro FMTLOG_BLOCK=1, then no log will be missing. User can register a callback function when log queue is full by fmtlog::setLogQFullCB(cb, userData), by which user can be aware that the consumer(polling thread) is not keeping up. Normally, the queue being full is seldom a problem, but incautious user could leave log statements that are invoked in an unexpected high frequency, e.g. a tcp client spamming with "Connection refused" errors without a connection retry delay. To handle this problem in an elegant way, fmtlog provides a log macro which limits frequency of this log: FMTLOG_LIMIT and 4 shortcuts logdl, logil, logwl and logel respectively, user needs to pass the mininum interval in nanosecond as the first argument, e.g.

logil(1e9, "this log will be displayed at most once per second").

The other optimization is that static information of a log(such as format string, log level and location) is saved in a table at its first call, and fmtlog simply pushes the index of the static info table entry with dynamic arguments to the queue, minimizing the msg size. In addition, fmtlog defines a decoding function for each log statment, which is invoked in fmtlog::poll() when the log msg is popped from the queue.

However, these decoding functions bloat program size with each function consuming around 50 bytes. In addition, the static infomation entry also consumes 50-ish bytes runtime memory for each log statement. Such memory overhead may not be worthwhile for those infrequent and latency insensitive logs(e.g. program initialization info), thus fmtlog provides user with another log macro which disables this optimization: FMTLOG_ONCE and of couse shortcuts: logdo, logio, logwoand logeo. FMTLOG_ONCE will not create a static info table entry, nor add a decoding function: it pushes static info along with formatted msg body onto the queue. Note that passing argument by pointer is not supported by FMTLOG_ONCE.

For those who prefer to further optimize memory usage by filtering log at compile time, macro FMTLOG_ACTIVE_LEVEL is applied with a default value FMTLOG_LEVEL_INF, meaning debug logs will simply be discarded at compile time. Note that FMTLOG_ACTIVE_LEVEL only applies to log shortcut macros, e.g. logi, but not FMTLOG. Similarly, runtime log level filtering can be disabled by defining macro FMTLOG_NO_CHECK_LEVEL, which will increase performance and reduce generated code size a bit.

fmtlog's People

Contributors

javenwang avatar mengrao avatar moonshadow565 avatar timstricker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fmtlog's Issues

Remove Spdlog and Nanolog

Hi,

I am trying to embed library in my C++ project as a submodule. It also brings Nanolog and spdlog as its own submodule. I believe we can avoid that if you create bench as separate repository.

Thanks

关于多线程时间戳乱序问题

采用heap处理多线程时间戳好像有些问题,不知是否是我理解有问题
代码demo如下

#include <vector>
#include <iostream>
struct HeapNode
{
    HeapNode(int t) : cur_time(t) {}
    int cur_time;
};
std::vector<HeapNode> bgThreadBuffers;

void adjustHeap(size_t i)
{
    while (true)
    {
        size_t min_i = i;
        size_t ch = i * 2 + 1;
        size_t end = std::min(ch + 2, bgThreadBuffers.size());
        for (; ch < end; ch++)
        {
            auto h_ch = bgThreadBuffers[ch].cur_time;
            auto h_min = bgThreadBuffers[min_i].cur_time;
            if (h_ch && (!h_min || h_ch < h_min))
                min_i = ch;
        }
        if (min_i == i)
            break;
        std::swap(bgThreadBuffers[i], bgThreadBuffers[min_i]);
        i = min_i;
    }
}
int main()
{
    HeapNode node1(0);
    HeapNode node2(3);
    HeapNode node3(1);
    HeapNode node4(2);
    HeapNode node5(4);
    HeapNode node6(7);
    bgThreadBuffers.push_back(node1);
    bgThreadBuffers.push_back(node2);
    bgThreadBuffers.push_back(node3);
    bgThreadBuffers.push_back(node4);
    bgThreadBuffers.push_back(node5);
    bgThreadBuffers.push_back(node6);

    for (int i = bgThreadBuffers.size() / 2; i >= 0; i--)
    {
        adjustHeap(i);
    }
    std::cout << "--------------end-----------------" << std::endl;
    for (auto node : bgThreadBuffers)
    {
        std::cout << node.cur_time << " ";
    }
    std::cout << std::endl;
}

输出结果为1 2 7 3 4 0
请大佬指教

Cmake error m1 MacBook

In file included from fmtlog.cc:1:
In file included from ./fmtlog-inl.h:24:
./fmtlog.h:272:14: error: use of undeclared identifier '__builtin_ia32_rdtsc'
return __builtin_ia32_rdtsc();
^
1 error generated.

BufSize setting

Fmtlog has achieved such a low latency yet with a much smaller throughput comparing to nanalog. I noticed that fmtlog is simply using the default BUFSIZE setting by the system (the default value is 8K), which could be the bottleneck to the overall throughput. It is one issue for spdlog as well.

Please checkout the issue in spdlog: gabime/spdlog#1799. We can see that a reasonable BUFSIZ setting can double the throughput.

替换不安全的C代码吧

通过VisualStudio使用C++17/C17标准编译时,报了以下错误:

fmtlog-inl.h(564,5): error C4996: 'setbuf': This function or variable may be unsafe. Consider using setvbuf instead.
fmtlog-inl.h(546,17): error C4996: 'fopen': This function or variable may be unsafe. Consider using fopen_s instead.
fmtlog-inl.h(548,76): error C4996: 'strerror': This function or variable may be unsafe. Consider using strerror_s instead.
fmtlog-inl.h(276,27): error C4996: 'localtime': This function or variable may be unsafe. Consider using localtime_s instead.

Could you plz add a new feature? Rotate every midnight.

Our team is developing a server software which is running 7x24. And we want the log rotate every midnight so it's easy to be analyzed. We dont need it rotate precisely 00:00:00. When log flush check the datetime to rotate.
There is a easy way. Add a callback in the flush function to user. Let user do it.

Make CMakeLists.txt compatible with FetchContent

I'm working on revising the fmtlog CMakeLists.txt to work when calling FetchContent from another parent CMake project. This allows people working with CMake to not have to know about git submodules when building a fresh project that has fmtlog as a dependency.

This mainly involves having fmtlog call FetchContent on fmtlib, to not build the tests when being built as a subproject, and to optionally build either the static or shared libraries.

Would you like a PR once it's cleaned up?

Building on Win32 / Visual Studio generates a .dll with no exports

Hi!
I succesfuly built fmtlog with CMake on a Windows 10 box, with CMake 3.25.0 and Visual Studio 2019.
Targets fmtlog-static.lib and fmtlog-shared.dll are generated.
Tests targets enc_dec_test.exe, log_test.exe, multithread_test.exe are generated linking against the static library.

But:

fmtlog-shared.dll has no exported symbols, because they are actually not exported i.e. no FMTLOG_API that resolves to __declspec(dllimport) / __declspec(dllexport), with some FMTLOG_EXPORTS compile symbol defined when building the DLL.
(So there is no fmtlog-shared.lib because no symbol is exported)

I know... Stupid Windows linker, GNU ld does not need this...

请问log_test编译报错,如何解决

你好,在win平台vs2019编译64位,log_test工程编译会出现如下错误:
1>\fmtlog\test../fmtlog.h(657,1): error C3861: “runtime”: 找不到标识符
1>\fmtlog\test../fmtlog.h(674,43): error C2039: "runtime": 不是 "fmt" 的成员
1>\fmtlog\fmt\include\fmt/format.h(266): message : 参见“fmt”的声明
1>\fmtlog\test../fmtlog.h(657,1): warning C4267: “参数”: 从“size_t”转换到“uint32_t”,可能丢失数据
1>已完成生成项目“log_test.vcxproj”的操作 - 失败。

double free or corruption (!prev) Aborted (core dumped)

Hello,

I would like to say thanks for the library, it is nice seeing someone else with interest in those things.
I am maintaining a repo of logger benchmarks. I have tried to add your logger there.

Your logger is performing very well, if you would like to see how it does compared to other loggers you can have a look at the results here

It runs fine when 1 thread is logging but at the end it terminates and a exception in thrown.

I was unable to benchmark 4 threads pushing logs at the same time as the logger crashed for the same reason

double free or corruption (!prev)
double free or corruption (out)

I am compiling on ubuntu with gcc 11.2 in release mode.

To reproduce you can try to run the benchmark here

fmtlog::setLogLevel(fmtlog::DBG);没有生效

使用了说明里的代码:
logi("A info msg"); logd("This msg will not be logged as the default log level is INF"); fmtlog::setLogLevel(fmtlog::DBG); logd("Now debug msg is shown"); fmtlog::poll();
结果输出中,依然仅仅输出了A info msg,而没有输出Now debug msg is shown

Does not compile with clang

you can try in compiler explorer :
https://godbolt.org/z/c68azzKvj

I get :

Output of x86-64 clang (trunk) (Compiler #1)
<source>:579:29: error: variable of non-literal type 'std::unique_ptr<char[]>' cannot be defined in a constexpr function
    std::unique_ptr<char[]> unnamed_str(new char[in.size() + 1 + num_named_args * 5]);
                            ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/12.0.0/../../../../include/c++/12.0.0/bits/unique_ptr.h:616:7: note: 'unique_ptr<char[]>' is not literal because it has a user-provided destructor
      ~unique_ptr()
      ^
1 error generated.
Compiler returned: 1

What I've done :

  • Copy paste fmtlog.h

  • Add a main as :

int main()
{
    FMTLOG(fmtlog::INF, "The answer is {}.", 42);
    return 0;
}
  • Adding fmt as lib (I chose trunk)
  • Adding -std=c++17

warning generated by clang (12): -Wstring-plus-int

When compiling with clang I get this warning, seems harmless to me, but I'd like a clean compile. Is there a way to fix this?

In file included from /home/matthijs/Projects/moneymaker/moneymaker/fmtlog/fmtlog.h:782:
/home/matthijs/Projects/moneymaker/moneymaker/fmtlog/fmtlog-inl.h:375:38: warning: adding 'int' to a string does not append to the string [-Wstring-plus-int]
    logLevel = "DBG INF WRN ERR OFF" + (info.logLevel << 2);
               ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
/home/matthijs/Projects/moneymaker/moneymaker/fmtlog/fmtlog-inl.h:454:7: note: in instantiation of member function 'fmtlogDetailT<0>::handleLog' requested here
      handleLog(fmt::string_view(tb->name, tb->nameSize), h);
      ^
/home/matthijs/Projects/moneymaker/moneymaker/fmtlog/fmtlog-inl.h:552:31: note: in instantiation of member function 'fmtlogDetailT<0>::poll' requested here
  fmtlogDetailWrapper<>::impl.poll(forceFlush);
                              ^
/home/matthijs/Projects/moneymaker/moneymaker/fmtlog/fmtlog-inl.h:375:38: note: use array indexing to silence this warning
    logLevel = "DBG INF WRN ERR OFF" + (info.logLevel << 2);
                                     ^
               &                     [                     ]
1 warning generated.

Thanks

Add color support ?

For a logging library, it is inconvenient to have no color output. Will fmtlog add color support?

Missing typename in fmtlog-inl.h causes a compiler error with GCC

The return type in this method in fmtlog-inl.h needs typename because it's a qualified template (line 514 in v2.1.0).

template<int _>
fmtlogT<_>::SPSCVarQueueOPT::MsgHeader* fmtlogT<_>::SPSCVarQueueOPT::allocMsg(uint32_t size) {
  return alloc(size);
}

Should be this instead:

template<int _>
typename fmtlogT<_>::SPSCVarQueueOPT::MsgHeader* fmtlogT<_>::SPSCVarQueueOPT::allocMsg(uint32_t size) {
  return alloc(size);
}

Threading issue with startPollingThread (sanitizer report)

Hello,

The following program (minimal reproducible example) is causing ThreadSanitizer warnings:

#include "fmtlog/fmtlog.h"

int main(int argc, char** argv)
{
  fmtlog::startPollingThread();
  fmtlog::setLogLevel(fmtlog::DBG);
  logd("test: reloading");
  fmtlog::stopPollingThread();

  return 0;
}

Compiled with the following line:

clang++-12 -DFMTLOG_HEADER_ONLY -DFMT_HEADER_ONLY -stdlib=libc++ -fno-omit-frame-pointer -fsanitize=thread -g -std=c++20 -o main main.cpp

Output:

% ./main
==================
WARNING: ThreadSanitizer: data race (pid=21198)
  Read of size 1 at 0x00000100d370 by thread T1:
    #0 fmtlogDetailT<0>::startPollingThread(long)::'lambda'()::operator()() const /home/matthijs/t5/./fmtlog/fmtlog-inl.h:331:14 (main+0x5146b9)
    #1 decltype(std::__1::forward<0>(fp)(std::__1::forward<fmtlogDetailT<0>::startPollingThread(long)::'lambda'()>(fp0)...)) std::__1::__invoke<fmtlogDetailT<0>::startPollingThread(long)::'lambda'()>(0&&, fmtlogDetailT<0>::startPollingThread(long)::'lambda'()&&...) /usr/lib/llvm-12/bin/../include/c++/v1/type_traits:3694:1 (main+0x5145cd)
    #2 void std::__1::__thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, fmtlogDetailT<0>::startPollingThread(long)::'lambda'()>(std::__1::tuple<0, fmtlogDetailT<0>::startPollingThread(long)::'lambda'()>&, std::__1::__tuple_indices<>) /usr/lib/llvm-12/bin/../include/c++/v1/thread:280:5 (main+0x5144e5)
    #3 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, fmtlogDetailT<0>::startPollingThread(long)::'lambda'()> >(void*) /usr/lib/llvm-12/bin/../include/c++/v1/thread:291:5 (main+0x513a35)

  Previous write of size 1 at 0x00000100d370 by main thread:
    #0 fmtlogDetailT<0>::stopPollingThread() /home/matthijs/t5/./fmtlog/fmtlog-inl.h:345:19 (main+0x4c23e7)
    #1 fmtlogT<0>::stopPollingThread() /home/matthijs/t5/./fmtlog/fmtlog-inl.h:607:31 (main+0x4c2376)
    #2 main /home/matthijs/t5/main.cpp:8:2 (main+0x4bf462)

  Location is global 'fmtlogDetailWrapper<0>::impl' of size 10496 at 0x00000100aa80 (main+0x00000100d370)

  Thread T1 (tid=21200, running) created by main thread at:
    #0 pthread_create <null> (main+0x44d02b)
    #1 std::__1::__libcpp_thread_create(unsigned long*, void* (*)(void*), void*) /usr/lib/llvm-12/bin/../include/c++/v1/__threading_support:509:10 (main+0x5139a9)
    #2 std::__1::thread::thread<fmtlogDetailT<0>::startPollingThread(long)::'lambda'(), void>(0&&, fmtlogDetailT<0>::startPollingThread(long)::'lambda'()&&...) /usr/lib/llvm-12/bin/../include/c++/v1/thread:307:16 (main+0x513581)
    #3 fmtlogDetailT<0>::startPollingThread(long) /home/matthijs/t5/./fmtlog/fmtlog-inl.h:330:11 (main+0x4c231e)
    #4 fmtlogT<0>::startPollingThread(long) /home/matthijs/t5/./fmtlog/fmtlog-inl.h:602:31 (main+0x4c228a)
    #5 main /home/matthijs/t5/main.cpp:5:2 (main+0x4bf456)

SUMMARY: ThreadSanitizer: data race /home/matthijs/t5/./fmtlog/fmtlog-inl.h:331:14 in fmtlogDetailT<0>::startPollingThread(long)::'lambda'()::operator()() const
==================
ThreadSanitizer: reported 1 warnings

Is this something to worry about?

Regards, Matthijs

日志时间戳乱序

尝试运行了test里的multithread_test.cc,日志时间戳会出现突然回退的情况
微信截图_20221121082544

是否可以考虑下多个日志文件的支持

如果没理解错的话,目前似乎只支持同一时间内只输出到同一个日志文件中?能否考虑下支持分门别类输出日志内容到不同的日志文件里。在打日志时增加一个logger的指定(如不指定就用默认,以保持和现在行为一致)

写对齐的对象会崩溃.

对齐到64字节的struct. 写到log里, 会崩溃. 把alignas(64)去掉就没问题了.
好像是SPSCVarQueue入队出队的时候出了问题.

Failure to build on MacOS

Pulled "main" today and included in a C++ program which I am compiling on macOS.

I had 2 issues, the first is as follows:

fmtlog/fmtlog.h:101:28: error: expected ';' at end of declaration list
  static void preallocate() FMT_NOEXCEPT;
                           ^
                           ;

I rectified this adding #define FMT_NOEXCEPT to my source file prior to #include <fmtlog/fmtlog.h> I now have:

#define FMTLOG_HEADER_ONLY
#define FMT_HEADER_ONLY
#define FMT_NOEXCEPT
#include <fmtlog/fmtlog.h>
#include <fmt/format.h>

The second issue is with macOS where the functionsyscall is deprecated:

fmtlog/fmtlog-inl.h:294:44: warning: 'syscall' is deprecated: first deprecated in macOS 10.12 - syscall(2) is unsupported; please switch to a supported interface. For SYS_kdebug_trace use kdebug_signpost(). [-Wdeprecated-declarations]
   uint32_t tid = static_cast<uint32_t>(::syscall(SYS_gettid));
                                           ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/unistd.h:746:6: note: 'syscall' has been explicitly marked deprecated here
int      syscall(int, ...);
         ^
1 warning generated.

I have patched locally by adding the following:

diff --git a/fmtlog-inl.h b/fmtlog-inl.h
index 9d85e51..34e1c11 100644
--- a/fmtlog-inl.h
+++ b/fmtlog-inl.h
@@ -38,6 +38,10 @@ SOFTWARE.
 #include <unistd.h>
 #endif

+#ifdef __APPLE__
+#include <pthread.h>
+#endif
+
 namespace {
 void fmtlogEmptyFun(void*) {
 }
@@ -290,6 +294,9 @@ public:
     fmtlog::threadBuffer = new fmtlog::ThreadBuffer();
 #ifdef _WIN32
     uint32_t tid = static_cast<uint32_t>(::GetCurrentThreadId());
+#elif __APPLE__
+    uint64_t tid = 0;
+    pthread_threadid_np(nullptr, &tid);
 #else
     uint32_t tid = static_cast<uint32_t>(::syscall(SYS_gettid));
 #endif

无法输出日志

根据 Output 那一栏的说明

当日志buffer 达到指定大小,指定时间,指定级别,或者手动调用poll()的时候都将把buffer中的内容 flush 到文件中。 然而测试发现只有 poll() 时才会输出,其余的条件都没有作用。请问是什么地方错误使用了吗?大小,时间,级别的设置,是否要结合 fmtlog::startPollingThread(interval) 一起使用?

代码大致如下:

int main()
{

fmtlog::setLogFile("logs/main.log");
fmtlog::flushOn(fmtlog::INF);
fmtlog::setFlushDelay(1000000000); //1s

logi(xxxxxx);
loge(xxxxxxxx);
logd(xxxxxxxxxxx);

...

}

对于windows的支持如何?

在VisualStudio 2022下,控制台程序中使用了以下示例代码:

fmtlog::setLogFile("D:/log.txt", true);
std::string str = "aaa";
logi("str: {}, pstr: {}", str, &str);
FMTLOG(fmtlog::INF, "The answer is {}.", 42);

logi("A info msg");
logd("This msg will not be logged as the default log level is INF");
fmtlog::setLogLevel(fmtlog::DBG);
logd("Now debug msg is shown");

但无论是调试窗口,还是log.txt中,都没有任何输出。请问哪里错了?

windows编译不过

使用cmake生成vs2019工程编译报错。
一开始是编译选项冲突,解决后又报语法错误,编译工具从最新改为c++17,又换成其他语法错误了。

Problem about align

我在阅读时看到这段注释,这里的意思是否是在处理对齐要求大于 16 的对象时,gcc 会嵌入对齐指令,所以尽量使用 memcpy 而非 new?

fmtlog/fmtlog.h

Lines 480 to 482 in d34990c

// If Arg has alignment >= 16, gcc could emit aligned move instructions(e.g. movdqa) for
// placement new even if the *out* is misaligned, which would cause segfault. So we use memcpy
// when possible

但是我在 gcc 9.2 上观察到传递给 new 非对齐的地址,new 返回的值并没有偏移到对齐的位置上。随后我查看 cppreference,发现 placement new 返回的是给定的指针。
所以,这里指的是什么情况,是否是我理解错了场景?

有个办法,可以解决log里时间倒退的问题.

后端写log时,从所有线程的buf里取时间最小的处理.
用最小堆优化,可以做到 n*log(K), n是log数量,k是线程数量.
虽然比原来慢了些,但后端的性能没那么要紧.
在有些场合,log时间不倒退还是有用的.

Fails to compile on mingw64

Partial error log from building a project with the fmtlog files included:

[...]/dep/fmtlog/include/fmtlog.h: In static member function 'static int64_t fmtlogT<__>::TSCNS::rdtsc()':
[...]/dep/fmtlog/include/fmtlog.h:274:14: error: there are no arguments to '__rdtsc' that depend on a template parameter, so a declaration of '__rdtsc' must be available [-fpermissive]
  274 |       return __rdtsc();
      |              ^~~~~~~
[...]/dep/fmtlog/include/fmtlog.h:274:14: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)

This issue can be resolved with a #include <x86intrin.h> at the top of fmtlog.h.
Note also that at least on my system __rdtsc() is defined as #define __rdtsc() __builtin_ia32_rdtsc ().

is_trivially_copyable_v

fmtlog.h:483:26: error: ‘is_trivially_copyable_v’ is not a member of ‘std’; did you mean ‘is_trivially_copyable’?

fmtlog not compiling with the latest fmtlib (9.1.0)

I've upgraded fmtlib in my project to 9.1.0 but now fmtlog doesnt compile anymore:

See compile error:

/project/fmtlog/fmtlog.h:101:28: error: expected ';' at end of declaration list
  static void preallocate() FMT_NOEXCEPT;
                           ^
                           ;
/project/fmtlog/fmtlog.h:117:40: error: expected ';' at end of declaration list
  static void setFlushDelay(int64_t ns) FMT_NOEXCEPT;
                                       ^
                                       ;
/project/fmtlog/fmtlog.h:120:46: error: expected ';' at end of declaration list
  static void flushOn(LogLevel flushLogLevel) FMT_NOEXCEPT;
                                             ^
                                             ;
/project/fmtlog/fmtlog.h:123:46: error: expected ';' at end of declaration list
  static void setFlushBufSize(uint32_t bytes) FMT_NOEXCEPT;
                                             ^
                                             ;
/project/fmtlog/fmtlog.h:139:59: error: expected ';' at end of declaration list
  static void setLogCB(LogCBFn cb, LogLevel minCBLogLevel) FMT_NOEXCEPT;
                                                          ^
                                                          ;
/project/fmtlog/fmtlog.h:142:61: error: expected ';' at end of declaration list
  static void setLogQFullCB(LogQFullCBFn cb, void* userData) FMT_NOEXCEPT;
                                                            ^
                                                            ;
/project/fmtlog/fmtlog.h:146:29: error: expected ';' at end of declaration list
  static void closeLogFile() FMT_NOEXCEPT;
                            ^
                            ;
/project/fmtlog/fmtlog.h:152:46: error: expected ';' at end of declaration list
  static void setThreadName(const char* name) FMT_NOEXCEPT;
                                             ^
                                             ;
/project/fmtlog/fmtlog.h:155:52: error: expected ';' at end of declaration list
  static inline void setLogLevel(LogLevel logLevel) FMT_NOEXCEPT;
                                                   ^
                                                   ;
/project/fmtlog/fmtlog.h:158:39: error: expected ';' at end of declaration list
  static inline LogLevel getLogLevel() FMT_NOEXCEPT;
                                      ^
                                      ;
/project/fmtlog/fmtlog.h:161:54: error: expected ';' at end of declaration list
  static inline bool checkLogLevel(LogLevel logLevel) FMT_NOEXCEPT;
                                                     ^
                                                     ;
/project/fmtlog/fmtlog.h:165:65: error: expected ';' at end of declaration list
  static void startPollingThread(int64_t pollInterval = 1000000) FMT_NOEXCEPT;
                                                                ^
                                                                ;
/project/fmtlog/fmtlog.h:168:34: error: expected ';' at end of declaration list
  static void stopPollingThread() FMT_NOEXCEPT;
                                 ^
                                 ;
/project/fmtlog/fmtlog.h:183:39: error: expected ';' at end of declaration list
    MsgHeader* allocMsg(uint32_t size) FMT_NOEXCEPT;
                                      ^
                                      ;
/project/fmtlog/fmtlog.h:377:58: error: expected ';' at end of declaration list
                              fmt::string_view fmtString) FMT_NOEXCEPT;
                                                         ^
                                                         ;
/project/fmtlog/fmtlog.h:385:87: error: expected ';' at end of declaration list
  static typename SPSCVarQueueOPT::MsgHeader* allocMsg(uint32_t size, bool logQFullCB) FMT_NOEXCEPT;
                                                                                      ^
                                                                                      ;
/project/fmtlog/fmtlog.h:671:20: error: expected ';' at end of declaration list
    Args&&... args) FMT_NOEXCEPT {
                   ^
                   ;
/project/fmtlog/fmtlog.h:732:25: error: out-of-line declaration of a member must be a definition [-Wout-of-line-declaration]
inline void fmtlogT<_>::setLogLevel(LogLevel logLevel) FMT_NOEXCEPT {
            ~~~~~~~~~~~~^
/project/fmtlog/fmtlog.h:732:55: error: expected ';' at end of declaration
inline void fmtlogT<_>::setLogLevel(LogLevel logLevel) FMT_NOEXCEPT {
                                                      ^
                                                      ;
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.

Implicit long/int truncation in fmtlog.h

Compiling on macOS or iOS with Clang produces this warning, due to these 64-bit platforms using 32-bit ints:

vendor/fmtlog/fmtlog.h:546:21: error: implicit conversion loses integer precision: 'std::vector<fmt::basic_format_arg<fmt::basic_format_context<fmt::appender, char>>>::size_type' (aka 'unsigned long') to 'int' [-Werror,-Wshorten-64-to-32]
      argIdx = args.size();
             ~ ~~~~~^~~~~~

Fix is simply to manually coerce the size_t to an int:

diff --git a/fmtlog.h b/fmtlog.h
index 9bf6501..77ebf2b 100644
--- a/fmtlog.h
+++ b/fmtlog.h
@@ -543,7 +543,7 @@ public:
     const char* dtor_args[std::max(num_dtors, (size_t)1)];
     const char* ret;
     if (argIdx < 0) {
-      argIdx = args.size();
+      argIdx = int(args.size());
       args.resize(argIdx + num_args);
       ret = decodeArgs<false, 0, 0, Args...>(data, args.data() + argIdx, dtor_args);
     }
@@ -630,13 +630,13 @@ public:
     size_t alloc_size = 8 + getArgSizes<0>(cstringSizes, args...);
     if (threadBuffer == nullptr) preallocate();
     do {
-      if (auto header = threadBuffer->varq.allocMsg(alloc_size)) {
+      if (auto header = threadBuffer->varq.allocMsg(uint32_t(alloc_size))) {
         header->logId = logId;
         char* out = (char*)(header + 1);
         *(int64_t*)out = tsc;
         out += 8;
         encodeArgs<0>(cstringSizes, out, std::forward<Args>(args)...);
-        header->push(alloc_size);
+        header->push(uint32_t(alloc_size));
         break;
       }
     } while (FMTLOG_BLOCK);

Edit: I found two more of these conversions as I started using the API, and added them to the patch.

Apologies for lack of a PR, but I didn't feel it was worth forking the repo for such a tiny fix.

Format string is hard-coded to be a string literal

If the format string is not a string literal, cryptic compiler messages are reported and at first it's not even clear what the problem is. For example:

template <typename ... T>
void mylog(const std::string_view& format, T&& ... arg)
{
  FMTLOG_ONCE(fmtlog::INF, format, std::forward<T>(arg)...);
}

int main(void)
{
  mylog("test log line {} {}"sv, "ABC", 123);
  return 0;
}

GCC reports use of parameter from containing function against the format parameter and an additional error 'constexpr' call flows off the end of the function in core.h in fmt. VC++ reports just function cannot access 'fmt'.

This is caused by FMT_STRING used in all logging macros, which only works with string literals. Removing FMT_STRING from logging macros fixes these errors.

Installation of header files

The header files are not set to be installed by the cmake. It is a minor issue... but I feel it would be good to install header files in the system so users do not need to copy these files in their project directory manually.

疑似丢数据

打开 startPollingThread()

使用四个级别各打印 9999 条记录,然后退出程序

检查日志,发现前面部分完好无误,接近结尾的部分数据丢失严重。

测试代码:


#include <csignal>
#include "fmtlog/fmtlog.h"

void exit_f(int signal)
{
 fmtlog::stopPollingThread();
}

int run()
{
  std::signal(SIGTERM, exit_f);
  std::signal(SIGINT, exit_f);

  fmtlog::setLogFile("logs/main.log");
  fmtlog::setHeaderPattern("{HMSF} {s:<16} {l}[{t:<6}]");
  fmtlog::setLogLevel(fmtlog::DBG);

  fmtlog::startPollingThread();

  for(int i = 0 ; i < 10000; ++i)
  {
    logi("aaaaaaaaaaaaaaaaaaaa:{}, {}", i, "dsfsdfsfsdssssssssssssssssssssssssssssssssssssssssssssssssssssssss");
    logd("bbbbbbbbbbbbbbbbb:{}, {}", i, "dsfsdfsfsdssssssssssssssssssssssssssssssssssssssssssssssssssssssss");
    logw("cccccccccccccccccccccc:{}, {}", i, "dsfsdfsfsdssssssssssssssssssssssssssssssssssssssssssssssssssssssss");
    loge("ddddddddddddddddddd:{}, {}", i, "dsfsdfsfsdssssssssssssssssssssssssssssssssssssssssssssssssssssssss");
 }

 logi("close:");

 return 0;

}


int main(int argc, char* argv[])
{

  run();
  return 0;
}

编译选项:

add_compile_definitions(FMTLOG_ACTIVE_LEVEL=FMTLOG_LEVEL_DBG)
add_compile_definitions(FMTLOG_BLOCK=1)

测试结果的临近末尾部分:
test

undefined behaviour found by asan (llvm)

Hi,

When running the following small program under the memory sanitizers:

#include "fmtlog/fmtlog.h"

int main(int argc, char** argv)
{
  fmtlog::startPollingThread();
  logi("some data: {}", argc);
  fmtlog::stopPollingThread();
  return 0;
}

I got the following output:

/home/matthijs/t5/fmtlog/fmtlog-inl.h:72:17: runtime error: store to misaligned address 0x000000f28e77 for type 'uint16_t' (aka 'unsigned short'), which requires 2 byte alignment
0x000000f28e77: note: pointer points here
 30 32 31 2d 00  00 2d 00 00 20 00 00 3a  00 00 3a 00 00 2e 00 00  00 00 00 00 00 00 00 00  00 00 00
             ^
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/matthijs/t5/fmtlog/fmtlog-inl.h:72:17 in
/home/matthijs/t5/fmtlog/fmtlog-inl.h:72:17: runtime error: store to misaligned address 0x000000f28e83 for type 'uint16_t' (aka 'unsigned short'), which requires 2 byte alignment
0x000000f28e83: note: pointer points here
 3a  00 00 3a 00 00 2e 30 31  31 35 36 38 30 35 37 00  00 00 00 00 00 00 00 00  00 68 35 5b 3c 7f 00
              ^
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/matthijs/t5/fmtlog/fmtlog-inl.h:72:17 in
/home/matthijs/t5/fmtlog/fmtlog-inl.h:72:17: runtime error: store to misaligned address 0x000000f28e7d for type 'uint16_t' (aka 'unsigned short'), which requires 2 byte alignment
0x000000f28e7d: note: pointer points here
 2d 30 39 20 00 00 3a  33 33 3a 31 36 2e 30 31  31 35 36 38 30 35 37 00  00 00 00 00 00 00 00 00  00
             ^
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/matthijs/t5/fmtlog/fmtlog-inl.h:72:17 in
22:33:16.011568 main.cpp:6       INF[4743  ] some data: 1

Compile line:

export ASAN_SYMBOLIZER_PATH="/usr/lib/llvm-12/bin/llvm-symbolizer"
export ASAN_OPTIONS="check_initialization_order=1:detect_stack_use_after_return=1:strict_string_checks=1:strict_init_order=1"
export LSAN_OPTIONS="use_unaligned=1"
/usr/bin/clang++-12 -DFMTLOG_HEADER_ONLY -DFMT_HEADER_ONLY -fno-omit-frame-pointer -O1 -fsanitize=address,undefined,pointer-compare,pointer-subtract,leak -fsanitize-address-use-after-scope -stdlib=libc++ -g -std=gnu++17 -o main main.cpp
./main

I am not sure if the sanitizers are to strict here or there is actual 'undefined behaviour' here. I also didn't take a look at the code on whats happening here...

Suspected log loss during dense recording

Please test the function in the test as follows, just increasing the RECORDS variable to 1000000.

void runBenchmark() {
  const int RECORDS = 1000000;
  fmtlog::setLogFile("./log", false);
  fmtlog::setLogCB(nullptr, fmtlog::WRN);

  std::chrono::high_resolution_clock::time_point t0, t1;

  t0 = std::chrono::high_resolution_clock::now();
  for (int i = 0; i < RECORDS; ++i) {
    logi("Simple log message with one parameters, {}", i);
  }
  t1 = std::chrono::high_resolution_clock::now();

  double span = std::chrono::duration_cast<std::chrono::duration<double>>(t1 - t0).count();
  fmt::print("benchmark, front latency: {:.1f} ns/msg average\n", (span / RECORDS) * 1e9);
}

Only 43689 lines are recorded each time.

writing multiple files from the logCB

Hi,

In the logCB i write multiple files based on some index that i send with each logged message.
Currently i write the index first and in the logCB i search for it through the message received. Then i convert to int, print remaining message to coresponding file...etc

Is there any more elegant way to do this, maybe with fmt::named tags and reading by name in the logCB?

thank you,

memory leak for threadbuffer

valgrind indicate a memory leak for log_test demo, which describe as follow:

<?xml version="1.0"?>

<valgrindoutput>

<protocolversion>4</protocolversion>
<protocoltool>memcheck</protocoltool>

<preamble>
  <line>Memcheck, a memory error detector</line>
  <line>Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.</line>
  <line>Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info</line>
  <line>Command: /home/fmtlog/cmake-build-debug/test/log_test</line>
</preamble>

<pid>4114</pid>
<ppid>28932</ppid>
<tool>memcheck</tool>

<args>
  <vargv>
    <exe>/usr/bin/valgrind.bin</exe>
    <arg>--tool=memcheck</arg>
    <arg>--xml=yes</arg>
    <arg>--xml-file=/tmp/clion-valgrindb95d4eb3-ea63-44ca-bf2f-f52eb3a1d7c1/valgrind_output_%p</arg>
    <arg>--gen-suppressions=all</arg>
    <arg>--leak-check=full</arg>
    <arg>--leak-resolution=med</arg>
    <arg>--track-origins=yes</arg>
    <arg>--vgdb=no</arg>
  </vargv>
  <argv>
    <exe>/home/fmtlog/cmake-build-debug/test/log_test</exe>
  </argv>
</args>

<status>
  <state>RUNNING</state>
  <time>00:00:00:00.049 </time>
</status>


<status>
  <state>FINISHED</state>
  <time>00:00:00:01.786 </time>
</status>

<error>
  <unique>0x1</unique>
  <tid>1</tid>
  <kind>Leak_DefinitelyLost</kind>
  <xwhat>
    <text>1,048,960 bytes in 1 blocks are definitely lost in loss record 2 of 2</text>
    <leakedbytes>1048960</leakedbytes>
    <leakedblocks>1</leakedblocks>
  </xwhat>
  <stack>
    <frame>
      <ip>0x4C340A6</ip>
      <obj>/usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so</obj>
      <fn>memalign</fn>
    </frame>
    <frame>
      <ip>0x50F038C</ip>
      <obj>/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25</obj>
      <fn>operator new(unsigned long, std::align_val_t)</fn>
    </frame>
    <frame>
      <ip>0x402B62</ip>
      <obj>/home/fmtlog/cmake-build-debug/test/log_test</obj>
      <fn>fmtlogDetailT&lt;0&gt;::preallocate()</fn>
    </frame>
    <frame>
      <ip>0x40513E</ip>
      <obj>/home/fmtlog/cmake-build-debug/test/log_test</obj>
      <fn>fmtlogT&lt;0&gt;::allocMsg(unsigned int, bool)</fn>
    </frame>
    <frame>
      <ip>0x4021F1</ip>
      <obj>/home/fmtlog/cmake-build-debug/test/log_test</obj>
      <fn>main</fn>
    </frame>
  </stack>
  <suppression>
    <sname>insert_a_suppression_name_here</sname>
    <skind>Memcheck:Leak</skind>
    <skaux>match-leak-kinds: definite</skaux>
    <sframe> <fun>memalign</fun> </sframe>
    <sframe> <fun>_ZnwmSt11align_val_t</fun> </sframe>
    <sframe> <fun>_ZN13fmtlogDetailTILi0EE11preallocateEv</fun> </sframe>
    <sframe> <fun>_ZN7fmtlogTILi0EE8allocMsgEjb</fun> </sframe>
    <sframe> <fun>main</fun> </sframe>
    <rawtext>
<![CDATA[
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: definite
   fun:memalign
   fun:_ZnwmSt11align_val_t
   fun:_ZN13fmtlogDetailTILi0EE11preallocateEv
   fun:_ZN7fmtlogTILi0EE8allocMsgEjb
   fun:main
}
]]>
    </rawtext>
  </suppression>
</error>

  <suppression>
    <sname>insert_a_suppression_name_here</sname>
    <skind>Memcheck:Leak</skind>
    <skaux>match-leak-kinds: definite</skaux>
    <sframe> <fun>memalign</fun> </sframe>
    <sframe> <fun>_ZnwmSt11align_val_t</fun> </sframe>
    <sframe> <fun>_ZN13fmtlogDetailTILi0EE11preallocateEv</fun> </sframe>
    <sframe> <fun>_ZN7fmtlogTILi0EE8allocMsgEjb</fun> </sframe>
    <sframe> <fun>main</fun> </sframe>
    <rawtext>
<![CDATA[
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: definite
   fun:memalign
   fun:_ZnwmSt11align_val_t
   fun:_ZN13fmtlogDetailTILi0EE11preallocateEv
   fun:_ZN7fmtlogTILi0EE8allocMsgEjb
   fun:main
}
]]>
    </rawtext>
  </suppression>
<errorcounts>
</errorcounts>

<suppcounts>
</suppcounts>

</valgrindoutput>

related code shown as follow:

    ~ThreadBufferDestroyer() {
      if (fmtlog::threadBuffer != nullptr) {
        fmtlog::threadBuffer->shouldDeallocate = true;
        fmtlog::threadBuffer = nullptr;
      }
    }

Feature suggestion: add a callback for logging threads (in addition to the async work callback)

Current callback set with fmtlog::setLogCB is called from fmtlog::poll, which doesn't help in scheduling background asynchronous work done by fmtlog::poll. Consider adding a callback that would be called in the context of the logging thread based on some criteria, which could be used to queue a work request for the thread calling fmtlog::poll.

Consider a scenario in which fmtlog::poll is being called periodically (e.g. once a second) and keeps the logging buffer reasonably empty for new log messages. If a logging burst occurs that would overflow the buffer, some of the messages would be lost or blocking will occur, based on FMTLOG_BLOCK. If the frequency of fmtlog::poll is increased, it would be called unnecessarily most of the time, since logging bursts would happen rarely.

If there was a callback invoked in the logging thread context (i.e. from the call stack of log and logOnce), and this callback would have some call condition (e.g. when logger buffer is 50% full), it would be possible to use this callback to queue an asynchronous work request for the logger thread at the application level (i.e. outside of fmtlog), which would trigger fmtlog::poll call between regular polling intervals.

Buffer满了之后的处理能否增加一个方式?

因为感觉和 quill [ https://github.com/odygrd/quill ] 这个库很像,我跑了几天的benchmark做了对比,目前的结论是 fmtlog 在buffer未满或者满了直接丢的情况下确实比 quill 要快。但是当buffer满了后,开 FMTLOG_BLOCK 的情况下,异步日志秒变同步,速度就和 spdlog 不相上下了。相比之下 quill 使用的新开一块内存 的方式在速度上更有优势。

当日志生成速度大于输出时,buffer是早晚会满的。此时有三种处理方式:

  1. 丢弃,缺点是丢日志
  2. 阻塞输入,等空出来,缺点是影响业务处理速度
  3. 新开buffer,缺点是内存占用会不断增加

目前fmtlog 实现了1,2两种,而quill实现了1,3两种。能否考虑下对fmtlog也增加第三种处理方式,这样选择面更大一些?

另外 quill 那个库有个 dual_queue 模式的设计,似乎也比较有趣。不过极限速度下还是不如 fmtlog。

测试代码大致如下,基本用的bench里改的。因为quill只支持后台线程写日志的方式,所以增加了一个方法:

struct FmtLogBase
{
   void flush() { fmtlog::stopPollingThread(); }
  void prepare() { fmtlog::startPollingThread(); }
};

template<typename T>
void bench(T o) {
  const int RECORDS = 100000; // 加了个0,以让它buffer满
  std::chrono::high_resolution_clock::time_point t0, t1, t2;
  o.prepare();   //此处增加了一步
  t0 =std::chrono::high_resolution_clock::now()
  ......
} 

quill 的bench 也类似,但是因为它用的fmt v7 的api,不完全兼容,得分开编译。

···

struct QuillBase
{
void flush() { quill::flush(); }
void prepare() { quill::start(); }
};

···

vs2022 上编译报错c2955

错误 C2955 “fmt::v8::basic_format_args”: 使用 类 模板 需要 模板 参数列表
错误 C2955 “fmt::v8::basic_format_args”: 使用 类 模板 需要 模板 参数列表
错误 C2661 “fmtlogT<0>::vformat_to”: 没有重载函数接受 2 个参数

vformat_to

fmtlog.h:601:51: error: missing template arguments before ‘(’ token
vformat_to(out, format, fmt::basic_format_args(args.data() + argIdx, num_args));

Quoted #include pattern picks up wrong (packaged) fmt library

Thank you for the logging library. It fits background logging quite well, leaving threading to the application, which many other logging libraries lack. One thing I wanted to mention is a small build issue in configurations that already have a packaged version of fmt installed.

A quoted #include pattern always looks in the current directory and only then in the search path. If fmtlog is installed on a system that already has a packaged version of fmt installed (e.g. via dnf), this causes the wrong headers picked up.

That is, fmtlog.h contains this line:

#include "fmt/format.h"

All compilers always check current directory, which always fails, and then the search path is checked. On systems where fmt is installed via a package manager, such as CentOS 8, there will be a version in /usr/include/fmt/, which would be picked via the search path.

Given that libfmt includes fmt as a Git submodule, it should always include the version of fmt in its own source subtree, which can be done by using quoted #include pattern to include the header relative to the directory of the fmtlog.h file:

#include "fmt/include/fmt/format.h"

In this case this line can be removed from CMakeLists.txt as well:

include_directories(fmt/include)

These two changes allow both versions of fmt coexist without a conflict - one is in the packaged location and one under /usr/local/include/fmtlog/fmt/, or any other directory suitable for a given project.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.