# AUTOSAR_SWS_LogAndTrace


<!--more-->

# AUTOSAR_SWS_LogAndTrace

----

## 7 功能规范

本规范定义了日志与追踪模块所定义的C++日志API的使用方法。自适应应用可通过这些函数将日志消息转发至各类接收器，例如网络、串行总线、控制台或文件系统。

本模块提供以下核心功能：
1. 日志框架的初始化方法（见7.3节）
2. 将十进制数值转换为十六进制或二进制数值的工具方法（见7.4节）
3. 日志消息的自动时间戳功能（见7.5节）
4. 日志与追踪的网络带宽限制功能（见7.6节）

自适应应用与功能集群可通过调用`ara::core::Initialize()`或`ara::core::Deinitialize()`，对所有具备直接ARA接口的功能集群（例如日志框架）执行启动（见7.1.1节）与关闭（见7.1.2节）操作。

### 7.1 功能集群生命周期

#### 7.1.1 启动

为完成日志框架的初始化，必须向日志框架提供必填信息。这些信息提取自应用执行清单与AUTOSAR元模型。执行清单中的`Executable.loggingBehavior`参数定义了是否需要初始化日志功能。在使用任何`ara::log` API前，必须通过`ara::core::Initialize`完成日志框架的初始化，未执行该操作将导致未定义行为。

[SWS_LOG_00001] 在日志框架具备处理能力之前（例如尚未与守护进程建立通信）产生的日志消息，必须进入队列缓存。队列大小由`LogAndTraceInstantiation.queueSize`定义。若超出队列容量，最早的条目将被丢弃。（对应需求：RS_LT_00003、RS_LT_00052）

#### 7.1.2 关闭

[SWS_LOG_00122] {草案} 当调用`ara::core::Deinitialize()`时，日志框架必须确保不再建立新的客户端连接。（对应需求：RS_LT_00003）

[SWS_LOG_00123] {草案} 当调用`ara::core::Deinitialize()`时，若已连接客户端，日志框架必须确保缓冲区中所有剩余消息可被客户端采集。（对应需求：RS_LT_00003）

## 7.2 必要参数与初始化

### 用户应用标识规则

为了能够区分系统内（例如单个电子控制单元ECU，乃至整车范围内）不同应用实例的日志，系统内的每个应用进程都必须具备唯一的ID与描述信息。

### 日志上下文规则

为了能够区分同一应用进程内不同逻辑模块的日志，必须为应用进程内的每个上下文分配唯一的ID与描述信息。每个应用进程可配置任意数量的上下文，但至少需包含一个默认上下文。

日志与追踪功能集群的机器级专属配置项集中在`LogAndTraceInstantiation`中定义。使用日志框架的应用进程，必须通过应用执行清单提供以下配置信息：

- 应用ID

- 应用描述

- 默认日志级别（若未在清单中设置，将使用预定义的默认值）

- 日志模式

- 日志文件路径（仅当日志模式配置为文件输出时必填）

使用日志框架的应用进程会为每个上下文创建一个日志实例。上下文在日志实例创建时定义，需提供以下信息：

- 上下文ID

- 上下文描述

- 默认日志级别（若未在清单中设置，将使用预定义的默认值）

### 7.2.1 应用ID

应用ID是用于将生成的日志信息与其所属的用户应用进行关联的标识符。应用ID以字符串形式传入，其长度可能受日志框架的具体实现（即日志后端）限制。为了能够将接收到的日志信息与来源明确对应，建议在单个ECU内为应用分配唯一的应用ID。应用ID无需在跨ECU场景下保持唯一，因为ECU ID会作为区分标识。

系统集成商需承担整体责任，确保每个应用进程实例具备唯一的应用ID。通过在清单中定义该数值，集成商可执行一致性校验。`DltLogChannel`中的`applicationId`用于标识应用实例，并会作为应用ID填入日志与追踪消息中。

注：应用ID是每个应用进程的唯一标识，这意味着若同一个应用进程被多次启动，每个实例都必须拥有专属的ID。

#### [7.2.1.1](7.2.1.1) 应用描述

由于应用ID的长度可能很短，可额外提供描述性文本。该描述以字符串形式传入，其最大长度由具体实现决定。`DltLogChannel`中的`applicationDescription`是可选配置项，用于为应用ID补充描述性文本说明。

### 7.2.2 默认日志级别

日志严重级别代表日志消息的严重程度，具体级别定义见7.3节。`DltLogChannel`中的`logTraceDefaultLogLevel`定义了应用实例的初始日志上报级别。

每条发起的日志消息都需配备对应的严重级别。默认日志严重级别通过应用配置按应用进程维度设置，其作用是作为上报过滤器：只有严重级别高于或等于该阈值的日志消息，才会被日志框架处理，其余消息将被忽略。

默认日志严重级别是指定应用进程的初始配置上报级别，同时支持按上下文维度覆盖该配置。

应用进程全局的日志上报级别必须支持运行时调整，具体实现由底层日志后端决定，例如可通过DLT Viewer等日志客户端进行远程配置。上下文级别的上报级别也遵循该规则。

相较于按上下文设置默认日志严重级别，采用应用全局初始默认日志严重级别的设计依据如下：

- 简化API使用。否则用户必须在使用API前，为每个日志分组单独定义上下文默认日志严重级别。

- 支持在运行时对日志消息进行上下文维度的隔离。

### 7.2.3 日志模式

根据日志框架的具体实现，传入的日志信息可通过不同方式处理，日志消息的目标接收器可以是控制台输出、文件系统中的文件，或是通信总线。系统集成商负责在机器清单中配置该信息。本规范同时提供了直接API，支持在开发阶段动态修改该数值。

在AUTOSAR元模型中，`logTraceLogMode`与本文描述的日志模式为同一概念，更多信息参见参考文献[3]。`LogAndTraceInstantiation`下`DltLogChannel`中的`logTraceLogMode`，定义了日志消息的转发目标。

图7.1 日志模式

（图示说明：执行清单配置日志模式，机器清单配置网络、日志存储等参数，日志与追踪功能集群的LT后端将日志分发至开发用文件、控制台、持久化日志文件、网络端LT日志查看器等目标）

如图所示，当日志模式配置为使用日志后端时，该后端的相关配置将集中在机器清单中统一管理。例如，可将日志后端配置为本地存储日志信息，该配置会保存在机器专属清单中。此外，日志消息的以太网输出通道，通过`PlatformModuleEthernetEndpointConfiguration`进行配置，该配置项由`LogAndTraceInstantiation`通过`DltLogChannel`以`endpointConfiguration`角色进行聚合。

#### [7.2.3.1](7.2.3.1) 日志文件路径

当日志模式设置为文件输出时，必须提供目标目录路径。`DltLogChannel`中的`logTraceFilePath`定义了日志信息写入的目标文件。该配置项仅用于开发、集成与原型验证场景，不适用于量产环境。

### 7.2.4 上下文ID

上下文ID是用于在应用进程范围内对日志信息进行逻辑分组的标识符。上下文ID以字符串形式传入，其长度可能受日志后端的具体实现限制。上下文ID在单个应用进程范围内必须唯一，由开发人员负责分配，该信息无需在清单中建模。上下文ID无需在多个不同应用进程间保持唯一，因为应用ID会作为区分标识。

注：需特别注意库组件的场景。库组件由应用进程调用，因此运行在应用进程的上下文范围内，从库中执行的日志操作，最终会归属到父应用进程的日志范围内。为了将库的内部日志与应用进程日志、同进程内其他库的日志区分开，每个库可能需要在系统范围内预留专属的上下文ID——至少当该库会被多个应用进程使用时，必须遵循该要求。

### 7.2.5 上下文描述

由于上下文ID的长度可能很短，必须额外提供描述性文本。该上下文描述以字符串形式传入，其最大长度由具体实现决定。

### 7.2.6 日志框架的初始化

应用ID与描述信息用于标识日志信息，并将其与对应的进程进行关联；日志模式与接收器信息则定义了日志信息的路由目标，可选目标包括控制台、文件系统或通信总线。

从应用进程的视角来看，当应用进程注册一个日志上下文时，日志框架完成初始化并创建对应的日志器实例，这些上下文用于对日志信息进行逻辑聚类。

[SWS_LOG_00002] {草案} 当日志框架或底层系统内部发生任何错误时，设计原则为不干扰应用进程，静默丢弃相关函数调用。为此，相关接口既不定义返回值，也不会抛出异常。（对应需求：RS_LT_00003）

[SWS_LOG_00004] {草案} 应用执行清单必须为日志框架的初始化提供以下信息：

- 唯一的应用ID

- 应用描述

- 默认日志严重级别

- 日志模式

- 目录路径（仅当日志模式设置为`LogMode::kFile`时必填）

（对应需求：RS_LT_00003、RS_LT_00047、RS_LT_00048）

注：根据日志框架的具体实现，可能并非所有特性都被支持，因此部分配置属性可能不会被使用。

[SWS_LOG_00005] `CreateLogger()`函数必须在日志框架内部创建一个日志器上下文实例，并将其以引用形式返回给调用的应用。在处理任何日志消息前，必须至少存在一个可用的日志器上下文。（对应需求：RS_LT_00003、RS_LT_00050）

注：上下文与日志框架之间的强所属关系，可确保相关资源的正确管理。该设计的核心逻辑是：一旦上下文向日志后端完成注册，其生命周期必须得到保障，直至应用进程终止。

[SWS_LOG_00006] 调用`CreateLogger()`时，必须提供以下参数：

- 上下文ID

- 上下文描述

- 日志严重级别（可选参数，默认值为`LogLevel::kWarn`）

（对应需求：RS_LT_00003、RS_LT_00050）

[SWS_LOG_00007] 应用进程必须可通过`IsLogEnabled()`函数，检查指定的日志严重级别是否处于启用配置。该机制可节省日志信息准备过程中消耗的CPU与内存资源，因为未启用级别的日志信息后续会被日志框架直接过滤。（对应需求：RS_LT_00003、RS_LT_00045）

## 7.3 日志消息

日志消息通常可输出至不同的目标端，日志与追踪功能集群支持以下日志输出目标：

- 控制台

- 本地文件系统中的文件

- 网络

本节的大部分内容均基于消息输出至网络的场景展开，因为该场景需要额外考虑网络负载最小化的需求。

日志与追踪功能集群提供两类核心的日志消息类型：**建模消息**与**非建模消息**。两类消息均支持向单条日志消息中添加一个或多个参数，不包含任何参数的日志消息无实际意义，会被直接丢弃。

非建模消息是传统的日志消息组织方式：消息的所有参数都会被添加到内部消息缓冲区，最终被序列化后输出至控制台/文件，或通过网络发送，消息的所有内容都会通过网络传输。在DLT协议中，这类消息被称为“详细模式（verbose）”消息。

建模消息的设计目标是降低网络流量，其核心是不在网络中传输消息中固定不变的静态内容。顾名思义，这些静态内容会被预先定义在应用的ARXML模型中。在DLT协议中，这类消息被称为“非详细模式（non-verbose）”消息。日志查看器应用可通过结合模型中的静态内容与接收到的消息中的动态内容，还原并展示完整的日志消息。

非建模消息主要用于开发阶段，因为此时建模消息所需的信息可能尚未完备。但非建模消息会产生较高的网络负载，因此在量产系统中，建模消息通常是更优选择。

`ara::log`功能集群支持在单个应用中同时定义和使用建模消息与非建模消息。

### 7.3.1 非建模消息

`ara::log`功能集群定义了一套基于建造者模式设计的API，用于构建非建模消息。`ara::log::Logger::WithLevel`成员函数用于创建`ara::log::LogStream`对象，随后可向该对象中填充消息内容（即消息参数）。除了`ara::log::Logger::WithLevel`，规范还为每个支持的日志级别，分别提供了独立的成员函数用于创建`ara::log::LogStream`对象。

可通过调用对应参数类型的`operator<<`重载函数，向详细模式消息中添加参数，示例如下：

```C++

logger.WithLevel(LogLevel::kInfo) << "text" << 4.2;
```

`ara::log`功能集群为所有C++算术类型、布尔类型、字符串类型，以及多种`ara::core`类型，定义了对应的`operator<<`重载函数。应用自定义的数据类型，也可通过实现适配的`operator<<`重载函数，支持日志输出。

由于应用模型支持为参数添加属性注解，`ara::log`的非建模消息API也支持该能力。特定类型的参数可被添加“名称”属性，部分类型还可额外添加“单位”属性，示例如下：

```C++

logger.WithLevel(LogLevel::kInfo)
<< Arg("text", "identifier")
<< Arg(4.2, "velocity", "m/s");
```

上述示例中，字符串参数“text”被添加了名为“identifier”的“名称”属性；双精度浮点参数4.2被添加了名为“velocity”的“名称”属性，以及单位“m/s”。仅`ara::log` API支持的部分内置类型可设置这些属性，具体包括：所有算术类型、布尔类型、字符串类型，以及原始数据块。

非建模消息还可包含日志调用点在源代码中的位置信息。可通过调用`ara::log::LogStream::WithLocation`成员函数，传入调用点的文件名与行号实现该能力，这些信息通常来自编译器预定义的`__FILE__`与`__LINE__`宏，示例如下：

```C++

logger
.WithLevel(LogLevel::kInfo)
.WithLocation(__FILE__, __LINE__) << ...;
```

该能力最便捷的实现方式是基于`ara::log`封装宏接口，但自适应平台中暂未定义相关标准宏。

### 7.3.2 建模消息

#### [7.3.2.1](7.3.2.1) API设计原理

`ara::log`功能集群定义了一个统一的成员函数`ara::log::Logger::Log`，用于发送建模消息。与非建模消息API不同，该函数是单调用接口，即通过一次函数调用，将所有参数传递给日志器实例，并完成消息生成与发送所需的全部操作。

该设计的优势在于：对于最终不会被输出的建模消息（因消息的日志级别未达到配置的阈值），其运行时开销可被降至极低。完成参数传递与函数调用后，仅需通过一条if语句检查日志级别阈值，若未达到阈值则直接返回。而非建模消息API即使最终会丢弃消息，也需要执行多次函数调用来构建消息对象，产生额外开销。

#### [7.3.2.2](7.3.2.2) 日志消息模型

所有建模消息均以`DltMessage`的形式定义，`DltMessage`由系统引用的`DltMessageCollectionSet`进行聚合。每个`DltMessage`包含：在单个ECU内必须唯一的`messageId`、标识日志级别的`messageTypeInfo`，以及可选的`messageSourceFile`（消息源文件）与`messageLineNumber`（消息行号）。`DltMessage`聚合了有序的`DltArgument`列表，而`DltArgument`会以`networkRepresentation`角色关联一个`SwDataDefProps`对象。日志消息参数的名称取自`DltArgument`的`shortName`，参数的类型与单位则取自`SwDataDefProps`。

**设计阶段**：`DltMessage`会被分配至`DltLogChannelDesign`，后者通过`DltLogChannelDesignToProcessDesignMapping`映射到`ProcessDesign`，最终关联到可执行文件`Executable`，完成与应用的绑定。

**部署阶段**：`DltLogChannel`会引用设计阶段的`DltLogChannelDesign`，并继承设计阶段定义的`DltMessage`。`DltLogChannel`通过`DltLogChannelToProcessMapping`引用可执行文件对应的`Process`，完成与应用的映射。所有`DltLogChannel`均由ECU的`LogAndTraceInstantiation`聚合，该对象包含`dltEcuId`、`queueSize`，以及标识是否启用会话ID的`sessionIdSupport`信息。

`DltLogChannel`包含`applicationId`、`contextId`，以及对应的`applicationDescription`与`contextDescription`；它可关联`ServiceInstanceToPortPrototypeMapping`，此时来自该端口的日志消息将使用该`DltLogChannel`的`contextId`。

`DltLogChannel`还包含`sessionId`，以及`nonVerboseMode`配置项（用于标识是否将建模消息以非建模消息的详细模式发送）。此外，`DltLogChannel`还包含：设置日志消息初始阈值的`logTraceDefaultLogLevel`、配置日志消息目标的`logTraceLogMode`，以及文件输出模式下的`logTraceFilePath`。

#### [7.3.2.3](7.3.2.3) 使用方法

该C++ API的使用，依赖配套工具的支持：工具会扫描源代码中的建模日志消息调用点，并按需生成带唯一ID的对应符号。

框架必须扫描所有源代码，查找`ara::log::Logger::Log`成员函数的调用点，并生成与该函数调用第一个入参匹配的符号。

例如，若清单的ARXML描述包含以下内容：

```XML

<DLT-MESSAGE-COLLECTION-SET>
  <DLT-MESSAGES>
    <SHORT-NAME>DltMessages</SHORT-NAME>
    <DLT-MESSAGE>
      <SHORT-NAME>SpeedMsg</SHORT-NAME>
    </DLT-MESSAGE>
  </DLT-MESSAGES>
</DLT-MESSAGE-COLLECTION-SET>
```

且源代码中包含以下代码：

```C++

Logger& logger = ...;
logger.Log(SpeedMsg, 4.2);
```

则框架会定义一个名为`SpeedMsg`的全局`constexpr`变量，其类型由具体实现定义。该变量包含消息的建模相关信息（如参数类型、日志级别），使`ara::log`的实现能够校验传入`ara::log::Logger::Log`的参数数量与类型，是否与对应消息的模型匹配。

消息变量的定义会通过`ara/log/logger.h`头文件对外暴露。

##### LogStream对象的变量存储方式

日志API还支持另一种使用方式：将`ara::log::LogStream`对象存储为本地命名变量。与临时对象的区别在于，该对象不会在语句结束时离开作用域，只要变量存在，对象就保持有效且可复用，因此可通过多行代码向其填充数据。

若要让日志框架处理消息缓冲区的内容，必须调用`ara::log::LogStream::Flush`方法；否则缓冲区内容会在对象销毁时（即变量在函数块结束离开作用域时）才会被处理。

**性能说明**：

由于`ara::log::LogStream`对象不再按单条消息创建，而是可被多条消息复用，因此对象创建的开销仅需按日志级别支付一次。该方式对实际性能的影响程度，取决于日志框架的具体实现。而该使用方式的核心设计目标，是提供多行代码的建造者模式能力。

注：强烈建议不要在多线程应用中持有全局的`ara::log::LogStream`对象，因为这种场景下，日志API将无法再保障并发访问的安全性。

**使用示例**：

```C++

// 基础用法
Logger& ctx0 = CreateLogger("CTX0", "Context Description CTX0");
ctx0.LogInfo() << "Some log information" << 123;

// 本地存储LogStream对象的用法
// 传入的参数会被缓存，直至调用Flush()或对象离开创建时的作用域才会被处理
Logger& ctx1 = CreateLogger("CTX1", "Context Description CTX1");
LogStream localLogInfo = ctx1.LogInfo();
localLogInfo << "Some log information" << 123;
localLogInfo << "Some other information";
localLogInfo.Flush(); // 触发日志处理
localLogInfo << "a new message..." << 456; // 复用对象写入新消息
```

**异常安全性**：所有`Log*()`接口均设计为保证不抛出异常，该原则适用于整个日志API。

**换行处理**：为提升使用便捷性，日志框架会自动为日志消息追加换行符。

**多负载参数处理**：当单条消息包含多个负载参数时，控制台输出场景下，各负载参数之间会以单个空格分隔。

[SWS_LOG_00008] {草案} 要发起致命（Fatal）级别的日志消息，必须调用`ara::log::Logger::LogFatal` API。该API返回一个`ara::log::LogStream`对象，需通过流插入运算符`<<`向其传入参数。（对应需求：RS_LT_00003、RS_LT_00049）

[SWS_LOG_00009] {草案} 要发起错误（Error）级别的日志消息，必须调用`ara::log::Logger::LogError` API。该API返回一个`ara::log::LogStream`对象，需通过流插入运算符`<<`向其传入参数。（对应需求：RS_LT_00003、RS_LT_00049）

[SWS_LOG_00010] {草案} 要发起警告（Warning）级别的日志消息，必须调用`ara::log::Logger::LogWarn` API。该API返回一个`ara::log::LogStream`对象，需通过流插入运算符`<<`向其传入参数。（对应需求：RS_LT_00003、RS_LT_00049）

[SWS_LOG_00011] {草案} 要发起信息（Info）级别的日志消息，必须调用`ara::log::Logger::LogInfo` API。该API返回一个`ara::log::LogStream`对象，需通过流插入运算符`<<`向其传入参数。（对应需求：RS_LT_00003、RS_LT_00049）

[SWS_LOG_00012] {草案} 要发起调试（Debug）级别的日志消息，必须调用`ara::log::Logger::LogDebug` API。该API返回一个`ara::log::LogStream`对象，需通过流插入运算符`<<`向其传入参数。（对应需求：RS_LT_00003、RS_LT_00049）

[SWS_LOG_00013] {草案} 要发起详细（Verbose）级别的日志消息，必须调用`ara::log::Logger::LogVerbose` API。该API返回一个`ara::log::LogStream`对象，需通过流插入运算符`<<`向其传入参数。（对应需求：RS_LT_00003、RS_LT_00049）

[SWS_LOG_00130] {草案} 要写入日志级别由程序运行时动态决定的日志消息，必须调用`Logger::WithLevel(LogLevel logLevel)` API。（对应需求：RS_LT_00003、RS_LT_00049）

## 7.4 转换函数

在部分场景下，需要将整型数值以十六进制或二进制格式而非十进制格式展示。

为此，规范定义了以下函数，用于将输入的十进制数值转换为十六进制或二进制格式。

[SWS_LOG_00120] {草案} 必须提供专用转换函数，用于将正十进制数值转换为十六进制或二进制格式的字符串。（对应需求：RS_LT_00003、RS_LT_00046）

[SWS_LOG_00015] {草案} 必须提供专用转换函数，用于将十进制数值转换为十六进制或二进制格式的字符串；对于负数，转换结果的最高位必须设置为'1'。（对应需求：RS_LT_00003、RS_LT_00046）

[SWS_LOG_00016] {草案} `HexFormat()`函数必须具备将十进制整型数值转换为十六进制格式字符串的能力。（对应需求：RS_LT_00003、RS_LT_00046）

[SWS_LOG_00017] {草案} `BinFormat()`函数必须具备将十进制整型数值转换为二进制格式字符串的能力。（对应需求：RS_LT_00003、RS_LT_00046）

## 7.5 日志与追踪时间戳

日志与追踪信息通过总线无关的LT协议进行传输。

只要消息以扩展头部格式发送，LT协议就支持在每条发送的消息中包含时间戳（更多信息参见参考文献[5]）。

同步时间基由时间同步功能集群提供，自适应应用通过`now()`方法从时间同步模块获取当前时间（更多信息参见参考文献[6]）。

根据需求[TPS_MANI_03162] ，参考时间基来源于机器清单中的`timeBaseResource`。

[SWS_LOG_00082] 日志与追踪模块必须能够访问同步时间基，必须通过`LogAndTraceInstantiation`中的`timeBaseResource`属性标识所使用的时间基。（对应需求：RS_LT_00003、RS_LT_00017）

[SWS_LOG_00083] 若清单配置中，日志与追踪模块未引用任何时间基资源，则不得传输任何时间戳信息。（对应需求：RS_LT_00003、RS_LT_00017）

[SWS_LOG_00091] {草案} 当调用`CreateLogger()`函数时，日志与追踪模块必须发送一条消息：若使用的是本地时间基，消息内容为“local time base used”；若使用的是全局同步时间基，消息内容为“global time base used”。（对应需求：RS_LT_00003、RS_LT_00017）

## 7.6 日志与追踪数据丢失防护

[SWS_LOG_00095] {草案} 当日志与追踪模块同时接收到多个自适应应用产生的高负载追踪信息时，必须在内部对该数据进行缓存，防止数据在持续传输过程中丢失。（对应需求：RS_LT_00003、RS_LT_00030）

----

