This document describes how the logger is configured, which log levels exist, which sinks are available by default, how custom sinks can be registered, and which sinks are created automatically when initialization runs without a config.
Logger Configuration
The logger can be configured in the config/vsdk.json file under logger key:
{
"logger": {
"log_directory": "logs",
"sinks": [
{ "type": "ConsoleSink" },
{ "type": "FileSink", "output": "app.log" }
]
},
"version": "2.0.0"
}
-
log_directory: directory used as the base path for relative sink paths. If it is relative, it is resolved againstcacheDirectory.-
Example:
"logs"becomescacheDirectory / "logs"
-
-
sinks: array of sink definitions. Each entry contains atypeand sink-specific options.
Logging levels
The logger uses the following ordered levels:
trace < debug < info < warn < error < fatal
-
trace: very verbose diagnostics. -
debug: development and debugging messages. -
info: normal informational events. -
warn: recoverable issues or non-critical warnings. -
error: runtime errors that need attention. -
fatal: unrecoverable failures.
A sink's log_level is the minimum accepted level:
{ "type": "ConsoleSink", "log_level": "info" }
This means the sink receives info, warn, error, and fatal, but skips debug and trace.
Sink filtering options
Each sink configuration can include filters that restrict which messages are emitted.
There are two types of sinks: IAudioSink for audio logging and ILogSink for text logging.
Common sink options
These options apply to all log sinks:
-
type: required string telling the logger which sink to instantiate. -
modules: list of accepted module names.-
If omitted or empty, all modules are allowed.
-
-
not_modules: list of module names to exclude.-
If omitted or empty, no exclusion is applied.
-
-
log_level: minimum accepted level for the sink (default:info). -
flush_level: flush level threshold.-
If omitted, the sink flushes on every log level.
-
Valid values are
trace,debug,info,warn,error,fatal, oralways.
-
Filtering logic is effectively:
level >= sink.log_level
AND module in modules if modules is non-empty
AND module not in not_modules if not_modules is non-empty
For flushing:
flush when no flush_level is set
OR when level >= flush_level
Built-in sinks
The library ships with three built-in sink types:
-
ConsoleSink -
FileSink -
AudioSink
These names are reserved and cannot be used for custom sink registration.
ConsoleSink
ConsoleSink writes logs to stdout/stderr depending on severity. Warnings and errors are printed to stderr; info/debug logs go to stdout.
Example:
{
"type": "ConsoleSink",
"modules": ["vsdk", "vdk-service"],
"not_modules": ["csdk"],
"log_level": "info",
"flush_level": "error"
}
Supported options:
-
type: must be"ConsoleSink" -
modules: optional list of accepted modules -
not_modules: optional list of forbidden modules -
log_level: optional minimum accepted level -
flush_level: optional flush threshold
There are no constructor arguments beyond the built-in default behavior.
FileSink
FileSink writes log messages to a file and rotates files when size limits are reached.
Example:
{
"type": "FileSink",
"output": "runtime.log",
"max_file_size": 10485760,
"max_file_count": 4,
"modules": ["vsdk"],
"log_level": "debug",
"flush_level": "always"
}
Supported options:
-
type: must be"FileSink" -
output: output file path-
relative paths are resolved under
log_directory
-
-
max_file_size: maximum file size in bytes before rollover -
max_file_count: how many rotated log files to keep -
modules: accepted modules -
not_modules: excluded modules -
log_level: minimum log level -
flush_level: flush threshold
Notes:
-
The file sink internally ensures a minimum of 2 files when rolling is enabled.
-
Default file sink behavior in the code uses
log.txtwhen no output is provided.
AudioSink
AudioSink is used to capture audio chunks into files. It writes PCM audio data, either as raw streams or WAV files, using a file-per-audio-name pattern.
Example:
{
"type": "AudioSink",
"output": "audio",
"file_prefix": "mic",
"format": "wav",
"max_file_size": 104857600,
"max_file_count": 4,
"audios": ["pipeline-1_producer", "pipeline-2_producer"],
"not_audios": ["pipeline-2_modifier[1]"],
"flush_threshold": 1024
}
Supported options:
-
type: must be"AudioSink" -
output: base directory for generated audio files -
file_prefix: prefix used in the filename -
format:"raw"or"wav" -
max_file_size: maximum size for each generated audio file -
max_file_count: rollback count for rotated audio files -
audios: whitelist of audio names to accept -
not_audios: blacklist of audio names to reject -
flush_threshold: threshold in bytes for audio flush behavior
Audio names are filtered through audios and not_audios. These names exclude the file_prefix and the extension. Adding an audio logger without a filter logs all audio.
Example: the audio name pipeline-0_producer logs to the file mic_pipeline-0_producer.wav, where mic is the file_prefix.
Default sinks used when no config is provided
If the logger is initialized without a sinks array, the code creates the following built-in sinks automatically:
[
{ "type": "ConsoleSink", "log_level": "info" },
{ "type": "FileSink", "log_level": "info", "output": "log.txt" }
]
The log directory default is:
"logs/"
which is resolved relative to the cacheDirectory passed to init.
Example:
Vsdk::initFromJson({{"version", "2.0.0"}}, "/tmp/my-app");
This ends up using:
/tmp/my-app/cache/logs/
and the file sink writes to:
/tmp/my-app/cache/logs/log.txt
Creating and registering a custom sink
You can add your own sink by creating a class that derives from either ILogSink or IAudioSink (or both).
Example: custom log sink
#include <vsdk/log/Logger.hpp>
#include <nlohmann/json.hpp>
class MyCustomSink : public Vsdk::Log::ILogSink
{
public:
explicit MyCustomSink(nlohmann::json const & config = {})
{
// read config values here
}
void log(SourceLocation src, LogLevel level,
std::string_view module, std::string_view msg) override
{
// custom output
}
void logException(SourceLocation src, std::string_view module,
Vsdk::Exception const & e) override
{
// custom exception handling
}
void flush() override {}
};
Register it before initialization:
#include <vsdk/log/Logger.hpp>
Vsdk::Log::Logger::instance().registerSinkFactory<MyCustomSink>("MyCustomSink");
// For custom constructor use
Vsdk::Log::Logger::instance().registerSinkFactory("MyCustomSink", [] { return std::make_shared<MyCustomSink>(...); });
Then configure it in JSON:
{
"sinks": [
{
"type": "MyCustomSink",
"settings": {
"endpoint": "https://example.internal/logs",
"token": "secret"
},
"modules": ["app"],
"log_level": "info"
}
]
}
Important registration rules
-
Register custom sinks before calling
init(...). -
The registered type name must not be one of the reserved names:
-
ConsoleSink -
FileSink -
AudioSink
-
-
The logger passes
settingsto the factory if it exists.
Custom audio sink example
class MyAudioSink : public Vsdk::Log::IAudioSink
{
public:
explicit MyAudioSink(nlohmann::json const & config = {}) {}
void logAudio(std::string_view name,
std::vector<int16_t> const & buffer,
int sampleRate,
int channels,
bool last) override
{
// write audio data
}
void flush() override {}
};
Use it with:
Vsdk::Log::Logger::instance().registerSinkFactory<MyAudioSink>("MyAudioSink");
{
"sinks": [
{
"type": "MyAudioSink",
"settings": { "output_dir": "audio" }
}
]
}
Quick reference example
{
"log_directory": "logs",
"sinks": [
{
"type": "ConsoleSink",
"log_level": "info",
"flush_level": "error"
},
{
"type": "FileSink",
"output": "app.log",
"max_file_size": 10485760,
"max_file_count": 5,
"log_level": "debug"
},
{
"type": "AudioSink",
"output": "audio",
"file_prefix": "voice",
"format": "wav",
"max_file_size": 104857600,
"max_file_count": 4
}
]
}
This configuration sets a shared log directory, sends info+ logs to the console, writes debug+ messages to a rotating file, and records audio files under the audio/ folder.
Practical tips
-
Use
modulesandnot_modulesto split logs by subsystem. -
Use
flush_levelto reduce I/O overhead; if you omit it, the logger flushes after each message. -
Register custom sinks before the first logger initialization.
-
Keep your custom sink type names unique and avoid the reserved built-in names.