This library is a Logger written in C++ with many features, such as multi-threading, levels and more.
- Clone the repository
git clone git@github.com:lypitech/shuvlog.git
cd shuvlog/- Build the project
# Configure and generate build files
cmake -B build/
# Compile the project
cmake --build build/If you want the build to be faster (to use all of your CPU cores), simply add --parallel to the options!
Library file will be located in the build/ folder.
If you want to build the project in another folder, simply replace build/ with the folder you want the library to be
built in.
Make sure the folder you specify is empty!
If you use CMake, using this library in your project is really simple.
Simply include the following lines to your CMakeLists.txt:
add_subdirectory(<library file path>)
target_include_directories(${PROJECT_NAME} PRIVATE
<library file path>/include
)
target_link_libraries(${PROJECT_NAME} PRIVATE
shuvlog
)If you don't use a proper build system, you can still manually link the library to your project when compiling it.
Simply include the following to your compilation flags:
-L <library file path> -l shuvlog
Logs can be outputted anywhere you want, thanks to Sinks.
Sinks, by default, accepts an optional sink::Settings structure as the last parameter.
Here is a list of the sinks implemented by default:
Important
In formats, everything that is under curly bracket ({}) is configurable and so, optional.
Important
Two sinks can't point to the same output. Why would you print everything more than once in the same file?
Important
Logs that have a level greater than or equal to kError will be outputted to the standard error output.
Caution
Only one instance of ConsoleSink can be added. Why would you print everything twice?
Parameters:
- Whether to use colors or not (optional, default to
true)
Caution
Colors does NOT work on versions of Windows prior to Windows 10.
Header format: None.
Log format:
{{date} time{:milliseconds}} {[{thread_name} {(thread_id)}]} level: message {(source{:line}{:column})}
2025-11-18 16:59:26.006 [MainThread (0x1eda26080)] DEBUG: レイテンシーガール (src/main.cpp:3:16)
Caution
This kind of Sink should be saved in .log files. If it's not the case, a warning will be thrown.
Parameters:
- Path of the file where the output will be written. (
const std::string&)
Header format:
/*************************************************
|
| Project : R-Type Server (project_name)
| Version : 1.0-alpha (project_version)
| Build type : Debug (build_type)
| Minimum level : DEBUG (logger_minimum_level)
|
| Command : ./r-type_server -p 4242 (command_with_arguments)
| Start time : 2025-11-18 16:59:26.006 (program_start_time)
|
| OS : macOS 26.1 (build 25B78) (os_name & kernel_version)
| Compiler : AppleClang 17.0.0.17000404 (compiler & compiler_version)
| Compilation flags : None (compilation_flags)
| Build system : CMake 4.0.2 (build_system & build_system_version)
|
\*************************************************
Log format:
{{date} time{:milliseconds}} {[{thread_name} {(thread_id)}]} level: message {(source{:line}{:column})}
2025-11-18 16:57:25 [MainThread (0x1eda26080)] DEBUG: インフレイション!! (src/main.cpp:3:16)
Caution
This kind of Sink should be saved in .json files. If it's not the case, a warning will be thrown.
Note
JSON will be properly formatted in the formats below. Note that the Logger will write unformatted JSON (one-lined).
Caution
This Sink does not take its assigned settings into account when logging.
That's because JSONs are meant to have everything logged into it, as humans won't read it raw. They will use a
dedicated Log viewer.
Parameters:
- Path of the file where the output will be written. (
const std::string&)
Header format:
{
"projectName": "R-Type Server", // project_name
"version": "1.0-alpha", // project_version
"buildType": "Debug", // build_type
"minimumLevel": "DEBUG", // logger_minimum_level
"command": "./r-type_server -p 4242", // command_with_arguments
"startTime": "2025-11-18 16:59:26.006", // program_start_time
"osName": "macOS 26.1", // os_name
"kernelVersion": "(build 25B78)", // kernel_version
"compiler": "AppleClang 17.0.0.17000404", // compiler & compiler_version
"compilationFlags": "None", // compilation_flags
"buildSystem": "CMake 4.0.2", // build_system & build_system_version
"logs": []
}Log format:
Note
All logs will be put in the logs list in the main JSON object.
{
"timestamp": "2025-11-18 16:59:26.006",
"level": "DEBUG",
"thread": {
"name": "MainThread",
"id": "0x1eda26080"
},
"source": "src/main.cpp",
"functionName": "int main(const int, const char **)",
"line": 3,
"column": 16,
"message": "成仏させてよ"
}Caution
This kind of Sink should be saved in .json files. If it's not the case, a warning will be thrown.
This Sink has the same parameters and formats as the JsonFileSink, but following the JSON
formatting of NDJSON.
Sinks are configurable, thanks to the sink::Settings structure.
// in namespace logger::sink
struct Settings
{
bool showTimestamp = true;
bool showOnlyTime = false;
bool showMilliseconds = true;
bool showThreadInfo = true;
bool showThreadId = true;
bool showSource = true;
bool showLineNumber = true;
bool showColumnNumber = true;
};As said earlier, Sinks can accept a Settings structure, and will automatically adapt their log behaviour accordingly
(depends on the Sink though ; JSON sinks does not.).
That means you can set different settings for different sinks!
Example:
// Most concise settings. No timestamp, no thread info, no source.
logger::sink::Settings conciseSinkSettings{
.showTimestamp = false,
.showThreadInfo = false,
.showSource = false
};
// Only time, no milliseconds.
logger::sink::Settings shortTimeSinkSettings{
.showOnlyTime = true,
.showMilliseconds = false
};
Logger#addSink<logger::LogFileSink>("1.log", conciseSinkSettings);
Logger#addSink<logger::LogFileSink>("2.log", shortTimeSinkSettings);
LOG_DEBUG("ゲンダイ");
/**
* First sink will output something like that:
* DEBUG: ゲンダイ
*
* ... while second sink will output something like that:
* 23:04:06 [MainThread (0x1eda26080)] DEBUG: ゲンダイ (src/main.cpp:3:16)
*/Each sink can choose what kind of levels they want to process.
3 modes are available:
| Mode | Description |
|---|---|
| All | All levels are processed by the sink (default behavior). |
| Explicit | Only explicitly specified levels are processed by the sink. |
| Minimum level | Only levels that are equal or greater than levelMask are processed by the sink. |
Example:
All levels will be processed by the sink.
Logger#addSink<logger::LogFileSink>(
"all.log",
// What is below is facultative, as these are the default values.
sink::FilterMode::kAll,
static_cast<uint16_t>(Level::kTraceR1) // When using FilterMode::kAll, this value is ignored.
);
// Logs that have a level greater or equal than Level::kTraceR1 will be processed by the sink.
Logger#addSink<logger::LogFileSink>(
"minimumLevels.log",
sink::FilterMode::kMinimumLevel,
static_cast<uint16_t>(Level::kTraceR1)
);
// Logs that have kTraceR1, kCritical or kFatal will be processed by the sink.
Logger#addSink<logger::LogFileSink>(
"minimumLevels.log",
sink::FilterMode::kExplicit,
Level::kTraceR1 | Level::kCritical | Level::kFatal // Bitwise OR (|) to use multiple values at once.
);Before starting the setup, keep in mind that non-static functions in the Logger class must be accessed via the
getInstance() function.
The only header you will need is logger/Logger.h.
Before initializing anything, you need to specify to the Logger where the logs will go.
Otherwise, they won't go anywhere and the Logger will print an error (WARNING: Trying to log with no sink.) each time
you will log something!
For that, just call the Logger#addSink<T>(...) function.
T is the Sink class type you want to create
and ... the parameters that the Sink class requires (some requires none).
Example:
Logger::getInstance().addSink<logger::ConsoleSink>();
Logger::getInstance().addSink<logger::LogFileSink>("output.log");
// and so on...Remember the headers of Sinks? They include information about how the project has been built. This works thanks
to the logger::BuildInfo class.
// To save you some reading, this is not the real implementation of the BuildInfo class, but something close.
// Each field can be accessed through getters (eg. getType, getVersion...).
// For more information, read the source code.
struct BuildInfo
{
static BuildInfo unknown();
static BuildInfo fromCMake();
std::string _type;
std::string _version;
std::string _compiler;
std::string _compilerFlags;
std::string _buildSystem;
}To create an instance of this class, you have two options:
- Either using the static builder
BuildInfo::fromCMake(), that retrieves the necessary information from CMake (if properly configured) - Or using the static builder
BuildInfo::unknown(), that returns an instance filled up with "Unknown".
For now, you cannot manually specify your own information. This is being worked on.
As shown previously, a BuildInfo object can be populated using information provided by CMake.
To enable this, you need to add a small configuration snippet to your parent CMake project.
Simply include the following lines:
target_compile_definitions(project_name INTERFACE
SHLG_BUILD_TYPE="${CMAKE_BUILD_TYPE}"
SHLG_BUILD_VERSION="${PROJECT_VERSION}"
SHLG_COMPILER_ID="${CMAKE_CXX_COMPILER_ID}"
SHLG_COMPILER_VERSION="${CMAKE_CXX_COMPILER_VERSION}"
SHLG_COMPILER_FLAGS="${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}}"
SHLG_BUILD_SYSTEM="CMake ${CMAKE_VERSION}"
)Logger have some settings that can be customized.
// To save you some reading, this is not the real implementation of the logger::Settings class, but something close.
// Each field can be accessed and modified through getters and setters (eg. getMinimumLevel, setFlushIntervalMs...).
// For more information, read the source code.
struct Settings
{
Level _minimumLevel = Level::kInfo;
size_t _maxBatchSize = 64;
int _flushIntervalMs = 250;
};The most important one is the minimum level required for a log to be logged (_minimumLevel).
Default is Level::kInfo, but you can set something else.
As this Logger is multithreaded, logs are registered into a queue before being flushed. Every _flushIntervalMs
milliseconds, the worker removes _maxBatchSize logs from the queue and processes them (formatting and flushing).
We recommend you to leave those two variables at their default values.
If you encounter some synchronization issues with the Logger, you can try to set _maxBatchSize to 1 and
_flushIntervalMs to 0.
Now is the time to initialize the Logger, with the function Logger::initialize.
Variables content will be placeholders for demonstration purposes ; you are of course free to use your own.
Example:
constexpr std::string projectName("R-Type Server");
const auto buildInfo = logger::BuildInfo::fromCMake();
const auto loggerSettings = logger::Settings(logger::Level::kDebug);
Logger::initialize(
projectName,
argc, argv,
buildInfo,
loggerSettings
);
// You can now properly use the logger.Using the Logger is way easier than setting it up.
To log something, just use the available macros:
LOG_DEBUG("Debug log.");
LOG_INFO("Info log.");
LOG_WARN("Warning log.");
LOG_ERR("Error log.");
LOG_CRIT("Critical log.");
LOG_FATAL("Fatal log.");You can also format your logs, just as in std::format:
constexpr std::string currentMusic("Silly Joke - Instrumental");
LOG_INFO("Hi my name is {}, I'm {} years old and I currently listen to {}.", "Ly", 19, currentMusic);If you want, you can also manually use the Logger#log function. But that's too much writing for nothing.
And Logger does the rest! Enjoy logging! :)
Finally, the Logger shuts down by itself when destroyed. That being said, if you want to manually shutdown the Logger,
you can simply call the Logger#shutdown() function.
Caution
If you try to log something with an uninitialized Logger, an error will be written in the standard error output
(CAUTION: Logger has been used uninitialized. Make sure you call the initialize() function before performing any log.).
Caution
As mentionned earlier, if you try to log something with no Sink attached to the Logger, an error will be written in
the standard error output too (WARNING: Trying to log with no sink.).
Contributions are welcome, whether it’s bug fixes, new features, documentation improvements, or ideas to make the library better. Thank you for taking the time to support the project!
- Fork the repository
- Create a feature branch
- Commit your modifications with clear messages
- Push the branch
- Open a Pull Request describing your changes
Please make sure:
- The project successfully builds with CMake on Linux, macOS, and Windows
- Your changes do not introduce warnings (or silence them if intentional)
- All sinks and Logger behaviors remain thread-safe
- You tested your changes with unit tests
Found a bug? Have an idea? Feel free to open an issue.
When reporting a bug, please include:
- A minimal code snippet reproducing the bug
- Your compiler version
- Your OS
- Your CMake version