GNU Affero General Public License v3.0

项目介绍

F1F88/sm-ext-log4sp 是一个托管在 GitHub 上的开源项目,主要使用 C++ 编写,采用 GNU Affero General Public License v3.0 许可证,目前获得 16 个 Star、3 次 Fork。

仓库全名
F1F88/sm-ext-log4sp
开源许可证
GNU Affero General Public License v3.0
Star 数
16
Fork 数
3
未关闭 Issue
1

README 摘要

English | 中文 Log4sp Log4sp is a high‑performance logging library built on spdlog. It is designed to help developers efficiently record, format, and manage application logs. Building upon spdlog’s exceptional performance and flexibility, log4sp provides a more easily integrable logging interface for SourceMod, catering to logging needs ranging from lightweight plugins to large‑scale, high‑performance projects. With…

完整内容见 GitHub 原始 README 或本页 。

常见问题

sm-ext-log4sp 用什么语言开发?

主要使用 C++ 编写,采用 GNU Affero General Public License v3.0 许可证,目前有 16 个 Star。

sm-ext-log4sp 可以免费商用吗?

仓库标注的许可证为 GNU Affero General Public License v3.0,商用前请先阅读仓库 LICENSE 文件确认授权范围。

如何获取 sm-ext-log4sp 的源码?

可以直接克隆仓库:git clone https://github.com/F1F88/sm-ext-log4sp.git,默认分支为 main;安装与运行步骤见仓库 README。

sm-ext-log4sp 最近还在维护吗?

仓库最近一次更新为 9月30日,当前有 1 个未关闭 Issue;Star 数为 16,可作为社区活跃度的参考。

关于

A high-performance logging framework

描述取自 GitHub 官方 API,中文内容由 GitRead 中文社区 编辑团队整理。

简要分析

热度分
24
价值分
10
活跃状态
活跃

语言占比

C
C++
Python
SourcePawn

Release

README

English | 中文

Log4sp

Log4sp is a high‑performance logging library built on spdlog. It is designed to help developers efficiently record, format, and manage application logs. Building upon spdlog’s exceptional performance and flexibility, log4sp provides a more easily integrable logging interface for SourceMod, catering to logging needs ranging from lightweight plugins to large‑scale, high‑performance projects.

With log4sp, developers can easily track plugin behavior, debug issues, and monitor performance without adding significant overhead to their code.

Features

  1. Very fast, much faster than SourceMod Logging.
  2. Support log filtering - log levels can be modified at runtime as well as compile time.
  3. Support for large log message - over 1024 characters will not be truncated
  4. Support custom log message pattern.
  5. Support logging no error throwing.
  6. Support various sinks (log targets).
  7. Support console commands and management menus.
  8. Support int64.
  9. Support x64.

Installation

  1. Download the appropriate version from Releases
    • sm-ext-log4sp contains the extension file and scripting include files.
    • sm-plugin-log4sp_manager plugin adds user commands and menus to manage logger (depends on extension)
  2. Uploading “addons/sourcemod” files to the server

Usage

Natives Documentation: ./sourcemod/scripting/include/log4sp/

Hello World

#include <sourcemod>
#include <log4sp>

public void OnPluginStart()
{
    ServerConsoleSink sink = new ServerConsoleSink();
    Logger logger = new Logger("my-logger");
    logger.AddSink(sink);

    logger.Info("Hello World!");

    sink.Close();
    logger.Close();
}

Server console output:

[2001-02-03 12:34:56.789] [my-logger] [info] Hello World!

Log Levels

Log4sp defines 7 log levels, from low to high: trace < debug < info < warn < error < fatal < off.

Log messages are formatted and delivered to sinks only if message level ≥ logger log level;

Log messages are log to sink only if message level ≥ Sink log level.

Developers can filter logs based on severity, ensuring that only relevant messages are recorded, which improves debugging efficiency and maintains cleaner, more organized log files.

The default log level of Logger is info;

The default log level of Sink is trace.

logger.SetLevel(LogLevel_Warn);     // Set the logger log level to warn
logger.ShouldLog(LogLevel_Warn);    // true

sink.SetLevel(LogLevel_Debug);      // Set the sink log level to debug
sink.ShouldLog(LogLevel_Trace);     // false
#include <sourcemod>
#include <log4sp>

public void OnPluginStart()
{
    char path[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, path, sizeof(path), "logs/simple-file.log");
    BasicFileSink sink = new BasicFileSink(path);

    Logger logger = new Logger("my-logger");
    logger.AddSink(sink);
    logger.SetLevel(LogLevel_Warn);

    logger.Debug("Test ...");
    logger.Info("Information ...");
    logger.Warn("Warning ...");
    logger.Error("Oops ...");

    sink.Close();
    logger.Close();
}

File game/addons/sourcemod/logs/simple-file.log:

[2001-02-03 12:34:56.789] [my-logger] [warn] Warning …
[2001-02-03 12:34:56.789] [my-logger] [error] Oops …

Format

Taking Logger::Log as an example, ordinary Log method only output the log message as is, while the LogEx and LogAmxTpl method will format the parameters first and then output the formatted log message.

Parameters formatting is performed at Logger layer and is triggered only if log message level >= logger log level.

Log LogEx LogAmxTpl SM - LogMessage
Speed Very Fast Fast Fast Slow
Max character unlimited unlimited 2048 1024
Param format × √ √ √
Formatter × Log4sp Format SM Format SM Format
Usage × Format wiki Format wiki Format wiki
Format error × Handover to Error Handler Throw error Throw error
Pads BUG × Fixed in v1.5.0 Fixed in v1.5.0 Fixed in 1.13.0.7198
Float Inf × Fixed in v1.10.0 Fixed in v1.10.0 Fixed in 1.13.0.7269
Symbols BUG × Fixed in v1.8.0 Fixed in v1.8.0 Fixed in 1.13.0.7270
Justify BUG × Fixed in v1.5.0 Fixed in v1.5.0 Fixed in 1.13.0.7271
Specifiers %E × Added in v1.10.0 Added in v1.10.0 Added in 1.13.0.7276
Specifiers %ld, %li, %lu × Added in v1.11.0 Added in v1.11.0 Added in 1.13.0.7326
Float -Inf × Added in v1.11.0 Added in v1.11.0 Added in 1.13.0.7330
Pads BUG × Fixed in v1.8.0 Fixed in v1.8.0 Fixed in 1.13.0.7331
Specifiers %lb, %lX, %lx × Added in v1.11.0 Added in v1.11.0 Added in 1.13.0.7342
#include <sourcemod>
#include <log4sp>

public void OnPluginStart()
{
    LoadTranslations("common.phrases");

    char path[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, path, sizeof(path), "logs/daily-file.log");
    DailyFileSink sink = new DailyFileSink(path);

    Logger logger = new Logger("my-logger");
    logger.AddSink(sink);

    logger.InfoEx("d: %d, u: %u, b: %b", 1, 2, 3);
    logger.WarnEx("f: %f, x: %x, X: %X", 4.0, 5, 6);
    logger.ErrorEx("s: %s, c: %c, T: %T", "Some String", '!', "Yes", LANG_SERVER);

    sink.Close();
    logger.Close();
}

File game/addons/sourcemod/logs/daily-file_20010203.log:

[2001-02-03 12:34:56.789] [my-logger] [info] d: 1, u: 2, b: 11
[2001-02-03 12:34:56.789] [my-logger] [warn] f: 4.000000, x: 5, X: 6
[2001-02-03 12:34:56.789] [my-logger] [error] s: Some String, c: !, T: Yes

Pattern

Log message pattern is a configuration mechanism for formatting log messages, allowing customization of the log presentation style through metadata fields such as timestamps, severity levels, and logger names.

Pattern formatting is performed at the Sink layer, which first parses and renders the template before outputting the formatted log message.

Sink default pattern and sample outputs are:

[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] [%s:%#] %v
[2001-02-03 12:34:56.789] [my-logger] [info] [example.sp:123] Hello World!
sink.SetPattern("[%Y-%m-%d %H:%M:%S] [%n] [%l] %v 1");   // Setting the pattern for single sink
logger.SetPattern("[%Y-%m-%d %H:%M:%S] [%n] [%l] %v 2"); // Setting the pattern for all sinks

All pattern flags see: <spdlog wiki> - Custom-formatting

#include <sourcemod>
#include <log4sp>

public void OnPluginStart()
{
    LoadTranslations("common.phrases");

    char path[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, path, sizeof(path), "logs/rotate-file.log");
    const int maxFileSize = 1024 * 10;
    const int maxFiles = 3;
    RotatingFileSink sink = new RotatingFileSink(path, maxFileSize, maxFiles);

    Logger logger = new Logger("my-logger");
    logger.AddSink(sink);

    logger.LogSrc(LogLevel_Info, "Some message");
    logger.SetPattern("[%D %r] [%n] [%L] [%!:%#] %v");
    logger.LogSrc(LogLevel_Info, "Some message");

    sink.Close();
    logger.Close();
}

File game/addons/sourcemod/logs/rotate-file.log:

[2001-02-03 12:34:56.789] [my-logger] [info] [test.sp:17] Some message
[02/03/01 12:34:56 PM] [my-logger] [I] [OnPluginStart:19] Some message

Flush Policy

Log4sp lets the underlying libc flush whenever it sees fit in order to achieve good performance.

You can override this with:

  1. Manual flush

    sink.Flush();   // Flush single sink contents
    logger.Flush(); // Flush all sinks contents
    
  2. Flush levels

    logger.FlushOn(LogLevel_Warn);  // Flush contents immediately when log message level ≥ "Warn"
    
    Tip 提示

    The default auto-flush level for Logger is LogLevel_Off (auto-flush is disabled).

  3. Interval based flush

    #include <sourcemod>
    #include <log4sp>
    
    public void OnPluginStart()
    {
        CreateTimer(5.0, Timer_FlushAll, _, TIMER_REPEAT);
    }
    
    Action Timer_FlushAll(Handle timer)
    {
        Logger.ApplyAll(ApplyAll_FlushAll);
        return Plugin_Continue;
    }
    
    void ApplyAll_FlushAll(Logger logger)
    {
        logger.Flush();
    }
    

Error Handler

Normally, Log4sp natives will throw an error and interrupt code execution when the parameters are invalid.

However, in the following situations, errors will not be thrown directly, but instead the error handler will be called:

  1. Error when Logger.LogEx formats parameters;

  2. Error when Logger traverses Sinks to log;

  3. Error when Logger traverses Sinks to flush.

Each Logger has an error handler, and the default handler solution is to simply log the error information to the SourceMod’s errors.log file.

You can refer to the following code to override the default error handler of the Logger:

void SetMyErrorHandler(Logger logger)
{
    logger.SetErrorHandler(MyErrorHandler);
}

void MyErrorHandler(const char[] msg, const char[] name, const char[] file, int line, const char[] func)
{
    LogError("[%s::%d] [%s] %s", file, line, name, msg);
}
Tip 提示

Parameter formatting errors can be thrown directly or handed over to the Error Handler, depending on whether the formatting is handled by Log4sp (LogEx) or SourceMod (LogAmxTpl).

Global Logger

The global logger is named “log4sp” and is created by the extension when it is loaded. Its life cycle is the same as the extension and it will not be closed by any plugin.

The global logger initially has only one sink of type ServerConsoleSink, and the rest of the properties are default values.

Logger GetGlobalLogger()
{
    Logger logger = Logger.Get(LOG4SP_GLOBAL_LOGGER_NAME);

    static bool init = false;
    if (!init)
    {
        logger.SetPattern("[%Y-%m-%d %H:%M:%S] [Global] [%l] %v");
        logger.SetErrorHandler(MyErrorHandler);
        logger.Info("Hello log4sp global logger!");
        init = true;
    }
    return logger;
}

void MyErrorHandler(const char[] msg, const char[] name, const char[] file, int line, const char[] func)
{
    LogMessage("[%s::%d] [%s] %s", file, line, name, msg);
}

Multiple Sinks

The library supports sending log messages to a variety of destinations, including the console, files, and custom sinks. This gives developers complete control over log storage, visibility, and accessibility, making it easier to monitor, maintain, and troubleshoot applications.

#include <sourcemod>
#include <log4sp>

public void OnPluginStart()
{
    char basicFile[PLATFORM_MAX_PATH], dailyFile[PLATFORM_MAX_PATH], rotatingFile[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, basicFile, sizeof(basicFile), "logs/log4sp-multi-basic-sink.log");
    BuildPath(Path_SM, dailyFile, sizeof(dailyFile), "logs/log4sp-multi-daily-sink.log");
    BuildPath(Path_SM, rotatingFile, sizeof(rotatingFile), "logs/log4sp-multi-rotating-sink.log");

    BasicFileSink basicFileSink = new BasicFileSink(basicFile);
    DailyFileSink dailyFileSink = new DailyFileSink(dailyFile);
    RotatingFileSink rotatingFileSink = new RotatingFileSink(rotatingFile, 10, 10);
    ServerConsoleSink serverConsoleSink = new ServerConsoleSink();

    Logger logger = new Logger("multi-sink-logger");
    logger.AddSink(basicFileSink);
    logger.AddSink(dailyFileSink);
    logger.AddSink(rotatingFileSink);
    logger.AddSink(serverConsoleSink);

    logger.Info("Some message");
    logger.Warn("Some warning");

    basicFileSink.Close();
    dailyFileSink.Close();
    rotatingFileSink.Close();
    serverConsoleSink.Close();
    logger.Close();
}

File game/addons/sourcemod/logs/log4sp-multi-basic-sink.log:

[2001-02-03 12:34:56.789] [multi-sink-logger] [info] Some message
[2001-02-03 12:34:56.789] [multi-sink-logger] [warn] Some warning

File game/addons/sourcemod/logs/log4sp-multi-daily-sink_20010203.log:

[2001-02-03 12:34:56.789] [multi-sink-logger] [info] Some message
[2001-02-03 12:34:56.789] [multi-sink-logger] [warn] Some warning

File game/addons/sourcemod/logs/log4sp-multi-rotating-sink.1.log:

[2001-02-03 12:34:56.789] [multi-sink-logger] [info] Some message

File game/addons/sourcemod/logs/log4sp-multi-rotating-sink.log:

[2001-02-03 12:34:56.789] [multi-sink-logger] [warn] Some warning

Server console:

[2001-02-03 12:34:56.789] [multi-sink-logger] [info] Some message
[2001-02-03 12:34:56.789] [multi-sink-logger] [warn] Some warning

Handle Lifecycle

The underlying Logger and Sink objects are only deleted from memory when the reference count is 0.

Lines 13-15 close the sink handles, but the underlying Sink objects are not deleted because the logger created in line 11 references these Sinks objects.

Logger CreateMultiSinksLogger()
{
    char file[PLATFORM_MAX_PATH];
    BuildPath(Path_SM, file, sizeof(file), "logs/log4sp-multi-sinks.log");

    Sink sinks[3];
    sinks[0] = new DailyFileSink(file);
    sinks[1] = new ServerConsoleSink();
    sinks[2] = new ClientChatAllSink();

    Logger logger = Logger.CreateLoggerWith("multi-sink-logger", sinks, 3);

    delete sinks[0];
    delete sinks[1];
    delete sinks[2];

    logger.Info("Successfully created logger with multiple sinks");
    return logger;
}
  • Before line 11, the Sinks object is only referenced by the Handles system, so the number of Sinks references is 1;

  • After line 11, the logger references the Sinks object, so the number of Sinks references increases to 2;

  • After lines 13-15, the Handles system removes the reference to the Sinks object, so the number of Sinks references is reduced to 1;

  • After closing the logger handle, the logger object will automatically remove the reference to the Sinks object, so the Sinks reference count is reduced to 0 and deleted from memory.

Handle Type Logger Sink
Closeable Yes (Except for global logger) Yes
Cloneable Yes Yes

Flowchart

flowchart LR subgraph Logger["Logger"] LoggerShouldLog{"Should Log?"} LoggerShouldJunction["Junction"] LoggerLogJunction["Junction"] LoggerShouldFlush{"Should Flush?"} LoggerLog("Log") LoggerLogEx("LogEx") LoggerLogAmxTpl("LogAmxTpl") LoggerLogFormat["Raw"] LoggerLogExFormat["Log4sp params format"] LoggerLogAmxTplFormat["SourceMod params format"] end subgraph Sinks["Sink List"] SinkShouldJunction["Junction"] SinkShouldLog{"Should Log?"} SinkLog("Log") SinkPatternFormat["Pattern format"] SinkFlushJunction["Junction"] SinkFlush("Flush") end Start((("Start"))) L_Start_LoggerShouldLog_0@== Log Message ==> LoggerShouldLog LoggerShouldLog -- Yes --- LoggerShouldJunction LoggerShouldJunction --> LoggerLogJunction & LoggerShouldFlush LoggerShouldLog -. No .-> Stop((("End"))) LoggerLogJunction --- LoggerLog & LoggerLogEx & LoggerLogAmxTpl LoggerLog --- LoggerLogFormat LoggerLogFormat --- SinkShouldJunction LoggerLogEx --- LoggerLogExFormat LoggerLogExFormat --- SinkShouldJunction LoggerLogAmxTpl --- LoggerLogAmxTplFormat LoggerLogAmxTplFormat --- SinkShouldJunction LoggerShouldFlush -- Yes --- SinkFlushJunction SinkFlushJunction --> SinkFlush SinkFlush --> Stop LoggerShouldFlush -. No .-> Stop SinkShouldJunction --> SinkShouldLog SinkShouldLog -- Yes --- SinkPatternFormat SinkPatternFormat --- SinkLog SinkLog --> Stop SinkShouldLog -. No .-> Stop LoggerShouldJunction@{ shape: junction} LoggerLogJunction@{ shape: junction} LoggerLogFormat@{ shape: das} LoggerLogExFormat@{ shape: das} LoggerLogAmxTplFormat@{ shape: das} SinkShouldJunction@{ shape: junction} SinkPatternFormat@{ shape: das} SinkFlushJunction@{ shape: junction} style LoggerShouldLog stroke-width:4px,stroke-dasharray: 0 style LoggerShouldFlush stroke-width:1px,stroke-dasharray: 1 style LoggerLog stroke-width:4px,stroke-dasharray: 0 style LoggerLogEx stroke-width:4px,stroke-dasharray: 0 style LoggerLogAmxTpl stroke-width:4px,stroke-dasharray: 0 style LoggerLogFormat stroke-width:1px,stroke-dasharray: 1 style LoggerLogExFormat stroke-width:1px,stroke-dasharray: 1 style LoggerLogAmxTplFormat stroke-width:1px,stroke-dasharray: 1 style SinkShouldLog stroke-width:4px,stroke-dasharray: 0 style SinkLog stroke-width:4px,stroke-dasharray: 0 style SinkPatternFormat stroke-width:1px,stroke-dasharray: 1 style SinkFlush stroke-width:4px,stroke-dasharray: 0 linkStyle 1 stroke:#00C853,fill:none linkStyle 2 stroke:#00C853,fill:none linkStyle 3 stroke:#00C853,fill:none linkStyle 4 stroke:#D50000,fill:none linkStyle 14 stroke:#00C853,fill:none linkStyle 15 stroke:#00C853,fill:none linkStyle 17 stroke:#D50000,fill:none linkStyle 19 stroke:#00C853,fill:none linkStyle 22 stroke:#D50000,fill:none L_Start_LoggerShouldLog_0@{ animation: fast }

Benchmarks

Test platform: Windows 11 + VMware + Ubuntu 24.04 LTS + SourceMod 1.13.0.7410

Host configuration: Intel® Core™ Ultra 7 255H + 32 GB Memory

VM Ubuntu configuration: 1 CPU + 8 kernel + 8 GB Memory

Test case: ./sourcemod/scripting/testsuite/bench/bench-log4sp.sp

[benchmark] basic-file      Runs: 10   Calls: 10000000   Elapsed: 1.694      5900143/sec
[benchmark] callback        Runs: 10   Calls: 10000000   Elapsed: 1.814      5511962/sec
[benchmark] daily-file      Runs: 10   Calls: 10000000   Elapsed: 1.708      5853536/sec
[benchmark] ring-buffer     Runs: 10   Calls: 10000000   Elapsed: 0.763     13093170/sec
[benchmark] rotate-file     Runs: 10   Calls: 10000000   Elapsed: 2.039      4903350/sec
[benchmark] server-console  Runs: 10   Calls: 10000000   Elapsed: 20.733      482301/sec

As a reference, SourceMod - Logging was also tested

Test case: ./sourcemod/scripting/testsuite/bench/bench-sm-logging.sp

[benchmark] LogMessage      Runs: 10   Calls: 10000000   Elapsed: 58.743    170230/sec
[benchmark] LogToFile       Runs: 10   Calls: 10000000   Elapsed: 54.598    183156/sec
[benchmark] LogToFileEx     Runs: 10   Calls: 10000000   Elapsed: 53.439    187128/sec
[benchmark] PrintToServer   Runs: 10   Calls: 10000000   Elapsed: 18.596    537744/sec

Build

Linux

  1. Download dependencies and project files

    mkdir alliedmodders && cd alliedmodders
    git clone https://github.com/alliedmodders/ambuild
    git clone --recursive https://github.com/alliedmodders/sourcemod -b 1.12-dev
    git clone https://github.com/F1F88/sm-ext-log4sp.git
    
  2. Install AMBuild

    pip install ./ambuild
    
  3. Build

    mkdir sm-ext-log4sp/build && cd sm-ext-log4sp/build
    python3 ../configure.py --enable-optimize --sm-path ../../sourcemod --targets=x86,x64
    ambuild
    
    Tip 提示

    Local builds are able to enable additional optimizations

Windows

  1. Install Visual Studio、Python、Git (See Building SourceMod)

  2. Download dependencies and project files

    mkdir alliedmodders && cd alliedmodders
    git clone https://github.com/alliedmodders/ambuild
    git clone --recursive https://github.com/alliedmodders/sourcemod -b 1.12-dev
    git clone https://github.com/F1F88/sm-ext-log4sp.git
    
  3. Install AMBuild

    pip install ./ambuild
    
  4. Build (In the Developer Command Prompt environment)

    mkdir sm-ext-log4sp/build && cd sm-ext-log4sp/build
    python3 ../configure.py --enable-optimize --sm-path ../../sourcemod --targets=x86
    ambuild
    

FAQ

AMBuild

error: externally-managed-environment

# ref: https://blog.csdn.net/2202_75762088/article/details/134625775
# Ubuntu does not have pip installed by default
sudo apt install python3-pip
# Try installing AMBuild again
pip install ./ambuild --break-system-packages

Extension

Build Problem

Linux switch clang/gcc compiler

export CC=clang
export CXX=clang++

Unable to find a suitable CXX compiler

# ref: https://blog.csdn.net/weixin_38939826/article/details/105174347
sudo yum install gcc-c++ libstdc++-devel

gnu/stubs-32.h: No such file or directory

# ref: https://blog.csdn.net/wang_xijue/article/details/47128423
sudo yum install glibc-devel.i686

bits/c++config.h: No such file or directory

# ref: https://blog.csdn.net/Edidaughter/article/details/122627186
sudo apt-get install gcc-multilib g++-multilib

/usr/bin/ld: cannot find -lstdc++

Remove “[cxx.linkflags += [‘-static-libstdc++’]](./AMBuildScript#L307)” in AMBuildScript file

Runtime Problem

[SM] Unable to load extension “log4sp.ext”: Could not find interface

Check if the extension version matches the operating system

Check if the extension version matches the SourceMod version

bin/libstdc++.so.6: version ‘GLIBCXX_3.4.20’ not found

See: #6

Plugins

Build Problem

error 139: could not find type “Logger”

Check if #include <log4sp> is missing in the scripting code files

Check whether the Log4sp includes file is missing in the compilation environment

Check if the Log4sp includes file version is outdated

Runtime Problem

[SM] Unable to load plugin “…smx”: Required extension “Log4sp” file(“log4sp.ext”) not running

Check if the log4sp.ext extension file is missing in the server "addons/sourcemod/extensions" folder

The log file remains empty

See: <spdlog wiki> FAQ

Credits

  • gabime’s spdlog project implements most of the functionality, and log4sp wraps it into SourceMod natives.

  • Fyren, nosoop, Deathreus provides a solution for managing the sink handle.

  • blueblur0730, Digby helped improve the traversal operation of all loggers.

  • Bakugo, Anonymous Player, Fyren help fix crash with asynchronous calls to SourcePawn.

  • blueblur0730 added log4sp_manager plugin.

If I missed anyone, please contact me.

评论

暂无评论