QtLogger Docs > Architecture
This document explains the internal architecture of QtLogger, how log messages flow through the system, and how different components interact.
QtLogger intercepts Qt’s standard logging output through qInstallMessageHandler() and processes each log message through a configurable pipeline of handlers. This architecture allows for flexible message processing, filtering, formatting, and routing to multiple destinations.
The core design principles are:
graph LR
subgraph "Qt Framework"
Q[qDebug/qInfo/qWarning/qCritical]
end
subgraph "QtLogger"
L[Logger]
P[Pipeline]
A[AttrHandler]
F[Filter]
FM[Formatter]
S[Sink]
end
subgraph "Output"
O1[Console]
O2[File]
O3[Network]
O4[Syslog]
end
Q --> L
L --> P
P --> A
A --> F
F -->|Pass| FM
F -->|Drop| X((Discard))
FM --> S
S --> O1
S --> O2
S --> O3
S --> O4
When you call qDebug() << "message":
Logger creates a LogMessage object with all contextQtLogger uses a hierarchy of handler types, each with a specific role:
classDiagram
class Handler {
<<abstract>>
+process(LogMessage&) bool
+type() HandlerType
}
class AttrHandler {
<<abstract>>
+attributes(LogMessage&) QVariantHash
}
class Filter {
<<abstract>>
+filter(LogMessage&) bool
}
class Formatter {
<<abstract>>
+format(LogMessage&) QString
}
class Sink {
<<abstract>>
+send(LogMessage&) void
+flush() bool
}
class Pipeline {
+append(Handler)
+process(LogMessage&) bool
}
Handler <|-- AttrHandler
Handler <|-- Filter
Handler <|-- Formatter
Handler <|-- Sink
Handler <|-- Pipeline
The base class for all components. Every handler implements process(LogMessage &lmsg) which returns:
true — Continue to the next handlerfalse — Stop processing (message is dropped)Adds custom attributes to the log message. Examples:
SeqNumberAttr — Adds a sequential message numberAppInfoAttrs — Adds application name, version, PIDHostInfoAttrs — Adds hostname and IP addressDecides whether a message should continue through the pipeline:
LevelFilter — Filter by severity levelCategoryFilter — Filter by logging categoryRegExpFilter — Filter by regex patternDuplicateFilter — Suppress repeated messagesConverts the LogMessage into a formatted string:
PatternFormatter — Customizable pattern-based formattingJsonFormatter — JSON output for structured loggingPrettyFormatter — Human-readable colored outputQtLogMessageFormatter — Uses Qt’s default formattingOutputs the formatted message to a destination:
StdOutSink / StdErrSink — Console outputFileSink / RotatingFileSink — File outputHttpSink — HTTP endpointSyslogSink / SdJournalSink — System logsA container that holds multiple handlers and processes them in sequence. Pipelines can be nested, allowing complex routing scenarios.
A pipeline is an ordered list of handlers:
Pipeline pipeline;
pipeline << LevelFilterPtr::create(QtWarningMsg)
<< PatternFormatterPtr::create("%{time} %{message}")
<< StdErrSinkPtr::create();
Messages flow through handlers in order. If any handler returns false, processing stops.
SortedPipeline automatically organizes handlers by type:
AttrHandlers → Filters → Formatters → Sinks → Pipelines
This ensures handlers are always processed in the correct order regardless of insertion order.
SimplePipeline extends SortedPipeline with a fluent API:
gQtLogger
.addSeqNumber() // AttrHandler
.filterLevel(QtInfoMsg) // Filter
.formatPretty() // Formatter
.sendToStdErr(); // Sink
Pipelines can contain other pipelines for complex routing:
gQtLogger
.pipeline() // Sub-pipeline 1
.filterLevel(QtWarningMsg)
.formatPretty()
.sendToStdErr(true)
.end()
.pipeline() // Sub-pipeline 2
.formatToJson()
.sendToFile("app.log")
.end();
Each sub-pipeline processes messages independently. A message rejected by one sub-pipeline can still be processed by another.
sequenceDiagram
participant App as Application
participant Qt as Qt Framework
participant Logger as Logger
participant Pipeline as Pipeline
participant Handler as Handlers
participant Sink as Sinks
App->>Qt: qDebug() << "message"
Qt->>Logger: messageHandler(type, context, msg)
Logger->>Logger: Create LogMessage
Logger->>Pipeline: process(lmsg)
loop For each Handler
Pipeline->>Handler: process(lmsg)
alt Handler returns false
Handler-->>Pipeline: false (stop)
else Handler returns true
Handler-->>Pipeline: true (continue)
end
end
Pipeline->>Sink: send(lmsg)
Sink-->>Pipeline: done
Pipeline-->>Logger: done
false, remaining handlers are skippedlmsg.formattedMessage() for subsequent sinksGiven this configuration:
gQtLogger
.addSeqNumber()
.filterLevel(QtWarningMsg)
.format("%{seq_number} %{message}")
.sendToStdErr();
For qWarning() << "Test":
| Step | Handler | Action | Result |
|---|---|---|---|
| 1 | SeqNumberAttr | Add seq_number=1 |
Continue |
| 2 | LevelFilter | Check level >= Warning | Continue |
| 3 | PatternFormatter | Format to "1 Test" |
Continue |
| 4 | StdErrSink | Output to stderr | Continue |
For qDebug() << "Test":
| Step | Handler | Action | Result |
|---|---|---|---|
| 1 | SeqNumberAttr | Add seq_number=2 |
Continue |
| 2 | LevelFilter | Check level >= Warning | Stop (Debug < Warning) |
All handlers use QSharedPointer for automatic memory management:
using HandlerPtr = QSharedPointer<Handler>;
using FilterPtr = QSharedPointer<Filter>;
using SinkPtr = QSharedPointer<Sink>;
// ... etc
This ensures:
The global gQtLogger is a singleton that manages the default pipeline:
#define gQtLogger (*QtLogger::Logger::instance())
It is automatically created on first access and destroyed at application exit.
When you add a handler to a pipeline:
auto filter = LevelFilterPtr::create(QtWarningMsg);
gQtLogger << filter; // Pipeline takes shared ownership
The pipeline holds a shared reference. If you keep a reference, the handler stays alive even if removed from the pipeline.
QtLogger provides the following thread safety guarantees:
qDebug() etc. simultaneouslySynchronous (Default):
gQtLogger.configure(); // Synchronous
Log messages are processed in the calling thread. This is simpler but may block if sinks are slow.
Asynchronous:
gQtLogger.moveToOwnThread().configure(); // Asynchronous
Messages are queued and processed in a dedicated thread. Benefits:
See Advanced Usage for details on async logging.
Each LogMessage captures the originating thread:
lmsg.threadId() // Thread ID as integer
lmsg.qthreadptr() // QThread pointer value
Use %{threadid} or %{qthreadptr} in patterns to include this information.
| Previous | Next |
|---|---|
| ← Getting Started | Configuration → |