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.
- Very fast, much faster than SourceMod Logging.
- Support log filtering - log levels can be modified at runtime as well as compile time.
- Support for large log message - over 1024 characters will not be truncated
- Support custom log message pattern.
- Support logging no error throwing.
- Support various sinks (log targets).
- Support console commands and management menus.
- Support int64.
- Support x64.
- Download the appropriate version from Releases
sm-ext-log4spcontains the extension file and scripting include files.sm-plugin-log4sp_managerplugin adds user commands and menus to manage logger (depends on extension)
- Uploading "addons/sourcemod" files to the server
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!
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 ...
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
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 sinksAll 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
Log4sp lets the underlying libc flush whenever it sees fit in order to achieve good performance.
You can override this with:
-
Manual flush
sink.Flush(); // Flush single sink contents logger.Flush(); // Flush all sinks contents
-
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).
-
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(); }
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:
-
Error when Logger.LogEx formats parameters;
-
Error when Logger traverses Sinks to log;
-
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).
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);
}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
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 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 }
Test platform: Windows 11 + VMware + Ubuntu 24.04 LTS + SourceMod 1.13.0.7410
Host configuration: Intel(R) Core(TM) 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
-
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
-
Install AMBuild
pip install ./ambuild
-
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
-
Install Visual Studio、Python、Git (See Building SourceMod)
-
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
-
Install AMBuild
pip install ./ambuild
-
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
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-packagesLinux 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++-develgnu/stubs-32.h: No such file or directory
# ref: https://blog.csdn.net/wang_xijue/article/details/47128423
sudo yum install glibc-devel.i686bits/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
AMBuildScriptfile
[SM] Unable to load extension "log4sp.ext": Could not find interface
Check if the
extensionversion matches the operating systemCheck if the
extensionversion matches the SourceMod version
bin/libstdc++.so.6: version 'GLIBCXX_3.4.20' not found
See: #6
error 139: could not find type "Logger"
Check if
#include <log4sp>is missing in the scripting code filesCheck whether the Log4sp includes file is missing in the compilation environment
Check if the Log4sp includes file version is outdated
[SM] Unable to load plugin "....smx": Required extension "Log4sp" file("log4sp.ext") not running
Check if the
log4sp.extextension file is missing in the server"addons/sourcemod/extensions"folder
The log file remains empty
See: <spdlog wiki> FAQ
-
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.