Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/components/datarouter/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,30 @@ This section is reserved for data router-specific documentation.
:glob:

*

.. document:: Data Router Detailed Design
:id: doc__data_router_detailed_design
:status: valid
:safety: QM
:security: YES
:realizes: wp__sw_implementation


Static View
-----------

.. uml:: detailed_design/datarouter_class_diagram.puml

.. uml:: detailed_design/inter_process_communication.puml

.. uml:: detailed_design/mw_log_shared_memory_reader.puml


Dynamic View
------------

.. uml:: detailed_design/datarouter_backend_datarouterbackend.puml

.. uml:: detailed_design/shared_memory_reader_read.puml

.. uml:: detailed_design/datarouter_message_client_impl_connecttodatarouter.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
@startuml component_diagram

skinparam componentStyle rectangle
skinparam backgroundColor #FEFEFE
skinparam component {
BackgroundColor<<always>> #D5E8D4
BorderColor<<always>> #82B366
BackgroundColor<<additive>> #DAE8FC
BorderColor<<additive>> #6C8EBF
BackgroundColor<<core>> #FFF2CC
BorderColor<<core>> #D6B656
BackgroundColor<<recorder>> #E1D5E7
BorderColor<<recorder>> #9673A6
BackgroundColor<<app>> #F8CECC
BorderColor<<app>> #B85450
}
skinparam note {
BackgroundColor #FFFFCC
BorderColor #999999
}

title **mw::log — Lightweight Selectable Backend Architecture**\nComponent Overview

' === Application Layer ===
package "Application Binary" <<app>> {
component "Application Code" as app <<app>> {
note right of app
#include "score/mw/log/logging.h"
LOG_INFO() << "message";
end note
}
}

' === Frontend Layer (public API) ===
package "mw/log:frontend" <<always>> {
component "Logger" as logger <<always>>
component "LoggerContainer" as container <<always>>
component "LogStream" as stream <<always>>
component "Runtime\n(Meyers' Singleton)" as runtime <<always>>
component "IRecorderFactory\n(pure virtual)" as irecorder_factory <<core>>
}

' === Backend Infrastructure (always linked) ===
package "mw/log:console [alwayslink=True]" <<always>> {
component "gBackendCreators\n(std::array<FnPtr, 6>)\n---\nConstant-initialized\nZero-init at load time\nNo constructor / No destructor" as backend_table <<core>>
component "RegistryAwareRecorderFactory\n: IRecorderFactory" as registry_factory <<always>>
component "ConsoleRegistrant\n(static const)" as console_reg <<always>>
component "CreateRecorderFactory()\n[link-time injection]" as create_factory <<always>>
}

' === Additive Backend Plugins (optional, each alwayslink=True) ===
package "Additive Backend Plugins" <<additive>> {
component "backend:file\nFileRegistrant" as file_reg <<additive>>
component "backend:remote\nRemoteRegistrant" as remote_reg <<additive>>
component "backend:slog\nSlogRegistrant\n(QNX only)" as slog_reg <<additive>>
component "backend:custom\nCustomRegistrant" as custom_reg <<additive>>
}

' === Recorder Implementations ===
package "Recorder Implementations" <<recorder>> {
component "TextRecorder\n(Console)" as text_recorder <<always>>
component "FileRecorder\n(DLT File)" as file_recorder <<recorder>>
component "DataRouterRecorder\n(Remote DLT)" as dr_recorder <<recorder>>
component "SlogRecorder\n(QNX slog2)" as slog_recorder <<recorder>>
component "EmptyRecorder\n(No-op stub)" as empty_recorder <<recorder>>
component "CompositeRecorder\n(Multiplexer)" as composite <<recorder>>
}

' === Configuration ===
package "Configuration" <<core>> {
component "TargetConfigReader" as config_reader <<core>>
component "Configuration" as config <<core>>
}

' === Relationships ===

' App -> Frontend
app --> logger : "LOG_xxx()"
app --> stream
logger --> runtime : "GetRecorder()"

' Frontend -> Factory
runtime --> create_factory : "CreateRecorderFactory()\n(link-time injection)"
create_factory --> registry_factory : "returns"
registry_factory --|> irecorder_factory : "implements"

' Factory -> Backend Table
registry_factory --> backend_table : "queries\ngBackendCreators[mode]"
registry_factory --> config_reader : "reads config files"
config_reader --> config : "produces"

' Registrants -> Backend Table
console_reg --> backend_table : "writes slot[0]\n(kConsole)"
file_reg --> backend_table : "writes slot[1]\n(kFile)"
remote_reg --> backend_table : "writes slot[2]\n(kRemote)"
slog_reg --> backend_table : "writes slot[3]\n(kSystem)"
custom_reg --> backend_table : "writes slot[4]\n(kCustom)"

' Registrants -> Recorder Factories -> Recorders
console_reg ..> text_recorder : "creates via\nConsoleRecorderFactory"
file_reg ..> file_recorder : "creates via\nFileRecorderFactory"
remote_reg ..> dr_recorder : "creates via\nRemoteDltRecorderFactory"
slog_reg ..> slog_recorder : "creates via\nSlogRecorderFactory"

' Factory -> Recorders
registry_factory ..> composite : "wraps multiple\nrecorders"
registry_factory ..> empty_recorder : "last resort\nfallback"

' Legend
legend bottom
|= Color |= Meaning |
| <back:#D5E8D4> green </back> | **Always linked** — part of mw/log:console |
| <back:#DAE8FC> blue </back> | **Additive plugin** — opt-in via Bazel deps |
| <back:#FFF2CC> yellow </back> | **Core infrastructure** — backend table, config, interfaces |
| <back:#E1D5E7> purple </back> | **Recorder implementations** — linked only when needed |
| <back:#F8CECC> red </back> | **Application code** — user binary |
end legend

@enduml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
@startuml sequence_diagram
' Sequence Diagram: Backend Registration and Recorder Creation Flow

skinparam backgroundColor #FEFEFE
skinparam sequenceArrowThickness 2
skinparam sequenceParticipantBorderColor #666666
skinparam sequenceLifeLineBorderColor #999999
skinparam noteBackgroundColor #FFFFCC
skinparam noteBorderColor #999999
skinparam sequenceGroupBackgroundColor #F5F5F5

title **mw::log — Backend Registration & Recorder Creation Sequence**

' === Participants ===
participant "OS Loader" as loader #F8CECC
participant "gBackendCreators\n[std::array<FnPtr,6>]" as table #FFF2CC
participant "ConsoleRegistrant\n(static const)" as console_reg #D5E8D4
participant "FileRegistrant\n(static const)" as file_reg #DAE8FC
participant "RemoteRegistrant\n(static const)" as remote_reg #DAE8FC
participant "Application\nmain()" as app #F8CECC
participant "Runtime\n(Meyers' Singleton)" as runtime #D5E8D4
participant "RegistryAware\nRecorderFactory" as factory #D5E8D4
participant "TargetConfigReader" as config_reader #FFF2CC
participant "CompositeRecorder" as composite #E1D5E7

' === Phase 1: Constant Initialization (load time) ===
autonumber
group #E8F5E9 Phase 1: Constant Initialization (before main)
loader -> table : Zero-initialize gBackendCreators
note over table
gBackendCreators = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}
end note
end

' === Phase 2: Dynamic Initialization (static constructors, before main) ===
group #E3F2FD Phase 2: Dynamic Initialization (static constructors, TU order unspecified)
console_reg -> table : RegisterBackend(kConsole, &CreateConsoleRecorder)
note right : gBackendCreators[0] = &CreateConsoleRecorder
activate table
deactivate table

file_reg -> table : RegisterBackend(kFile, &CreateFileRecorder)
note right : gBackendCreators[1] = &CreateFileRecorder
activate table
deactivate table

note over table
Assuming the user has linked the respective backends (E.g.: mw/log/backend:file)*
- If a backend is not linked, its slot remains nullptr.
mw/log:console is always linked, so kConsole is guaranteed to be registered.
end note
end

' === Phase 3: main() starts ===
group #FFF3E0 Phase 3: Application Runtime (after main)
app -> runtime : First LOG_INFO() call
activate runtime

runtime -> runtime : Instance() — Meyers' singleton created
note right of runtime
Single creation point.
All registrants have completed.
end note

runtime -> factory : CreateRecorderFactory()
activate factory

factory -> config_reader : ReadConfig()
activate config_reader
config_reader --> factory : Result<Configuration>\n{logMode: kConsole | kFile | kRemote}
deactivate config_reader

' --- Iterate over configured log modes ---
loop for each mode in config.GetLogMode()
alt mode = kConsole
factory -> table : IsBackendAvailable(kConsole)
table --> factory : true
factory -> table : CreateRecorderForMode(kConsole, config, memory_resource)
table --> factory : unique_ptr<TextRecorder>
else mode = kFile
factory -> table : IsBackendAvailable(kFile)
table --> factory : true
factory -> table : CreateRecorderForMode(kFile, config, memory_resource)
table --> factory : unique_ptr<FileRecorder>
else mode = kRemote (not linked)
factory -> table : IsBackendAvailable(kRemote)
table --> factory : false (nullptr)
note right
**Fallback:** Backend not linked.
Create console recorder instead.
end note
factory -> table : CreateRecorderForMode(kConsole, config, memory_resource)
table --> factory : unique_ptr<TextRecorder> (fallback)
end
end

alt recorders.size() == 1
factory --> runtime : single Recorder
else recorders.size() > 1
factory -> composite : CompositeRecorder(move(recorders))
activate composite
composite --> factory : unique_ptr<CompositeRecorder>
deactivate composite
factory --> runtime : unique_ptr<CompositeRecorder>
end
deactivate factory

runtime --> app : Recorder& (ready for logging)
deactivate runtime
end

' === Phase 4: Shutdown ===
group #FFEBEE Phase 4: Program Shutdown
app -> runtime : Process exit
note over runtime, table
- Runtime singleton destroyed releasing owned Recorder instances
- BackendRegistrant instances destroyed -> no-op (no destructor body)
- gBackendCreators "destroyed" -> trivially destructible, no dtor runs

end note
end

@enduml
108 changes: 108 additions & 0 deletions docs/components/mw/log/detailed_design/configuration_sequence.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
@startuml configuration_sequence
title Configuration Sequence

participant "App" as App
participant "Logger" as LOG
participant "LogStreamFactory" as LSF
participant "Runtime" as RT
participant "RecorderFactory" as RF
participant "TargetConfigReader" as TCR
participant "ConfigurationFileDiscoverer" as CFD
participant "JSON" as JSON
participant "OS" as OS

activate App

App -> LOG **: CreateLogger(...)
note right of LOG
The initialization of the runtime is triggered by the first log call.
endnote

activate LOG

App -> LOG: LogError()

LOG -> LSF: GetStream(LogLevel)

LSF -> RT: GetRecorder()

alt Runtime instance has not been created
RT -> RT: Constructor
activate RT
end

RT -> RF **: CreateRecorderFactory()

activate RF

RT -> RF: CreateFromConfiguration()


RF -> CFD **: make_unique<ConfigurationFileDiscoverer>(...)
activate CFD

RF -> TCR **: make_unique<TargetConfigReader>(ConfigurationFileDiscoverer)
activate TCR


RF -> TCR: ReadConfig()

TCR -> CFD: FindConfigurationFiles()

note right of CFD
Find and return the existing
config files.
end note

CFD -> OS: access(/etc/ecu_logging_config.json)
alt MW_LOG_CONFIG_FILE is defined
CFD -> OS: access(MW_LOG_CONFIG_FILE)
else MW_LOG_CONFIG_FILE is undefined
CFD -> OS: access(<cwd>/etc/logging.json)
CFD -> OS: access(<cwd>/logging.json)
CFD -> OS: access(<binary path>/../etc/logging.json)
end

CFD -> TCR: Return global and environmental or application config file paths.

TCR -> JSON: FromFile(global_file_path)
JSON -> TCR:

alt MW_LOG_CONFIG_FILE is defined
TCR -> JSON: FromFile(<environmental file path>)
JSON -> TCR:
else MW_LOG_CONFIG_FILE is undefined
TCR -> JSON: FromFile(<app_file_path>)
JSON -> TCR:
end

note right of TCR
Overwrite global configiuration
with environmental or application config file paths.
end note

TCR -> RF: Return Configuration instance

alt "Console only recorder (compile option)"

Create "TextRecorder" as TR
RF -> TR: Create instance with configuration

RF -> ER: if logmode other than KConsole is provided.

else "Datarouter (compile option)"

Create "CompositeRecorder" as CR

RF -> CR
note left of CR
Recorder setup depends on the datarouter configuration.
endnote
end

RF -> RT: Store returned instance

RT -> LSF: Return Recorder reference
LSF -> LOG: LogStream with Recorder

@enduml
Loading
Loading