diff --git a/score/mw/log/design/backend/datarouter_backend/datarouter_backend_datarouterbackend.puml b/docs/components/datarouter/detailed_design/datarouter_backend_datarouterbackend.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/datarouter_backend_datarouterbackend.puml rename to docs/components/datarouter/detailed_design/datarouter_backend_datarouterbackend.puml diff --git a/score/mw/log/design/backend/datarouter_backend/datarouter_class_diagram.puml b/docs/components/datarouter/detailed_design/datarouter_class_diagram.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/datarouter_class_diagram.puml rename to docs/components/datarouter/detailed_design/datarouter_class_diagram.puml diff --git a/score/mw/log/design/backend/datarouter_backend/datarouter_message_client_impl_connecttodatarouter.puml b/docs/components/datarouter/detailed_design/datarouter_message_client_impl_connecttodatarouter.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/datarouter_message_client_impl_connecttodatarouter.puml rename to docs/components/datarouter/detailed_design/datarouter_message_client_impl_connecttodatarouter.puml diff --git a/score/mw/log/design/backend/datarouter_backend/inter_process_communication.puml b/docs/components/datarouter/detailed_design/inter_process_communication.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/inter_process_communication.puml rename to docs/components/datarouter/detailed_design/inter_process_communication.puml diff --git a/score/mw/log/design/backend/datarouter_backend/mw_log_shared_memory_reader.puml b/docs/components/datarouter/detailed_design/mw_log_shared_memory_reader.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/mw_log_shared_memory_reader.puml rename to docs/components/datarouter/detailed_design/mw_log_shared_memory_reader.puml diff --git a/score/mw/log/design/backend/datarouter_backend/shared_memory_reader_read.puml b/docs/components/datarouter/detailed_design/shared_memory_reader_read.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/shared_memory_reader_read.puml rename to docs/components/datarouter/detailed_design/shared_memory_reader_read.puml diff --git a/docs/components/datarouter/index.rst b/docs/components/datarouter/index.rst index 87afb0d0..37dbd45e 100644 --- a/docs/components/datarouter/index.rst +++ b/docs/components/datarouter/index.rst @@ -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 diff --git a/docs/components/mw/log/index.rst b/docs/components/mw/log/index.rst deleted file mode 100644 index 2742b25c..00000000 --- a/docs/components/mw/log/index.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. - # ******************************************************************************* - # Copyright (c) 2026 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # SPDX-License-Identifier: Apache-2.0 - # ******************************************************************************* - - -Logging Documentation -===================== - -This section is reserved for middleware-specific documentation. - -.. comp:: Logging Component - :id: comp__mw_logging - :security: YES - :safety: ASIL_B - :status: valid - :implements: logic_arc_int__logging__logging - :belongs_to: feat__logging - - This is the logging component library responsible for selecting the appropriate log sinks based on configuration at runtime. It can perform tasks such as log formatting, filtering, and composite backend selection based on runtime context and configuration. The logging component is designed to be extensible, allowing for custom logging backend to be added as needed. - -.. toctree:: - :titlesonly: - :maxdepth: 1 - :glob: - - * diff --git a/docs/components/mw_log/detailed_design/backend_registration_component_diagram.puml b/docs/components/mw_log/detailed_design/backend_registration_component_diagram.puml new file mode 100644 index 00000000..554e0c21 --- /dev/null +++ b/docs/components/mw_log/detailed_design/backend_registration_component_diagram.puml @@ -0,0 +1,119 @@ +@startuml component_diagram + +skinparam componentStyle rectangle +skinparam backgroundColor #FEFEFE +skinparam component { + BackgroundColor<> #D5E8D4 + BorderColor<> #82B366 + BackgroundColor<> #DAE8FC + BorderColor<> #6C8EBF + BackgroundColor<> #FFF2CC + BorderColor<> #D6B656 + BackgroundColor<> #E1D5E7 + BorderColor<> #9673A6 + BackgroundColor<> #F8CECC + BorderColor<> #B85450 +} +skinparam note { + BackgroundColor #FFFFCC + BorderColor #999999 +} + +title **mw::log — Lightweight Selectable Backend Architecture**\nComponent Overview + +' === Application Layer === +package "Application Binary" <> { + component "Application Code" as app <> { + note right of app + #include "score/mw/log/logging.h" + LOG_INFO() << "message"; + end note + } +} + +' === Frontend Layer (public API) === +package "mw/log:frontend" <> { + component "Logger" as logger <> + component "LoggerContainer" as container <> + component "LogStream" as stream <> + component "Runtime\n(Meyers' Singleton)" as runtime <> + component "IRecorderFactory\n(pure virtual)" as irecorder_factory <> +} + +' === Backend Infrastructure (always linked) === +package "mw/log:console [alwayslink=True]" <> { + component "gBackendCreators\n(std::array)\n---\nConstant-initialized\nZero-init at load time\nNo constructor / No destructor" as backend_table <> + component "RegistryAwareRecorderFactory\n: IRecorderFactory" as registry_factory <> + component "ConsoleRegistrant\n(static const)" as console_reg <> + component "CreateRecorderFactory()\n[link-time injection]" as create_factory <> +} + +' === Additive Backend Plugins (optional, each alwayslink=True) === +package "Additive Backend Plugins" <> { + component "backend:file\nFileRegistrant" as file_reg <> + component "backend:remote\nRemoteRegistrant" as remote_reg <> + component "backend:slog\nSlogRegistrant\n(QNX only)" as slog_reg <> + component "backend:custom\nCustomRegistrant" as custom_reg <> +} + +' === Recorder Implementations === +package "Recorder Implementations" <> { + component "TextRecorder\n(Console)" as text_recorder <> + component "FileRecorder\n(DLT File)" as file_recorder <> + component "DataRouterRecorder\n(Remote DLT)" as dr_recorder <> + component "SlogRecorder\n(QNX slog2)" as slog_recorder <> + component "EmptyRecorder\n(No-op stub)" as empty_recorder <> + component "CompositeRecorder\n(Multiplexer)" as composite <> +} + +' === Configuration === +package "Configuration" <> { + component "TargetConfigReader" as config_reader <> + component "Configuration" as config <> +} + +' === 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 | + | green | **Always linked** — part of mw/log:console | + | blue | **Additive plugin** — opt-in via Bazel deps | + | yellow | **Core infrastructure** — backend table, config, interfaces | + | purple | **Recorder implementations** — linked only when needed | + | red | **Application code** — user binary | +end legend + +@enduml diff --git a/docs/components/mw_log/detailed_design/backend_registration_sequence_diagram.puml b/docs/components/mw_log/detailed_design/backend_registration_sequence_diagram.puml new file mode 100644 index 00000000..e5a6f7fd --- /dev/null +++ b/docs/components/mw_log/detailed_design/backend_registration_sequence_diagram.puml @@ -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]" 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\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 + else mode = kFile + factory -> table : IsBackendAvailable(kFile) + table --> factory : true + factory -> table : CreateRecorderForMode(kFile, config, memory_resource) + table --> factory : unique_ptr + 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 (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 + deactivate composite + factory --> runtime : unique_ptr + 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 diff --git a/score/mw/log/detail/wait_free_producer_queue/design/class_diagram.puml b/docs/components/mw_log/detailed_design/class_diagram.puml similarity index 100% rename from score/mw/log/detail/wait_free_producer_queue/design/class_diagram.puml rename to docs/components/mw_log/detailed_design/class_diagram.puml diff --git a/docs/components/mw_log/detailed_design/configuration_sequence.puml b/docs/components/mw_log/detailed_design/configuration_sequence.puml new file mode 100644 index 00000000..b769683c --- /dev/null +++ b/docs/components/mw_log/detailed_design/configuration_sequence.puml @@ -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(...) +activate CFD + +RF -> TCR **: make_unique(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(/etc/logging.json) + CFD -> OS: access(/logging.json) + CFD -> OS: access(/../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() + JSON -> TCR: +else MW_LOG_CONFIG_FILE is undefined + TCR -> JSON: FromFile() + 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 diff --git a/docs/components/mw_log/detailed_design/configuration_static.puml b/docs/components/mw_log/detailed_design/configuration_static.puml new file mode 100644 index 00000000..22d54913 --- /dev/null +++ b/docs/components/mw_log/detailed_design/configuration_static.puml @@ -0,0 +1,249 @@ +@startuml configuration_static + +interface "score:mw::log::Recorder" as Recorder { +} + +class "TextRecorder" as TextRecorder { +} + +class "DataRouterRecorder" as DataRouterRecorder { + - configuration: score::mw::log::Configuration +} + +class "FileRecorder" as FileRecorder { +} + +class "Empty" as Empty { +} + +class "CompositeRecorder" as CompositeRecorder { + - config_: score::mw::log::detail::Configuration +} + +class "RecorderComposite" as RecorderComposite { + + RecorderComposite(recorders: vector) + __ + - recorders_: vector +} + +class "score::mw::log::Configuration" as Configuration { + - ecu_id_: LoggingIdentifier + - app_id_: LoggingIdentifier + - app_description_: std::string + - log_mode_: std::unordered_set + - log_file_path_: std::string + - default_log_level_: LogLevel + - default_console_log_level_: LogLevel + - context_log_level_: ContextLogLevelMap + - stack_buffer_size_: std::size_t + - ring_buffer_size_: std::size_t + - ring_buffer_overwrite_on_full_: bool + - number_of_slots_: std::size_t + - slot_size_bytes_: std::size_t + - data_router_uid_: std::size_t + - dynamic_datarouter_identifiers_: bool + __ + + Configuration() + + GetEcuId(): std::string_view + + SetEcuId(const std::string_view): void + + GetAppId(): std::string_view + + SetAppId(const std::string_view): void + + GetAppDescription(): std::string_view + + SetAppDescription(const std::string_view): void + + GetLogMode(): const std::unordered_set& + + SetLogMode(const std::unordered_set&): void + + GetLogFilePath(): std::string_view + + SetLogFilePath(const std::string_view): void + + GetDefaultLogLevel(): LogLevel + + SetDefaultLogLevel(const LogLevel): void + + GetDefaultConsoleLogLevel(): LogLevel + + SetDefaultConsoleLogLevel(const LogLevel): void + + GetContextLogLevel(): ContextLogLevelMap& + + SetContextLogLevel(const ContextLogLevelMap&): void + + GetStackBufferSize(): std::size_t + + SetStackBufferSize(const std::size_t stack_buffer_size) + + GetRingBufferSize(): std::size_t + + SetRingBufferSize(const std::size_t ring_buffer_size): void + + GetRingBufferOverwriteOnFull(): bool + + SetRingBufferOverwriteOnFull(const bool): void + + GetNumberOfSlots(): std::size_t + + SetNumberOfSlots(const std::size_t number_of_slots): void + + GetSlotSizeInBytes(): std::size_t + + SetSlotSizeInBytes(const std::size_t slot_size_bytes): void + + SetDataRouterUid(const std::size_t uid): void + + GetDataRouterUid(): std::size_t + + GetDynamicDatarouterIdentifiers(): bool + + SetDynamicDatarouterIdentifiers(const bool enable_dynamic_identifier): void + + IsLogLevelEnabled(const LogLevel& log_level,\n const std::string_view context,\n const bool check_for_console = false): bool +} + +class "TargetConfigReader" as TargetConfigReader { + - discoverer_: unique_ptr + __ + + TargetConfigReader(discoverer: IConfigurationFileDiscoverer) + + ReadConfig(): score::Result +} + +interface "ITargetConfigReader" as ITargetConfigReader { + + {abstract} ReadConfig(): score::Result +} + +class "TargetConfigReaderMock" as TargetConfigReaderMock { +} + +class "RecorderFactory" as RecorderFactory { + - GetRemoteRecorder(const Configuration&):\n std::unique_ptr + - GetFileRecorder(const Configuration& config\n const std::unique_ptr&):\n std::unique_ptr + - GetConsoleRecorder(const Configuration&):\n std::unique_ptr + __ + + CreateFromConfiguration(): std::unique_ptr + + CreateFromConfiguration(\n const std::unique_ptr):\n std::unique_ptr + + CreateWithConsoleLoggingOnly(): std::unique_ptr + + CreateStub(): std::unique_ptr + + CreateRecorderFromLogMode(\n const LogMode&,\n const Configuration&,\n const std::unique_ptr) +} + +interface "IRecorderFactory" as IRecorderFactory { + + CreateFromConfiguration: std::unique_ptr + + CreateWithConsoleLoggingOnly(): std::unique_ptr + + CreateStub(): std::unique_ptr +} + +class "score::mw::log::detail::Runtime" as Runtime { +} + +interface "IConfigurationFileDiscoverer" as IConfigurationFileDiscoverer { + + {abstract} FindConfigurationFiles(): vector +} + +class "ConfigurationFileDiscoverer" as ConfigurationFileDiscoverer { + - path_: std::unique_ptr + - stdlib_: std::unique_ptr + - unistd_: std::unique_ptr + __ + + ConfigurationFileDiscoverer(\n std::unique_ptr&&,\n std::unique_ptr&&,\n std::unique_ptr&&) + + FindConfigurationFiles(): std::vector +} + +class "ConfigurationFileDiscovererMock" as ConfigurationFileDiscovererMock { +} + +class "LoggingIdentifier" as LoggingIdentifier { + + LoggingIdentifier(const std::string_view) + + GetStringView(): std::string_view + + friend operator==(const LoggingIdentifier&, const LoggingIdentifier&): bool + + friend operator!=(const LoggingIdentifier&, const LoggingIdentifier&): bool + + kMaxLength: std::size_t + + data_: std::array +} + +package "score::json" { + class JsonUtils { + + FromFile(path): Result + } +} + +package "score::os" { + class "utils/path" as Path { + + {static} get_exec_path(): Result + + {static} get_parent_dir(): string + } + + class "Unistd" as Unistd { + + access() + } + + class "Stdlib" as Stdlib { + + getenv() + } +} + +' Inheritance relationships +Recorder <|-- DataRouterRecorder +Recorder <|-- TextRecorder +Recorder <|-- FileRecorder +Recorder <|-- Empty +Recorder <|-- CompositeRecorder +Recorder <|.. RecorderComposite + +ITargetConfigReader <|.. TargetConfigReader +ITargetConfigReader <|.. TargetConfigReaderMock + +IConfigurationFileDiscoverer <|-- ConfigurationFileDiscoverer +IConfigurationFileDiscoverer <|-- ConfigurationFileDiscovererMock + +' Composition and aggregation relationships +DataRouterRecorder *-- Configuration +CompositeRecorder *-- Configuration +RecorderComposite *-- DataRouterRecorder +RecorderComposite *-- TextRecorder +Configuration *-- LoggingIdentifier + +' Usage relationships +RecorderFactory ..> RecorderComposite : creates +RecorderFactory ..> TargetConfigReader : uses +RecorderFactory ..> IRecorderFactory : uses +Runtime ..> Recorder : owns instance of +TargetConfigReader ..> Configuration : creates +TargetConfigReader *-- IConfigurationFileDiscoverer +TargetConfigReader ..> JsonUtils : uses +ConfigurationFileDiscoverer ..> Path : uses +ConfigurationFileDiscoverer ..> Unistd : uses +ConfigurationFileDiscoverer ..> Stdlib : uses + +note right of RecorderComposite + Forwards the logs to all recorders in the list + passed in the constructor. +end note + +note right of RecorderFactory + CreateForTarget(): Loads the configuration + and instantiates the requested + recorder types. + + CreateForUnitTests(): Prepares a recorder + that puts logs in standard output stream + for unit testing. + + CreateStub(): Returns Empty recorder that + discards all the logs. +end note + +note right of TargetConfigReader + Reads config from + /opt//etc/logging.json + and /ecu/logging.json and creates the + Configuration object. +end note + +note bottom of TargetConfigReaderMock + Used for unit testing the RecorderFactory. +end note + +note top of TargetConfigReader + Dependency injection for unit testing. +end note + +note right of DataRouterRecorder + Configuration passed + the constructor by value. +end note + +note right of ConfigurationFileDiscoverer + Finds the file paths to the global, environmental and + application configuration files and returns all that exists. + + Global configuration: + 1. /etc/ecu_logging_config.json if it exists. + + Environmental configuration: + 1. path under the MW_LOG_CONFIG_FILE environmental + variable if it defined + + Application configuration: + 1. /etc/logging.json + 2. /logging.json + 3. /../etc/logging.json +end note + +@enduml diff --git a/docs/components/mw_log/detailed_design/configuration_use_cases.puml b/docs/components/mw_log/detailed_design/configuration_use_cases.puml new file mode 100644 index 00000000..b96afdad --- /dev/null +++ b/docs/components/mw_log/detailed_design/configuration_use_cases.puml @@ -0,0 +1,26 @@ +@startuml configuration_use_cases +left to right direction +title Configuration Use Cases + +actor "Developer\nDebug application\ndeployed on target" as Dev1 + +actor "Developer,\nUnit testing" as Dev2 +actor "Performance\nEngineer" as PerfEng + +usecase "ECU wide configuration\n--\nECUID = MPP1\nLogLevel = kError" as UC1_ECU +usecase "Application configuration\n--\nAPPID = Para\nLogMode = kRemote | kConsole\nLogLevel = kDebug" as UC1_App + +usecase "Print the logs on the console for\nanalysis of unit test failures." as UC2_Console +usecase "No logging.json files" as UC2_Default + +usecase "Disable logging completely\nMeasure performance impact of logging" as UC3_Performance + +Dev1 --> UC1_ECU : "Use case 1" +Dev1 --> UC1_App : "Use case 1" + +Dev2 --> UC2_Console : "Use case 2" +Dev2 --> UC2_Default : "Use case 2" + +PerfEng --> UC3_Performance : "Use case 3" + +@enduml diff --git a/docs/components/mw_log/detailed_design/dynamic_backend_selection.puml b/docs/components/mw_log/detailed_design/dynamic_backend_selection.puml new file mode 100644 index 00000000..68db5552 --- /dev/null +++ b/docs/components/mw_log/detailed_design/dynamic_backend_selection.puml @@ -0,0 +1,131 @@ +@startuml backend_selection + +title mw::log - Two-Layer Backend Selection Flow + +skinparam defaultFontSize 12 +skinparam activityFontSize 11 +skinparam partitionFontSize 12 +skinparam arrowColor #444444 +skinparam activityBorderColor #444444 +skinparam partitionBorderColor #888888 + +|#LightBlue|Layer 1: Integrator| +|#LightYellow|Layer 2: User (logging.json)| +|#LightGreen|Runtime Initialization (Runtime::Runtime)| + +|Runtime Initialization (Runtime::Runtime)| +start +:Runtime::Runtime() called\n(Meyer's Singleton - first caller triggers init); + +|Layer 1: Integrator| +:ReadBackendConfig()\n""/opt/libmwlog/backend_config.json""; +note right + **backend_config.json** + Lists which .so files to dlopen(). + Integrator concern - makes + backends available. + ---- + { + "backends": [ + "libmwlog_file_backend.so", + "libmwlog_remote_backend.so" + ] + } +end note + +if (Config file found?) then (yes) + :DynamicBackendLoader::LoadConfiguredBackends\n(backend_filenames); + repeat + :For each filename in config; + :Build absolute path:\n/opt/libmwlog/backends/; + if (File exists?) then (no) + :Report WARNING\n(not deployed); + else (yes) + :dlopen(absolute_path,\nRTLD_NOW | RTLD_LOCAL); + if (dlopen() OK?) then (no) + :Report ERROR; + else (yes) + :dlsym - MwLogVersion(); + if (ABI version matches?) then (no) + :Report ERROR\n(ABI mismatch); + else (yes) + :dlsym - MwLogRegisterBackends(); + :MwLogRegisterBackends() called; + :RegisterBackend(LogMode, CreatorFn)\n - writes slot in gBackendCreators[]; + note right + gBackendCreators[] is now populated + for this backend's LogMode slot. + The .so remains mapped for + process lifetime (no dlclose). + end note + endif + endif + endif + repeat while (more backends?) + +else (no / missing) + :No backends loaded\n - gBackendCreators[] has\nonly built-in console slot; +endif + +|Runtime Initialization (Runtime::Runtime)| +note right + **State after Layer 1:** + gBackendCreators[] slots populated + for each successfully loaded .so. +end note + +|Layer 2: User (logging.json)| +:RegistryAwareRecorderFactory::\nCreateFromConfiguration(); +:TargetConfigReader reads\n""logging.json"" - Configuration; +note right + **logging.json** + Selects which available backends + to instantiate - User concern, runtime config. + ---- + { + "logMode": "kRemote|kFile" + } +end note + +:config.GetLogMode() -\nunordered_set\n{kRemote, kFile}; + +repeat + :For each LogMode in user's logMode set; + if (IsBackendAvailable(mode)?\ngBackendCreators[slot] != nullptr) then (yes) + :CreateRecorderForMode(mode, config, mem)\n - calls gBackendCreators[slot](config, mem); + :Recorder instantiated\n - added to recorders vector; + else (no - .so not loaded by integrator) + :Report ERROR:\n"Requested log mode not available.\nFalling back to console."; + if (Console available && not yet added?) then (yes) + :Add console recorder as fallback; + endif + endif +repeat while (more modes?) + +|Runtime Initialization (Runtime::Runtime)| +if (recorders vector empty?) then (yes) + :return EmptyRecorder (no-op stub); +else if (exactly one recorder?) then (yes) + :default_recorder_ = single recorder; +else (multiple) + :default_recorder_ =\nCompositeRecorder\n(multiplexes to all); +endif + +:Runtime init complete\ngBackendCreators[] immutable\nfor process lifetime; +stop + +legend right + |= Symbol |= Meaning | + |<#LightBlue> | Integrator configuration layer | + |<#LightYellow> | User configuration layer | + |<#LightGreen> | Runtime initialization | + + **Key invariant (REQ-DD-7):** + LoadConfiguredBackends() (Layer 1) always + completes before CreateFromConfiguration() + (Layer 2) reads gBackendCreators[]. + No synchronization needed - same thread, + same constructor, Meyer's Singleton guarantee. +end legend + +@enduml diff --git a/docs/components/mw_log/detailed_design/error_domain.puml b/docs/components/mw_log/detailed_design/error_domain.puml new file mode 100644 index 00000000..3f3509ca --- /dev/null +++ b/docs/components/mw_log/detailed_design/error_domain.puml @@ -0,0 +1,13 @@ +@startuml error_domain + +interface "score::result::ErrorDomain" as ErrorDomainInterface { + {abstract} +MessageFor(const score::result::ErrorCode&): std::string_view +} + +class "score::mw::log::detail::ErrorDomain" as ErrorDomain { + +MessageFor(const score::result::ErrorCode&): std::string_view +} + +ErrorDomainInterface <|.. ErrorDomain + +@enduml diff --git a/docs/components/mw_log/detailed_design/frontend_dependency_graph.puml b/docs/components/mw_log/detailed_design/frontend_dependency_graph.puml new file mode 100644 index 00000000..f13b2085 --- /dev/null +++ b/docs/components/mw_log/detailed_design/frontend_dependency_graph.puml @@ -0,0 +1,56 @@ +@startuml frontend_dependency_graph +title Frontend Dependency Graph + +package "mw::log frontend" as frontend { + class score::mw::log::Logger { + +Includes classes and free functions + } + class score::mw::log::detail::LogStreamFactory + class score::mw::log::LogStream + class score::mw::log::detail::Runtime + class score::mw::log::LoggerContainer + class score::mw::log::LogTypes + abstract score::mw::log::Recorder + abstract score::mw::log::IRecorderFactory +} + +package "mw::log implementation details" as details { + class score::mw::log::detail::Configuration {} + class score::mw::log::detail::TargetConfigReader {} + + class score::mw::log::detail::ConsoleRecorder {} + class score::mw::log::detail::ConsoleRecorderFactory {} + class score::mw::log::detail::EmptyRecorder {} + + class score::mw::log::detail::RecorderFactory {} +} + + +note top of frontend + Frontend contains the public user API, + and the necessary classes to interface + with the recorder interface. +end note + +note bottom of details + Instable components shall + depend on stable classes. +end note + +score::mw::log::detail::LogStreamFactory ..> score::mw::log::LogStream +score::mw::log::detail::Runtime o-- score::mw::log::LoggerContainer +score::mw::log::detail::Runtime --> score::mw::log::Recorder +score::mw::log::detail::Runtime o-- score::mw::log::Recorder +score::mw::log::detail::Runtime --> score::mw::log::detail::Configuration +score::mw::log::detail::Configuration --> score::mw::log::detail::TargetConfigReader +score::mw::log::Recorder <|-- score::mw::log::detail::EmptyRecorder + +score::mw::log::Recorder <|-- score::mw::log::detail::ConsoleRecorder +score::mw::log::IRecorderFactory <|-- score::mw::log::detail::ConsoleRecorderFactory +score::mw::log::detail::ConsoleRecorderFactory ..> score::mw::log::detail::ConsoleRecorder + +score::mw::log::LogStream --> score::mw::log::Recorder +score::mw::log::Logger ..> score::mw::log::LogStream +score::mw::log::LoggerContainer o-- score::mw::log::Logger + +@enduml diff --git a/score/mw/log/design/backend/datarouter_backend/mw_log_datarouter_recorder.puml b/docs/components/mw_log/detailed_design/mw_log_datarouter_recorder.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/mw_log_datarouter_recorder.puml rename to docs/components/mw_log/detailed_design/mw_log_datarouter_recorder.puml diff --git a/docs/components/mw_log/detailed_design/mw_log_default_recorders.puml b/docs/components/mw_log/detailed_design/mw_log_default_recorders.puml new file mode 100644 index 00000000..1a8b6a4c --- /dev/null +++ b/docs/components/mw_log/detailed_design/mw_log_default_recorders.puml @@ -0,0 +1,196 @@ +@startuml mw_log_recorders + +' Interface +interface "mw::log::Recorder" as Recorder { + + StartRecord(ctx:std::string_view, log_level:LogLevel) : score::cpp::optional + + StopRecord(const SlotHandle&): void + + Log(const SlotHandle&, const T data) - family of functions + + IsLogEnabled(const LogLevel&, const std::string_view): bool +} + +' Interface +interface "mw::log::detail::Backend" as Backend { + + ReserveSlot() : score::cpp::optional + + FlushSlot(const SlotHandle&) : void + + GetLogRecord(const SlotHandle&) : LogRecord& +} + +' Interface +interface "mw::log::detail::IMessageBuilder" as IMessageBuilder { + + GetNextSpan() : score::cpp::optional> + + SetNextMessage(LogRecord&) : void +} + +' Recorder implementations +class "mw::log::detail::TextRecorder" as TextRecorder { + - backend_ : std::unique_ptr + - config_ : Configuration + - check_log_level_for_console_ : bool + __ + + TextRecorder(const detail::Configuration&,\n std::unique_ptr,\n const bool) + + StartRecord(ctx:std::string_view, const LogLevel): score::cpp::optional + + StopRecord(const SlotHandle&): void + + IsLogEnabled(const LogLevel&, const std::string_view): bool + + Log(const SlotHandle&, T data) - family of functions +} + +class "mw::log::detail::CompositeRecorder" as CompositeRecorder { + - recorders_ : std::vector> + __ + + CompositeRecorder(std::vector> recorders) + + StartRecord(const std::string_view, const LogLevel): score::cpp::optional + + StopRecord(const SlotHandle&): void + + GetRecorders(): const std::vector>& + + IsLogEnabled(const LogLevel&, const std::string_view): bool + + Log(const SlotHandle&, const T) - family of functions +} + +class "mw::log::detail::EmptyRecorder" as EmptyRecorder { + + StartRecord(const std::string_view, const LogLevel):\n score::cpp::optional + + StopRecord(const SlotHandle&): void + + IsLogEnabled(const LogLevel&, const std::string_view): bool + + Log(const SlotHandle&, T data) - family of functions +} + +class "mw::log::detail::RecorderMock" as RecorderMock #pink { +} + +' Backend implementations +class "mw::log::detail::FileOutputBackend" as FileOutputBackend { + - buffer_allocator_ : std::shared_ptr> + - slot_drainer_ : SlotDrainer + __ + + FileOutputBackend(std::unique_ptr,\n const std::int32_t file_descriptor,\n std::unique_ptr>,\n score::cpp::pmr::unique_ptr,\n score::cpp::pmr::unique_ptr) + + ReserveSlot() : score::cpp::optional + + FlushSlot(const SlotHandle&) : void + + GetLogRecord(const SlotHandle&) : LogRecord& +} + +class "mw::log::detail::BackendMock" as BackendMock #pink { +} + +' Supporting classes +class "mw::log::detail::SlotDrainer" as SlotDrainer { + - allocator_ : std::shared_ptr> + - message_builder_ : std::unique_ptr + - non_blocking_writer_ : NonBlockingWriter + - circular_buffer_ : score::cpp::circular_buffer + - limit_slots_in_one_cycle_ : const std::size_t + __ + + SlotDrainer(std::unique_ptr,\n std::shared_ptr>,\n const std::int32_t file_descriptor,\n score::cpp::pmr::unique_ptr,\n const std::size_t limit_slots_in_one_cycle) + + PushBack(const SlotHandle&) : void + + Flush() : void +} + +class "mw::log::detail::NonBlockingWriter" as NonBlockingWriter { + - unistd_ : score::cpp::pmr::unique_ptr + - file_handle_ : std::int32_t + - buffer_ : score::cpp::span + __ + + NonBlockingWriter(const std::int32_t,\n std::size_t max_chunk_size,\n score::cpp::pmr::unique_ptr) + + FlushIntoFile() : score::cpp::expected + + SetSpan(const score::cpp::span&) : void + {static} + GetMaxChunkSize() : std::size_t +} + +class "mw::log::detail::TextMessageBuilder" as TextMessageBuilder { + - log_record_ : score::cpp::optional> + - header_payload_ : VerbosePayload + - parsing_phase_ : ParsingPhase + - ecu_id_ : LoggingIdentifier + __ + + TextMessageBuilder(const std::string_view ecu_id) + + GetNextSpan() : score::cpp::optional> + + SetNextMessage(LogRecord&) : void +} + +' Helper classes +class "mw::log::detail::DltArgumentCounter" as DltArgumentCounter { + - counter_ : std::uint8_t& + __ + + DltArgumentCounter(std::uint8_t&) + + TryAddArgument(AddArgumentCallback) : AddArgumentResult +} + +class "mw::log::detail::TextFormat" as TextFormat { + {static} + PutFormattedTime(PT& payload) : void + {static} + TerminateLog(PT& payload) : void + {static} + Log(PT& payload, const T data,\n const IntegerRepresentation) : void - family of functions + {static} + Log(PT& payload, const T data) : void\n - family of functions +} + +class "mw::log::detail::CircularAllocator" as CircularAllocator { + - claimed_sequence_ : std::atomic + - buffer_ : std::vector> + __ + + CircularAllocator(std::size_t capacity, const T& initial_value) + + AcquireSlotToWrite() : score::cpp::optional + + GetUnderlyingBufferFor(std::size_t) : T& + + ReleaseSlot(std::size_t) : void + + GetUsedCount() : std::size_t +} + +class "mw::log::detail::LogRecord" as LogRecord { + - log_entry_ : LogEntry + - verbose_payload_ : VerbosePayload + __ + + LogRecord(const std::size_t max_payload_size_bytes) + + GetLogEntry() : LogEntry& + + GetVerbosePayload() : VerbosePayload& +} + +class "mw::log::detail::VerbosePayload" as VerbosePayload { + - buffer_ : std::reference_wrapper + __ + + VerbosePayload(const std::size_t max_size, ByteVector&) + + Put(const Byte*, const std::size_t) : void + + Put(const ReserveCallback, const std::size_t) : std::size_t + + GetSpan() : score::cpp::span + + Reset() : void + + WillOverflow(const std::size_t) : bool + + RemainingCapacity() : std::size_t +} + +' External dependencies +class "OSAL::Fcntl" as Fcntl { +} + +class "OSAL::Unistd" as Unistd { +} + +' Relationships - Recorder interface +TextRecorder .up.|> Recorder +CompositeRecorder .up.|> Recorder +EmptyRecorder .up.|> Recorder +RecorderMock .up.|> Recorder + +' Relationships - Backend interface +FileOutputBackend .up.|> Backend +BackendMock .up.|> Backend + +' Relationships - IMessageBuilder interface +TextMessageBuilder .up.|> IMessageBuilder + +' Composition relationships +TextRecorder *-- Backend +CompositeRecorder *-- Recorder : contains multiple + +' Dependencies +TextRecorder ..> TextFormat : Log +TextRecorder ..> DltArgumentCounter : uses +NonBlockingWriter ..> Unistd : write +TextFormat ..> VerbosePayload : formats into + +' Notes +note bottom of Recorder + Refer recorder.h for all the Log(const SlotHandle&, const T data) + - family of functions +end note + +note top of TextFormat + Template-based static methods. + Refer text_format.h for all the Log(...) + - family of functions +end note + +@enduml diff --git a/score/mw/log/design/backend/mw_log_file_backend.puml b/docs/components/mw_log/detailed_design/mw_log_file_backend.puml similarity index 100% rename from score/mw/log/design/backend/mw_log_file_backend.puml rename to docs/components/mw_log/detailed_design/mw_log_file_backend.puml diff --git a/score/mw/log/design/backend/mw_log_recorders.puml b/docs/components/mw_log/detailed_design/mw_log_recorders.puml similarity index 100% rename from score/mw/log/design/backend/mw_log_recorders.puml rename to docs/components/mw_log/detailed_design/mw_log_recorders.puml diff --git a/docs/components/mw_log/detailed_design/non_verbose_logging_static.puml b/docs/components/mw_log/detailed_design/non_verbose_logging_static.puml new file mode 100644 index 00000000..8b630761 --- /dev/null +++ b/docs/components/mw_log/detailed_design/non_verbose_logging_static.puml @@ -0,0 +1,103 @@ +@startuml non_verbose_logging_static + +skinparam classBackgroundColor white +skinparam classBorderColor black +skinparam ArrowColor black +skinparam NoteBackgroundColor blue +skinparam NoteFontColor white +skinparam NoteBorderColor black + +class "score::platform::logger" <> { + - config_: Configuration + - nvconfig_: NvConfig + - shared_memory_writer_: SharedMemoryWriter + - log_entry: log_entry + -- + + instance(const score::cpp::optional&, + const score::cpp::optional&, + score::cpp::optional) + + RegisterType(): score::cpp::optional + + get_type_level(): LogLevel + + get_type_threshold: LogLevel + + get_config(): const Configuration& + + get_non_verbose_config(): const NvConfig& + + GetSharedMemoryWriter(): SharedMemoryWriter& + + InjectTestInstance(logger* const logger_ptr): static void +} + +class log_entry { + - default_enabled_: bool + - shared_memory_id_: std::atomic + -- + + instance(): static log_entry& + + RegisterTypeGetId(): score::cpp::optional + + TrySerializeIntoSharedMemory(T): void + + TryWriteIntoSharedMemoryrd(const T&): void + + log_at_time(timestamp_t, const T&): void + + log_serialized(const char*, const msgsize_t): void + + enabled(): bool + + enabled_at(LogLevel): bool +} + +class Configuration { +} + +class NvConfig { + - json_path_: const std::string + - typemap_: bool + -- + + NvConfig(const std::string&) + + parseFromJson(): ReadResult + + getDltMsgDesc(const std::string&): const config::NvMsgDescriptor* +} + +class SharedMemoryWriter { +} + +class NvMsgDescriptor { + + id_msg_descriptor_: uint32_t + + appid_: LoggingIdentifier + + ctxid_: LoggingIdentifier + + logLevel_: uint8_t +} + +class LoggingIdentifier { +} + +class TRACE { +} + +class "TRACE_*" { +} + +class TRACE_LEVEL { +} + +class LOG_ENTRY { +} + +"score::platform::logger" *--> Configuration : owns +"score::platform::logger" *--> NvConfig : owns +"score::platform::logger" *--> SharedMemoryWriter : owns + +"score::platform::logger" .left.> log_entry : <> +log_entry --> "score::platform::logger" : <> + +NvConfig --> NvMsgDescriptor : <> +NvMsgDescriptor *--> LoggingIdentifier + +note as N_free_functions #blue + Free public functions +end note + +N_free_functions .. LOG_ENTRY +N_free_functions .. TRACE +N_free_functions .. "TRACE_*" +N_free_functions .. TRACE_LEVEL + +log_entry <-- LOG_ENTRY +log_entry <-- TRACE +log_entry <-- "TRACE_*" +log_entry <-- TRACE_LEVEL + +@enduml diff --git a/docs/components/mw_log/detailed_design/rarf_activity_diagram.puml b/docs/components/mw_log/detailed_design/rarf_activity_diagram.puml new file mode 100644 index 00000000..e8ec75cb --- /dev/null +++ b/docs/components/mw_log/detailed_design/rarf_activity_diagram.puml @@ -0,0 +1,87 @@ +@startuml activity_diagram + +skinparam backgroundColor #FEFEFE +skinparam activityBackgroundColor #FFFFFF +skinparam activityBorderColor #666666 +skinparam activityDiamondBackgroundColor #FFF2CC +skinparam activityDiamondBorderColor #D6B656 +skinparam noteBackgroundColor #FFFFCC +skinparam noteBorderColor #999999 + +title mw::log — RegistryAwareRecorderFactory::CreateFromConfiguration() Decision Flow\n(kRemote backend) + +|RegistryAwareRecorderFactory| +start + +:Receive **memory_resource** parameter; + +if (memory_resource == nullptr?) then (yes) + :ReportInitializationError(kMemoryResourceError); + #FFCCCC:Return fallback - EmptyRecorder; + stop +else (no) +endif + +:Initialize **recorders** vector (empty); +:Get logModes from config; +note right + logModes contains **kRemote** + (and kConsole, always present). + Console recorder is created + by default. +end note + +|Backend Table| +:Query **IsBackendAvailable(kRemote)**; + +if (kRemote backend registered?) then (yes) + |RegistryAwareRecorderFactory| + :recorder = **CreateRecorderForMode**(kRemote, config, memory_resource); + note right + Calls gBackendCreators[kRemote](config, memory_resource) + via the registered function pointer. + end note + + if (recorder != nullptr?) then (yes) + :Add remote recorder to **recorders** vector; + else (no - creation failed) + note right + Recorder factory returned nullptr. + kRemote slot is skipped. + Console recorder remains. + end note + endif + +else (no - slot is nullptr) + |RegistryAwareRecorderFactory| + :ReportInitializationError( + kRecorderFactoryUnsupportedLogMode, + "kRemote not registered. Falling back to console only."); + note right + kRemote logmode was requested + but remote backend is not registered + in the backend table. + end note +endif + +|RegistryAwareRecorderFactory| + +if (recorders.size() == 1?) then (yes) + :Return **move(recorders[0])**; + note right + Only console recorder is made available + (kRemote creation failed + or was not registered). + end note + stop +else (no - multiple recorders) + :Return **CompositeRecorder(move(recorders))**; + note right + Wraps console + remote recorders. + Each Log() call is + forwarded to console and remote backend. + end note + stop +endif + +@enduml diff --git a/score/mw/log/design/backend/datarouter_backend/shared_memory_writer_allocandwrite.puml b/docs/components/mw_log/detailed_design/shared_memory_writer_allocandwrite.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/shared_memory_writer_allocandwrite.puml rename to docs/components/mw_log/detailed_design/shared_memory_writer_allocandwrite.puml diff --git a/docs/components/mw_log/detailed_design/slot_drainer_action_diagram_design.puml b/docs/components/mw_log/detailed_design/slot_drainer_action_diagram_design.puml new file mode 100644 index 00000000..55831c9a --- /dev/null +++ b/docs/components/mw_log/detailed_design/slot_drainer_action_diagram_design.puml @@ -0,0 +1,44 @@ +@startuml slot_drainer_action_diagram_design + +skinparam ActivityBackgroundColor white +skinparam ActivityBorderColor black +skinparam ArrowColor black +skinparam PartitionBackgroundColor orange +skinparam PartitionBorderColor black + +title Slot disposal diagram + +start + +:Flush slot call +circular_buffer.push(LogRecord&); + +partition SlotDrainer #orange { + while (circular_buffer not empty) is (yes) + :get_next_slot; + + partition NonBlockingWriter #3c7a00 { + while (next span available) is (yes) + :get_next_span; + :write_file; + if (result?) then ([would block]) + #orange:exit; + detach + else ([all data written]) + endif + endwhile (no) + } + + :Load number of buffers flushed; + if (count?) then ([limit reached]) + #orange:exit; + detach + else ([within limit]) + endif + + endwhile (no) +} + +stop + +@enduml diff --git a/docs/components/mw_log/detailed_design/slot_drainer_sequence_design.puml b/docs/components/mw_log/detailed_design/slot_drainer_sequence_design.puml new file mode 100644 index 00000000..c5265970 --- /dev/null +++ b/docs/components/mw_log/detailed_design/slot_drainer_sequence_design.puml @@ -0,0 +1,71 @@ +@startuml slot_drainer_sequence_design +title "SlotDrainer Sequence Design" + +participant "Backend" as Backend +participant "SlotDrainer" as SlotDrainer +participant "NonBlockingWriter" as NonBlockingWriter +participant "CircularBuffer" as CircularBuffer +participant "MessageBuilder" as MessageBuilder +participant "SlotAllocator" as SlotAllocator + +note over SlotDrainer : Slot Drainer Flush Sequence + +activate SlotDrainer +activate Backend +activate NonBlockingWriter +activate CircularBuffer +activate SlotAllocator +activate MessageBuilder + +group "First Slot" +Backend -> SlotDrainer : Flush() + +SlotDrainer -> MessageBuilder : GetNextSpan(log_record) + +SlotDrainer -> CircularBuffer : empty() +CircularBuffer -> SlotDrainer: false + +SlotDrainer -> CircularBuffer : front() +SlotDrainer -> CircularBuffer : pop_front() + +SlotDrainer -> CircularBuffer : empty() +CircularBuffer -> SlotDrainer: false +SlotDrainer -> CircularBuffer : front() + +SlotDrainer -> SlotAllocator : GetUnderlyingBufferFor(slot) +SlotDrainer -> MessageBuilder : SetNextMessage(log_record) + +SlotDrainer -> NonBlockingWriter : FlushIntoFile() +NonBlockingWriter -> SlotDrainer: kDone +SlotDrainer -> SlotAllocator: ReleaseSlot(slot) + + +SlotDrainer -> MessageBuilder : GetNextSpan() +end + +group "Last Slot" + +SlotDrainer -> MessageBuilder : GetNextSpan() + +SlotDrainer -> NonBlockingWriter : FlushIntoFile() +NonBlockingWriter -> SlotDrainer: kDone + +SlotDrainer -> MessageBuilder : GetNextSpan() + +SlotDrainer -> CircularBuffer : empty() +CircularBuffer -> SlotDrainer: true + +Backend <-- SlotDrainer +end + +group "Error Case" + +Backend -> SlotDrainer : Flush() +SlotDrainer -> NonBlockingWriter : FlushIntoFile() +NonBlockingWriter -> SlotDrainer: kWouldBlock +note left of SlotDrainer : SlotDrainer ignores errors +Backend <--- SlotDrainer + +end + +@enduml diff --git a/docs/components/mw_log/detailed_design/verbose_console_logging_sequence.puml b/docs/components/mw_log/detailed_design/verbose_console_logging_sequence.puml new file mode 100644 index 00000000..65f32fd3 --- /dev/null +++ b/docs/components/mw_log/detailed_design/verbose_console_logging_sequence.puml @@ -0,0 +1,108 @@ +@startuml verbose_logging_sequence + +participant "_:App_" as App +participant "FreeFunctions" as FreeFunctions +participant "LogStreamFactory" as LogStreamFactory +participant "Runtime" as Runtime +participant "_:LogStream_" as LogStream +participant "_:TextRecorder_" as TextRecorder +participant "TextFormat" as TextFormat +participant "_:EmptyRecorder_" as EmptyRecorder +participant "Console\n(stdout)" as Console + +activate App + +App -> FreeFunctions : score::mw::log::Error() + +FreeFunctions -> LogStreamFactory : GetStream(LogLevel) + +activate LogStreamFactory + +LogStreamFactory -> Runtime : GetRecorder() +Runtime -> TextRecorder : creates +LogStreamFactory <-- Runtime : Recorder + +LogStreamFactory -> LogStream : construct + +activate LogStream + +LogStream -> TextRecorder : StartRecord(ctx, LogLevel) +activate TextRecorder + +LogStream <-- TextRecorder : score::cpp::optional +deactivate TextRecorder + +alt SlotHandle is empty (TextRecorder failure) + + LogStream -> Runtime : GetRecorder() + Runtime -> EmptyRecorder : fallback + activate EmptyRecorder + + LogStream -> EmptyRecorder : StartRecord(ctx, LogLevel) + + note right of EmptyRecorder #orange + EmptyRecorder acts as /dev/null. + All log data is silently discarded. + end note + + LogStream <-- EmptyRecorder : score::cpp::optional + deactivate EmptyRecorder + +end + +LogStreamFactory <-- LogStream : LogStream +deactivate LogStream + +FreeFunctions <-- LogStreamFactory : LogStream +deactivate LogStreamFactory + +App <-- FreeFunctions : LogStream + +App -> LogStream : << Some Data + +activate LogStream + +LogStream -> TextRecorder : Log(SlotHandle, Data) +activate TextRecorder + +TextRecorder -> TextFormat : Format(data) +activate TextFormat + +TextFormat -> TextFormat : format data as\nhuman-readable text + +TextRecorder <-- TextFormat : formatted text +deactivate TextFormat + +TextRecorder -> Console : write to stdout +activate Console +TextRecorder <-- Console +deactivate Console + +LogStream <-- TextRecorder +deactivate TextRecorder + +App <-- LogStream : LogStream +deactivate LogStream + +App -> LogStream : Destruct + +activate LogStream + +LogStream -> TextRecorder : StopRecord(SlotHandle) +activate TextRecorder + +TextRecorder -> Console : flush to stdout +activate Console +TextRecorder <-- Console +deactivate Console + +LogStream <-- TextRecorder +deactivate TextRecorder + +destroy LogStream + +App <-- LogStream + +deactivate App + +@enduml diff --git a/docs/components/mw_log/detailed_design/verbose_console_logging_static.puml b/docs/components/mw_log/detailed_design/verbose_console_logging_static.puml new file mode 100644 index 00000000..ac3838c5 --- /dev/null +++ b/docs/components/mw_log/detailed_design/verbose_console_logging_static.puml @@ -0,0 +1,214 @@ +@startuml verbose_logging_static + +skinparam classBackgroundColor white +skinparam classBorderColor black +skinparam ArrowColor black +skinparam NoteBackgroundColor white +skinparam NoteBorderColor black +skinparam packageStyle rectangle + +title This diagram only illustrates the use-case of verbose logging. + +' ============================================================ +' LEFT SIDE: NOT MOCKABLE PART +' ============================================================ + +package "Not Mockable Part" as NotMockablePart { + + class "score::mw::LogLevel" as LogLevel <> { + kOff + kFatal + kError + kWarn + kInfo + kDebug + kVerbose + } + + class "SlotHandle::RecorderIdentifier" as RecorderIdentifier { + + value: std::size_t + } + + class "score::mw::log::SlotHandle" as SlotHandle { + - recorder_to_slot_: std::array + - recorder_slot_available_: std::bitset + - selected_recorder_: RecorderIdentifier + -- + + SlotHandle() + + SlotHandle(const SlotIndex) + + GetSlotOfSelectedRecorder(): SlotIndex + + GetSlot(const RecorderIdentifier): SlotIndex + + SetSlot(const SlotIndex, const RecorderIdentifier): void + + GetSelectedRecorder(): RecorderIdentifier + + SetSelectedRecorder(const RecorderIdentifier): void + + IsRecorderActive(const RecorderIdentifier): bool + + friend operator==(const SlotHandle& l_value, const SlotHandle& r_value): bool + + friend operator!=(const SlotHandle& l_value, const SlotHandle& r_value): bool + + kMaxRecorders: static constexpr std::size_t + } + + class "score::mw::log::detail::Runtime" as Runtime { + - Runtime(Recorder* const recorder) + - {static} Instance(Recorder* const): Runtime& + - logger_container_instance_: LoggerContainer + - recorder_instance_: Recorder + - default_recorder_: std::unique_ptr + -- + + {static} GetRecorder(): Recorder* + + {static} SetRecorder(Recorder*): void + + {static} GetLoggerContainer(): score::mw::log::LoggerContainer& + } + + class "score::mw::LogStream" as LogStream { + - recorder_: Recorder& + - slot_handle_: score::cpp::optional + - context_id_: detail::LoggingIdentifier + - log_level_: LogLevel + - LogStream(Recorder&, const LogLevel, const std::string_view) + -- + + LogStream(LogStream&&) + + Flush(): void + } + + class "score::mw::log::detail::LogStreamFactory" as LogStreamFactory { + + {static} GetStream(LogLevel, ctxId: std::string_view = "DFLT"): LogStream + } + + package "User-Facing API" as UserFacingAPI { + class "LoggingIdentifier" as LoggingIdentifier { + } + + class "score::mw::Logger" as Logger { + - context_: LoggingIdentifier + -- + + Logger(std::string_view ctxId) + + LogFatal(): LogStream + + LogError(): LogStream + + LogWarn(): LogStream + + LogInfo(): LogStream + + LogDebug(): LogStream + + LogVerbose(): LogStream + + WithLevel(const LogLevel): log::LogStream + + IsLogEnabled(const LogLevel): bool + + IsEnabled(const LogLevel): bool + + GetContext(): std::string_view + } + + class "Free Functions" as FreeFunctions { + + score::mw::LogFatal(): LogStream + + score::mw::LogError(): LogStream + + score::mw::LogWarn(): LogStream + + score::mw::LogInfo(): LogStream + + score::mw::LogDebug(): LogStream + + score::mw::LogVerbose(): LogStream + + score::mw::LogFatal(std::string_view): LogStream + + score::mw::LogError(std::string_view): LogStream + + score::mw::LogWarn(std::string_view): LogStream + + score::mw::LogInfo(std::string_view): LogStream + + score::mw::LogDebug(std::string_view): LogStream + + score::mw::LogVerbose(std::string_view): LogStream + + score::mw::GetDefaultLogRecorder(): Recorder& + + score::mw::test::SetLogRecorder(score::mw::log::Recorder* const): void + } + } +} + +' ============================================================ +' RIGHT SIDE: MOCKABLE PART +' ============================================================ + +package "Mockable Part" as MockablePart { + class "score::mw::log::LoggerContainer" as LoggerContainer { + - InsertNewLogger(const std::string_view): Logger& + - FindExistingLogger(const std::string_view): score::cpp::optional> + - stack_: WaitFreeStack + - default_logger_: Logger + -- + + GetLogger(const std::string_view): Logger& + + GetCapacity(): size_t + + GetDefaultLogger(): Logger& + } + + class "template \nscore::mw::log::detail::WaitFreeStack" as WaitFreeStack { + - elements_: std::vector> + - elements_written_: std::vector + - write_index_: AtomicIndex + - capacity_full_: AtomicBool + -- + + WaitFreeStack(const size_t) + + TryPush(Element&&): score::cpp::optional> + + Find(const FindPredicate&): score::cpp::optional> + } + + class "TextFormat" as TextFormat { + } + + interface "score::mw::log::Recorder" as Recorder { + + Recorder() + + {abstract} StartRecord(context_id: std::string_view, LogLevel): score::cpp::optional + + {abstract} StopRecord(SlotHandle const&): void + + {abstract} Log(SlotHandle, std::uint8_t): void + + {abstract} Log(SlotHandle, std::int8_t): void + } + + note as N_RecorderUsage #blue + A user is allowed to use the Recorder interface + for its abstraction. But its not recommended due + to its more complicated usage. + end note + + class "TextRecorder" as TextRecorder { + } + + class "EmptyRecorder" as EmptyRecorder { + } + + note as N_EmptyRecorder #orange + EmptyRecorder acts as /dev/null. + All log data is silently discarded. + Used as fallback when TextRecorder + initialization fails. + end note + + class "Console (stdout)" as Console { + } + +} + +' ============================================================ +' RELATIONS - NON-MOCKABLE PART +' ============================================================ + +LogStreamFactory <-down- Logger : uses +LogStreamFactory <-down- FreeFunctions : uses +LogStreamFactory -up-> LogStream : create +SlotHandle *-up-> LogStream : owns +LoggingIdentifier *-down-> Logger : owns + +' ============================================================ +' RELATIONS - CROSSING MOCKABLE BOUNDARY +' ============================================================ + +Runtime -down-> LogStreamFactory : get current\nrecorder +Runtime -down-> FreeFunctions +LogStream *-right-> Recorder : records data +Runtime *-right-> Recorder : owns statically +Runtime *-up-> LoggerContainer : owns +LoggerContainer *-right-> WaitFreeStack : owns +LoggerContainer *-down-> Logger : owns + +' ============================================================ +' RELATIONS - MOCKABLE PART +' ============================================================ + +TextRecorder .up.|> Recorder +EmptyRecorder .up.|> Recorder + +TextRecorder *-down-> TextFormat : formats log output +TextRecorder *-down-> Console : writes to stdout + +TextRecorder .right.> EmptyRecorder : fallback on failure + +N_EmptyRecorder .. EmptyRecorder + +@enduml diff --git a/score/mw/log/design/backend/datarouter_backend/verbose_logging_sequence.puml b/docs/components/mw_log/detailed_design/verbose_logging_sequence.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/verbose_logging_sequence.puml rename to docs/components/mw_log/detailed_design/verbose_logging_sequence.puml diff --git a/score/mw/log/design/backend/datarouter_backend/verbose_logging_static.puml b/docs/components/mw_log/detailed_design/verbose_logging_static.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/verbose_logging_static.puml rename to docs/components/mw_log/detailed_design/verbose_logging_static.puml diff --git a/score/mw/log/detail/wait_free_producer_queue/design/wait_free_alternating_buffers.puml b/docs/components/mw_log/detailed_design/wait_free_alternating_buffers.puml similarity index 100% rename from score/mw/log/detail/wait_free_producer_queue/design/wait_free_alternating_buffers.puml rename to docs/components/mw_log/detailed_design/wait_free_alternating_buffers.puml diff --git a/score/mw/log/detail/wait_free_producer_queue/design/wait_free_linear_buffer.puml b/docs/components/mw_log/detailed_design/wait_free_linear_buffer.puml similarity index 100% rename from score/mw/log/detail/wait_free_producer_queue/design/wait_free_linear_buffer.puml rename to docs/components/mw_log/detailed_design/wait_free_linear_buffer.puml diff --git a/score/mw/log/design/backend/datarouter_backend/write_factory_design.puml b/docs/components/mw_log/detailed_design/write_factory_design.puml similarity index 100% rename from score/mw/log/design/backend/datarouter_backend/write_factory_design.puml rename to docs/components/mw_log/detailed_design/write_factory_design.puml diff --git a/docs/components/mw_log/index.rst b/docs/components/mw_log/index.rst new file mode 100644 index 00000000..4f1b96b6 --- /dev/null +++ b/docs/components/mw_log/index.rst @@ -0,0 +1,117 @@ +.. + # ******************************************************************************* + # Copyright (c) 2026 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + + +Logging Documentation +===================== + +This section is reserved for middleware-specific documentation. + +.. comp:: Logging Component + :id: comp__mw_logging + :security: YES + :safety: ASIL_B + :status: valid + :implements: logic_arc_int__logging__logging + :belongs_to: feat__logging + + This is the logging component library responsible for selecting the appropriate log sinks based on configuration at runtime. It can perform tasks such as log formatting, filtering, and composite backend selection based on runtime context and configuration. The logging component is designed to be extensible, allowing for custom logging backend to be added as needed. + +.. toctree:: + :titlesonly: + :maxdepth: 1 + :glob: + + * + +.. document:: mw/log Detailed Design + :id: doc__mw_logging_detailed_design + :status: valid + :safety: ASIL_B + :security: YES + :realizes: wp__sw_implementation + + +Static View +----------- + +.. comp_arc_sta:: mw/log Static View + :id: comp_arc_sta__mw_logging__static_view + :security: YES + :safety: ASIL_B + :status: valid + :fulfils: comp_req__log__use_log_trace_framework + :belongs_to: comp__mw_logging + + .. uml:: detailed_design/mw_log_recorders.puml + + .. uml:: detailed_design/mw_log_file_backend.puml + + .. uml:: detailed_design/mw_log_datarouter_recorder.puml + + .. uml:: detailed_design/write_factory_design.puml + + .. uml:: detailed_design/class_diagram.puml + + .. uml:: detailed_design/verbose_logging_static.puml + + .. uml:: detailed_design/backend_registration_component_diagram.puml + + .. uml:: detailed_design/configuration_static.puml + + .. uml:: detailed_design/configuration_use_cases.puml + + .. uml:: detailed_design/error_domain.puml + + .. uml:: detailed_design/frontend_dependency_graph.puml + + .. uml:: detailed_design/mw_log_default_recorders.puml + + .. uml:: detailed_design/non_verbose_logging_static.puml + + .. uml:: detailed_design/verbose_console_logging_static.puml + + +Dynamic View +------------ + +.. comp_arc_dyn:: mw/log Dynamic View + :id: comp_arc_dyn__mw_logging__dynamic_view + :security: YES + :safety: ASIL_B + :status: valid + :fulfils: comp_req__log__use_log_trace_framework + :belongs_to: comp__mw_logging + + .. uml:: detailed_design/shared_memory_writer_allocandwrite.puml + + .. uml:: detailed_design/verbose_logging_sequence.puml + + .. uml:: detailed_design/wait_free_linear_buffer.puml + + .. uml:: detailed_design/wait_free_alternating_buffers.puml + + .. uml:: detailed_design/backend_registration_sequence_diagram.puml + + .. uml:: detailed_design/configuration_sequence.puml + + .. uml:: detailed_design/dynamic_backend_selection.puml + + .. uml:: detailed_design/rarf_activity_diagram.puml + + .. uml:: detailed_design/slot_drainer_action_diagram_design.puml + + .. uml:: detailed_design/slot_drainer_sequence_design.puml + + .. uml:: detailed_design/verbose_console_logging_sequence.puml diff --git a/docs/components/mw/log/requirements.rst b/docs/components/mw_log/requirements.rst similarity index 100% rename from docs/components/mw/log/requirements.rst rename to docs/components/mw_log/requirements.rst diff --git a/docs/index.rst b/docs/index.rst index 486953ef..d2f15257 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -100,7 +100,7 @@ Components :glob: components/datarouter/index.rst - components/mw/log/index.rst + components/mw_log/index.rst Requirements