Skip to content

moehoshio/NekoLog

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Neko Logging

Neko Logging (nlog) is an easy-to-use, modern, lightweight, and efficient C++20 logging library.

License Require CMake Module Support CI Status

Features

  • No Macro
  • Header Only (No build/link required)
  • C++20 Modules Support - Optional modern module interface
  • Auto SrcLoc (Automatically capture source code location)
  • Supports multiple appenders (Console, File, Custom)
  • Supports custom formatted log messages
  • Supports asynchronous logging
  • Thread-safe
  • RAII-style scope logging

Preview

preview\

Requirements

  • C++20 or higher compatible compiler
  • CMake 3.14 or higher (if using CMake)

Quick Start

Configure: CMake | Conan | vcpkg | Manual | Tests

Example: Basic | Logging | Level | Set Thread Name | RAII Scope Logging

Advanced: Appenders | Formatting Logs | Asynchronous Logging | Disable Tests

CMake

  1. Using CMake's FetchContent to include NekoLog in your project:
include(FetchContent)

# Add NekoLog to your CMake project
FetchContent_Declare(
    NekoLog
    GIT_REPOSITORY https://github.com/moehoshio/NekoLog.git
    GIT_TAG        main
)
FetchContent_MakeAvailable(NekoLog)

# Add your target and link NekoLog
add_executable(your_target main.cpp)

target_link_libraries(your_target PRIVATE Neko::Log)
  1. Or using find_package if NekoLog is installed:
find_package(NekoLog CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE Neko::Log)
  1. Include the header in your source code
#include <neko/log/nlog.hpp>

CMake with Module Support

To enable C++20 module support, use the NEKO_LOG_ENABLE_MODULE option:

FetchContent_Declare(
    ...
)

# Set Options Before Building
set(NEKO_LOG_ENABLE_MODULE ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(NekoLog)
...

target_link_libraries(your_target PRIVATE Neko::Log::Module)

Import the module in your source code:

import neko.log;

vcpkg

  1. Install NekoLog via vcpkg:
vcpkg install neko-log
  1. Use in your CMakeLists.txt:
find_package(NekoLog CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE Neko::Log)

Conan

  1. Add NekoLog to your conanfile.txt:
[requires]
neko-log/[*]

[generators]
CMakeDeps
CMakeToolchain
  1. Install dependencies and configure your project:
conan install . --output-folder=build --build=missing
cmake -B build -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake
cmake --build build
  1. Link in your CMakeLists.txt:
find_package(NekoLog CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE Neko::Log)

Conan with C++20 Module Support

To enable C++20 module support with Conan, use the enable_module option:

conan install . --build=missing -o neko-log/*:enable_module=True

Or specify it in your conanfile.txt:

[requires]
neko-log/[*]

[options]
neko-log/*:enable_module=True

[generators]
CMakeDeps
CMakeToolchain

Or in your conanfile.py:

from conan import ConanFile

class YourProject(ConanFile):
    requires = "neko-log/[*]"
    generators = "CMakeDeps", "CMakeToolchain"
    
    def configure(self):
        self.options["neko-log"].enable_module = True

Then link against the module target in your CMakeLists.txt:

find_package(NekoLog CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE Neko::Log::Module)

Manual

When installing manually, you need to manually fetch the dependency NekoSchema.

After installing the dependency, please continue:

  1. Clone or download the repository to your host
git clone https://github.com/moehoshio/NekoLog.git

or

curl -L -o NekoLog.zip https://github.com/moehoshio/NekoLog/archive/refs/heads/main.zip

unzip NekoLog.zip
  1. Copy the include folder to your include directory
cp -r NekoLog/include/ /path/to/your/include/
  1. Include the header in your source code
#include <neko/log/nlog.hpp>

Basic Example

Now you can start logging with minimal setup:

#include "neko/log/nlog.hpp"

int main() {
    using namespace neko;
    log::setCurrentThreadName("Main Thread"); // Set the current thread name
    log::setLevel(log::Level::Debug); // Set the log level

    log::info("This is an info message.");
    log::debug("This is a debug message.");
    log::warn("This is a warning message.");
    log::error("This is an error message.");
}

output:

[2025-09-16 01:53:58.678] [Info] [Main Thread] [main.cpp:9] This is an info message.
[2025-09-16 01:53:58.679] [Debug] [Main Thread] [main.cpp:10] This is a debug message.
[2025-09-16 01:53:58.679] [Warn] [Main Thread] [main.cpp:11] This is a warning message.
[2025-09-16 01:53:58.679] [Error] [Main Thread] [main.cpp:12] This is an error message.

Usage

Logging

Logging is simple, just like in the example above. Use the neko::log::info, neko::log::debug, neko::log::warn, and neko::log::error functions to log. Each of these functions has two versions.

Single string:

inline void debug(const std::string &message, const neko::SrcLocInfo &location = {});

debug("msg"); // (basic format)... msg

And with format arguments (via std::format):

    template <typename... Args>
    void debug(std::format_string<Args...> fmt, const neko::SrcLocInfo &location, Args &&...args);

    debug("Hello , {} . 1 + 1 = {}", "World" , {} , 1 + 1); // (basic format)... Hello , World . 1 + 1 = 2

Functions for other levels are the same.

Tip: SrcLoc can automatically get the source code location. You just need a default object, which you can generate via {} or a default parameter.

Level

You can set the log level for the logger, which controls which level of logs should be recorded.

For example, if you set setLevel to Info, only logs of Info level and above will be recorded (Debug messages will be discarded).

log::setLevel(log::Level::Info);

log::info("Info"); // Outputs Info
log::warn("Warn"); // Outputs Warn
log::debug("Debug"); // More detailed messages will be discarded

If needed, you can add more log levels and log with the log function. For example:

// nlog.hpp
enum class Level : neko::uint8 {
        Debug = 1, ///< Debug
        Info = 2,  ///< General information
        Warn = 3,  ///< Potential issues
        Error = 4, ///< Error
        lv5 = 5, /// Custom level
        lv6 = 6,
        lv10 = 10,
        Off = 255  ///< Logging off
    };

// main.cpp
using namespace neko;

log::logger.log(log::Level::lv10,"Hello Lv10");

Set Thread Name

You can set the names of different threads in the logs using neko::log::setCurrentThreadName and neko::log::setThreadName.

Example:

using namespace neko;

// Set the current thread
log::setCurrentThreadName("Thread 1");
log::info(""); // ... [Thread 1] ...

// Specify by id
auto id = std::this_thread::get_id();
log::setThreadName(id, "Thread-1");

log::info(""); // ... [Thread-1] ...

Appenders

You can add multiple appenders simultaneously to output logs to different places. By default, appenders for console output and file writing are provided.

Logging to a file:

// Add a file appender and overwrite the file
log::addFileAppender("app.log", true); 

Output to console (enabled by default):

log::addConsoleAppender(); // Add a console appender

Custom Appender

You can easily add your own appender to output to any destination.

Just inherit from neko::log::IAppender and override the append and flush methods for your output.

Example:

using namespace neko;

class MyAppender : public log::IAppender {
    public:
    void append(const log::LogRecord &record) override {
        std::unique_ptr<log::IFormatter> formatter = std::make_unique<log::DefaultFormatter>(); // Create a default log formatter
        auto formatted = formatter->format(record); // Format the log

        // Output the string to your target
        yourOutput << formatted;
    }
    
    void flush() override {
        yourOutput.flush();
    }
};

// Add a custom log appender
log::addAppender(std::make_unique<MyAppender>());

Formatting Logs

A formatter is a helper for an appender, used to format logs. It is independent for each appender, and the appender calls the formatter's method to format the log. So, it is recommended to have a built-in formatter object when creating a custom appender.

Default Formatter

The default formatter's format is: [date time] [level] [thread] [file:line] [msg]

When constructing it, you can specify a root path to truncate and whether to use the full path. The function is defined as follows:

explicit DefaultFormatter(const std::string &rootPath = "", bool useFullPath = false)

When rootPath is an empty string (default), file = main.cpp.
When rootPath is a path, e.g., /to/path/, and the file is at /to/path/src/main.cpp, file = /src/main.cpp.
When useFullPath is true, rootPath is ignored, and the full path is always displayed. file = /to/path/src/main.cpp.

Custom Formatter

Inherit from neko::log::IFormatter and override the format function. You need to implement the formatting of the incoming record in the format function.

Example:

using namespace neko;

class MyFormatter : public log::IFormatter {
    public:
    std::string format(const log::LogRecord &record) override {
        // You can use any method to combine the record format, std::format, ostringstream, etc.
        
        std::ostringstream oss;
        oss << "lv: " << log::levelToString(record.level) << " , msg: "<< record.message ;
        return oss.str();
    }
};

// Create a console appender and specify MyFormatter as the formatter
log::ConsoleAppender consoleAppender(std::make_unique<MyFormatter>());

log::info("Hello");

output:

lv: Info , msg: Hello

Asynchronous Logging

By default, logging is written to IO by the logging thread.

For better performance, you can enable asynchronous mode. This lets logs be processed in a background thread.

#include "neko/log/nlog.hpp"
#include <thread>
using namespace neko;

int main() {
    // Set to asynchronous mode
    log::setMode(neko::SyncMode::Async);

    // Start the log processing loop (usually in a dedicated thread)
    std::thread logThread([]{ log::runLogLoop(); });

    // Main thread submits logs
    log::info("This will be logged asynchronously.");

    // ... application execution ...

    // Stop the log loop and flush remaining logs
    log::stopLogLoop();
    logThread.join();
}

In async mode, logs are not flushed in real-time. It's possible for a log to be submitted before a crash, but not be recorded in time. It is recommended to disable this during debugging.

Tip: When using asynchronous mode, a thread must be running the neko::log::runLogLoop() function. Otherwise, no logs will be processed.

RAII Scope Logging

Use neko::log::autoLog to automatically log the start and end of a scope.

void someFunction() {
    // Logs "Start" when someFunction is entered, and "End" when it is exited
    log::autoLog log("Start", "End"); 
    
    // ... function body ...
}

Testing

You can run the tests to verify that everything is working correctly.

If you haven't configured the build yet, please run:

cmake -B ./build -DNEKO_LOG_BUILD_TESTS=ON -DNEKO_LOG_AUTO_FETCH_DEPS=ON -S .

Now, you can build the test files (you must build them manually at least once before running the tests!).

cmake --build ./build --config Debug

Then, you can run the tests with the following commands:

cd ./build && ctest --output-on-failure

If everything is set up correctly, you should see output similar to the following:

  Test project /path/to/nlog/build
        Start  1: NLogTest.FileLogging
   1/36 Test  #1: NLogTest.FileLogging ................
  .................   Passed    0.02 sec

    # ......

        Start 36: PerformanceTest.ConstexprMapLookupSpeed
  36/36 Test #36: PerformanceTest.ConstexprMapLookupSpeed ..............   Passed    0.02 sec

  100% tests passed, 0 tests failed out of 36

  Total Test time (real) =   0.78 sec

Disable Tests

If you want to disable building and running tests, you can set the following CMake option when configuring your project:

cmake -B ./build . -DNEKO_LOG_BUILD_TESTS=OFF

This will skip test targets during the build process.

License

License MIT OR Apache-2.0

See More

  • NekoNetwork: A modern , easy-to-use C++20 networking library via libcurl.
  • NekoLog: An easy-to-use, modern, lightweight, and efficient C++20 logging library.
  • NekoEvent: A modern easy to use type-safe and high-performance event handling system for C++.
  • NekoSchema: A lightweight, header-only C++20 schema library.
  • NekoSystem: A modern C++20 cross-platform system utility library.
  • NekoFunction: A comprehensive modern C++ utility library that provides practical functions for common programming tasks.
  • NekoThreadPool: An easy to use and efficient C++ 20 thread pool that supports priorities and submission to specific threads.

About

An easy-to-use, lightweight, and efficient C++20 logging library.

Topics

Resources

License

Stars

Watchers

Forks