Squashed 'libs/loguru/' content from commit 9c2fea0d
git-subtree-dir: libs/loguru git-subtree-split: 9c2fea0d4530657f23259be4132f8101c98b579e
This commit is contained in:
commit
f637c3cee6
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
build
|
||||||
|
*.sublime-workspace
|
232
README.md
Normal file
232
README.md
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
# Loguru: a lightweight and flexible C++ logging library.
|
||||||
|
|
||||||
|
[![Build status](https://ci.appveyor.com/api/projects/status/hret4rx3xakjs7j4?svg=true)](https://ci.appveyor.com/project/emilk/loguru)
|
||||||
|
|
||||||
|
## At a glance
|
||||||
|
|
||||||
|
![Loguru terminal output](docs/terminal_colors.png)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
Documentation can be found at https://emilk.github.io/loguru/index.html.
|
||||||
|
|
||||||
|
## License
|
||||||
|
This software is in the public domain. Where that dedication is not recognized, you are granted a perpetual, irrevocable license to copy, modify and distribute it as you see fit.
|
||||||
|
|
||||||
|
That being said, I would appreciate credit!
|
||||||
|
If you find Loguru useful, tweet me at @ernerfeldt mail me at emil.ernerfeldt@gmail.com.
|
||||||
|
|
||||||
|
## Why another logging library?
|
||||||
|
I have yet to come across a nice, light-weight logging library for C++ that does everything I want. So I made one!
|
||||||
|
|
||||||
|
In particular, I want logging that produces logs that are both human-readable and easily grep:ed. I also want to be able to hook into the logging process to print some of the more severe messages on-screen in my app (for dev-purposes).
|
||||||
|
|
||||||
|
## Features:
|
||||||
|
* Simple integration
|
||||||
|
* Just two files: `loguru.hpp` and `loguru.cpp`.
|
||||||
|
* Either build and link `loguru.cpp` or just `#include <loguru.cpp>` in one of your own .cpp files.
|
||||||
|
* Small, simple library.
|
||||||
|
* Small header with no `#include`s for **fast compile times** (see separate heading).
|
||||||
|
* No dependencies.
|
||||||
|
* Cross-platform
|
||||||
|
* Flexible:
|
||||||
|
* User can install callbacks for logging (e.g. to draw log messages on screen in a game).
|
||||||
|
* User can install callbacks for fatal error (e.g. to pause an attached debugger or throw an exception).
|
||||||
|
* Support multiple file outputs, either trunc or append:
|
||||||
|
* e.g. a logfile with just the latest run at low verbosity (high readability).
|
||||||
|
* e.g. a full logfile at highest verbosity which is appended to on every run.
|
||||||
|
* Full featured:
|
||||||
|
* Verbosity levels.
|
||||||
|
* Supports assertions: `CHECK_F(fp != nullptr, "Failed to open '%s'", filename)`
|
||||||
|
* Supports abort: `ABORT_F("Something went wrong, debug value is %d", value)`.
|
||||||
|
* Stack traces printed on abort.
|
||||||
|
* Stack traces are cleaned up somewhat.
|
||||||
|
* Before cleanup: `some_function_name(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&)`
|
||||||
|
* After cleanup: `some_function_name(std::vector<std::string> const&)`
|
||||||
|
* Stack traces are printed [the right way](http://yellerapp.com/posts/2015-01-22-upside-down-stacktraces.html):
|
||||||
|
* Chronological order with the most relevant at the end.
|
||||||
|
* (most) signals writes stack traces.
|
||||||
|
* Fast:
|
||||||
|
- When configured in unbuffered mode (loguru::g_flush_interval_ms = 0):
|
||||||
|
+ 6-8 us when logging to stderr + file (rMBP + SSD + Clang).
|
||||||
|
+ About 25%-75% faster than GLOG on my MacBook Pro (Clang).
|
||||||
|
+ About the same as GLOG on my Linux Desktop (GCC).
|
||||||
|
- With loguru::g_flush_interval_ms set to ~100 ms:
|
||||||
|
+ 3-5 us when logging to stderr + file (rMBP + SSD + Clang).
|
||||||
|
+ About twice as fast as GLOG.
|
||||||
|
* Drop-in replacement for most of GLOG (except for setup code).
|
||||||
|
* Choose between using printf-style or std::cout-style formatting.
|
||||||
|
* Compile-time checked printf-formating (on supported compilers).
|
||||||
|
* Support for [fmtlib](https://github.com/fmtlib/fmt) formatting.
|
||||||
|
* Add `#define LOGURU_USE_FMTLIB 1`, before including `loguru.hpp`
|
||||||
|
* You also need to set up the `fmtlib` include directory for building as well as linking against `fmtlib`, alternatively use the `FMT_HEADER_ONLY` preprocessor definition.
|
||||||
|
* Assertion failures are marked with `noreturn` for the benefit of the static analyzer and optimizer.
|
||||||
|
* All logging also written to stderr.
|
||||||
|
* With colors on supported terminals.
|
||||||
|
* Thread-safe.
|
||||||
|
* Can be configured to either:
|
||||||
|
* Flush every `loguru::g_flush_interval_ms` in a background thread
|
||||||
|
* Flushes output on each call so you won't miss anything even on hard crashes (and still faster than buffered GLOG!).
|
||||||
|
* Prefixes each log line with:
|
||||||
|
* Date and time to millisecond precision.
|
||||||
|
* Application uptime to millisecond precision.
|
||||||
|
* Thread name or id (you can set the name with `loguru::set_thread_name`).
|
||||||
|
* File and line.
|
||||||
|
* Log level.
|
||||||
|
* Indentation (see *Scopes*).
|
||||||
|
* Error context:
|
||||||
|
* Catch the values of local variables and print them only on a crash (see *Error context*).
|
||||||
|
* Scopes (see *Scopes*).
|
||||||
|
* grep:able logs:
|
||||||
|
* Each line has all the info you need (e.g. date).
|
||||||
|
* You can easily filter out high verbosity levels after the fact.
|
||||||
|
|
||||||
|
## Compiling
|
||||||
|
|
||||||
|
Just include <loguru.hpp> where you want to use Loguru.
|
||||||
|
Then either compile and link with `loguru.cpp` or in one .cpp file: `#include <loguru.cpp>`
|
||||||
|
Make sure you compile with `-std=c++11 -lpthread -ldl` on relevant environments.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
#include <loguru.hpp>
|
||||||
|
|
||||||
|
…
|
||||||
|
|
||||||
|
// Optional, but useful to time-stamp the start of the log.
|
||||||
|
// Will also detect verbosity level on command line as -v.
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
|
||||||
|
// Put every log message in "everything.log":
|
||||||
|
loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX);
|
||||||
|
|
||||||
|
// Only log INFO, WARNING, ERROR and FATAL to "latest_readable.log":
|
||||||
|
loguru::add_file("latest_readable.log", loguru::Truncate, loguru::Verbosity_INFO);
|
||||||
|
|
||||||
|
// Only show most relevant things on stderr:
|
||||||
|
loguru::g_stderr_verbosity = 1;
|
||||||
|
|
||||||
|
LOG_SCOPE_F(INFO, "Will indent all log messages within this scope.");
|
||||||
|
LOG_F(INFO, "I'm hungry for some %.3f!", 3.14159);
|
||||||
|
LOG_F(2, "Will only show if verbosity is 2 or higher");
|
||||||
|
VLOG_F(get_log_level(), "Use vlog for dynamic log level (integer in the range 0-9, inclusive)");
|
||||||
|
LOG_IF_F(ERROR, badness, "Will only show if badness happens");
|
||||||
|
auto fp = fopen(filename, "r");
|
||||||
|
CHECK_F(fp != nullptr, "Failed to open file '%s'", filename);
|
||||||
|
CHECK_GT_F(length, 0); // Will print the value of `length` on failure.
|
||||||
|
CHECK_EQ_F(a, b, "You can also supply a custom message, like to print something: %d", a + b);
|
||||||
|
|
||||||
|
// Each function also comes with a version prefixed with D for Debug:
|
||||||
|
DCHECK_F(expensive_check(x)); // Only checked #if !NDEBUG
|
||||||
|
DLOG_F(INFO, "Only written in debug-builds");
|
||||||
|
|
||||||
|
// Turn off writing to stderr:
|
||||||
|
loguru::g_stderr_verbosity = loguru::Verbosity_OFF;
|
||||||
|
|
||||||
|
// Turn off writing err/warn in red:
|
||||||
|
loguru::g_colorlogtostderr = false;
|
||||||
|
|
||||||
|
// Throw exceptions instead of aborting on CHECK fails:
|
||||||
|
loguru::set_fatal_handler([](const loguru::Message& message){
|
||||||
|
throw std::runtime_error(std::string(message.prefix) + message.message);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
If you prefer logging with streams:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
#define LOGURU_WITH_STREAMS 1
|
||||||
|
#include <loguru.hpp>
|
||||||
|
...
|
||||||
|
LOG_S(INFO) << "Look at my custom object: " << a.cross(b);
|
||||||
|
CHECK_EQ_S(pi, 3.14) << "Maybe it is closer to " << M_PI;
|
||||||
|
```
|
||||||
|
|
||||||
|
For more info, see [the official documentation](https://emilk.github.io/loguru/index.html).
|
||||||
|
|
||||||
|
## Grep:able logs
|
||||||
|
``` bash
|
||||||
|
# Only show warnings, errors and fatal messages:
|
||||||
|
cat logfile.txt | egrep "[^0-9]\|"
|
||||||
|
|
||||||
|
# Ignore verbosity-levels 4 and above:
|
||||||
|
cat logfile.txt | egrep "[^4-9]\|"
|
||||||
|
|
||||||
|
# Only show verbosity-level 6:
|
||||||
|
cat logfile.txt | egrep "6\|"
|
||||||
|
|
||||||
|
# Only show messages from the main thread:
|
||||||
|
cat logfile.txt | egrep "\[main thread \]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## No includes in loguru.hpp
|
||||||
|
I abhor logging libraries that `#include`'s everything from `iostream` to `windows.h` into every compilation unit in your project. Logging should be frequent in your source code, and thus as lightweight as possible. Loguru's header has *no #includes*. This means it will not slow down the compilation of your project.
|
||||||
|
|
||||||
|
In a test of a medium-sized project, including `loguru.hpp` instead of `glog/logging.hpp` everywhere gave about 10% speedup in compilation times.
|
||||||
|
|
||||||
|
Note, however, that this gives you the bare-bones version of Loguru with printf-style logging. If you want `std::ostream` style logging (or GLOG functionality) you need to `#define LOGURU_WITH_STREAMS 1` before `#include <loguru.hpp>`, and that will make `loguru.hpp` include `<sstream>`. No away around it!
|
||||||
|
|
||||||
|
## Scopes
|
||||||
|
The library supports scopes for indenting the log-file. Here's an example:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
LOG_SCOPE_FUNCTION(INFO);
|
||||||
|
LOG_F(INFO, "Doing some stuff...");
|
||||||
|
for (int i=0; i<2; ++i) {
|
||||||
|
VLOG_SCOPE_F(1, "Iteration %d", i);
|
||||||
|
auto result = some_expensive_operation();
|
||||||
|
LOG_IF_F(WARNING, result == BAD, "Bad result");
|
||||||
|
}
|
||||||
|
LOG_F(INFO, "Time to go!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
This will output:
|
||||||
|
|
||||||
|
```
|
||||||
|
loguru.cpp:184 0| arguments: ./loguru_test test -v1
|
||||||
|
loguru.cpp:185 0| Verbosity level: 1
|
||||||
|
loguru.cpp:186 0| -----------------------------------
|
||||||
|
loguru_test.cpp:108 0| { int main_test(int, char **)
|
||||||
|
loguru_test.cpp:109 0| . Doing some stuff...
|
||||||
|
loguru_test.cpp:111 1| . { Iteration 0
|
||||||
|
loguru_test.cpp:111 1| . } 0.133 s: Iteration 0
|
||||||
|
loguru_test.cpp:111 1| . { Iteration 1
|
||||||
|
loguru_test.cpp:113 0| . . Bad result
|
||||||
|
loguru_test.cpp:111 1| . } 0.134 s: Iteration 1
|
||||||
|
loguru_test.cpp:115 0| . Time to go!
|
||||||
|
loguru_test.cpp:108 0| } 0.267 s: int main_test(int, char **)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# `ERROR_CONTEXT`
|
||||||
|
You can also optionally log things ONLY if there is a crash. This is a very useful feature:
|
||||||
|
|
||||||
|
```
|
||||||
|
void process_file(const char* filename)
|
||||||
|
{
|
||||||
|
ERROR_CONTEXT("filename", filename);
|
||||||
|
parse_file(filename); // Only if this crashes will filename be logged.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Streams vs printf
|
||||||
|
Some logging libraries only supports stream style logging, not printf-style. This means that what in Loguru is:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
LOG_F(INFO, "Some float: %+05.3f", number);
|
||||||
|
```
|
||||||
|
|
||||||
|
in Glog becomes something along the lines of:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
LOG(INFO) << "Some float: " << std::setfill('0') << std::setw(5) << std::setprecision(3) << number;
|
||||||
|
```
|
||||||
|
|
||||||
|
Loguru allows you to use whatever style you prefer.
|
12
appveyor.yml
Normal file
12
appveyor.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
version: 0.1.{build}-{branch}
|
||||||
|
pull_requests:
|
||||||
|
do_not_increment_build_number: true
|
||||||
|
|
||||||
|
image:
|
||||||
|
- Ubuntu
|
||||||
|
- Visual Studio 2017
|
||||||
|
|
||||||
|
# loguru is header-only
|
||||||
|
build: off
|
||||||
|
|
||||||
|
test_script: cmake -P test/appveyor.cmake
|
679
docs/index.html
Normal file
679
docs/index.html
Normal file
@ -0,0 +1,679 @@
|
|||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
# Loguru
|
||||||
|
This document contains the official documentation of Loguru, a lightweight and flexible C++ logging library. This documentations is not 100% complete. Read the `loguru.hpp` for more details.
|
||||||
|
|
||||||
|
The official project homepage of Loguru is at https://github.com/emilk/loguru.
|
||||||
|
|
||||||
|
Loguru provides a set of functions and macros to aid logging to one or several files and/or to `stderr`. It prefixes each log line with useful information such as time.
|
||||||
|
|
||||||
|
# Getting started
|
||||||
|
Download `loguru.hpp` and `loguru.cpp` from https://github.com/emilk/loguru. To use Loguru in any file, simply `#include <loguru.hpp>`. Then either compile and link with `loguru.cpp` or add `#include <loguru.cpp>` to one of your `.cpp` files.
|
||||||
|
|
||||||
|
Loguru uses C++11, so make sure you compile with `-std=c++11`. On Linux you also need the following linker flags: `-lpthread -ldl`.
|
||||||
|
|
||||||
|
Now you are ready to use Loguru! Here is a simple example:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
#include <loguru.cpp>
|
||||||
|
|
||||||
|
int main_test(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX);
|
||||||
|
LOG_F(INFO, "Hello log file!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## A slightly more advanced example
|
||||||
|
`main.cpp`:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <loguru.cpp>
|
||||||
|
|
||||||
|
void sleep_ms(int ms)
|
||||||
|
{
|
||||||
|
VLOG_F(2, "Sleeping for %d ms", ms);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
void complex_calculation()
|
||||||
|
{
|
||||||
|
LOG_SCOPE_F(INFO, "complex_calculation");
|
||||||
|
LOG_F(INFO, "Starting time machine...");
|
||||||
|
sleep_ms(200);
|
||||||
|
LOG_F(WARNING, "The flux capacitor is not getting enough power!");
|
||||||
|
sleep_ms(400);
|
||||||
|
LOG_F(INFO, "Lighting strike!");
|
||||||
|
VLOG_F(1, "Found 1.21 gigawatts...");
|
||||||
|
sleep_ms(400);
|
||||||
|
std::thread([](){
|
||||||
|
loguru::set_thread_name("the past");
|
||||||
|
LOG_F(ERROR, "We ended up in 1985!");
|
||||||
|
}).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
LOG_F(INFO, "Hello from main.cpp!");
|
||||||
|
complex_calculation();
|
||||||
|
LOG_F(INFO, "main function about to end!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
g++ -std=c++11 main.cpp -o loguru_example
|
||||||
|
./loguru_example -v 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
![Notice how the color matches the verbosity level (`0 = INFO`)](terminal_colors.png)
|
||||||
|
|
||||||
|
|
||||||
|
## Older versions
|
||||||
|
Before Loguru 2.0 was released in 2018, Loguru consisted of a single `loguru.hpp` header. If you are still using that version (and for some reason don't want to upgrade), this is how you use it:
|
||||||
|
|
||||||
|
In ONE `.cpp` file you need to add this:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
#define LOGURU_IMPLEMENTATION
|
||||||
|
#include <loguru.hpp>
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Most of this documentation will still apply the same.
|
||||||
|
|
||||||
|
|
||||||
|
# How Loguru works
|
||||||
|
## Outputs and verbosity
|
||||||
|
Loguru has several outputs. By default, Loguru logs everything to `stderr` and nowhere else. You can add files to write to using `loguru::add_file`. Each output (`stderr` and files) has a *verbosity level* attached to it. Each time you log something, you also pick a verbosity level, for instance `INFO` or `ERROR`. This way you can have one log file where only the most important messages go (e.g. `WARNING`s and `ERROR`s) and another where all logging goes. You can control the verbosity level of `stderr` either from code (with `loguru::g_stderr_verbosity`) or from the command line arguments with the `-v` flags.
|
||||||
|
|
||||||
|
|
||||||
|
*************************************************************
|
||||||
|
* .---------. *
|
||||||
|
* | LOG_F +->+ .-------. *
|
||||||
|
* '---------' | +->| stderr | *
|
||||||
|
* | | '-------' *
|
||||||
|
* .---------. | | .------------------. *
|
||||||
|
* | CHECK_F +->+ _________ +->| optional callback | *
|
||||||
|
* '---------' | / / | '------------------' *
|
||||||
|
* +->-++ Loguru ++---+ .-------. *
|
||||||
|
* .---------. | /________/ +->| file 1 | *
|
||||||
|
* | ABORT_F +->+ | '-------' *
|
||||||
|
* '---------' | | .---------. *
|
||||||
|
* | +->| file 2 … | *
|
||||||
|
* .---------. | '---------' *
|
||||||
|
* | Crash +->+ *
|
||||||
|
* '---------' *
|
||||||
|
*************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
## Verbosity levels
|
||||||
|
Loguru has a hierarchy of verbosity levels:
|
||||||
|
|
||||||
|
* FATAL (caused by failed checks, signals or calls to ABORT_F)
|
||||||
|
* ERROR (= -2)
|
||||||
|
* WARNING (= -1)
|
||||||
|
* INFO (= 0)
|
||||||
|
* 1-9
|
||||||
|
|
||||||
|
When calling a log function you must pick one of the above log levels (except FATAL). Loguru will then only process it (write to `stderr`, a file, or your custom callbacks) if the verbosity is below the cutoff for that output. For instance, by default only INFO, WARNING and ERROR is written to `stderr`.
|
||||||
|
|
||||||
|
A common approach is to print only high-verbosity things to `stderr`, but write everything to a logfile.
|
||||||
|
|
||||||
|
## Log functions
|
||||||
|
|
||||||
|
### `LOG_F(verbosity_name, fmt, ...)`
|
||||||
|
The first and foremost logging function is `LOG_F` which looks like this.
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
LOG_F(INFO, "Warming up %d lasers", 3);
|
||||||
|
LOG_F(WARNING, "%s is an old code, but it checks out", code_id);
|
||||||
|
LOG_F(ERROR, "The hyperdrive doesn't work");
|
||||||
|
```
|
||||||
|
|
||||||
|
You can pass any number of arguments to `LOG_F` just like with `printf`. On modern compilers you will get compile-time checking of the argument number and types.
|
||||||
|
|
||||||
|
Those are the named verbosity levels, and those you will most often use. However, sometimes you may want to log some details that you may very seldom be interested in. For this you can pick an integer verbosity level in the range 0-9 (inclusive). `0` is identical with `INFO`. A higher number means "more verbose", e.g. "more likely to be irrelevant". Example:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
LOG_F(1, "This may be important, but probably not");
|
||||||
|
LOG_F(9, "Nobody will ever care about this.");
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to pick verbosity level dynamically, use the `VLOG_F` function.
|
||||||
|
|
||||||
|
### `VLOG_F(verbosity, fmt, ...)`
|
||||||
|
This is for when you want a dynamic verbosity, i.e. an verbosity derived from a function call:
|
||||||
|
``` C++
|
||||||
|
VLOG_F(is_vip(guest) ? 0 : 9, "%s has entered the room", guest);
|
||||||
|
|
||||||
|
loguru::Verbosity verbosity = 5; // Higher = less likely to be printed
|
||||||
|
VLOG_F(verbosity, "...");
|
||||||
|
|
||||||
|
verbosity = loguru::Verbosity_WARNING;
|
||||||
|
VLOG_F(verbosity, "...");
|
||||||
|
```
|
||||||
|
|
||||||
|
### `LOG_IF_F(verbosity_name, condition, fmt, ...)`
|
||||||
|
Conditionally log something.
|
||||||
|
``` C++
|
||||||
|
LOG_IF_F(WARNING, value == invalid, "Invalid value: %d", value);
|
||||||
|
|
||||||
|
// Equivalent to:
|
||||||
|
if (value == invalid) {
|
||||||
|
LOG_F(WARNING, "Invalid value: %d", value);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `RAW_LOG_F(verbosity_name, fmt, ...)`
|
||||||
|
Log something without the usual prefix of date, time, file etc.
|
||||||
|
|
||||||
|
|
||||||
|
### All logging functions
|
||||||
|
There are also combinations of the `RAW`, `IF`, and `V` versions. Here is the complete list:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
LOG_F(verbosity_name, fmt, ...)
|
||||||
|
VLOG_F(verbosity, fmt, ...)
|
||||||
|
LOG_IF_F(verbosity_name, cond, fmt, ...)
|
||||||
|
VLOG_IF_F(verbosity, cond, fmt, ...)
|
||||||
|
RAW_LOG_F(verbosity_name, fmt, ...)
|
||||||
|
RAW_VLOG_F(verbosity, fmt, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug-only logging:
|
||||||
|
In addition, there are variants who only log in debug builds (and/or when `LOGURU_DEBUG_LOGGING=1`):
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
DLOG_F(verbosity_name, fmt, ...)
|
||||||
|
DVLOG_F(verbosity, fmt, ...)
|
||||||
|
DLOG_IF_F(verbosity_name, cond, fmt, ...)
|
||||||
|
DVLOG_IF_F(verbosity, cond, fmt, ...)
|
||||||
|
DRAW_LOG_F(verbosity_name, fmt, ...)
|
||||||
|
DRAW_VLOG_F(verbosity, fmt, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Log scopes
|
||||||
|
Loguru has the concept of *log scopes*. This is a way to indent you logging to make them more readable. This is great for readability of your log files. Here's an example:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
LOG_SCOPE_FUNCTION(INFO);
|
||||||
|
LOG_F(INFO, "Doing some stuff...");
|
||||||
|
for (int i=0; i<2; ++i) {
|
||||||
|
VLOG_SCOPE_F(1, "Iteration %d", i);
|
||||||
|
auto result = some_expensive_operation();
|
||||||
|
LOG_IF_F(WARNING, result == BAD, "Bad result");
|
||||||
|
}
|
||||||
|
LOG_F(INFO, "Time to go!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will output:
|
||||||
|
|
||||||
|
```
|
||||||
|
loguru.cpp:184 0| arguments: ./loguru_test test -v1
|
||||||
|
loguru.cpp:185 0| Verbosity level: 1
|
||||||
|
loguru.cpp:186 0| -----------------------------------
|
||||||
|
loguru_test.cpp:108 0| { int main_test(int, char **)
|
||||||
|
loguru_test.cpp:109 0| . Doing some stuff...
|
||||||
|
loguru_test.cpp:111 1| . { Iteration 0
|
||||||
|
loguru_test.cpp:111 1| . } 0.133 s: Iteration 0
|
||||||
|
loguru_test.cpp:111 1| . { Iteration 1
|
||||||
|
loguru_test.cpp:113 0| . . Bad result
|
||||||
|
loguru_test.cpp:111 1| . } 0.134 s: Iteration 1
|
||||||
|
loguru_test.cpp:115 0| . Time to go!
|
||||||
|
loguru_test.cpp:108 0| } 0.267 s: int main_test(int, char **)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Here are the LOG_SCOPE family of functions:
|
||||||
|
``` C++
|
||||||
|
LOG_SCOPE_F(verbosity_name, fmt, ...)
|
||||||
|
VLOG_SCOPE_F(name, fmt, ...)
|
||||||
|
LOG_SCOPE_FUNCTION(verbosity_name) // Logs the name of the current function.
|
||||||
|
```
|
||||||
|
|
||||||
|
Scopes affects logging on all threads.
|
||||||
|
|
||||||
|
|
||||||
|
## Logging with streams
|
||||||
|
By default, Loguru will only let you log with `printf`-style formatting. If you prefer to use C++-style stream logging you need to define `LOGURU_WITH_STREAMS=1` (e.g. set `#define LOGURU_WITH_STREAMS 1` before including `loguru.hpp` or pass `-DLOGURU_WITH_STREAMS=1` to your compiler).
|
||||||
|
|
||||||
|
This will enable new logging functions with `_S` suffixes:
|
||||||
|
|
||||||
|
```
|
||||||
|
LOG_S(INFO) << "Look at my custom object: " << a.cross(b);
|
||||||
|
CHECK_EQ_S(pi, 3.14) << "Maybe it is closer to " << M_PI;
|
||||||
|
```
|
||||||
|
|
||||||
|
Here are all the stream logging functions:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
VLOG_IF_S(verbosity, cond) << ...
|
||||||
|
LOG_IF_S(verbosity_name, cond) << ...
|
||||||
|
VLOG_S(verbosity) << ...
|
||||||
|
LOG_S(verbosity_name) << ...
|
||||||
|
DVLOG_IF_S(verbosity, cond) << ...
|
||||||
|
DLOG_IF_S(verbosity_name, cond) << ...
|
||||||
|
DVLOG_S(verbosity) << ...
|
||||||
|
DLOG_S(verbosity_name) << ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
* Any arguments to LOG functions or LOG_SCOPE are only evaluated iff the verbosity test passes.
|
||||||
|
* Any arguments to LOG_IF functions are only evaluated if the test passes.
|
||||||
|
|
||||||
|
|
||||||
|
# Logging crashes
|
||||||
|
One of the most important parts of any logging library is to log things when they go horribly wrong. In Loguru, we call this *FATAL* logging. This can be caused by CHECK fails, signals or manual aborts.
|
||||||
|
|
||||||
|
When they happen, Loguru will:
|
||||||
|
|
||||||
|
* Print the cause of the fatality
|
||||||
|
* Print the error context (see separate chapter)
|
||||||
|
* Print a stack trace
|
||||||
|
* Call any installed fatal handler (see `set_fatal_handler`).
|
||||||
|
* Abort your program (unless your fatal handler chose to instead throw an exception).
|
||||||
|
|
||||||
|
Let's examine all the causes for FATALs:
|
||||||
|
|
||||||
|
## CHECKS
|
||||||
|
Loguru can also do runtime checks which are similar to [`assert`](http://en.cppreference.com/w/cpp/error/assert) but far more powerful. In particular, by default Loguru will run the checks in release builds as well as debug builds, and helpful and descriptive errors will be logged when a check fails. Here is a simple example of some checks:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
CHECK_F(list.empty(), "Expected empty list, but list has %lu elements in it", list.size());
|
||||||
|
CHECK_NOTNULL_F(some_pointer);
|
||||||
|
CHECK_GT_F(number, 0, "Expected a positive integer");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variants
|
||||||
|
``` C++
|
||||||
|
CHECK_F(test, ...)
|
||||||
|
CHECK_NOTNULL_F(x, ...)
|
||||||
|
CHECK_EQ_F(a, b, ...)
|
||||||
|
CHECK_NE_F(a, b, ...)
|
||||||
|
CHECK_LT_F(a, b, ...)
|
||||||
|
CHECK_LE_F(a, b, ...)
|
||||||
|
CHECK_GT_F(a, b, ...)
|
||||||
|
CHECK_GE_F(a, b, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
You also have debug-versions of these:
|
||||||
|
``` C++
|
||||||
|
DCHECK_F(test, ...)
|
||||||
|
DCHECK_NOTNULL_F(x, ...)
|
||||||
|
DCHECK_EQ_F(a, b, ...)
|
||||||
|
DCHECK_NE_F(a, b, ...)
|
||||||
|
DCHECK_LT_F(a, b, ...)
|
||||||
|
DCHECK_LE_F(a, b, ...)
|
||||||
|
DCHECK_GT_F(a, b, ...)
|
||||||
|
DCHECK_GE_F(a, b, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
These can be used to run a check ONLY in debug build (or when `LOGURU_DEBUG_CHECKS` is defined).
|
||||||
|
|
||||||
|
### Format string
|
||||||
|
The format string to CHECK:s are optional. All these work:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
CHECK_F(exists(filename));
|
||||||
|
CHECK_F(exists(filename), "File does not exist");
|
||||||
|
CHECK_F(exists(filename), "File does not exist: '%s'", filename);
|
||||||
|
```
|
||||||
|
|
||||||
|
Arguments to CHECK:s are only evaluated once.
|
||||||
|
|
||||||
|
## `ABORT_F`
|
||||||
|
You can also call `ABORT_F` to abort your program with a message written to the log: `ABORT_F("Something went wrong wile processing '%s'", filename);`
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
By calling `loguru::init` Loguru will also catch signals such as segmentation errors and divisions by zero. Here is the full list of signals caught by Loguru:
|
||||||
|
|
||||||
|
* `SIGABRT` (can be turned off with LOGURU_CATCH_SIGABRT=0)
|
||||||
|
* `SIGBUS`
|
||||||
|
* `SIGFPE`
|
||||||
|
* `SIGILL`
|
||||||
|
* `SIGINT`
|
||||||
|
* `SIGSEGV`
|
||||||
|
* `SIGTERM`
|
||||||
|
|
||||||
|
## Error context
|
||||||
|
A stack trace gives you the names of the function at the point of a crash. With `ERROR_CONTEXT`, you can also get the values of select local variables. `ERROR_CONTEXT` is in effect a logging that only occurs if there is a crash.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
void process_customers(const std::string& filename)
|
||||||
|
{
|
||||||
|
ERROR_CONTEXT("Processing file", filename.c_str());
|
||||||
|
for (size_t i = 0; i < num_customers; ++i) {
|
||||||
|
ERROR_CONTEXT("Customer index", i);
|
||||||
|
if (i == 42) { crashy_code(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The context extends to the containing scope of the `ERROR_CONTEXT` macro.
|
||||||
|
|
||||||
|
Example output (in case of crash):
|
||||||
|
|
||||||
|
```
|
||||||
|
------------------------------------------------
|
||||||
|
[ErrorContext] main.cpp:416 Processing file: "customers.json"
|
||||||
|
[ErrorContext] main.cpp:417 Customer index: 42
|
||||||
|
------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
Error contexts are printed automatically on crashes. Note that values captured by `ERROR_CONTEXT` are **only printed on a crash**. They do not litter the log file otherwise. They also have an almost negligible performance hit (about 12 nanoseconds per `ERROR_CONTEXT` on my MacBook Pro, compared to about 4-7 milliseconds a line in the logfile).
|
||||||
|
|
||||||
|
`ERROR_CONTEXT` works with built-in types (`float`, `int`, `char` etc) as well as `const char*`. You can also add support for your own types by overloading `loguru::ec_to_text` (see [`loguru.hpp`](https://github.com/emilk/loguru/blob/master/loguru.hpp) for details).
|
||||||
|
|
||||||
|
The `ERROR_CONTEXT` feature of Loguru is actually orthogonal to the logging. If you want to, you can use Loguru just for its `ERROR_CONTEXT` (and use some other library for logging). You can print the error context stack at any time like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
auto text = loguru::get_error_context();
|
||||||
|
printf("%s", text.c_str());
|
||||||
|
some_stream << text.c_str(); // Or like this
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
## `void init(int& argc, char* argv[], const Options& options = {})`
|
||||||
|
|
||||||
|
Should be called from the main thread.
|
||||||
|
You don't *need* to call this, but if you do you get:
|
||||||
|
* Signal handlers installed
|
||||||
|
* Program arguments logged
|
||||||
|
* Working dir logged
|
||||||
|
* Optional `-v` verbosity flag parsed
|
||||||
|
* Main thread name set to "main thread"
|
||||||
|
* Explanation of the preamble (date, threanmae etc) logged
|
||||||
|
|
||||||
|
`loguru::init()` will look for arguments meant for loguru and remove them.
|
||||||
|
Loguru currently only looks for the `-v` argument:
|
||||||
|
```
|
||||||
|
-v n Set loguru::g_stderr_verbosity level. Examples:
|
||||||
|
-v 3 Show verbosity level 3 and lower.
|
||||||
|
-v 0 Only show INFO, WARNING, ERROR, FATAL (default).
|
||||||
|
-v INFO Only show INFO, WARNING, ERROR, FATAL (default).
|
||||||
|
-v WARNING Only show WARNING, ERROR, FATAL.
|
||||||
|
-v ERROR Only show ERROR, FATAL.
|
||||||
|
-v FATAL Only show FATAL.
|
||||||
|
-v OFF Turn off logging to stderr.
|
||||||
|
```
|
||||||
|
|
||||||
|
Tip: You can set `g_stderr_verbosity` before calling `loguru::init`.
|
||||||
|
That way you can set the default but have the user override it with the `-v` flag.
|
||||||
|
Note that `-v` does not affect file logging (see `loguru::add_file`).
|
||||||
|
|
||||||
|
You can you something other than the `-v` flag by setting the `verbosity_flag` option.
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
// Runtime options passed to loguru::init
|
||||||
|
struct Options
|
||||||
|
{
|
||||||
|
// This allows you to use something else instead of "-v" via verbosity_flag.
|
||||||
|
// Set to nullptr to if you don't want Loguru to parse verbosity from the args.'
|
||||||
|
const char* verbosity_flag = "-v";
|
||||||
|
|
||||||
|
// loguru::init will set the name of the calling thread to this.
|
||||||
|
// If you don't want Loguru to set the name of the main thread,
|
||||||
|
// set this to nullptr.
|
||||||
|
// NOTE: on SOME platforms loguru::init will only overwrite the thread name
|
||||||
|
// if a thread name has not already been set.
|
||||||
|
// To always set a thread name, use loguru::set_thread_name instead.
|
||||||
|
const char+ main_thread_name = "main thread";
|
||||||
|
|
||||||
|
// Make Loguru try to do unsafe but useful things,
|
||||||
|
// like printing a stack trace, when catching signals.
|
||||||
|
// This may lead to bad things like deadlocks in certain situations.
|
||||||
|
bool unsafe_signal_handler = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## `void shutdown()`
|
||||||
|
Will call `loguru::remove_all_callbacks()`, thus stopping all logging except to `stderr`.
|
||||||
|
|
||||||
|
You generally don't need to call this.
|
||||||
|
|
||||||
|
## `bool add_file(const char* path, FileMode mode, Verbosity verbosity)`
|
||||||
|
|
||||||
|
Will log to a file at the given path. Any logging message with a verbosity lower or equal to the given verbosity will be included. The function will create all directories in 'path' if needed (like `mkdir -p`).
|
||||||
|
|
||||||
|
If the path starts with a `~`, it will be replaced with your home path.
|
||||||
|
To stop the file logging, just call `loguru::remove_callback(path)` with the same path.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
// Put every log message in "everything.log":
|
||||||
|
loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX);
|
||||||
|
|
||||||
|
// Only log INFO, WARNING, ERROR and FATAL to "latest_readable.log":
|
||||||
|
loguru::add_file("latest_readable.log", loguru::Truncate, loguru::Verbosity_INFO);
|
||||||
|
|
||||||
|
char log_path[PATH_MAX];
|
||||||
|
loguru::suggest_log_path("~/loguru/", log_path, sizeof(log_path));
|
||||||
|
loguru::add_file(log_path, loguru::FileMode::Truncate, loguru::Verbosity_MAX);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## `void suggest_log_path(const char* prefix, char* buff, unsigned buff_size)`
|
||||||
|
Given a prefix of e.g. `"~/loguru/"` this might return:
|
||||||
|
|
||||||
|
`"/home/your_username/loguru/app_name/20151017_161503.123.log"`
|
||||||
|
|
||||||
|
where "app_name" is a sanitized version of `argv[0]`.
|
||||||
|
|
||||||
|
## `void set_thread_name(const char* name)`
|
||||||
|
Thread names can be set for the benefit of readable logs.
|
||||||
|
If you do not set the thread name, a hex id will be shown instead.
|
||||||
|
These thread names may or may not be the same as the system thread names,
|
||||||
|
depending on the system.
|
||||||
|
Try to limit the thread name to 15 characters or less.
|
||||||
|
`loguru::init` will set the name of the calling thread to `"main thread"`.
|
||||||
|
|
||||||
|
## `Text stacktrace(int skip = 1)`
|
||||||
|
Generates a readable stacktrace as a string. 'skip' specifies how many stack frames to skip. For instance, the default skip means: don't include the function loguru::stacktrace in the stack trace.
|
||||||
|
|
||||||
|
`Text` here is just a simple wrapper type. You can print its content with `.c_str()` just like you would with an `std::string`.
|
||||||
|
|
||||||
|
## `Text errno_as_text();`
|
||||||
|
A thread-safe version `strerror`.
|
||||||
|
|
||||||
|
|
||||||
|
# Callbacks
|
||||||
|
## Logging callbacks
|
||||||
|
Say you want to send your log messages over a network, or print them to the screen in your game. What you want then is a callback from Loguru each time a logging function is called. This is what `loguru::add_callback` is for. Here is an example how to do this:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
struct MyNetworkLogger{...};
|
||||||
|
|
||||||
|
// Note: this function should be thread safe,
|
||||||
|
// as logging can be done from any thread at any time.
|
||||||
|
void log_to_network(void* user_data, const loguru::Message& message)
|
||||||
|
{
|
||||||
|
MyNetworkLogger* logger = reinterpret_cast<MyNetworkLogger*>(user_data);
|
||||||
|
logger->log("%s%s", message.prefix, message.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
MyNetworkLogger network_logger{...};
|
||||||
|
loguru::add_callback("network_logger",
|
||||||
|
log_to_network, &network_logger, loguru::Verbosity_INFO);
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's the relevant excerpt from `Loguru.hpp`:
|
||||||
|
``` C++
|
||||||
|
struct Message
|
||||||
|
{
|
||||||
|
// You would generally print a Message by just concating the buffers without spacing.
|
||||||
|
// Optionally, ignore preamble and indentation.
|
||||||
|
Verbosity verbosity; // Already part of preamble
|
||||||
|
const char* filename; // Already part of preamble
|
||||||
|
unsigned line; // Already part of preamble
|
||||||
|
const char* preamble; // Date, time, uptime, thread, file:line, verbosity.
|
||||||
|
const char* indentation; // Just a bunch of spacing.
|
||||||
|
const char* prefix; // Assertion failure info goes here (or "").
|
||||||
|
const char* message; // User message goes here.
|
||||||
|
};
|
||||||
|
|
||||||
|
// May not throw!
|
||||||
|
typedef void (*log_handler_t)(void* user_data, const Message& message);
|
||||||
|
typedef void (*close_handler_t)(void* user_data);
|
||||||
|
typedef void (*flush_handler_t)(void* user_data);
|
||||||
|
|
||||||
|
/* Will be called on each log messages with a verbosity less or equal to the given one.
|
||||||
|
Useful for displaying messages on-screen in a game, for example.
|
||||||
|
The given on_close is also expected to flush (if desired).
|
||||||
|
*/
|
||||||
|
void add_callback(
|
||||||
|
const char* id,
|
||||||
|
log_handler_t callback,
|
||||||
|
void* user_data,
|
||||||
|
Verbosity verbosity,
|
||||||
|
close_handler_t on_close = nullptr,
|
||||||
|
flush_handler_t on_flush = nullptr);
|
||||||
|
|
||||||
|
// Returns true iff the callback was found (and removed).
|
||||||
|
bool remove_callback(const char* id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fatal handler
|
||||||
|
You can install a callback to be called when your program is about to crash (a CHECK failed, a signal was caught, or somebody called `ABORT_F`).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
``` C+++
|
||||||
|
// Throw exceptions instead of aborting:
|
||||||
|
loguru::set_fatal_handler([](const loguru::Message& message){
|
||||||
|
throw std::runtime_error(std::string(message.prefix) + message.message);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Options
|
||||||
|
## Run-time option:
|
||||||
|
There are some options you can set via global variables in the `loguru::` namespace:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
// Only write warnings, errors and crashes to stderr:
|
||||||
|
loguru::g_stderr_verbosity = loguru::Verbosity_WARNING;
|
||||||
|
|
||||||
|
// Turn off writing err/warn in red:
|
||||||
|
loguru::g_colorlogtostderr = false;
|
||||||
|
```
|
||||||
|
|
||||||
|
Generally you would do the above once before calling `loguru::init`.
|
||||||
|
|
||||||
|
|
||||||
|
### `loguru::g_stderr_verbosity`
|
||||||
|
Everything with a verbosity equal or greater than g_stderr_verbosity will be
|
||||||
|
written to stderr. You can set this in code or via the -v argument.
|
||||||
|
Set to loguru::Verbosity_OFF to write nothing to stderr.
|
||||||
|
Default is 0, i.e. only log ERROR, WARNING and INFO are written to stderr.
|
||||||
|
|
||||||
|
### `loguru::g_flush_interval_ms`:
|
||||||
|
If set to zero Loguru will flush on every line (unbuffered mode).
|
||||||
|
Else Loguru will flush outputs every `g_flush_interval_ms` milliseconds (buffered mode). The default is `g_flush_interval_ms=0`, i.e. unbuffered mode.
|
||||||
|
|
||||||
|
### List
|
||||||
|
Here is the full list:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
Verbosity g_stderr_verbosity = 0; // 0 (INFO) by default.
|
||||||
|
bool g_colorlogtostderr = true; // If you don't want color in your terminal.
|
||||||
|
unsigned g_flush_interval_ms = 0; // Unbuffered (0) by default.
|
||||||
|
bool g_preamble = true; // Prefix each log line with date, time etc?
|
||||||
|
|
||||||
|
// Turn off individual parts of the preamble
|
||||||
|
bool g_preamble_date = true; // The date field
|
||||||
|
bool g_preamble_time = true; // The time of the current day
|
||||||
|
bool g_preamble_uptime = true; // The time since init call
|
||||||
|
bool g_preamble_thread = true; // The logging thread
|
||||||
|
bool g_preamble_file = true; // The file from which the log originates from
|
||||||
|
bool g_preamble_verbose = true; // The verbosity field
|
||||||
|
bool g_preamble_pipe = true; // The pipe symbol right before the message
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Compile-time options
|
||||||
|
Loguru let's you tweak many aspects of how it works via macros that you can set either manually with `#define LOGURU_FOO 1` or with a compilation flag as `-DLOGURU_FOO=1`.
|
||||||
|
|
||||||
|
Here follows a list of flags and their default values:
|
||||||
|
|
||||||
|
### `LOGURU_EXPORT`
|
||||||
|
Define to your project's export declaration if needed for use in a shared library.
|
||||||
|
|
||||||
|
### `LOGURU_DEBUG_LOGGING` (defaults to the opposite of `NDEBUG`):
|
||||||
|
Enables debug versions of logging statements (`DLOG_F` etc).
|
||||||
|
|
||||||
|
### `LOGURU_DEBUG_CHECKS` (defaults to the opposite of `NDEBUG`):
|
||||||
|
Enables debug versions of checks (`DCHECK_F` etc).
|
||||||
|
|
||||||
|
### `LOGURU_SCOPE_TEXT_SIZE = 196`
|
||||||
|
Maximum length of text that can be printed by a `LOG_SCOPE`. This should be long enough to get most things, but short enough not to clutter the stack.
|
||||||
|
|
||||||
|
### `LOGURU_CATCH_SIGABRT = 1`
|
||||||
|
Should Loguru catch SIGABRT to print stack trace etc?
|
||||||
|
|
||||||
|
### `LOGURU_REDEFINE_ASSERT = 0`
|
||||||
|
Redefine "assert" to call Loguru version (!NDEBUG only).
|
||||||
|
|
||||||
|
### `LOGURU_WITH_STREAMS = 0`
|
||||||
|
Add support for _S versions for all LOG and CHECK functions:
|
||||||
|
``` C++
|
||||||
|
LOG_S(INFO) << "My vec3: " << x.cross(y);
|
||||||
|
CHECK_EQ_S(a, b) << "I expected a and b to be the same!";
|
||||||
|
```
|
||||||
|
This is off by default to keep down compilation times.
|
||||||
|
|
||||||
|
### `LOGURU_REPLACE_GLOG = 0`
|
||||||
|
Make Loguru mimic GLOG as close as possible, including #defining `LOG`, `CHECK`, `VLOG_IS_ON` etc. Great if you plan to migrate from GLOG to Loguru and don't want to add _S suffixes to all your logging functions (see `LOGURU_WITH_STREAMS`). `LOGURU_REPLACE_GLOG` implies `LOGURU_WITH_STREAMS`.
|
||||||
|
|
||||||
|
### `LOGURU_USE_FMTLIB = 0`
|
||||||
|
Use fmtlib formatting. See https://github.com/fmtlib/fmt
|
||||||
|
This will make `loguru.hpp` depend on `<fmt/format.h>`
|
||||||
|
You will need to link against `fmtlib` or use the `FMT_HEADER_ONLY` preprocessor definition.
|
||||||
|
Feature by kolis (https://github.com/emilk/loguru/pull/22)
|
||||||
|
|
||||||
|
### `LOGURU_WITH_FILEABS = 0`
|
||||||
|
When `LOGURU_WITH_FILEABS` is turned on, a check of file change will be performed on every call to file_log.
|
||||||
|
If the file is moved, or inode changes, file is reopened using the same FileMode as is done by add_file.
|
||||||
|
Such a scheme is useful if you have a daemon program that moves the log file every 24 hours and expects new file to be created.
|
||||||
|
Feature by scinart (https://github.com/emilk/loguru/pull/23).
|
||||||
|
|
||||||
|
### `LOGURU_STACKTRACES` (default 1 on supported platforms)
|
||||||
|
Print stack traces on abort.
|
||||||
|
|
||||||
|
### `LOGURU_RTTI`
|
||||||
|
Set to 0 if your platform does not support runtime type information (-fno-rtti).
|
||||||
|
|
||||||
|
### Flags for controlling output formats:
|
||||||
|
#### `LOGURU_FILENAME_WIDTH = 23`
|
||||||
|
Width of the column containing the file name
|
||||||
|
|
||||||
|
#### `LOGURU_THREADNAME_WIDTH = 16`
|
||||||
|
Width of the column containing the thread name.
|
||||||
|
|
||||||
|
#### `LOGURU_VERBOSE_SCOPE_ENDINGS = 1`
|
||||||
|
Show milliseconds and scope name at end of scope.
|
||||||
|
|
||||||
|
#### `LOGURU_SCOPE_TIME_PRECISION = 3`
|
||||||
|
Resolution of scope timers. 3=ms, 6=us, 9=ns
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Markdeep: --><style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="markdeep.min.js"></script><script src="https://casual-effects.com/markdeep/latest/markdeep.min.js"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|
BIN
docs/terminal_colors.png
Normal file
BIN
docs/terminal_colors.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 196 KiB |
71
glog_bench/CMakeLists.txt
Normal file
71
glog_bench/CMakeLists.txt
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
project(glog_bench)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "Release" CACHE STRING
|
||||||
|
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||||
|
endif(NOT CMAKE_BUILD_TYPE)
|
||||||
|
|
||||||
|
MESSAGE(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
|
add_compile_options(-std=c++11 -Werror -Wall -Wextra)
|
||||||
|
|
||||||
|
file(GLOB source
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
# GFLAGS
|
||||||
|
find_library(GFLAGS_LIB NAMES "gflags" PATHS
|
||||||
|
${GFLAGS_ROOT}/lib
|
||||||
|
${GFLAGS_ROOT}/Lib)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h PATHS ${GFLAGS_ROOT}/Include)
|
||||||
|
set(GFLAGS_LIB_DIR ${GFLAGS_ROOT}/Lib)
|
||||||
|
else()
|
||||||
|
find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h PATHS
|
||||||
|
${GFLAGS_ROOT}/include)
|
||||||
|
set(GFLAGS_LIB_DIR ${GFLAGS_ROOT}/lib)
|
||||||
|
endif()
|
||||||
|
mark_as_advanced(
|
||||||
|
GFLAGS_INCLUDE_DIR
|
||||||
|
GFLAGS_LIB
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
# GLOG
|
||||||
|
|
||||||
|
find_library(GLOG_LIB NAMES "glog" libglog PATHS
|
||||||
|
${GLOG_ROOT})
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
find_path(GLOG_INCLUDE_DIR glog/logging.h PATHS ${GLOG_ROOT})
|
||||||
|
set(GLOG_LIB_DIR ${GLOG_ROOT})
|
||||||
|
add_definitions(-DUSE_OWN_CHECK)
|
||||||
|
else()
|
||||||
|
find_path(GLOG_INCLUDE_DIR glog/logging.h PATHS
|
||||||
|
${GLOG_ROOT}/include)
|
||||||
|
set(GLOG_LIB_DIR ${GLOG_ROOT}/lib)
|
||||||
|
endif()
|
||||||
|
add_definitions(-DGLOG_NO_ABBREVIATED_SEVERITIES=1)
|
||||||
|
|
||||||
|
mark_as_advanced(
|
||||||
|
GLOG_INCLUDE_DIR
|
||||||
|
GLOG_LIB
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
|
||||||
|
add_executable(glog_bench ${source})
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${GFLAGS_INCLUDE_DIR}
|
||||||
|
${GLOG_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(glog_bench
|
||||||
|
${GFLAGS_LIB}
|
||||||
|
${GLOG_LIB}
|
||||||
|
)
|
12
glog_bench/build_and_run.sh
Executable file
12
glog_bench/build_and_run.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e # Fail on error
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||||
|
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
|
||||||
|
./glog_bench $@ 2>/dev/null
|
93
glog_bench/glog_bench.cpp
Normal file
93
glog_bench/glog_bench.cpp
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
#include <glog/raw_logging.h>
|
||||||
|
|
||||||
|
const size_t kNumIterations = 50 * 1000;
|
||||||
|
const size_t kNumRuns = 10;
|
||||||
|
const double kPi = 3.1415926535897932384626433;
|
||||||
|
|
||||||
|
static long long now_ns()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
return duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Function>
|
||||||
|
double time_sec(const Function& function)
|
||||||
|
{
|
||||||
|
auto start_ns = now_ns();
|
||||||
|
function();
|
||||||
|
return (now_ns() - start_ns) * 1e-9;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Function>
|
||||||
|
void bench(const std::string& name, const Function& function)
|
||||||
|
{
|
||||||
|
function(); // Warm-up
|
||||||
|
|
||||||
|
printf("%-30s ", name.c_str());
|
||||||
|
fflush(stdout);
|
||||||
|
std::vector<double> times;
|
||||||
|
double sum = 0;
|
||||||
|
for (size_t i = 0; i < kNumRuns; ++i)
|
||||||
|
{
|
||||||
|
times.push_back(time_sec(function) / kNumIterations);
|
||||||
|
sum += times.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
double mean = sum / kNumRuns;
|
||||||
|
double std_dev_sum = 0;
|
||||||
|
|
||||||
|
for (double time : times) {
|
||||||
|
std_dev_sum += (time - mean) * (time - mean);
|
||||||
|
}
|
||||||
|
|
||||||
|
double variance = std::sqrt(std_dev_sum / kNumRuns);
|
||||||
|
|
||||||
|
printf("%6.3f +- %.3f us per call\n", mean * 1e6, variance * 1e6);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void stream_strings()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < kNumIterations; ++i) {
|
||||||
|
LOG(WARNING) << "Some long, complex message.";
|
||||||
|
}
|
||||||
|
google::FlushLogFiles(google::GLOG_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stream_float()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < kNumIterations; ++i) {
|
||||||
|
LOG(WARNING) << std::setfill('0') << std::setw(6) << std::setprecision(3) << kPi;
|
||||||
|
}
|
||||||
|
google::FlushLogFiles(google::GLOG_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_string_float()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < kNumIterations; ++i) {
|
||||||
|
RAW_LOG(WARNING, "Some long, complex message.");
|
||||||
|
}
|
||||||
|
google::FlushLogFiles(google::GLOG_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
FLAGS_alsologtostderr = true;
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
|
||||||
|
bench("LOG(WARNING) << string (buffered):", stream_strings);
|
||||||
|
bench("LOG(WARNING) << float (buffered):", stream_float);
|
||||||
|
|
||||||
|
FLAGS_logbufsecs = 0; // Flush all output in realtime
|
||||||
|
bench("LOG(WARNING) << string (unbuffered):", stream_strings);
|
||||||
|
bench("LOG(WARNING) << float (unbuffered):", stream_float);
|
||||||
|
}
|
71
glog_example/CMakeLists.txt
Normal file
71
glog_example/CMakeLists.txt
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
project(glog_example)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "Release" CACHE STRING
|
||||||
|
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||||
|
endif(NOT CMAKE_BUILD_TYPE)
|
||||||
|
|
||||||
|
MESSAGE(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
|
add_compile_options(-std=c++11 -Werror -Wall -Wextra)
|
||||||
|
|
||||||
|
file(GLOB source
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
# GFLAGS
|
||||||
|
find_library(GFLAGS_LIB NAMES "gflags" PATHS
|
||||||
|
${GFLAGS_ROOT}/lib
|
||||||
|
${GFLAGS_ROOT}/Lib)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h PATHS ${GFLAGS_ROOT}/Include)
|
||||||
|
set(GFLAGS_LIB_DIR ${GFLAGS_ROOT}/Lib)
|
||||||
|
else()
|
||||||
|
find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h PATHS
|
||||||
|
${GFLAGS_ROOT}/include)
|
||||||
|
set(GFLAGS_LIB_DIR ${GFLAGS_ROOT}/lib)
|
||||||
|
endif()
|
||||||
|
mark_as_advanced(
|
||||||
|
GFLAGS_INCLUDE_DIR
|
||||||
|
GFLAGS_LIB
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
# GLOG
|
||||||
|
|
||||||
|
find_library(GLOG_LIB NAMES "glog" libglog PATHS
|
||||||
|
${GLOG_ROOT})
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
find_path(GLOG_INCLUDE_DIR glog/logging.h PATHS ${GLOG_ROOT})
|
||||||
|
set(GLOG_LIB_DIR ${GLOG_ROOT})
|
||||||
|
add_definitions(-DUSE_OWN_CHECK)
|
||||||
|
else()
|
||||||
|
find_path(GLOG_INCLUDE_DIR glog/logging.h PATHS
|
||||||
|
${GLOG_ROOT}/include)
|
||||||
|
set(GLOG_LIB_DIR ${GLOG_ROOT}/lib)
|
||||||
|
endif()
|
||||||
|
add_definitions(-DGLOG_NO_ABBREVIATED_SEVERITIES=1)
|
||||||
|
|
||||||
|
mark_as_advanced(
|
||||||
|
GLOG_INCLUDE_DIR
|
||||||
|
GLOG_LIB
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
|
||||||
|
add_executable(glog_example ${source})
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
${GFLAGS_INCLUDE_DIR}
|
||||||
|
${GLOG_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(glog_example
|
||||||
|
${GFLAGS_LIB}
|
||||||
|
${GLOG_LIB}
|
||||||
|
)
|
12
glog_example/build_and_run.sh
Executable file
12
glog_example/build_and_run.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e # Fail on error
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||||
|
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
|
||||||
|
./glog_example $@
|
26
glog_example/glog_example.hpp
Normal file
26
glog_example/glog_example.hpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <glog/logging.h>
|
||||||
|
|
||||||
|
inline void sleep_ms(int ms)
|
||||||
|
{
|
||||||
|
VLOG(2) << "Sleeping for " << ms << " ms";
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void complex_calculation()
|
||||||
|
{
|
||||||
|
LOG(INFO) << "complex_calculation started";
|
||||||
|
LOG(INFO) << "Starting time machine...";
|
||||||
|
sleep_ms(200);
|
||||||
|
LOG(WARNING) << "The flux capacitor is not getting enough power!";
|
||||||
|
sleep_ms(400);
|
||||||
|
LOG(INFO) << "Lighting strike!";
|
||||||
|
VLOG(1) << "Found 1.21 gigawatts...";
|
||||||
|
sleep_ms(400);
|
||||||
|
std::thread([](){
|
||||||
|
LOG(ERROR) << "We ended up in 1985!";
|
||||||
|
}).join();
|
||||||
|
LOG(INFO) << "complex_calculation stopped";
|
||||||
|
}
|
13
glog_example/main.cpp
Normal file
13
glog_example/main.cpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include "glog_example.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
FLAGS_alsologtostderr = true;
|
||||||
|
FLAGS_colorlogtostderr = true;
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
google::InitGoogleLogging(argv[0]);
|
||||||
|
LOG(INFO) << "Hello from main.cpp!";
|
||||||
|
complex_calculation();
|
||||||
|
LOG(INFO) << "main function about to end!";
|
||||||
|
}
|
1849
loguru.cpp
Normal file
1849
loguru.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1388
loguru.hpp
Normal file
1388
loguru.hpp
Normal file
File diff suppressed because it is too large
Load Diff
22
loguru_bench/CMakeLists.txt
Normal file
22
loguru_bench/CMakeLists.txt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
project(loguru_bench)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "Release" CACHE STRING
|
||||||
|
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||||
|
endif(NOT CMAKE_BUILD_TYPE)
|
||||||
|
|
||||||
|
MESSAGE(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Werror -Wall -Wextra")
|
||||||
|
|
||||||
|
file(GLOB source
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(loguru_bench ${source})
|
||||||
|
|
||||||
|
find_package(Threads)
|
||||||
|
target_link_libraries(loguru_bench ${CMAKE_THREAD_LIBS_INIT}) # For pthreads
|
||||||
|
target_link_libraries(loguru_bench dl) # For ldl
|
12
loguru_bench/build_and_run.sh
Executable file
12
loguru_bench/build_and_run.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e # Fail on error
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||||
|
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
|
||||||
|
./loguru_bench $@ 2>/dev/null
|
121
loguru_bench/loguru_bench.cpp
Normal file
121
loguru_bench/loguru_bench.cpp
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define LOGURU_WITH_STREAMS 1
|
||||||
|
#define LOGURU_REDEFINE_ASSERT 1
|
||||||
|
#include "../loguru.cpp"
|
||||||
|
|
||||||
|
const size_t kNumRuns = 10;
|
||||||
|
const double kPi = 3.1415926535897932384626433;
|
||||||
|
|
||||||
|
static long long now_ns()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
return duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Function>
|
||||||
|
void bench(const std::string& name, const Function& function, size_t num_iterations)
|
||||||
|
{
|
||||||
|
function(num_iterations); // Warm-up.
|
||||||
|
|
||||||
|
printf("%-30s ", name.c_str());
|
||||||
|
fflush(stdout);
|
||||||
|
std::vector<double> times;
|
||||||
|
double sum = 0;
|
||||||
|
for (size_t i = 0; i < kNumRuns; ++i) {
|
||||||
|
auto start_ns = now_ns();
|
||||||
|
function(num_iterations);
|
||||||
|
double total_time_sec = (now_ns() - start_ns) * 1e-9;
|
||||||
|
times.push_back(total_time_sec / num_iterations);
|
||||||
|
sum += times.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
double mean = sum / kNumRuns;
|
||||||
|
double std_dev_sum = 0;
|
||||||
|
|
||||||
|
for (double time : times) {
|
||||||
|
std_dev_sum += (time - mean) * (time - mean);
|
||||||
|
}
|
||||||
|
|
||||||
|
double variance = std::sqrt(std_dev_sum / kNumRuns);
|
||||||
|
|
||||||
|
printf("%6.3f ± %.3f μs per call\n", mean * 1e6, variance * 1e6);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void format_strings(size_t num_iterations)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < num_iterations; ++i) {
|
||||||
|
LOG_F(WARNING, "Some long, complex message.");
|
||||||
|
}
|
||||||
|
loguru::flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void format_float(size_t num_iterations)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < num_iterations; ++i) {
|
||||||
|
LOG_F(WARNING, "%+05.3f", kPi);
|
||||||
|
}
|
||||||
|
loguru::flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stream_strings(size_t num_iterations)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < num_iterations; ++i) {
|
||||||
|
LOG_S(WARNING) << "Some long, complex message.";
|
||||||
|
}
|
||||||
|
loguru::flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stream_float(size_t num_iterations)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < num_iterations; ++i) {
|
||||||
|
LOG_S(WARNING) << std::setfill('0') << std::setw(5) << std::setprecision(3) << kPi;
|
||||||
|
}
|
||||||
|
loguru::flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_string_float(size_t num_iterations)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < num_iterations; ++i) {
|
||||||
|
RAW_LOG_F(WARNING, "Some long, complex message.");
|
||||||
|
}
|
||||||
|
loguru::flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_context(size_t num_iterations)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < num_iterations; ++i) {
|
||||||
|
ERROR_CONTEXT("key", "value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
const size_t kNumIterations = 50 * 1000;
|
||||||
|
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
loguru::add_file("loguru_bench.log", loguru::Truncate, loguru::Verbosity_INFO);
|
||||||
|
|
||||||
|
bench("ERROR_CONTEXT", error_context, kNumIterations * 100);
|
||||||
|
|
||||||
|
loguru::g_flush_interval_ms = 200;
|
||||||
|
bench("LOG_F string (buffered):", format_strings, kNumIterations);
|
||||||
|
bench("LOG_F float (buffered):", format_float, kNumIterations);
|
||||||
|
bench("LOG_S string (buffered):", stream_strings, kNumIterations);
|
||||||
|
bench("LOG_S float (buffered):", stream_float, kNumIterations);
|
||||||
|
bench("RAW_LOG_F (buffered):", raw_string_float, kNumIterations);
|
||||||
|
|
||||||
|
loguru::g_flush_interval_ms = 0;
|
||||||
|
bench("LOG_F string (unbuffered):", format_strings, kNumIterations);
|
||||||
|
bench("LOG_F float (unbuffered):", format_float, kNumIterations);
|
||||||
|
bench("LOG_S string (unbuffered):", stream_strings, kNumIterations);
|
||||||
|
bench("LOG_S float (unbuffered):", stream_float, kNumIterations);
|
||||||
|
bench("RAW_LOG_F (unbuffered):", raw_string_float, kNumIterations);
|
||||||
|
}
|
38
loguru_example/CMakeLists.txt
Normal file
38
loguru_example/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
project(loguru_example)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING
|
||||||
|
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||||
|
endif(NOT CMAKE_BUILD_TYPE)
|
||||||
|
|
||||||
|
MESSAGE(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Wall")
|
||||||
|
else()
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Werror -Wall -Wextra -pedantic")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat-pedantic")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-noreturn")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-prototypes")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments")
|
||||||
|
endif() # Clang
|
||||||
|
|
||||||
|
file(GLOB source
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
|
||||||
|
# "${CMAKE_CURRENT_SOURCE_DIR}/../*.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(loguru_example ${source})
|
||||||
|
|
||||||
|
find_package(Threads)
|
||||||
|
target_link_libraries(loguru_example ${CMAKE_THREAD_LIBS_INIT}) # For pthreads
|
||||||
|
if(NOT WIN32)
|
||||||
|
target_link_libraries(loguru_example dl) # For ldl
|
||||||
|
endif()
|
12
loguru_example/build_and_run.sh
Executable file
12
loguru_example/build_and_run.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e # Fail on error
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||||
|
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
|
||||||
|
./loguru_example $@
|
26
loguru_example/loguru_example.hpp
Normal file
26
loguru_example/loguru_example.hpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "../loguru.hpp"
|
||||||
|
|
||||||
|
inline void sleep_ms(int ms)
|
||||||
|
{
|
||||||
|
VLOG_F(2, "Sleeping for %d ms", ms);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void complex_calculation()
|
||||||
|
{
|
||||||
|
LOG_SCOPE_F(INFO, "complex_calculation");
|
||||||
|
LOG_F(INFO, "Starting time machine...");
|
||||||
|
sleep_ms(200);
|
||||||
|
LOG_F(WARNING, "The flux capacitor is not getting enough power!");
|
||||||
|
sleep_ms(400);
|
||||||
|
LOG_F(INFO, "Lighting strike!");
|
||||||
|
VLOG_F(1, "Found 1.21 gigawatts...");
|
||||||
|
sleep_ms(400);
|
||||||
|
std::thread([](){
|
||||||
|
loguru::set_thread_name("the past");
|
||||||
|
LOG_F(ERROR, "We ended up in 1985!");
|
||||||
|
}).join();
|
||||||
|
}
|
13
loguru_example/main.cpp
Normal file
13
loguru_example/main.cpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "loguru_example.hpp"
|
||||||
|
|
||||||
|
#include "../loguru.cpp"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
LOG_F(INFO, "Hello from main.cpp!");
|
||||||
|
complex_calculation();
|
||||||
|
LOG_F(INFO, "main function about to end!");
|
||||||
|
}
|
80
test/CMakeLists.txt
Normal file
80
test/CMakeLists.txt
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
project(loguru_test)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING
|
||||||
|
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||||
|
endif(NOT CMAKE_BUILD_TYPE)
|
||||||
|
|
||||||
|
MESSAGE(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Wall")
|
||||||
|
else()
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Werror -Wall -Wextra -pedantic -fsanitize=address -fno-omit-frame-pointer")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat-pedantic")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-noreturn")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-prototypes")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments")
|
||||||
|
endif() # Clang
|
||||||
|
|
||||||
|
add_executable(loguru_test loguru_test.cpp)
|
||||||
|
|
||||||
|
find_package(Threads)
|
||||||
|
target_link_libraries(loguru_test ${CMAKE_THREAD_LIBS_INIT}) # For pthreads
|
||||||
|
if(NOT WIN32)
|
||||||
|
target_link_libraries(loguru_test dl) # For ldl
|
||||||
|
endif()
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
if(NOT WIN32)
|
||||||
|
# Sadly, Windows doesn't allow us to safely run the following tests
|
||||||
|
list(APPEND ExtraFailureTests
|
||||||
|
SIGSEGV
|
||||||
|
throw_on_signal)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
list(APPEND ExtraFailureTests
|
||||||
|
assert)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Failure Tests
|
||||||
|
foreach(Test
|
||||||
|
ABORT_F
|
||||||
|
ABORT_S
|
||||||
|
LOG_F_FATAL
|
||||||
|
LOG_S_FATAL
|
||||||
|
CHECK_NOTNULL_F
|
||||||
|
CHECK_F
|
||||||
|
CHECK_EQ_F_int
|
||||||
|
CHECK_EQ_F_unsigned
|
||||||
|
CHECK_EQ_F_size_t
|
||||||
|
CHECK_EQ_F
|
||||||
|
CHECK_EQ_F_message
|
||||||
|
CHECK_EQ_S
|
||||||
|
CHECK_LT_S
|
||||||
|
CHECK_LT_S_message
|
||||||
|
deep_abort
|
||||||
|
abort
|
||||||
|
error_context
|
||||||
|
throw_on_fatal
|
||||||
|
${ExtraFailureTests}
|
||||||
|
)
|
||||||
|
add_test(NAME loguru_test_${Test}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/fail_test_wrapper.cmake
|
||||||
|
$<TARGET_FILE:loguru_test> ${Test})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Success Tests
|
||||||
|
foreach(Test
|
||||||
|
callback)
|
||||||
|
add_test(loguru_test_${Test} loguru_test ${Test})
|
||||||
|
endforeach()
|
50
test/appveyor.cmake
Normal file
50
test/appveyor.cmake
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env cmake -P
|
||||||
|
# Group AppVeyor builds to use fewer VM instances and upload useful artifacts from each.
|
||||||
|
find_program(AppVeyor appveyor)
|
||||||
|
if(NOT AppVeyor)
|
||||||
|
message(FATAL_ERROR "script specific to appveyor only")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
function(message type) # overriding CMake built-in message function for AppVeyor
|
||||||
|
set(msg "${ARGV}")
|
||||||
|
if(type STREQUAL FATAL_ERROR OR type STREQUAL SEND_ERROR)
|
||||||
|
set(msg "${ARGN}")
|
||||||
|
set(options -Category Error)
|
||||||
|
elseif(type STREQUAL WARNING OR type STREQUAL AUTHOR_WARNING)
|
||||||
|
set(msg "${ARGN}")
|
||||||
|
set(options -Category Warning)
|
||||||
|
elseif(type STREQUAL STATUS)
|
||||||
|
set(msg "${ARGN}")
|
||||||
|
set(options -Category Information)
|
||||||
|
endif()
|
||||||
|
execute_process(COMMAND ${AppVeyor} AddMessage "${msg}" ${options})
|
||||||
|
|
||||||
|
_message(${ARGV}) # the built-in functionality
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(BuildAndRun)
|
||||||
|
if(DEFINED Generator)
|
||||||
|
string(REPLACE " " "" gen "${Generator}/") # Remove spaces from generator
|
||||||
|
endif()
|
||||||
|
set(BUILD_DIR "${CMAKE_CURRENT_LIST_DIR}/build/${gen}${Configuration}")
|
||||||
|
message("Building ${gen}${Configuration}")
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/build_and_run.cmake)
|
||||||
|
message("Built ${gen}${Configuration}")
|
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} -E tar czf "${BUILD_DIR}/Testing.zip" "${BUILD_DIR}/Testing")
|
||||||
|
execute_process(COMMAND ${AppVeyor} PushArtifact "${BUILD_DIR}/Testing.zip" -FileName "${gen}${Configuration}.zip")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
foreach(Configuration "Debug" "Release")
|
||||||
|
if(WIN32)
|
||||||
|
foreach(Generator
|
||||||
|
"Visual Studio 14 2015"
|
||||||
|
"Visual Studio 14 2015 Win64"
|
||||||
|
"Visual Studio 15 2017"
|
||||||
|
"Visual Studio 15 2017 Win64"
|
||||||
|
)
|
||||||
|
BuildAndRun()
|
||||||
|
endforeach()
|
||||||
|
elseif(UNIX)
|
||||||
|
BuildAndRun()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
21
test/build.sh
Executable file
21
test/build.sh
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e # Fail on error
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||||
|
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
cmake ..
|
||||||
|
|
||||||
|
# Use GCC:
|
||||||
|
# cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ ..
|
||||||
|
|
||||||
|
# Use GCC5:
|
||||||
|
# cmake -DCMAKE_C_COMPILER=/usr/bin/gcc-5 -DCMAKE_CXX_COMPILER=/usr/bin/g++-5 ..
|
||||||
|
|
||||||
|
# Use clang-3.7:
|
||||||
|
# cmake -DCMAKE_C_COMPILER=/usr/bin/clang-3.7 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-3.7 ..
|
||||||
|
|
||||||
|
make
|
45
test/build_and_run.cmake
Normal file
45
test/build_and_run.cmake
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env cmake -P
|
||||||
|
|
||||||
|
# for lack of an equivalent of `set -e` in bash
|
||||||
|
macro(EXEC_CMD_CHECK)
|
||||||
|
message("running ${ARGN}")
|
||||||
|
execute_process(COMMAND ${ARGN} RESULT_VARIABLE CMD_RESULT)
|
||||||
|
if(CMD_RESULT)
|
||||||
|
message(FATAL_ERROR "Error running ${ARGN}")
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# Read the configuration from the environement
|
||||||
|
if(NOT DEFINED Configuration AND DEFINED ENV{Configuration})
|
||||||
|
set(Configuration $ENV{Configuration})
|
||||||
|
endif()
|
||||||
|
# to override the default configuration: ex. -DConfiguration=Release
|
||||||
|
if(DEFINED Configuration)
|
||||||
|
list(APPEND CMAKE_EXTRA_ARGS "-DCMAKE_BUILD_TYPE=${Configuration}")
|
||||||
|
set(CMAKE_BUILD_EXTRA_ARGS --config ${Configuration})
|
||||||
|
set(CTEST_EXTRA_ARGS -C ${Configuration})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# The generator also, if we have an entry in the envirnoment
|
||||||
|
if(NOT DEFINED Generator AND DEFINED ENV{Generator})
|
||||||
|
set(Generator $ENV{Generator})
|
||||||
|
endif()
|
||||||
|
# to override the default generator: ex. -DGenerator=Ninja
|
||||||
|
if(DEFINED Generator)
|
||||||
|
list(APPEND CMAKE_EXTRA_ARGS "-G${Generator}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT DEFINED BUILD_DIR)
|
||||||
|
set(BUILD_DIR ${CMAKE_CURRENT_LIST_DIR}/build)
|
||||||
|
endif()
|
||||||
|
file(MAKE_DIRECTORY ${BUILD_DIR})
|
||||||
|
|
||||||
|
# cd build && cmake .. && cd -
|
||||||
|
EXEC_CMD_CHECK(${CMAKE_COMMAND} ${CMAKE_EXTRA_ARGS} ${CMAKE_CURRENT_LIST_DIR}
|
||||||
|
WORKING_DIRECTORY ${BUILD_DIR})
|
||||||
|
|
||||||
|
# platform-independent equivalent of `make`
|
||||||
|
EXEC_CMD_CHECK(${CMAKE_COMMAND} --build ${BUILD_DIR} ${CMAKE_BUILD_EXTRA_ARGS})
|
||||||
|
|
||||||
|
EXEC_CMD_CHECK(${CMAKE_CTEST_COMMAND} ${CTEST_EXTRA_ARGS} --output-on-failure
|
||||||
|
WORKING_DIRECTORY ${BUILD_DIR})
|
57
test/build_and_run.sh
Executable file
57
test/build_and_run.sh
Executable file
@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e # Fail on error
|
||||||
|
|
||||||
|
ROOT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||||
|
|
||||||
|
$ROOT_DIR/build.sh
|
||||||
|
|
||||||
|
cd $ROOT_DIR/build/
|
||||||
|
|
||||||
|
function test_failure
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
./loguru_test $1 && echo "Expected command to fail!" && exit 1
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_success
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
./loguru_test $1 || echo "Expected command to succeed!"
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "---------------------------------------------------------"
|
||||||
|
echo "Testing failures..."
|
||||||
|
echo "---------------------------------------------------------"
|
||||||
|
test_failure "ABORT_F"
|
||||||
|
test_failure "ABORT_S"
|
||||||
|
test_failure "assert"
|
||||||
|
test_failure "LOG_F_FATAL"
|
||||||
|
test_failure "LOG_S_FATAL"
|
||||||
|
test_failure "CHECK_NOTNULL_F"
|
||||||
|
test_failure "CHECK_F"
|
||||||
|
test_failure "CHECK_EQ_F_int"
|
||||||
|
test_failure "CHECK_EQ_F_unsigned"
|
||||||
|
test_failure "CHECK_EQ_F_size_t"
|
||||||
|
test_failure "CHECK_EQ_F"
|
||||||
|
test_failure "CHECK_EQ_F_message"
|
||||||
|
test_failure "CHECK_EQ_S"
|
||||||
|
test_failure "CHECK_LT_S"
|
||||||
|
test_failure "CHECK_LT_S_message"
|
||||||
|
test_failure "deep_abort"
|
||||||
|
test_failure "SIGSEGV"
|
||||||
|
test_failure "abort"
|
||||||
|
test_failure "error_context"
|
||||||
|
test_failure "throw_on_fatal"
|
||||||
|
test_failure "throw_on_signal"
|
||||||
|
test_success "callback"
|
||||||
|
echo "---------------------------------------------------------"
|
||||||
|
echo "ALL TESTS PASSED!"
|
||||||
|
echo "---------------------------------------------------------"
|
||||||
|
|
||||||
|
./loguru_test $@
|
||||||
|
|
||||||
|
./loguru_test hang
|
24
test/fail_test_wrapper.cmake
Normal file
24
test/fail_test_wrapper.cmake
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env cmake -P
|
||||||
|
cmake_minimum_required(VERSION 2.8.7)
|
||||||
|
|
||||||
|
get_filename_component(CurrentFile ${CMAKE_CURRENT_LIST_FILE} NAME)
|
||||||
|
|
||||||
|
# The script is invoked as cmake -P <script name> <loguru_test> <arg(s)>
|
||||||
|
# the following loop extracts the arguments after the one matching the script name
|
||||||
|
foreach(i RANGE 0 ${CMAKE_ARGC})
|
||||||
|
if(CollectArgs)
|
||||||
|
list(APPEND TestArgs ${CMAKE_ARGV${i}})
|
||||||
|
elseif(CMAKE_ARGV${i} MATCHES "${CurrentFile}$")
|
||||||
|
set(CollectArgs true)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# TestArgs contains <loguru_test> <arg(s)>
|
||||||
|
execute_process(COMMAND ${TestArgs}
|
||||||
|
RESULT_VARIABLE CmdResult)
|
||||||
|
|
||||||
|
# To invert the failure logic
|
||||||
|
if(NOT CmdResult)
|
||||||
|
# the wrapper must fail if the child process returned success
|
||||||
|
message(FATAL_ERROR "${TestArgs} passed.")
|
||||||
|
endif()
|
429
test/loguru_test.cpp
Normal file
429
test/loguru_test.cpp
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
// Include loguru first to test that it needs no dependencies:
|
||||||
|
#define LOGURU_FILENAME_WIDTH 16
|
||||||
|
#define LOGURU_WITH_STREAMS 1
|
||||||
|
#define LOGURU_REDEFINE_ASSERT 1
|
||||||
|
#define LOGURU_USE_FMTLIB 0
|
||||||
|
#define LOGURU_WITH_FILEABS 0
|
||||||
|
// #define LOGURU_STACKTRACES 1
|
||||||
|
// #define LOGURU_RTTI 1
|
||||||
|
#include "../loguru.cpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
void the_one_where_the_problem_is(const std::vector<std::string>& v) {
|
||||||
|
ABORT_F("Abort deep in stack trace, msg: %s", v[0].c_str());
|
||||||
|
}
|
||||||
|
void deep_abort_1(const std::vector<std::string>& v) { the_one_where_the_problem_is(v); }
|
||||||
|
void deep_abort_2(const std::vector<std::string>& v) { deep_abort_1(v); }
|
||||||
|
void deep_abort_3(const std::vector<std::string>& v) { deep_abort_2(v); }
|
||||||
|
void deep_abort_4(const std::vector<std::string>& v) { deep_abort_3(v); }
|
||||||
|
void deep_abort_5(const std::vector<std::string>& v) { deep_abort_4(v); }
|
||||||
|
void deep_abort_6(const std::vector<std::string>& v) { deep_abort_5(v); }
|
||||||
|
void deep_abort_7(const std::vector<std::string>& v) { deep_abort_6(v); }
|
||||||
|
void deep_abort_8(const std::vector<std::string>& v) { deep_abort_7(v); }
|
||||||
|
void deep_abort_9(const std::vector<std::string>& v) { deep_abort_8(v); }
|
||||||
|
void deep_abort_10(const std::vector<std::string>& v) { deep_abort_9(v); }
|
||||||
|
|
||||||
|
void sleep_ms(int ms)
|
||||||
|
{
|
||||||
|
LOG_F(3, "Sleeping for %d ms", ms);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_thread_names()
|
||||||
|
{
|
||||||
|
LOG_SCOPE_FUNCTION(INFO);
|
||||||
|
|
||||||
|
{
|
||||||
|
char thread_name[17];
|
||||||
|
loguru::get_thread_name(thread_name, sizeof(thread_name), false);
|
||||||
|
LOG_F(INFO, "Hello from main thread ('%s')", thread_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto a = std::thread([](){
|
||||||
|
char thread_name[17];
|
||||||
|
loguru::get_thread_name(thread_name, sizeof(thread_name), false);
|
||||||
|
LOG_F(INFO, "Hello from nameless thread ('%s')", thread_name);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto b = std::thread([](){
|
||||||
|
loguru::set_thread_name("renderer");
|
||||||
|
char thread_name[17];
|
||||||
|
loguru::get_thread_name(thread_name, sizeof(thread_name), false);
|
||||||
|
LOG_F(INFO, "Hello from render thread ('%s')", thread_name);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto c = std::thread([](){
|
||||||
|
loguru::set_thread_name("abcdefghijklmnopqrstuvwxyz");
|
||||||
|
char thread_name[17];
|
||||||
|
loguru::get_thread_name(thread_name, sizeof(thread_name), false);
|
||||||
|
LOG_F(INFO, "Hello from thread with a very long name ('%s')", thread_name);
|
||||||
|
});
|
||||||
|
|
||||||
|
a.join();
|
||||||
|
b.join();
|
||||||
|
c.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_scopes()
|
||||||
|
{
|
||||||
|
LOG_SCOPE_FUNCTION(INFO);
|
||||||
|
|
||||||
|
LOG_F(INFO, "Should be indented one step");
|
||||||
|
LOG_F(1, "First thing");
|
||||||
|
LOG_F(1, "Second thing");
|
||||||
|
|
||||||
|
{
|
||||||
|
LOG_SCOPE_F(1, "Some indentation at level 1");
|
||||||
|
LOG_F(INFO, "Should only be indented one more step iff verbosity is 1 or higher");
|
||||||
|
LOG_F(2, "Some info");
|
||||||
|
sleep_ms(123);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep_ms(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_levels()
|
||||||
|
{
|
||||||
|
LOG_SCOPE_FUNCTION(INFO);
|
||||||
|
{
|
||||||
|
VLOG_SCOPE_F(1, "Scope with verbosity 1");
|
||||||
|
LOG_F(3, "Only visible with -v 3 or higher");
|
||||||
|
LOG_F(2, "Only visible with -v 2 or higher");
|
||||||
|
LOG_F(1, "Only visible with -v 1 or higher");
|
||||||
|
}
|
||||||
|
LOG_F(0, "LOG_F(0)");
|
||||||
|
LOG_F(INFO, "This is some INFO");
|
||||||
|
LOG_F(WARNING, "This is a WARNING");
|
||||||
|
LOG_F(ERROR, "This is a serious ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LOGURU_WITH_STREAMS
|
||||||
|
void test_stream()
|
||||||
|
{
|
||||||
|
LOG_SCOPE_FUNCTION(INFO);
|
||||||
|
LOG_S(INFO) << "Testing stream-logging.";
|
||||||
|
LOG_S(INFO) << "First line" << std::endl << "Seconds line.";
|
||||||
|
LOG_S(1) << "Stream-logging with verbosity 1";
|
||||||
|
LOG_S(2) << "Stream-logging with verbosity 2";
|
||||||
|
LOG_S(3) << "Stream-logging with verbosity 3";
|
||||||
|
LOG_IF_S(INFO, true) << "Should be visible";
|
||||||
|
LOG_IF_S(INFO, false) << "SHOULD NOT BE VISIBLE";
|
||||||
|
LOG_IF_S(1, true) << "Should be visible if verbosity is at least 1";
|
||||||
|
LOG_IF_S(1, false) << "SHOULD NOT BE VISIBLE";
|
||||||
|
CHECK_LT_S(1, 2);
|
||||||
|
CHECK_GT_S(3, 2) << "Weird";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int some_expensive_operation() { static int r=31; sleep_ms(132); return r++; }
|
||||||
|
const int BAD = 32;
|
||||||
|
|
||||||
|
int always_increasing() { static int x = 0; return x++; }
|
||||||
|
|
||||||
|
int main_test(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
LOG_SCOPE_FUNCTION(INFO);
|
||||||
|
LOG_F(INFO, "Doing some stuff...");
|
||||||
|
for (int i=0; i<2; ++i) {
|
||||||
|
LOG_SCOPE_F(1, "Iteration %d", i);
|
||||||
|
auto result = some_expensive_operation();
|
||||||
|
LOG_IF_F(WARNING, result == BAD, "Bad result");
|
||||||
|
}
|
||||||
|
LOG_F(INFO, "Time to go!");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_SIGSEGV_0()
|
||||||
|
{
|
||||||
|
LOG_F(INFO, "Intentionally writing to nullptr:");
|
||||||
|
int* ptr = nullptr;
|
||||||
|
*ptr = 42;
|
||||||
|
LOG_F(FATAL, "We shouldn't get here");
|
||||||
|
}
|
||||||
|
void test_SIGSEGV_1() { test_SIGSEGV_0(); }
|
||||||
|
void test_SIGSEGV_2() { test_SIGSEGV_1(); }
|
||||||
|
|
||||||
|
void test_abort_0()
|
||||||
|
{
|
||||||
|
LOG_F(INFO, "Calling std::abort");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
void test_abort_1() { test_abort_0(); }
|
||||||
|
void test_abort_2() { test_abort_1(); }
|
||||||
|
|
||||||
|
struct CustomType
|
||||||
|
{
|
||||||
|
std::string contents;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace loguru {
|
||||||
|
Text ec_to_text(const CustomType* custom)
|
||||||
|
{
|
||||||
|
return Text(strdup(custom->contents.c_str()));
|
||||||
|
}
|
||||||
|
} // namespace loguru
|
||||||
|
|
||||||
|
void test_error_contex()
|
||||||
|
{
|
||||||
|
{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
|
||||||
|
ERROR_CONTEXT("Parent thread value", 42);
|
||||||
|
{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
|
||||||
|
char parent_thread_name[17];
|
||||||
|
loguru::get_thread_name(parent_thread_name, sizeof(parent_thread_name), false);
|
||||||
|
ERROR_CONTEXT("Parent thread name", &parent_thread_name[0]);
|
||||||
|
|
||||||
|
const auto parent_ec_handle = loguru::get_thread_ec_handle();
|
||||||
|
|
||||||
|
std::thread([=]{
|
||||||
|
loguru::set_thread_name("EC test thread");
|
||||||
|
ERROR_CONTEXT("parent error context", parent_ec_handle);
|
||||||
|
{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
|
||||||
|
ERROR_CONTEXT("const char*", "test string");
|
||||||
|
ERROR_CONTEXT("integer", 42);
|
||||||
|
ERROR_CONTEXT("float", 3.14f);
|
||||||
|
ERROR_CONTEXT("double", 3.14);
|
||||||
|
{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
|
||||||
|
ERROR_CONTEXT("char A", 'A');
|
||||||
|
ERROR_CONTEXT("char backslash", '\\');
|
||||||
|
ERROR_CONTEXT("char double-quote", '\"');
|
||||||
|
ERROR_CONTEXT("char single-quote", '\'');
|
||||||
|
ERROR_CONTEXT("char zero", '\0');
|
||||||
|
ERROR_CONTEXT("char bell", '\b');
|
||||||
|
ERROR_CONTEXT("char feed", '\f');
|
||||||
|
ERROR_CONTEXT("char newline", '\n');
|
||||||
|
ERROR_CONTEXT("char return", '\r');
|
||||||
|
ERROR_CONTEXT("char tab", '\t');
|
||||||
|
ERROR_CONTEXT("char x13", '\u0013');
|
||||||
|
{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
|
||||||
|
CustomType custom{"custom_contents"};
|
||||||
|
ERROR_CONTEXT("CustomType", &custom);
|
||||||
|
ABORT_F("Intentional abort");
|
||||||
|
}).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_hang_0()
|
||||||
|
{
|
||||||
|
LOG_F(INFO, "Press ctrl-C to kill.");
|
||||||
|
for(;;) {
|
||||||
|
// LOG_F(INFO, "Press ctrl-C to break out of this infinite loop.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void test_hang_1() { test_hang_0(); }
|
||||||
|
void test_hang_2() { test_hang_1(); }
|
||||||
|
|
||||||
|
void throw_on_fatal()
|
||||||
|
{
|
||||||
|
loguru::set_fatal_handler([](const loguru::Message& message){
|
||||||
|
LOG_F(INFO, "Throwing exception...");
|
||||||
|
throw std::runtime_error(std::string(message.prefix) + message.message);
|
||||||
|
});
|
||||||
|
{
|
||||||
|
LOG_SCOPE_F(INFO, "CHECK_F throw + catch");
|
||||||
|
try {
|
||||||
|
CHECK_F(false, "some CHECK_F message");
|
||||||
|
} catch (std::runtime_error& e) {
|
||||||
|
LOG_F(INFO, "CHECK_F threw this: '%s'", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if LOGURU_WITH_STREAMS
|
||||||
|
{
|
||||||
|
LOG_SCOPE_F(INFO, "CHECK_S throw + catch");
|
||||||
|
try {
|
||||||
|
CHECK_S(false) << "Some CHECK_S message";
|
||||||
|
} catch (std::runtime_error& e) {
|
||||||
|
LOG_F(INFO, "CHECK_S threw this: '%s'", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_F(INFO, "Trying an uncaught exception:");
|
||||||
|
CHECK_S(false);
|
||||||
|
#else
|
||||||
|
CHECK_F(false);
|
||||||
|
#endif // LOGURU_WITH_STREAMS
|
||||||
|
}
|
||||||
|
|
||||||
|
void throw_on_signal()
|
||||||
|
{
|
||||||
|
loguru::set_fatal_handler([](const loguru::Message& message){
|
||||||
|
LOG_F(INFO, "Throwing exception...");
|
||||||
|
throw std::runtime_error(std::string(message.prefix) + message.message);
|
||||||
|
});
|
||||||
|
test_SIGSEGV_0();
|
||||||
|
}
|
||||||
|
|
||||||
|
// void die(std::ofstream& of)
|
||||||
|
// {
|
||||||
|
// (void)of;
|
||||||
|
// test_hang_2();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct CallbackTester
|
||||||
|
{
|
||||||
|
size_t num_print = 0;
|
||||||
|
size_t num_flush = 0;
|
||||||
|
size_t num_close = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void callbackPrint(void* user_data, const loguru::Message& message)
|
||||||
|
{
|
||||||
|
printf("Custom callback: %s%s\n", message.prefix, message.message);
|
||||||
|
reinterpret_cast<CallbackTester*>(user_data)->num_print += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void callbackFlush(void* user_data)
|
||||||
|
{
|
||||||
|
printf("Custom callback flush\n");
|
||||||
|
reinterpret_cast<CallbackTester*>(user_data)->num_flush += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void callbackClose(void* user_data)
|
||||||
|
{
|
||||||
|
printf("Custom callback close\n");
|
||||||
|
reinterpret_cast<CallbackTester*>(user_data)->num_close += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_log_callback()
|
||||||
|
{
|
||||||
|
CallbackTester tester;
|
||||||
|
loguru::add_callback(
|
||||||
|
"user_callback", callbackPrint, &tester,
|
||||||
|
loguru::Verbosity_INFO, callbackClose, callbackFlush);
|
||||||
|
CHECK_EQ_F(tester.num_print, 0u);
|
||||||
|
LOG_F(INFO, "Test print");
|
||||||
|
CHECK_EQ_F(tester.num_print, 1u);
|
||||||
|
CHECK_EQ_F(tester.num_close, 0u);
|
||||||
|
CHECK_EQ_F(tester.num_flush, 1u);
|
||||||
|
loguru::flush();
|
||||||
|
CHECK_EQ_F(tester.num_flush, 2u);
|
||||||
|
loguru::remove_callback("user_callback");
|
||||||
|
CHECK_EQ_F(tester.num_close, 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined _WIN32 && defined _DEBUG
|
||||||
|
#define USE_WIN_DBG_HOOK
|
||||||
|
static int winDbgHook(int reportType, char *message, int *)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Report type: %d\nMessage: %s\n", reportType,
|
||||||
|
(nullptr != message ? message : "nullptr message"));
|
||||||
|
return 1; // To prevent the Abort, Retry, Ignore dialog
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
#ifdef USE_WIN_DBG_HOOK
|
||||||
|
_CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, winDbgHook);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// loguru::g_preamble = false;
|
||||||
|
|
||||||
|
if (argc > 1 && argv[1] == std::string("test"))
|
||||||
|
{
|
||||||
|
return main_test(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
loguru::init(argc, argv);
|
||||||
|
|
||||||
|
// auto verbose_type_name = loguru::demangle(typeid(std::ofstream).name());
|
||||||
|
// loguru::add_stack_cleanup(verbose_type_name.c_str(), "std::ofstream");
|
||||||
|
// std::ofstream os;
|
||||||
|
// die(os);
|
||||||
|
|
||||||
|
if (argc == 1)
|
||||||
|
{
|
||||||
|
loguru::add_file("latest_readable.log", loguru::Truncate, loguru::Verbosity_INFO);
|
||||||
|
loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX);
|
||||||
|
|
||||||
|
LOG_F(INFO, "Loguru test");
|
||||||
|
test_thread_names();
|
||||||
|
|
||||||
|
test_scopes();
|
||||||
|
test_levels();
|
||||||
|
#if LOGURU_WITH_STREAMS
|
||||||
|
test_stream();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
loguru::shutdown();
|
||||||
|
|
||||||
|
LOG_F(INFO, "goes to stderr, but not to file");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string test = argv[1];
|
||||||
|
if (test == "ABORT_F") {
|
||||||
|
ABORT_F("ABORT_F format message");
|
||||||
|
} else if (test == "ABORT_S") {
|
||||||
|
ABORT_S() << "ABORT_S stream message";
|
||||||
|
} else if (test == "assert") {
|
||||||
|
const char* ptr = nullptr;
|
||||||
|
(void)ptr;
|
||||||
|
assert(ptr && "Error that was unexpected");
|
||||||
|
} else if (test == "LOG_F_FATAL") {
|
||||||
|
LOG_F(FATAL, "Fatal format message");
|
||||||
|
} else if (test == "LOG_S_FATAL") {
|
||||||
|
LOG_S(FATAL) << "Fatal stream message";
|
||||||
|
} else if (test == "CHECK_NOTNULL_F") {
|
||||||
|
const char* ptr = nullptr;
|
||||||
|
CHECK_NOTNULL_F(ptr);
|
||||||
|
} else if (test == "CHECK_F") {
|
||||||
|
CHECK_F(1 > 2);
|
||||||
|
} else if (test == "CHECK_EQ_F") {
|
||||||
|
CHECK_EQ_F(always_increasing(), 0);
|
||||||
|
CHECK_EQ_F(always_increasing(), 1);
|
||||||
|
CHECK_EQ_F(always_increasing(), 42);
|
||||||
|
} else if (test == "CHECK_EQ_F_int") {
|
||||||
|
int x = 42;
|
||||||
|
CHECK_EQ_F(x, x + 1);
|
||||||
|
} else if (test == "CHECK_EQ_F_unsigned") {
|
||||||
|
unsigned x = 42;
|
||||||
|
CHECK_EQ_F(x, x + 1);
|
||||||
|
} else if (test == "CHECK_EQ_F_size_t") {
|
||||||
|
size_t x = 42;
|
||||||
|
CHECK_EQ_F(x, x + 1);
|
||||||
|
} else if (test == "CHECK_EQ_F_message") {
|
||||||
|
CHECK_EQ_F(always_increasing(), 0, "Should pass");
|
||||||
|
CHECK_EQ_F(always_increasing(), 1, "Should pass");
|
||||||
|
CHECK_EQ_F(always_increasing(), 42, "Should fail");
|
||||||
|
} else if (test == "CHECK_EQ_S") {
|
||||||
|
std::string str = "right";
|
||||||
|
CHECK_EQ_F(str, "wrong", "Expected to fail, since `str` isn't \"wrong\" but \"%s\"", str.c_str());
|
||||||
|
} else if (test == "CHECK_LT_S") {
|
||||||
|
CHECK_EQ_F(always_increasing(), 0);
|
||||||
|
CHECK_EQ_F(always_increasing(), 1);
|
||||||
|
CHECK_EQ_F(always_increasing(), 42);
|
||||||
|
} else if (test == "CHECK_LT_S_message") {
|
||||||
|
CHECK_EQ_F(always_increasing(), 0, "Should pass");
|
||||||
|
CHECK_EQ_F(always_increasing(), 1, "Should pass");
|
||||||
|
CHECK_EQ_F(always_increasing(), 42, "Should fail!");
|
||||||
|
} else if (test == "deep_abort") {
|
||||||
|
deep_abort_10({"deep_abort"});
|
||||||
|
} else if (test == "SIGSEGV") {
|
||||||
|
test_SIGSEGV_2();
|
||||||
|
} else if (test == "abort") {
|
||||||
|
test_abort_2();
|
||||||
|
} else if (test == "error_context") {
|
||||||
|
test_error_contex();
|
||||||
|
} else if (test == "throw_on_fatal") {
|
||||||
|
throw_on_fatal();
|
||||||
|
} else if (test == "throw_on_signal") {
|
||||||
|
throw_on_signal();
|
||||||
|
} else if (test == "callback") {
|
||||||
|
test_log_callback();
|
||||||
|
} else if (test == "hang") {
|
||||||
|
loguru::add_file("hang.log", loguru::Truncate, loguru::Verbosity_INFO);
|
||||||
|
test_hang_2();
|
||||||
|
} else {
|
||||||
|
LOG_F(ERROR, "Unknown test: '%s'", test.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user