已合并
fix(logging): 统一日志文件名校验规范 #2124
fix(logging): 统一日志文件名校验规范 #2124
已合并
guoyang创建于 4 天前
26 个文件变更+275-74
M.repo_context/modules/infra/logging/design.md+6-3
@@ -424,7 +424,7 @@ Failure-sensitive steps:
424| --- | --- | --- | --- | --- | --- | --- |424| --- | --- | --- | --- | --- | --- | --- |
425| `spdlog` via `ds_spdlog` | ordinary file and stderr logging, async logger support, rotating sink support | mature logging backend with async and sink abstractions while preserving a repository-owned macro surface | keep a fully custom logger, or expose backend implementation details directly | `MIT` | pinned to `spdlog-1.12.0` in `bazel/ds_deps.bzl`; upgrade through wrapper and patch review | repository applies namespace and rotating-sink patches under `third_party/patches/spdlog/*`; retention remains externally managed by `LogManager` |425| `spdlog` via `ds_spdlog` | ordinary file and stderr logging, async logger support, rotating sink support | mature logging backend with async and sink abstractions while preserving a repository-owned macro surface | keep a fully custom logger, or expose backend implementation details directly | `MIT` | pinned to `spdlog-1.12.0` in `bazel/ds_deps.bzl`; upgrade through wrapper and patch review | repository applies namespace and rotating-sink patches under `third_party/patches/spdlog/*`; retention remains externally managed by `LogManager` |
426| `abseil-cpp` | failure-signal handling and symbolization for crash-path output | reduces custom signal-handler code and keeps crash output integrated with stack symbolization | custom signal handling only | `Apache-2.0` | pinned to `abseil-cpp-20250127.1` in `bazel/ds_deps.bzl`; upgrade with crash-path regression checks | signal-handler behavior is high risk; verify `container.log` output and handler install paths after upgrades |426| `abseil-cpp` | failure-signal handling and symbolization for crash-path output | reduces custom signal-handler code and keeps crash output integrated with stack symbolization | custom signal handling only | `Apache-2.0` | pinned to `abseil-cpp-20250127.1` in `bazel/ds_deps.bzl`; upgrade with crash-path regression checks | signal-handler behavior is high risk; verify `container.log` output and handler install paths after upgrades |
427-| `RE2` | log-name validation in `Logging::ValidateLogName(...)` | safe and predictable regex engine for user-controlled environment input | manual validation logic | `BSD-3-Clause` | pinned to `re2-2024-07-02` in `bazel/ds_deps.bzl`; upgrade with validation tests | repository carries an absl-related patch for Bazel integration |427+| `RE2` | log-name validation in `Validator::ValidateLogName(...)` | safe and predictable regex engine for user-controlled configuration and environment input | manual validation logic | `BSD-3-Clause` | pinned to `re2-2024-07-02` in `bazel/ds_deps.bzl`; upgrade with validation tests | repository carries an absl-related patch for Bazel integration |
428 428 
429TransferEngine reuses the same patched `libds-spdlog.so` ABI but constructs its file and console loggers directly without registering them globally. The private facade owns severity files, stderr thresholds, verbosity controls, flush intervals, rotation size, and default `/tmp`-style directory selection under the `TRANSFER_ENGINE_*` namespace. This boundary is required because `_transfer_engine.so` can coexist with other native Python modules that bring independent logging runtimes.429TransferEngine reuses the same patched `libds-spdlog.so` ABI but constructs its file and console loggers directly without registering them globally. The private facade owns severity files, stderr thresholds, verbosity controls, flush intervals, rotation size, and default `/tmp`-style directory selection under the `TRANSFER_ENGINE_*` namespace. This boundary is required because `_transfer_engine.so` can coexist with other native Python modules that bring independent logging runtimes.
430 430 
@@ -442,6 +442,8 @@ TransferEngine reuses the same patched `libds-spdlog.so` ABI but constructs its
442| `DATASYSTEM_CLIENT_LOG_DIR` | environment | client-only override | changes client log destination directory | can redirect output unexpectedly or fail on bad permissions |442| `DATASYSTEM_CLIENT_LOG_DIR` | environment | client-only override | changes client log destination directory | can redirect output unexpectedly or fail on bad permissions |
443| `DATASYSTEM_CLIENT_LOG_NAME` | environment | client-only override validated by RE2 | changes client ordinary log base name | unsafe naming would break file handling if validation changed |443| `DATASYSTEM_CLIENT_LOG_NAME` | environment | client-only override validated by RE2 | changes client ordinary log base name | unsafe naming would break file handling if validation changed |
444| `DATASYSTEM_CLIENT_ACCESS_LOG_NAME` | environment | client-only override validated by RE2 | changes client access-log base name | can break downstream file discovery assumptions |444| `DATASYSTEM_CLIENT_ACCESS_LOG_NAME` | environment | client-only override validated by RE2 | changes client access-log base name | can break downstream file discovery assumptions |
445+| `KVClientConfig::LogName` | client initialization config | explicit value takes priority over `DATASYSTEM_CLIENT_LOG_NAME`; explicit empty selects the default name | changes client ordinary log base name | config presence must remain distinct from an empty value |
446+| `KVClientConfig::AccessLogName` | client initialization config | explicit value takes priority over `DATASYSTEM_CLIENT_ACCESS_LOG_NAME`; explicit empty selects the default name | changes client access-log base name | config presence must remain distinct from an empty value |
445| `DATASYSTEM_LOG_MONITOR_ENABLE` | environment | client-only override | enables or disables client monitor logging | may create client and server observability mismatch |447| `DATASYSTEM_LOG_MONITOR_ENABLE` | environment | client-only override | enables or disables client monitor logging | may create client and server observability mismatch |
446| `log_only_write_info_file` / `DATASYSTEM_LOG_ONLY_WRITE_INFO_FILE` | gflag or client-only env override | default `true` | INFO files always receive all severities; `true` suppresses additional WARNING/ERROR files, while `false` restores dedicated WARNING/ERROR files and severity fanout | changes file discovery assumptions and disk usage |448| `log_only_write_info_file` / `DATASYSTEM_LOG_ONLY_WRITE_INFO_FILE` | gflag or client-only env override | default `true` | INFO files always receive all severities; `true` suppresses additional WARNING/ERROR files, while `false` restores dedicated WARNING/ERROR files and severity fanout | changes file discovery assumptions and disk usage |
447| `TRANSFER_ENGINE_LOG_DIR` | environment | unset; then `TEST_TMPDIR`, `TMPDIR`, `TMP`, `/tmp`, `.` | changes TransferEngine severity-log destination | invalid or unwritable paths fall through to stderr fallback and reduce persisted diagnostics |449| `TRANSFER_ENGINE_LOG_DIR` | environment | unset; then `TEST_TMPDIR`, `TMPDIR`, `TMP`, `/tmp`, `.` | changes TransferEngine severity-log destination | invalid or unwritable paths fall through to stderr fallback and reduce persisted diagnostics |
@@ -546,7 +548,7 @@ TransferEngine reuses the same patched `libds-spdlog.so` ABI but constructs its
546 - most logging behavior remains configurable through gflags and client environment overrides;548 - most logging behavior remains configurable through gflags and client environment overrides;
547 - `log_monitor` is the main kill switch for structured monitor logging.549 - `log_monitor` is the main kill switch for structured monitor logging.
548- Attack or fault scenarios the module should continue to tolerate:550- Attack or fault scenarios the module should continue to tolerate:
549- - malformed client-provided log names rejected by `ValidateLogName(...)`;551+ - malformed non-empty client-provided log names rejected by `Validator::ValidateLogName(...)`;
550 - async trace discontinuity at one callsite should not corrupt unrelated threads;552 - async trace discontinuity at one callsite should not corrupt unrelated threads;
551 - crash handler should still emit direct file output when normal logger sinks are unhealthy.553 - crash handler should still emit direct file output when normal logger sinks are unhealthy.
552 554 
@@ -556,7 +558,8 @@ TransferEngine reuses the same patched `libds-spdlog.so` ABI but constructs its
556 - the module is infrastructure only and must not become an authority for permission decisions.558 - the module is infrastructure only and must not become an authority for permission decisions.
557- Input validation requirements:559- Input validation requirements:
558 - `Context::SetTraceId(...)` validates trace-prefix characters and length;560 - `Context::SetTraceId(...)` validates trace-prefix characters and length;
559- - `Logging::ValidateLogName(...)` only accepts `[a-zA-Z0-9_]*` for client-supplied log names.561+ - `Validator::ValidateLogName(...)` only accepts `[a-zA-Z0-9_]+` for client-supplied log names; optional configuration
562+ adapters handle an explicit empty value before invoking the common validator.
560- Data sensitivity or privacy requirements:563- Data sensitivity or privacy requirements:
561 - request and response metadata logged by `AccessRecorder` may contain sensitive business context, so any schema expansion requires review for secrets, personal data, or credentials.564 - request and response metadata logged by `AccessRecorder` may contain sensitive business context, so any schema expansion requires review for secrets, personal data, or credentials.
562- Secrets, credentials, or key-management requirements:565- Secrets, credentials, or key-management requirements:
M.repo_context/modules/infra/logging/log-lifecycle-and-rotation.md+3-0
@@ -52,6 +52,9 @@
52 - `Logging::Start()` initializes client-specific config from environment when the explicit process role is52 - `Logging::Start()` initializes client-specific config from environment when the explicit process role is
53 `LogProcessRole::CLIENT`; worker and coordinator callers pass `LogProcessRole::WORKER` and53 `LogProcessRole::CLIENT`; worker and coordinator callers pass `LogProcessRole::WORKER` and
54 `LogProcessRole::COORDINATOR` respectively.54 `LogProcessRole::COORDINATOR` respectively.
55+ - client log-name configuration keeps explicit `KVClientConfig` presence separate from the resolved `log_filename`;
56+ an explicit empty `LogName` or `AccessLogName` suppresses the corresponding environment override and selects the
57+ default client log base name.
55 - it sets `log_filename`, initializes the provider, starts `LogManager`, initializes `AccessRecorderManager`, and starts58 - it sets `log_filename`, initializes the provider, starts `LogManager`, initializes `AccessRecorderManager`, and starts
56 the operation logger with an explicit `client`, `worker`, or `coordinator` role;59 the operation logger with an explicit `client`, `worker`, or `coordinator` role;
57 - when `log_monitor` is enabled, `AccessRecorderManager` initializes exporters according to its construction-time60 - when `log_monitor` is enabled, `AccessRecorderManager` initializes exporters according to its construction-time
Mcli/deploy/conf/coordinator_config.json+1-1
@@ -49,7 +49,7 @@
49 },49 },
50 "log_filename": {50 "log_filename": {
51 "value": "datasystem_coordinator",51 "value": "datasystem_coordinator",
52- "description": "Prefix of log filename, default is program invocation short name. Use standard characters only."52+ "description": "Prefix of log filename. Only letters, digits, and underscores are allowed when non-empty."
53 },53 },
54 "minloglevel": {54 "minloglevel": {
55 "value": "0",55 "value": "0",
Mcli/deploy/conf/worker_config.json+1-1
@@ -441,7 +441,7 @@
441 },441 },
442 "log_filename": {442 "log_filename": {
443 "value": "",443 "value": "",
444- "description": "Prefix of log filename, default is program invocation short name. Use standard characters only."444+ "description": "Prefix of log filename. Only letters, digits, and underscores are allowed when non-empty."
445 },445 },
446 "log_async_queue_size": {446 "log_async_queue_size": {
447 "value": "65536",447 "value": "65536",
Mdocs/source_zh_cn/api_reference/api/cpp/KVClientConfig.rst+6-3
@@ -78,18 +78,21 @@ KVClientConfig
78 .. cpp:function:: Builder &LogName(const std::string &name)78 .. cpp:function:: Builder &LogName(const std::string &name)
79 79 
80 设置客户端日志文件名(不含目录和扩展名)。对应内部配置项 ``log_filename``。80 设置客户端日志文件名(不含目录和扩展名)。对应内部配置项 ``log_filename``。
81- 不允许为允许字符为英文字母、数字及路径字符集中的符号。默认值 ``ds_client``。81+ 时仅允许英文字母、数字和下划线;空时忽略环境变量
82+ ``DATASYSTEM_CLIENT_LOG_NAME`` 并使用默认基名 ``ds_client``。默认情况下实际基名为
83+ ``ds_client_<pid>``,启用 ``LogWithoutPid`` 后为 ``ds_client``。
82 84 
83 .. cpp:function:: Builder &LogWithoutPid(bool enable)85 .. cpp:function:: Builder &LogWithoutPid(bool enable)
84 86 
85 设置客户端日志文件名是否不带 pid。对应环境变量 ``DATASYSTEM_CLIENT_LOG_WITHOUT_PID``。87 设置客户端日志文件名是否不带 pid。对应环境变量 ``DATASYSTEM_CLIENT_LOG_WITHOUT_PID``。
86 为 ``true`` 时输出为 ``ds_client.log``、``ds_client_access.log``;为 ``false`` 时恢复带88 为 ``true`` 时输出为 ``ds_client.log``、``ds_client_access.log``;为 ``false`` 时恢复带
87- ``<pid>`` 后缀的命名,适合多 client 进程同时运行且不希望覆盖日志的场景。默认值为 ``true``。89+ ``<pid>`` 后缀的命名,适合多 client 进程同时运行且不希望覆盖日志的场景。默认值为 ``false``。
88 90 
89 .. cpp:function:: Builder &AccessLogName(const std::string &name)91 .. cpp:function:: Builder &AccessLogName(const std::string &name)
90 92 
91 设置客户端访问日志文件名(不含目录和扩展名)。非空时仅允许英文字母、数字和下划线。93 设置客户端访问日志文件名(不含目录和扩展名)。非空时仅允许英文字母、数字和下划线。
92- 默认值为 ``ds_client_access``94+空时忽略环境变量 ``DATASYSTEM_CLIENT_ACCESS_LOG_NAME`` 并使用默认基名
95+ ``ds_client_access``。
93 96 
94 .. cpp:function:: Builder &MinLogLevel(int level)97 .. cpp:function:: Builder &MinLogLevel(int level)
95 98 
Mdocs/source_zh_cn/appendix/client_env_guide.md+3-1
@@ -39,7 +39,9 @@
39| 19 | `DATASYSTEM_LOG_COMPRESS` | `false` | 日志文件是否压缩,默认不压缩。 |39| 19 | `DATASYSTEM_LOG_COMPRESS` | `false` | 日志文件是否压缩,默认不压缩。 |
40| 23 | `DATASYSTEM_MIN_LOG_LEVEL` | `0` | 数据系统SDK的最小日志级别。数值越大,输出的日志越少;默认值为0,通常表示INFO级别及以上日志都可输出。 |40| 23 | `DATASYSTEM_MIN_LOG_LEVEL` | `0` | 数据系统SDK的最小日志级别。数值越大,输出的日志越少;默认值为0,通常表示INFO级别及以上日志都可输出。 |
41| 26 | `DATASYSTEM_LOG_ONLY_WRITE_INFO_FILE` | `true` | INFO日志文件始终写入所有级别日志。该值为`true`时不额外生成WARNING/ERROR日志文件;为`false`时会额外生成WARNING/ERROR日志文件,高级别日志会按等级写入多个日志文件。 |41| 26 | `DATASYSTEM_LOG_ONLY_WRITE_INFO_FILE` | `true` | INFO日志文件始终写入所有级别日志。该值为`true`时不额外生成WARNING/ERROR日志文件;为`false`时会额外生成WARNING/ERROR日志文件,高级别日志会按等级写入多个日志文件。 |
42-| 28 | `DATASYSTEM_CLIENT_LOG_WITHOUT_PID` | `true` | 是否让客户端日志文件名不带进程号。默认`true`时输出为`ds_client.log`、`ds_client_access.log`;设置为`false`时恢复为带`<pid>`后缀的命名,适合多client进程同时运行且不希望覆盖日志的场景。 |42+| 28 | `DATASYSTEM_CLIENT_LOG_WITHOUT_PID` | `false` | 是否让客户端日志文件名不带进程号。默认`false`时输出带`<pid>`后缀的日志名;设置为`true`时输出为`ds_client.log`、`ds_client_access.log`。 |
43+| 33 | `DATASYSTEM_CLIENT_LOG_NAME` | `""` | 客户端运行日志基名,非空时仅允许英文字母、数字和下划线。仅当 `KVClientConfig::LogName` 未显式配置时读取。 |
44+| 34 | `DATASYSTEM_CLIENT_ACCESS_LOG_NAME` | `""` | 客户端访问日志基名,非空时仅允许英文字母、数字和下划线。仅当 `KVClientConfig::AccessLogName` 未显式配置时读取。 |
43 45 
44## 运行时环境配置46## 运行时环境配置
45 47 
Mdocs/source_zh_cn/appendix/log_guide.md+3-3
@@ -29,9 +29,9 @@ openYuanrong datasystem 的日志分为以下类型:
29| 7 | datasystem_worker | `/path/yr_datasystem/logs/{log_filename}_operation.log` | 操作审计日志;记录 Init/Shutdown、构建 Commit、配置初始化快照及运行时 flag 变更(`UpdateConfig` 或配置文件热更新) |29| 7 | datasystem_worker | `/path/yr_datasystem/logs/{log_filename}_operation.log` | 操作审计日志;记录 Init/Shutdown、构建 Commit、配置初始化快照及运行时 flag 变更(`UpdateConfig` 或配置文件热更新) |
30| 8 | datasystem_worker | `/path/yr_datasystem/logs/kv_resource.log` | 资源快照 JSON-Lines;`resource.log` 同源字段的纯 JSON 输出,顶层 `time`/`pod_name`/`cluster_name`,`metrics` 按 schema 输出已配置的 metric 组;由 `json_log_monitor` 且 `log_monitor_exporter=harddisk` 控制 |30| 8 | datasystem_worker | `/path/yr_datasystem/logs/kv_resource.log` | 资源快照 JSON-Lines;`resource.log` 同源字段的纯 JSON 输出,顶层 `time`/`pod_name`/`cluster_name`,`metrics` 按 schema 输出已配置的 metric 组;由 `json_log_monitor` 且 `log_monitor_exporter=harddisk` 控制 |
31| 9 | datasystem_worker | `/path/yr_datasystem/logs/kv_metrics.log` | 指标摘要 JSON-Lines;INFO.log 中 `metrics_summary` 的并行输出,body 逐字一致,额外前置 `time`/`pod_name`/`cluster_name`;由 `json_log_monitor` 控制(INFO.log 原输出仍由 `log_monitor` 控制) |31| 9 | datasystem_worker | `/path/yr_datasystem/logs/kv_metrics.log` | 指标摘要 JSON-Lines;INFO.log 中 `metrics_summary` 的并行输出,body 逐字一致,额外前置 `time`/`pod_name`/`cluster_name`;由 `json_log_monitor` 控制(INFO.log 原输出仍由 `log_monitor` 控制) |
32-| 10 | Client | `/path/client/ds_client.INFO.log`(及 `.WARNING`、`.ERROR` 等;关闭 `DATASYSTEM_CLIENT_LOG_WITHOUT_PID` 后恢复为 `/path/client/ds_client_{pid}.INFO.log`) | SDK 运行日志;基名可由启动参数与环境变量 `DATASYSTEM_CLIENT_LOG_NAME` 覆盖 |32+| 10 | Client | `/path/client/ds_client_{pid}.INFO.log`(及 `.WARNING`、`.ERROR` 等;开启 `DATASYSTEM_CLIENT_LOG_WITHOUT_PID` 后为 `/path/client/ds_client.INFO.log`) | SDK 运行日志;基名可由 `KVClientConfig::LogName` 与环境变量 `DATASYSTEM_CLIENT_LOG_NAME` 覆盖,非空时仅允许英文字母、数字和下划线;显式配置空串时忽略环境变量并使用默认基名 |
33-| 11 | Client | `/path/client/ds_client_operation.log`(关闭 `DATASYSTEM_CLIENT_LOG_WITHOUT_PID` 后恢复为 `/path/client/ds_client_{pid}_operation.log`) | Client 操作审计日志;记录 Init/Shutdown 及 `UpdateConfig` 动态配置变更 |33+| 11 | Client | `/path/client/ds_client_{pid}_operation.log`(开启 `DATASYSTEM_CLIENT_LOG_WITHOUT_PID` 后为 `/path/client/ds_client_operation.log`) | Client 操作审计日志;记录 Init/Shutdown 及 `UpdateConfig` 动态配置变更 |
34-| 12 | Client | `/path/client/ds_client_access.log`(关闭 `DATASYSTEM_CLIENT_LOG_WITHOUT_PID` 后恢复为 `/path/client/ds_client_access_{pid}.log`) | SDK 接口访问日志;基名可由 `DATASYSTEM_CLIENT_ACCESS_LOG_NAME` 覆盖 |34+| 12 | Client | `/path/client/ds_client_access_{pid}.log`(开启 `DATASYSTEM_CLIENT_LOG_WITHOUT_PID` 后为 `/path/client/ds_client_access.log`) | SDK 接口访问日志;基名可由 `KVClientConfig::AccessLogName` 与环境变量 `DATASYSTEM_CLIENT_ACCESS_LOG_NAME` 覆盖,非空时仅允许英文字母、数字和下划线;显式配置空串时忽略环境变量并使用默认基名 |
35| 13 | datasystem_coordinator | `/path/yr_datasystem/logs/datasystem_coordinator.INFO.log`(及 `.WARNING`、`.ERROR` 等轮转文件) | Coordinator 运行日志;启动时记录 Git Commit 和 Branch |35| 13 | datasystem_coordinator | `/path/yr_datasystem/logs/datasystem_coordinator.INFO.log`(及 `.WARNING`、`.ERROR` 等轮转文件) | Coordinator 运行日志;启动时记录 Git Commit 和 Branch |
36| 14 | datasystem_coordinator | `/path/yr_datasystem/logs/datasystem_coordinator_operation.log` | Coordinator 操作审计日志;记录 Init/Shutdown、构建 Commit、配置初始化及运行时 `UpdateConfig` 变更 |36| 14 | datasystem_coordinator | `/path/yr_datasystem/logs/datasystem_coordinator_operation.log` | Coordinator 操作审计日志;记录 Init/Shutdown、构建 Commit、配置初始化及运行时 `UpdateConfig` 变更 |
37 37 
Mdocs/source_zh_cn/deployment/dscli.md+2-2
@@ -1240,7 +1240,7 @@ dscli query route \
1240| watch_event_dispatch_thread | int | `4` | 否 | Coordinator 分发 Watch 事件的线程数 |1240| watch_event_dispatch_thread | int | `4` | 否 | Coordinator 分发 Watch 事件的线程数 |
1241| rpc_thread_num | int | `64` | 否 | Coordinator RPC 服务线程数 |1241| rpc_thread_num | int | `64` | 否 | Coordinator RPC 服务线程数 |
1242| log_dir | string | `"./datasystem/logs"` | 否 | Coordinator 日志目录 |1242| log_dir | string | `"./datasystem/logs"` | 否 | Coordinator 日志目录 |
1243-| log_filename | string | `"datasystem_coordinator"` | 否 | Coordinator 日志文件名前缀 |1243+| log_filename | string | `"datasystem_coordinator"` | 否 | Coordinator 日志文件名前缀,非空时仅允许英文字母、数字和下划线 |
1244| minloglevel | int | `0` | 是 | 最低日志级别,低于该级别的日志不会被记录 |1244| minloglevel | int | `0` | 是 | 最低日志级别,低于该级别的日志不会被记录 |
1245| log_async_queue_size | int | `2048` | 否 | 异步日志消息队列最大容量 |1245| log_async_queue_size | int | `2048` | 否 | 异步日志消息队列最大容量 |
1246| max_log_size | int | `400` | 否 | 单个日志文件最大大小,单位为 MB |1246| max_log_size | int | `400` | 否 | 单个日志文件最大大小,单位为 MB |
@@ -1389,7 +1389,7 @@ Coordinator 按该成员列表启动 Raft 选主。启用选主后,`coordinato
1389| log_async_queue_size | int | `65536` | 否 | 异步日志的消息队列最大容量(消息条数) |1389| log_async_queue_size | int | `65536` | 否 | 异步日志的消息队列最大容量(消息条数) |
1390| log_compress | bool | `false` | 是 | 控制是否启用日志压缩功能。启用时,历史日志将自动压缩为gzip格式存储 |1390| log_compress | bool | `false` | 是 | 控制是否启用日志压缩功能。启用时,历史日志将自动压缩为gzip格式存储 |
1391| logbufsecs | int | `10` | 否 | 日志消息最多缓冲时长(以秒为单位) |1391| logbufsecs | int | `10` | 否 | 日志消息最多缓冲时长(以秒为单位) |
1392-| log_filename | string | `""` | 否 | 日志前缀名,当值为空时前缀名为 `datasystem_worker` |1392+| log_filename | string | `""` | 否 | 日志前缀名,非空时仅允许英文字母、数字和下划线;为空时前缀名为 `datasystem_worker` |
1393| log_retention_day | int | `0` | 否 | 日志保留天数,当该值大于0时,最后修改时间早于 `logRetentionDay` 的日志文件将会被删除;当该值为0时表示禁用该功能 |1393| log_retention_day | int | `0` | 否 | 日志保留天数,当该值大于0时,最后修改时间早于 `logRetentionDay` 的日志文件将会被删除;当该值为0时表示禁用该功能 |
1394| max_log_file_num | int | `5` | 是 | 最大日志文件个数,当日志文件个数超过该值时,会将最旧的日志文件删除,通过日志滚动机制保证日志文件最大个数小于等于该值 |1394| max_log_file_num | int | `5` | 是 | 最大日志文件个数,当日志文件个数超过该值时,会将最旧的日志文件删除,通过日志滚动机制保证日志文件最大个数小于等于该值 |
1395| max_log_size | int | `400` | 否 | 单个日志文件最大大小(以MB为单位) |1395| max_log_size | int | `400` | 否 | 单个日志文件最大大小(以MB为单位) |
Minclude/datasystem/utils/kv_client_config.h+5-2
@@ -62,7 +62,8 @@ public:
62 62 
63 /**63 /**
64 * @brief Client log file base name.64 * @brief Client log file base name.
65- * @param[in] name Equivalent to flag log_filename.65+ * @param[in] name Equivalent to flag log_filename. A non-empty name must contain only letters, digits, and
66+ * underscores. An empty name ignores DATASYSTEM_CLIENT_LOG_NAME and selects the default client log name.
66 * @return Reference to self for chaining.67 * @return Reference to self for chaining.
67 */68 */
68 Builder &LogName(const std::string &name);69 Builder &LogName(const std::string &name);
@@ -76,7 +77,9 @@ public:
76 77 
77 /**78 /**
78 * @brief Client access log file base name.79 * @brief Client access log file base name.
79- * @param[in] name Equivalent to client access log filename config.80+ * @param[in] name Equivalent to client access log filename config. A non-empty name must contain only letters,
81+ * digits, and underscores. An empty name ignores DATASYSTEM_CLIENT_ACCESS_LOG_NAME and selects the default
82+ * client access log name.
80 * @return Reference to self for chaining.83 * @return Reference to self for chaining.
81 */84 */
82 Builder &AccessLogName(const std::string &name);85 Builder &AccessLogName(const std::string &name);
Mk8s/helm_chart/datasystem/values.yaml+1-1
@@ -188,7 +188,7 @@ global:
188 # When enabled, requests whose RPC sub-phases exceed this threshold will include188 # When enabled, requests whose RPC sub-phases exceed this threshold will include
189 # a latencySummary in the access log.189 # a latencySummary in the access log.
190 slowLogRpcSlowerThan: 5000190 slowLogRpcSlowerThan: 5000
191- # Prefix of log filename, default is program invocation short name. Use standard characters only.191+ # Prefix of log filename. Only letters, digits, and underscores are allowed when non-empty.
192 logFilename: ""192 logFilename: ""
193 # Interval between logging stream metrics193 # Interval between logging stream metrics
194 scMetricsLogIntervalS: 60194 scMetricsLogIntervalS: 60
Msrc/datasystem/client/object_cache/object_client_impl.cpp+12-6
@@ -184,7 +184,8 @@ constexpr int64_t DRAINING_LOCATION_REFRESH_INITIAL_BACKOFF_MS = 1;
184constexpr int32_t HASH_RING_RPC_MIN_TIMEOUT_MS = 100;184constexpr int32_t HASH_RING_RPC_MIN_TIMEOUT_MS = 100;
185constexpr int BOUND_WORKER_PROBE_TIMEOUT_MS = 10;185constexpr int BOUND_WORKER_PROBE_TIMEOUT_MS = 10;
186constexpr int TRANSPORT_DIAG_LOG_RATE = 100;186constexpr int TRANSPORT_DIAG_LOG_RATE = 100;
187-const std::unordered_set<std::string> NON_GFLAG_KV_CLIENT_CONFIG_KEYS = {187+const std::unordered_set<std::string> KV_CLIENT_LOG_CONFIG_KEYS = {
188+ "log_filename",
188 "client_access_log_filename",189 "client_access_log_filename",
189 "client_log_without_pid",190 "client_log_without_pid",
190};191};
@@ -396,7 +397,7 @@ std::unordered_map<std::string, std::string> GetGflagArgs(const KVClientConfig &
396{397{
397 std::unordered_map<std::string, std::string> args;398 std::unordered_map<std::string, std::string> args;
398 for (const auto &arg : clientConfig.GetArgs()) {399 for (const auto &arg : clientConfig.GetArgs()) {
399- if (NON_GFLAG_KV_CLIENT_CONFIG_KEYS.find(arg.first) == NON_GFLAG_KV_CLIENT_CONFIG_KEYS.end()) {400+ if (KV_CLIENT_LOG_CONFIG_KEYS.find(arg.first) == KV_CLIENT_LOG_CONFIG_KEYS.end()) {
400 args.emplace(arg.first, arg.second);401 args.emplace(arg.first, arg.second);
401 }402 }
402 }403 }
@@ -405,12 +406,17 @@ std::unordered_map<std::string, std::string> GetGflagArgs(const KVClientConfig &
405 406 
406void ApplyKvClientLogConfig(const KVClientConfig &clientConfig)407void ApplyKvClientLogConfig(const KVClientConfig &clientConfig)
407{408{
408- auto logWithoutPid = clientConfig.GetArgs().find("client_log_without_pid");409+ const auto &args = clientConfig.GetArgs();
409- if (logWithoutPid != clientConfig.GetArgs().end()) {410+ auto logName = args.find("log_filename");
411+ if (logName != args.end()) {
412+ Logging::SetClientLogName(logName->second);
413+ }
414+ auto logWithoutPid = args.find("client_log_without_pid");
415+ if (logWithoutPid != args.end()) {
410 Logging::SetClientLogWithoutPid(ParseBoolFromString(logWithoutPid->second, false));416 Logging::SetClientLogWithoutPid(ParseBoolFromString(logWithoutPid->second, false));
411 }417 }
412- auto accessLogName = clientConfig.GetArgs().find("client_access_log_filename");418+ auto accessLogName = args.find("client_access_log_filename");
413- if (accessLogName != clientConfig.GetArgs().end()) {419+ if (accessLogName != args.end()) {
414 Logging::SetClientAccessLogName(accessLogName->second);420 Logging::SetClientAccessLogName(accessLogName->second);
415 }421 }
416}422}
Msrc/datasystem/common/flags/BUILD.bazel+0-1
@@ -108,7 +108,6 @@ ds_cc_library(
108 "//include/datasystem/utils:utils_headers",108 "//include/datasystem/utils:utils_headers",
109 "//src/datasystem/common/util:status_helper",109 "//src/datasystem/common/util:status_helper",
110 "//src/datasystem/common/util:validator",110 "//src/datasystem/common/util:validator",
111- "@re2",
112 ],111 ],
113 alwayslink = True,112 alwayslink = True,
114)113)
Msrc/datasystem/common/flags/common_flag_define.cpp+2-1
@@ -40,7 +40,8 @@ DS_DEFINE_string(
40 unix_domain_socket_dir, "~/datasystem/unix_domain_socket_dir",40 unix_domain_socket_dir, "~/datasystem/unix_domain_socket_dir",
41 "The directory to store unix domain socket file. The UDS generates temporary files in this path. Max lenth: 80");41 "The directory to store unix domain socket file. The UDS generates temporary files in this path. Max lenth: 80");
42DS_DEFINE_string(log_filename, "",42DS_DEFINE_string(log_filename, "",
43- "Prefix of log filename, default is program invocation short name. Use standard characters only.");43+ "Prefix of log filename, default is program invocation short name. Only letters, digits, and "
44+ "underscores are allowed.");
44DS_DEFINE_string(curve_key_dir, "",45DS_DEFINE_string(curve_key_dir, "",
45 "The directory to find ZMQ curve key files. This path must be specified "46 "The directory to find ZMQ curve key files. This path must be specified "
46 "when zmq authentication is enabled. Path must be less than 4095 characters (PATH_MAX).");47 "when zmq authentication is enabled. Path must be less than 4095 characters (PATH_MAX).");
Msrc/datasystem/common/flags/common_flags_validate.cpp+11-1
@@ -222,6 +222,16 @@ bool ValidateSampleRateRange(const char *flagName, double value)
222 }222 }
223 return true;223 return true;
224}224}
225+ 
226+bool ValidateOptionalLogName(const char *flagName, const std::string &value)
227+{
228+ if (value.empty() || Validator::ValidateLogName(value)) {
229+ return true;
230+ }
231+ LOG(ERROR) << FormatString("The value of %s flag is %s, which must contain only a-z, A-Z, 0-9, and underscore.",
232+ flagName, value);
233+ return false;
234+}
225} // namespace235} // namespace
226 236 
227DS_DEFINE_validator(l2_cache_type, &Validator::ValidateL2CacheType);237DS_DEFINE_validator(l2_cache_type, &Validator::ValidateL2CacheType);
@@ -253,7 +263,7 @@ DS_DEFINE_validator(rebalance_usage_gap_percent, &ValidatePercent);
253DS_DEFINE_validator(rebalance_task_report_grace_ms, &Validator::ValidateUint32);263DS_DEFINE_validator(rebalance_task_report_grace_ms, &Validator::ValidateUint32);
254DS_DEFINE_validator(monitor_config_file, &Validator::ValidatePathString);264DS_DEFINE_validator(monitor_config_file, &Validator::ValidatePathString);
255DS_DEFINE_validator(unix_domain_socket_dir, &Validator::ValidateUnixDomainSocketDir);265DS_DEFINE_validator(unix_domain_socket_dir, &Validator::ValidateUnixDomainSocketDir);
256-DS_DEFINE_validator(log_filename, &Validator::ValidateEligibleChar);266+DS_DEFINE_validator(log_filename, &ValidateOptionalLogName);
257DS_DEFINE_validator(curve_key_dir, &Validator::ValidatePathString);267DS_DEFINE_validator(curve_key_dir, &Validator::ValidatePathString);
258DS_DEFINE_validator(shared_disk_directory, &Validator::ValidatePathString);268DS_DEFINE_validator(shared_disk_directory, &Validator::ValidatePathString);
259DS_DEFINE_validator(distributed_disk_path, &Validator::ValidatePathString);269DS_DEFINE_validator(distributed_disk_path, &Validator::ValidatePathString);
Msrc/datasystem/common/flags/kv_client_config.cpp+7-19
@@ -18,8 +18,6 @@
18#include <sstream>18#include <sstream>
19#include <vector>19#include <vector>
20 20 
21-#include "re2/re2.h"
22- 
23#include "datasystem/common/util/validator.h"21#include "datasystem/common/util/validator.h"
24 22 
25namespace datasystem {23namespace datasystem {
@@ -95,21 +93,15 @@ bool ValidateNonEmptyArg(const std::unordered_map<std::string, std::string> &arg
95 return it == args.end() || !it->second.empty();93 return it == args.end() || !it->second.empty();
96}94}
97 95 
98-bool IsValidNonEmptyLogBaseName(const std::string &value)96+void ValidateLogNameArg(const std::unordered_map<std::string, std::string> &args, const std::string &key,
97+ const std::string &fieldName, std::vector<std::string> &errors)
99{98{
100- static const re2::RE2 pattern("^[a-zA-Z0-9_]*$");99+ auto it = args.find(key);
101- return re2::RE2::FullMatch(value, pattern);
102-}
103- 
104-void ValidateAccessLogNameArg(const std::unordered_map<std::string, std::string> &args,
105- std::vector<std::string> &errors)
106-{
107- auto it = args.find("client_access_log_filename");
108 if (it == args.end() || it->second.empty()) {100 if (it == args.end() || it->second.empty()) {
109 return;101 return;
110 }102 }
111- if (!IsValidNonEmptyLogBaseName(it->second)) {103+ if (!Validator::ValidateLogName(it->second)) {
112- AddError(errors, "AccessLogName", "must contain only a-z, A-Z, 0-9, and underscore");104+ AddError(errors, fieldName, "must contain only a-z, A-Z, 0-9, and underscore");
113 }105 }
114}106}
115 107 
@@ -140,12 +132,8 @@ void ValidateKvClientStringFields(const std::unordered_map<std::string, std::str
140 std::vector<std::string> &errors)132 std::vector<std::string> &errors)
141{133{
142 ValidateNonEmptyPathArg(args, "log_dir", "LogDir", errors);134 ValidateNonEmptyPathArg(args, "log_dir", "LogDir", errors);
143- if (!ValidateNonEmptyArg(args, "log_filename")) {135+ ValidateLogNameArg(args, "log_filename", "LogName", errors);
144- AddError(errors, "LogName", "must not be empty");136+ ValidateLogNameArg(args, "client_access_log_filename", "AccessLogName", errors);
145- } else if (!ValidateStringArg(args, "log_filename", &Validator::ValidateEligibleChar)) {
146- AddError(errors, "LogName", "contains unsupported characters");
147- }
148- ValidateAccessLogNameArg(args, errors);
149 ValidatePathArg(args, "monitor_config_file", "MonitorConfigPath", errors, true);137 ValidatePathArg(args, "monitor_config_file", "MonitorConfigPath", errors, true);
150}138}
151 139 
Msrc/datasystem/common/log/BUILD.bazel+0-1
@@ -142,7 +142,6 @@ ds_cc_library(
142 "//src/datasystem/common/util:request_context",142 "//src/datasystem/common/util:request_context",
143 "//src/datasystem/common/util:thread",143 "//src/datasystem/common/util:thread",
144 "//src/datasystem/common/util:uuid_generator",144 "//src/datasystem/common/util:uuid_generator",
145- "@re2",
146 "@securec",145 "@securec",
147 ],146 ],
148 alwayslink = 1,147 alwayslink = 1,
Msrc/datasystem/common/log/access_recorder.cpp+2-1
@@ -38,6 +38,7 @@
38#include "datasystem/common/util/format.h"38#include "datasystem/common/util/format.h"
39#include "datasystem/common/util/request_context.h"39#include "datasystem/common/util/request_context.h"
40#include "datasystem/common/util/status_helper.h"40#include "datasystem/common/util/status_helper.h"
41+#include "datasystem/common/util/validator.h"
41#include "datasystem/utils/status.h"42#include "datasystem/utils/status.h"
42 43 
43DS_DECLARE_bool(log_monitor);44DS_DECLARE_bool(log_monitor);
@@ -989,7 +990,7 @@ Status AccessRecorderManager::ResetWriteLogger(bool isEmbeddedClient)
989 990 
990 // Allow overriding client access log filename via config or environment variable.991 // Allow overriding client access log filename via config or environment variable.
991 std::string accessLogName = Logging::GetClientAccessLogName();992 std::string accessLogName = Logging::GetClientAccessLogName();
992- if (Logging::ValidateLogName(accessLogName)) {993+ if (Validator::ValidateLogName(accessLogName)) {
993 clientAccessLogName = std::move(accessLogName);994 clientAccessLogName = std::move(accessLogName);
994 }995 }
995 996 
Msrc/datasystem/common/log/logging.cpp+26-13
@@ -28,7 +28,6 @@
28#include <fcntl.h>28#include <fcntl.h>
29#include <unistd.h>29#include <unistd.h>
30 30 
31-#include "re2/re2.h"
32#include "datasystem/common/log/access_recorder.h"31#include "datasystem/common/log/access_recorder.h"
33#include "datasystem/common/metrics/hard_disk_exporter/hard_disk_exporter.h"32#include "datasystem/common/metrics/hard_disk_exporter/hard_disk_exporter.h"
34#include "datasystem/common/log/log.h"33#include "datasystem/common/log/log.h"
@@ -141,6 +140,8 @@ namespace {
141std::mutex g_clientLogConfigMutex;140std::mutex g_clientLogConfigMutex;
142bool g_hasClientLogWithoutPidConfig = false;141bool g_hasClientLogWithoutPidConfig = false;
143bool g_clientLogWithoutPidConfig = false;142bool g_clientLogWithoutPidConfig = false;
143+bool g_hasClientLogNameConfig = false;
144+std::string g_clientLogNameConfig;
144bool g_hasClientAccessLogNameConfig = false;145bool g_hasClientAccessLogNameConfig = false;
145std::string g_clientAccessLogNameConfig;146std::string g_clientAccessLogNameConfig;
146} // namespace147} // namespace
@@ -481,6 +482,23 @@ void Logging::SetClientLogWithoutPid(bool enabled)
481 g_clientLogWithoutPidConfig = enabled;482 g_clientLogWithoutPidConfig = enabled;
482}483}
483 484 
485+void Logging::SetClientLogName(const std::string &logName)
486+{
487+ std::lock_guard<std::mutex> lock(g_clientLogConfigMutex);
488+ g_hasClientLogNameConfig = true;
489+ g_clientLogNameConfig = logName;
490+}
491+ 
492+bool Logging::TryGetClientLogName(std::string &logName)
493+{
494+ std::lock_guard<std::mutex> lock(g_clientLogConfigMutex);
495+ if (!g_hasClientLogNameConfig) {
496+ return false;
497+ }
498+ logName = g_clientLogNameConfig;
499+ return true;
500+}
501+ 
484void Logging::SetClientAccessLogName(const std::string &logName)502void Logging::SetClientAccessLogName(const std::string &logName)
485{503{
486 std::lock_guard<std::mutex> lock(g_clientLogConfigMutex);504 std::lock_guard<std::mutex> lock(g_clientLogConfigMutex);
@@ -503,6 +521,8 @@ void Logging::ResetClientLogConfigForTest()
503 std::lock_guard<std::mutex> lock(g_clientLogConfigMutex);521 std::lock_guard<std::mutex> lock(g_clientLogConfigMutex);
504 g_hasClientLogWithoutPidConfig = false;522 g_hasClientLogWithoutPidConfig = false;
505 g_clientLogWithoutPidConfig = false;523 g_clientLogWithoutPidConfig = false;
524+ g_hasClientLogNameConfig = false;
525+ g_clientLogNameConfig.clear();
506 g_hasClientAccessLogNameConfig = false;526 g_hasClientAccessLogNameConfig = false;
507 g_clientAccessLogNameConfig.clear();527 g_clientAccessLogNameConfig.clear();
508}528}
@@ -552,14 +572,17 @@ void Logging::Start(const std::string &logFilename, LogProcessRole processRole,
552 if (isClient_) {572 if (isClient_) {
553 GetInstance()->InitClientConfig();573 GetInstance()->InitClientConfig();
554 574 
555- if (!FLAGS_log_filename.empty()) {575+ std::string configuredLogName;
576+ if (TryGetClientLogName(configuredLogName)) {
577+ clientLogName = configuredLogName.empty() ? GetClientLogName(logFilename, getpid()) : configuredLogName;
578+ } else if (!FLAGS_log_filename.empty()) {
556 clientLogName = FLAGS_log_filename;579 clientLogName = FLAGS_log_filename;
557 } else {580 } else {
558 clientLogName = GetClientLogName(logFilename, getpid());581 clientLogName = GetClientLogName(logFilename, getpid());
559 582 
560 // Allow overriding client log filename via environment variable583 // Allow overriding client log filename via environment variable
561 std::string logName = GetStringFromEnv(LOG_NAME_ENV.c_str(), "");584 std::string logName = GetStringFromEnv(LOG_NAME_ENV.c_str(), "");
562- if (ValidateLogName(logName)) {585+ if (Validator::ValidateLogName(logName)) {
563 clientLogName = std::move(logName);586 clientLogName = std::move(logName);
564 }587 }
565 }588 }
@@ -630,14 +653,4 @@ Status Logging::WriteLogToFile(int lineOfCode, const std::string &fileNameOfCode
630 return Status::OK();653 return Status::OK();
631}654}
632 655 
633-bool Logging::ValidateLogName(const std::string &logName)
634-{
635- if (logName.empty()) {
636- return false;
637- }
638- 
639- // Only allow: a-z, A-Z, 0-9, _
640- static const re2::RE2 re("^[a-zA-Z0-9_]*$");
641- return re2::RE2::FullMatch(logName, re);
642-}
643} // namespace datasystem656} // namespace datasystem
Msrc/datasystem/common/log/logging.h+13-2
@@ -126,8 +126,6 @@ public:
126 return podName_;126 return podName_;
127 }127 }
128 128 
129- static bool ValidateLogName(const std::string &logName);
130- 
131 /**129 /**
132 * @brief Build client log filename.130 * @brief Build client log filename.
133 * @param[in] baseName Log file base name.131 * @param[in] baseName Log file base name.
@@ -148,6 +146,19 @@ public:
148 */146 */
149 static void SetClientLogWithoutPid(bool enabled);147 static void SetClientLogWithoutPid(bool enabled);
150 148 
149+ /**
150+ * @brief Set client log filename configured by KVClientConfig.
151+ * @param[in] logName Client log file base name. An empty value selects the default name.
152+ */
153+ static void SetClientLogName(const std::string &logName);
154+ 
155+ /**
156+ * @brief Get client log filename explicitly configured by KVClientConfig.
157+ * @param[out] logName Client log file base name, which may be empty.
158+ * @return True if KVClientConfig explicitly configured the log name.
159+ */
160+ static bool TryGetClientLogName(std::string &logName);
161+ 
151 /**162 /**
152 * @brief Set client access log filename.163 * @brief Set client access log filename.
153 * @param[in] logName Access log file base name.164 * @param[in] logName Access log file base name.
Msrc/datasystem/common/util/validator.h+11-0
@@ -434,6 +434,17 @@ public:
434 return false;434 return false;
435 }435 }
436 436 
437+ /**
438+ * @brief Validate a log file base name.
439+ * @param[in] value The string to be checked.
440+ * @return True if valid.
441+ */
442+ static bool ValidateLogName(const std::string &value)
443+ {
444+ static const re2::RE2 re("^[a-zA-Z0-9_]+$");
445+ return re2::RE2::FullMatch(value, re);
446+ }
447+ 
437 /**448 /**
438 * @brief Validate the given string contains all legal characters.449 * @brief Validate the given string contains all legal characters.
439 * @param[in] flagName The characters flag.450 * @param[in] flagName The characters flag.
Mtests/perf/zmq/zmq_perf_agt_exe.cpp+1-1
@@ -44,7 +44,7 @@ void SignalHandler(int signum)
44int main(int argc, char **argv)44int main(int argc, char **argv)
45{45{
46 auto pid = getpid();46 auto pid = getpid();
47- FLAGS_log_filename = datasystem::FormatString("%s.%d", "zmq_perf_agent", pid);47+ FLAGS_log_filename = datasystem::FormatString("%s_%d", "zmq_perf_agent", pid);
48 FLAGS_v = datasystem::RPC_KEY_LOG_LEVEL;48 FLAGS_v = datasystem::RPC_KEY_LOG_LEVEL;
49 datasystem::Logging::GetInstance()->Start(FLAGS_log_filename, datasystem::LogProcessRole::WORKER);49 datasystem::Logging::GetInstance()->Start(FLAGS_log_filename, datasystem::LogProcessRole::WORKER);
50 datasystem::st::ZmqPerfAgent zmqPerfAgent;50 datasystem::st::ZmqPerfAgent zmqPerfAgent;
Mtests/st/client/kv_cache/kv_client_init_test.cpp+31-0
@@ -26,6 +26,7 @@
26 26 
27#include "client/object_cache/oc_client_common.h"27#include "client/object_cache/oc_client_common.h"
28#include "common.h"28#include "common.h"
29+#include "datasystem/common/constants.h"
29#include "datasystem/common/flags/flags.h"30#include "datasystem/common/flags/flags.h"
30#include "datasystem/common/inject/inject_point.h"31#include "datasystem/common/inject/inject_point.h"
31#include "datasystem/common/flags/common_flags.h"32#include "datasystem/common/flags/common_flags.h"
@@ -37,6 +38,7 @@ DS_DECLARE_uint32(max_log_size);
37DS_DECLARE_uint32(log_async_queue_size);38DS_DECLARE_uint32(log_async_queue_size);
38DS_DECLARE_int32(v);39DS_DECLARE_int32(v);
39DS_DECLARE_string(monitor_config_file);40DS_DECLARE_string(monitor_config_file);
41+DS_DECLARE_string(log_filename);
40 42 
41namespace datasystem {43namespace datasystem {
42namespace st {44namespace st {
@@ -215,6 +217,35 @@ TEST_F(KVClientInitTest, ConfigAfterDefaultInitDoesNotOverrideProcessConfig)
215 });217 });
216}218}
217 219 
220+TEST_F(KVClientInitTest, ExplicitEmptyLogNameUsesDefaultAndIgnoresEnv)
221+{
222+ if (FLAGS_use_brpc) {
223+ GTEST_SKIP() << "brpc fork-safety: brpc channel/bthread global state not fork-safe. Tracked separately.";
224+ }
225+ auto connectOptions = GetConnectOptions();
226+ RunInChildProcess([connectOptions]() -> int {
227+ setenv("DATASYSTEM_CLIENT_LOG_NAME", "env_client", 1);
228+ KVClientConfig config;
229+ auto buildStatus = KVClientConfig::Builder().LogName("").LogWithoutPid(true).Build(config);
230+ if (buildStatus.IsError()) {
231+ LOG(ERROR) << "Build KVClientConfig failed: " << buildStatus.ToString();
232+ return 1;
233+ }
234+ KVClient client(connectOptions);
235+ auto initStatus = client.Init(config);
236+ if (initStatus.IsError()) {
237+ LOG(ERROR) << "Init with explicit empty LogName failed: " << initStatus.ToString();
238+ return 1;
239+ }
240+ if (FLAGS_log_filename != CLIENT_LOG_FILENAME) {
241+ LOG(ERROR) << "Unexpected log_filename, expect: " << CLIENT_LOG_FILENAME
242+ << ", actual: " << FLAGS_log_filename;
243+ return 1;
244+ }
245+ return 0;
246+ });
247+}
248+ 
218TEST_F(KVClientInitTest, EmptyMonitorConfigPathAllowsUpdateConfig)249TEST_F(KVClientInitTest, EmptyMonitorConfigPathAllowsUpdateConfig)
219{250{
220 if (FLAGS_use_brpc) {251 if (FLAGS_use_brpc) {
Mtests/ut/common/flags/flags_test.cpp+84-10
@@ -60,6 +60,7 @@ DS_DECLARE_uint32(log_retention_day);
60DS_DECLARE_uint32(max_log_file_num);60DS_DECLARE_uint32(max_log_file_num);
61DS_DECLARE_uint32(max_log_size);61DS_DECLARE_uint32(max_log_size);
62DS_DECLARE_uint32(stderrthreshold);62DS_DECLARE_uint32(stderrthreshold);
63+DS_DECLARE_string(log_filename);
63DS_DECLARE_string(monitor_config_file);64DS_DECLARE_string(monitor_config_file);
64 65 
65namespace {66namespace {
@@ -282,6 +283,10 @@ private:
282void ApplyKvClientLogConfigFromConfig(const KVClientConfig &clientConfig)283void ApplyKvClientLogConfigFromConfig(const KVClientConfig &clientConfig)
283{284{
284 const auto &args = clientConfig.GetArgs();285 const auto &args = clientConfig.GetArgs();
286+ auto logName = args.find("log_filename");
287+ if (logName != args.end()) {
288+ Logging::SetClientLogName(logName->second);
289+ }
285 auto logWithoutPid = args.find("client_log_without_pid");290 auto logWithoutPid = args.find("client_log_without_pid");
286 if (logWithoutPid != args.end()) {291 if (logWithoutPid != args.end()) {
287 Logging::SetClientLogWithoutPid(ParseBoolFromString(logWithoutPid->second, false));292 Logging::SetClientLogWithoutPid(ParseBoolFromString(logWithoutPid->second, false));
@@ -298,6 +303,11 @@ protected:
298 {303 {
299 Logging::ResetClientLogConfigForTest();304 Logging::ResetClientLogConfigForTest();
300 }305 }
306+ 
307+ void TearDown() override
308+ {
309+ Logging::ResetClientLogConfigForTest();
310+ }
301};311};
302} // namespace312} // namespace
303 313 
@@ -479,6 +489,27 @@ TEST_F(ClientLogConfigPriorityTest, ExplicitClientAccessLogNameIsNotOverriddenBy
479 }489 }
480}490}
481 491 
492+TEST_F(ClientLogConfigPriorityTest, ExplicitEmptyClientLogNameIsPreserved)
493+{
494+ KVClientConfig config;
495+ ASSERT_EQ(KVClientConfig::Builder().LogName("").Build(config), Status::OK());
496+ ApplyKvClientLogConfigFromConfig(config);
497+ 
498+ std::string logName;
499+ ASSERT_TRUE(Logging::TryGetClientLogName(logName));
500+ ASSERT_TRUE(logName.empty());
501+}
502+ 
503+TEST_F(ClientLogConfigPriorityTest, UnspecifiedClientLogNameHasNoExplicitConfig)
504+{
505+ KVClientConfig config;
506+ ASSERT_EQ(KVClientConfig::Builder().Build(config), Status::OK());
507+ ApplyKvClientLogConfigFromConfig(config);
508+ 
509+ std::string logName;
510+ ASSERT_FALSE(Logging::TryGetClientLogName(logName));
511+}
512+ 
482TEST_F(ClientLogConfigPriorityTest, ExplicitEmptyClientAccessLogNameIsNotOverriddenByEnv)513TEST_F(ClientLogConfigPriorityTest, ExplicitEmptyClientAccessLogNameIsNotOverriddenByEnv)
483{514{
484 const char *envName = ACCESS_LOG_NAME_ENV.c_str();515 const char *envName = ACCESS_LOG_NAME_ENV.c_str();
@@ -630,16 +661,35 @@ TEST_F(FlagsTest, KVClientConfigBuilderAggregatesInvalidValues)
630 EXPECT_TRUE(config.GetArgs().empty());661 EXPECT_TRUE(config.GetArgs().empty());
631}662}
632 663 
633-TEST_F(FlagsTest, KVClientConfigBuilderRejectsEmptyGflagStrings)664+TEST_F(FlagsTest, KVClientConfigBuilderValidatesLogName)
665+{
666+ KVClientConfig config;
667+ Status status = KVClientConfig::Builder().LogName("client_log_01").Build(config);
668+ ASSERT_EQ(status, Status::OK());
669+ ASSERT_EQ(config.GetArgs().at("log_filename"), "client_log_01");
670+ 
671+ KVClientConfig emptyConfig;
672+ status = KVClientConfig::Builder().LogName("").Build(emptyConfig);
673+ ASSERT_EQ(status, Status::OK());
674+ ASSERT_EQ(emptyConfig.GetArgs().at("log_filename"), "");
675+ 
676+ const std::vector<std::string> invalidLogNames = { "client-log", "a b", "a/b", "../client",
677+ "client.log", "client:name", "客户端" };
678+ for (const auto &logName : invalidLogNames) {
679+ KVClientConfig invalidConfig;
680+ status = KVClientConfig::Builder().LogName(logName).Build(invalidConfig);
681+ EXPECT_EQ(status.GetCode(), StatusCode::K_INVALID) << logName;
682+ EXPECT_THAT(status.GetMsg(), testing::HasSubstr("LogName")) << logName;
683+ EXPECT_TRUE(invalidConfig.GetArgs().empty()) << logName;
684+ }
685+}
686+ 
687+TEST_F(FlagsTest, KVClientConfigBuilderRejectsEmptyRequiredGflagStrings)
634{688{
635 KVClientConfig config;689 KVClientConfig config;
636 Status status = KVClientConfig::Builder().LogDir("").Build(config);690 Status status = KVClientConfig::Builder().LogDir("").Build(config);
637 ASSERT_EQ(status.GetCode(), StatusCode::K_INVALID);691 ASSERT_EQ(status.GetCode(), StatusCode::K_INVALID);
638 EXPECT_THAT(status.GetMsg(), testing::HasSubstr("LogDir"));692 EXPECT_THAT(status.GetMsg(), testing::HasSubstr("LogDir"));
639- 
640- status = KVClientConfig::Builder().LogName("").Build(config);
641- ASSERT_EQ(status.GetCode(), StatusCode::K_INVALID);
642- EXPECT_THAT(status.GetMsg(), testing::HasSubstr("LogName"));
643}693}
644 694 
645TEST_F(FlagsTest, KVClientConfigBuilderAcceptsEmptyMonitorConfigPath)695TEST_F(FlagsTest, KVClientConfigBuilderAcceptsEmptyMonitorConfigPath)
@@ -682,13 +732,37 @@ TEST_F(FlagsTest, ParseCommandLineFlagsRejectsEmptyStringWhenValidatorDisallows)
682 EXPECT_THAT(errMsg, testing::HasSubstr("illegal value"));732 EXPECT_THAT(errMsg, testing::HasSubstr("illegal value"));
683}733}
684 734 
685-TEST_F(FlagsTest, KVClientConfigBuilderRejectsInvalidAccessLogName)735+TEST_F(FlagsTest, KVClientConfigBuilderValidatesAccessLogName)
686{736{
687 KVClientConfig config;737 KVClientConfig config;
688- Status status = KVClientConfig::Builder().AccessLogName("client-access").Build(config);738+ Status status = KVClientConfig::Builder().AccessLogName("client_access_01").Build(config);
689- ASSERT_EQ(status.GetCode(), StatusCode::K_INVALID);739+ ASSERT_EQ(status, Status::OK());
690- EXPECT_THAT(status.GetMsg(), testing::HasSubstr("AccessLogName"));740+ ASSERT_EQ(config.GetArgs().at("client_access_log_filename"), "client_access_01");
691- EXPECT_TRUE(config.GetArgs().empty());741+ 
742+ KVClientConfig emptyConfig;
743+ status = KVClientConfig::Builder().AccessLogName("").Build(emptyConfig);
744+ ASSERT_EQ(status, Status::OK());
745+ ASSERT_EQ(emptyConfig.GetArgs().at("client_access_log_filename"), "");
746+ 
747+ for (const auto &logName : { "client-access", "client/access", "client.access" }) {
748+ KVClientConfig invalidConfig;
749+ status = KVClientConfig::Builder().AccessLogName(logName).Build(invalidConfig);
750+ EXPECT_EQ(status.GetCode(), StatusCode::K_INVALID) << logName;
751+ EXPECT_THAT(status.GetMsg(), testing::HasSubstr("AccessLogName")) << logName;
752+ EXPECT_TRUE(invalidConfig.GetArgs().empty()) << logName;
753+ }
754+}
755+ 
756+TEST_F(FlagsTest, LogFilenameFlagUsesUnifiedCharacterContract)
757+{
758+ const std::string originalLogFilename = FLAGS_log_filename;
759+ std::string errMsg;
760+ EXPECT_TRUE(SetCommandLineOption("log_filename", "client_log_01", errMsg));
761+ EXPECT_FALSE(SetCommandLineOption("log_filename", "client-log", errMsg));
762+ EXPECT_EQ(FLAGS_log_filename, "client_log_01");
763+ EXPECT_TRUE(SetCommandLineOption("log_filename", "", errMsg));
764+ EXPECT_TRUE(FLAGS_log_filename.empty());
765+ FLAGS_log_filename = originalLogFilename;
692}766}
693 767 
694TEST_F(FlagsTest, ParseCommandLineFlagsClearsErrorStateOnRetry)768TEST_F(FlagsTest, ParseCommandLineFlagsClearsErrorStateOnRetry)
Mtests/ut/common/log/BUILD.bazel+1-0
@@ -36,6 +36,7 @@ ds_cc_test(
36 tags = ["manual"],36 tags = ["manual"],
37 deps = [37 deps = [
38 "//src/datasystem/common/log:common_log",38 "//src/datasystem/common/log:common_log",
39+ "//src/datasystem/common/util:validator",
39 "//tests/ut:ut_common",40 "//tests/ut:ut_common",
40 ],41 ],
41)42)
Mtests/ut/common/log/logging_test.cpp+34-1
@@ -54,6 +54,7 @@
54#include "datasystem/common/util/thread_pool.h"54#include "datasystem/common/util/thread_pool.h"
55#include "datasystem/common/util/timer.h"55#include "datasystem/common/util/timer.h"
56#include "datasystem/common/util/uri.h"56#include "datasystem/common/util/uri.h"
57+#include "datasystem/common/util/validator.h"
57#include "datasystem/utils/status.h"58#include "datasystem/utils/status.h"
58 59 
59DS_DECLARE_bool(alsologtostderr);60DS_DECLARE_bool(alsologtostderr);
@@ -157,7 +158,7 @@ public:
157 std::string ClientAccessLogName()158 std::string ClientAccessLogName()
158 {159 {
159 std::string accessLogName = GetStringFromEnv(ACCESS_LOG_NAME_ENV.c_str(), "");160 std::string accessLogName = GetStringFromEnv(ACCESS_LOG_NAME_ENV.c_str(), "");
160- if (Logging::ValidateLogName(accessLogName)) {161+ if (Validator::ValidateLogName(accessLogName)) {
161 return accessLogName;162 return accessLogName;
162 }163 }
163 return Logging::GetClientLogName(CLIENT_ACCESS_LOG_NAME, getpid());164 return Logging::GetClientLogName(CLIENT_ACCESS_LOG_NAME, getpid());
@@ -1057,5 +1058,37 @@ TEST_F(LoggingTest, TestLogName)
1057 filepath = FLAGS_log_dir + "/test_client.INFO.log";1058 filepath = FLAGS_log_dir + "/test_client.INFO.log";
1058 ASSERT_TRUE(FileExist(filepath));1059 ASSERT_TRUE(FileExist(filepath));
1059}1060}
1061+ 
1062+TEST_F(LoggingTest, TestExplicitEmptyClientLogNameUsesDefault)
1063+{
1064+ ScopedEnv logName(LOG_NAME_ENV, "env_client");
1065+ ScopedEnv clientLogWithoutPid(CLIENT_LOG_WITHOUT_PID_ENV, "true");
1066+ Logging::SetClientLogName("");
1067+ 
1068+ Logging::GetInstance()->Start(CLIENT_LOG_FILENAME, LogProcessRole::CLIENT, 1);
1069+ 
1070+ ASSERT_EQ(FLAGS_log_filename, CLIENT_LOG_FILENAME);
1071+}
1072+ 
1073+TEST_F(LoggingTest, TestExplicitClientLogNameOverridesEnv)
1074+{
1075+ ScopedEnv logName(LOG_NAME_ENV, "env_client");
1076+ Logging::SetClientLogName("config_client");
1077+ 
1078+ Logging::GetInstance()->Start(CLIENT_LOG_FILENAME, LogProcessRole::CLIENT, 1);
1079+ 
1080+ ASSERT_EQ(FLAGS_log_filename, "config_client");
1081+}
1082+ 
1083+TEST_F(LoggingTest, TestClientLogNameEnvAfterLoggingRestart)
1084+{
1085+ Logging::GetInstance()->Start("datasystem_coordinator", LogProcessRole::COORDINATOR, 1);
1086+ ResetLoggingForTest();
1087+ ScopedEnv logName(LOG_NAME_ENV, "test_client_after_restart");
1088+ 
1089+ Logging::GetInstance()->Start(CLIENT_LOG_FILENAME, LogProcessRole::CLIENT, 1);
1090+ 
1091+ ASSERT_EQ(FLAGS_log_filename, "test_client_after_restart");
1092+}
1060} // namespace ut1093} // namespace ut
1061} // namespace datasystem1094} // namespace datasystem
Mtests/ut/common/util/validator_test.cpp+9-0
@@ -70,6 +70,15 @@ TEST_F(ValidatorTest, TestIsRegexMatch)
70 EXPECT_FALSE(Validator::IsRegexMatch(simpleIdRe1, "wqeiqwo$"));70 EXPECT_FALSE(Validator::IsRegexMatch(simpleIdRe1, "wqeiqwo$"));
71}71}
72 72 
73+TEST_F(ValidatorTest, TestValidateLogName)
74+{
75+ EXPECT_TRUE(Validator::ValidateLogName("client_log_01"));
76+ EXPECT_FALSE(Validator::ValidateLogName(""));
77+ EXPECT_FALSE(Validator::ValidateLogName("client-log"));
78+ EXPECT_FALSE(Validator::ValidateLogName("client/log"));
79+ EXPECT_FALSE(Validator::ValidateLogName("client.log"));
80+}
81+ 
73TEST_F(ValidatorTest, TestValidateHostPortString)82TEST_F(ValidatorTest, TestValidateHostPortString)
74{83{
75 EXPECT_TRUE(Validator::ValidateHostPortString("FlagName", ""));84 EXPECT_TRUE(Validator::ValidateHostPortString("FlagName", ""));