From aa6884dc420fecb2cf5391ab0749102f76a8eaf0 Mon Sep 17 00:00:00 2001 From: rahulsasingh Date: Wed, 15 Jul 2026 12:48:11 +0000 Subject: [PATCH] docs: consolidate mw/log design diagrams into docs/components/ --- .../datarouter_backend_datarouterbackend.puml | 0 .../datarouter_class_diagram.puml | 0 ...ssage_client_impl_connecttodatarouter.puml | 0 .../datarouter/detailed_design/index.rst | 59 +++++ .../inter_process_communication.puml | 0 .../mw_log_shared_memory_reader.puml | 0 .../shared_memory_reader_read.puml | 0 docs/components/datarouter/index.rst | 1 + ...ackend_registration_component_diagram.puml | 119 +++++++++ ...backend_registration_sequence_diagram.puml | 123 +++++++++ .../log/detailed_design}/class_diagram.puml | 0 .../configuration_sequence.puml | 108 ++++++++ .../detailed_design/configuration_static.puml | 249 ++++++++++++++++++ .../configuration_use_cases.puml | 26 ++ .../dynamic_backend_selection.puml | 131 +++++++++ .../mw/log/detailed_design/error_domain.puml | 13 + .../frontend_dependency_graph.puml | 56 ++++ .../mw/log/detailed_design/index.rst | 97 +++++++ .../mw_log_datarouter_recorder.puml | 0 .../mw_log_default_recorders.puml | 196 ++++++++++++++ .../detailed_design}/mw_log_file_backend.puml | 0 .../detailed_design}/mw_log_recorders.puml | 0 .../non_verbose_logging_static.puml | 103 ++++++++ .../rarf_activity_diagram.puml | 87 ++++++ .../shared_memory_writer_allocandwrite.puml | 0 .../slot_drainer_action_diagram_design.puml | 44 ++++ .../slot_drainer_sequence_design.puml | 71 +++++ .../verbose_console_logging_sequence.puml | 108 ++++++++ .../verbose_console_logging_static.puml | 214 +++++++++++++++ .../verbose_logging_sequence.puml | 0 .../verbose_logging_static.puml | 0 .../wait_free_alternating_buffers.puml | 0 .../wait_free_linear_buffer.puml | 0 .../write_factory_design.puml | 0 docs/components/mw/log/index.rst | 1 + 35 files changed, 1806 insertions(+) rename {score/mw/log/design/backend/datarouter_backend => docs/components/datarouter/detailed_design}/datarouter_backend_datarouterbackend.puml (100%) rename {score/mw/log/design/backend/datarouter_backend => docs/components/datarouter/detailed_design}/datarouter_class_diagram.puml (100%) rename {score/mw/log/design/backend/datarouter_backend => docs/components/datarouter/detailed_design}/datarouter_message_client_impl_connecttodatarouter.puml (100%) create mode 100644 docs/components/datarouter/detailed_design/index.rst rename {score/mw/log/design/backend/datarouter_backend => docs/components/datarouter/detailed_design}/inter_process_communication.puml (100%) rename {score/mw/log/design/backend/datarouter_backend => docs/components/datarouter/detailed_design}/mw_log_shared_memory_reader.puml (100%) rename {score/mw/log/design/backend/datarouter_backend => docs/components/datarouter/detailed_design}/shared_memory_reader_read.puml (100%) create mode 100644 docs/components/mw/log/detailed_design/backend_registration_component_diagram.puml create mode 100644 docs/components/mw/log/detailed_design/backend_registration_sequence_diagram.puml rename {score/mw/log/detail/wait_free_producer_queue/design => docs/components/mw/log/detailed_design}/class_diagram.puml (100%) create mode 100644 docs/components/mw/log/detailed_design/configuration_sequence.puml create mode 100644 docs/components/mw/log/detailed_design/configuration_static.puml create mode 100644 docs/components/mw/log/detailed_design/configuration_use_cases.puml create mode 100644 docs/components/mw/log/detailed_design/dynamic_backend_selection.puml create mode 100644 docs/components/mw/log/detailed_design/error_domain.puml create mode 100644 docs/components/mw/log/detailed_design/frontend_dependency_graph.puml create mode 100644 docs/components/mw/log/detailed_design/index.rst rename {score/mw/log/design/backend/datarouter_backend => docs/components/mw/log/detailed_design}/mw_log_datarouter_recorder.puml (100%) create mode 100644 docs/components/mw/log/detailed_design/mw_log_default_recorders.puml rename {score/mw/log/design/backend => docs/components/mw/log/detailed_design}/mw_log_file_backend.puml (100%) rename {score/mw/log/design/backend => docs/components/mw/log/detailed_design}/mw_log_recorders.puml (100%) create mode 100644 docs/components/mw/log/detailed_design/non_verbose_logging_static.puml create mode 100644 docs/components/mw/log/detailed_design/rarf_activity_diagram.puml rename {score/mw/log/design/backend/datarouter_backend => docs/components/mw/log/detailed_design}/shared_memory_writer_allocandwrite.puml (100%) create mode 100644 docs/components/mw/log/detailed_design/slot_drainer_action_diagram_design.puml create mode 100644 docs/components/mw/log/detailed_design/slot_drainer_sequence_design.puml create mode 100644 docs/components/mw/log/detailed_design/verbose_console_logging_sequence.puml create mode 100644 docs/components/mw/log/detailed_design/verbose_console_logging_static.puml rename {score/mw/log/design/backend/datarouter_backend => docs/components/mw/log/detailed_design}/verbose_logging_sequence.puml (100%) rename {score/mw/log/design/backend/datarouter_backend => docs/components/mw/log/detailed_design}/verbose_logging_static.puml (100%) rename {score/mw/log/detail/wait_free_producer_queue/design => docs/components/mw/log/detailed_design}/wait_free_alternating_buffers.puml (100%) rename {score/mw/log/detail/wait_free_producer_queue/design => docs/components/mw/log/detailed_design}/wait_free_linear_buffer.puml (100%) rename {score/mw/log/design/backend/datarouter_backend => docs/components/mw/log/detailed_design}/write_factory_design.puml (100%) 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/docs/components/datarouter/detailed_design/index.rst b/docs/components/datarouter/detailed_design/index.rst new file mode 100644 index 00000000..1648d032 --- /dev/null +++ b/docs/components/datarouter/detailed_design/index.rst @@ -0,0 +1,59 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +Detailed Design +############### + +.. document:: Data Router Detailed Design + :id: doc__data_router_detailed_design + :status: valid + :safety: ASIL_B + :security: YES + :realizes: wp__sw_implementation + + +Static View +----------- + +.. comp_arc_sta:: Data Router Static View + :id: comp_arc_sta__data_router__static_view + :security: YES + :safety: ASIL_B + :status: valid + :fulfils: comp_req__data_router__dlt_server + :belongs_to: comp__data_router + + .. uml:: datarouter_class_diagram.puml + + .. uml:: inter_process_communication.puml + + .. uml:: mw_log_shared_memory_reader.puml + + +Dynamic View +------------ + +.. comp_arc_dyn:: Data Router Dynamic View + :id: comp_arc_dyn__data_router__dynamic_view + :security: YES + :safety: ASIL_B + :status: valid + :fulfils: comp_req__data_router__dlt_server + :belongs_to: comp__data_router + + .. uml:: datarouter_backend_datarouterbackend.puml + + .. uml:: shared_memory_reader_read.puml + + .. uml:: 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..d935eb19 100644 --- a/docs/components/datarouter/index.rst +++ b/docs/components/datarouter/index.rst @@ -34,3 +34,4 @@ This section is reserved for data router-specific documentation. :glob: * + detailed_design/index 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..3bc1766a --- /dev/null +++ b/docs/components/mw/log/detailed_design/configuration_static.puml @@ -0,0 +1,249 @@ +@startuml configuration_static + +interface "bmw: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/docs/components/mw/log/detailed_design/index.rst b/docs/components/mw/log/detailed_design/index.rst new file mode 100644 index 00000000..ccc367bb --- /dev/null +++ b/docs/components/mw/log/detailed_design/index.rst @@ -0,0 +1,97 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +Detailed Design +############### + +.. 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:: mw_log_recorders.puml + + .. uml:: mw_log_file_backend.puml + + .. uml:: mw_log_datarouter_recorder.puml + + .. uml:: write_factory_design.puml + + .. uml:: class_diagram.puml + + .. uml:: verbose_logging_static.puml + + .. uml:: backend_registration_component_diagram.puml + + .. uml:: configuration_static.puml + + .. uml:: configuration_use_cases.puml + + .. uml:: error_domain.puml + + .. uml:: frontend_dependency_graph.puml + + .. uml:: mw_log_default_recorders.puml + + .. uml:: non_verbose_logging_static.puml + + .. uml:: 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:: shared_memory_writer_allocandwrite.puml + + .. uml:: verbose_logging_sequence.puml + + .. uml:: wait_free_linear_buffer.puml + + .. uml:: wait_free_alternating_buffers.puml + + .. uml:: backend_registration_sequence_diagram.puml + + .. uml:: configuration_sequence.puml + + .. uml:: dynamic_backend_selection.puml + + .. uml:: rarf_activity_diagram.puml + + .. uml:: slot_drainer_action_diagram_design.puml + + .. uml:: slot_drainer_sequence_design.puml + + .. uml:: verbose_console_logging_sequence.puml 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 index 2742b25c..022245a0 100644 --- a/docs/components/mw/log/index.rst +++ b/docs/components/mw/log/index.rst @@ -34,3 +34,4 @@ This section is reserved for middleware-specific documentation. :glob: * + detailed_design/index