已合并
fix(logging): 统一日志文件名校验规范 #2124
guoyang创建于 4 天前
fix(logging): 统一日志文件名校验规范 #2124
已合并
共 26 个文件变更+275-74
| @@ -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 | ||
| 429 | TransferEngine 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. | 429 | TransferEngine 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: |
| @@ -52,6 +52,9 @@ | |||
| 52 | - `Logging::Start()` initializes client-specific config from environment when the explicit process role is | 52 | - `Logging::Start()` initializes client-specific config from environment when the explicit process role is |
| 53 | `LogProcessRole::CLIENT`; worker and coordinator callers pass `LogProcessRole::WORKER` and | 53 | `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 starts | 58 | - 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-time | 60 | - when `log_monitor` is enabled, `AccessRecorderManager` initializes exporters according to its construction-time |
| @@ -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", |
| @@ -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", |
| @@ -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 | ||
| @@ -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 | ||
| @@ -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 | ||
| @@ -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为单位) | |
| @@ -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); |
| @@ -188,7 +188,7 @@ global: | |||
| 188 | # When enabled, requests whose RPC sub-phases exceed this threshold will include | 188 | # 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: 5000 | 190 | 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 metrics | 193 | # Interval between logging stream metrics |
| 194 | scMetricsLogIntervalS: 60 | 194 | scMetricsLogIntervalS: 60 |
| @@ -184,7 +184,8 @@ constexpr int64_t DRAINING_LOCATION_REFRESH_INITIAL_BACKOFF_MS = 1; | |||
| 184 | constexpr int32_t HASH_RING_RPC_MIN_TIMEOUT_MS = 100; | 184 | constexpr int32_t HASH_RING_RPC_MIN_TIMEOUT_MS = 100; |
| 185 | constexpr int BOUND_WORKER_PROBE_TIMEOUT_MS = 10; | 185 | constexpr int BOUND_WORKER_PROBE_TIMEOUT_MS = 10; |
| 186 | constexpr int TRANSPORT_DIAG_LOG_RATE = 100; | 186 | constexpr 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 | ||
| 406 | void ApplyKvClientLogConfig(const KVClientConfig &clientConfig) | 407 | void 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 | } |
| @@ -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 | ) |
| @@ -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"); |
| 42 | DS_DEFINE_string(log_filename, "", | 42 | DS_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."); | ||
| 44 | DS_DEFINE_string(curve_key_dir, "", | 45 | DS_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)."); |
| @@ -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 | } // namespace | 235 | } // namespace |
| 226 | 236 | ||
| 227 | DS_DEFINE_validator(l2_cache_type, &Validator::ValidateL2CacheType); | 237 | DS_DEFINE_validator(l2_cache_type, &Validator::ValidateL2CacheType); |
| @@ -253,7 +263,7 @@ DS_DEFINE_validator(rebalance_usage_gap_percent, &ValidatePercent); | |||
| 253 | DS_DEFINE_validator(rebalance_task_report_grace_ms, &Validator::ValidateUint32); | 263 | DS_DEFINE_validator(rebalance_task_report_grace_ms, &Validator::ValidateUint32); |
| 254 | DS_DEFINE_validator(monitor_config_file, &Validator::ValidatePathString); | 264 | DS_DEFINE_validator(monitor_config_file, &Validator::ValidatePathString); |
| 255 | DS_DEFINE_validator(unix_domain_socket_dir, &Validator::ValidateUnixDomainSocketDir); | 265 | DS_DEFINE_validator(unix_domain_socket_dir, &Validator::ValidateUnixDomainSocketDir); |
| 256 | -DS_DEFINE_validator(log_filename, &Validator::ValidateEligibleChar); | 266 | +DS_DEFINE_validator(log_filename, &ValidateOptionalLogName); |
| 257 | DS_DEFINE_validator(curve_key_dir, &Validator::ValidatePathString); | 267 | DS_DEFINE_validator(curve_key_dir, &Validator::ValidatePathString); |
| 258 | DS_DEFINE_validator(shared_disk_directory, &Validator::ValidatePathString); | 268 | DS_DEFINE_validator(shared_disk_directory, &Validator::ValidatePathString); |
| 259 | DS_DEFINE_validator(distributed_disk_path, &Validator::ValidatePathString); | 269 | DS_DEFINE_validator(distributed_disk_path, &Validator::ValidatePathString); |
| @@ -18,8 +18,6 @@ | |||
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | 20 | ||
| 21 | - | ||
| 22 | - | ||
| 23 | 21 | ||
| 24 | 22 | ||
| 25 | namespace datasystem { | 23 | namespace 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 | ||
| @@ -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, |
| @@ -38,6 +38,7 @@ | |||
| 38 | 38 | ||
| 39 | 39 | ||
| 40 | 40 | ||
| 41 | + | ||
| 41 | 42 | ||
| 42 | 43 | ||
| 43 | DS_DECLARE_bool(log_monitor); | 44 | DS_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 | ||
| @@ -28,7 +28,6 @@ | |||
| 28 | 28 | ||
| 29 | 29 | ||
| 30 | 30 | ||
| 31 | - | ||
| 32 | 31 | ||
| 33 | 32 | ||
| 34 | 33 | ||
| @@ -141,6 +140,8 @@ namespace { | |||
| 141 | std::mutex g_clientLogConfigMutex; | 140 | std::mutex g_clientLogConfigMutex; |
| 142 | bool g_hasClientLogWithoutPidConfig = false; | 141 | bool g_hasClientLogWithoutPidConfig = false; |
| 143 | bool g_clientLogWithoutPidConfig = false; | 142 | bool g_clientLogWithoutPidConfig = false; |
| 143 | +bool g_hasClientLogNameConfig = false; | ||
| 144 | +std::string g_clientLogNameConfig; | ||
| 144 | bool g_hasClientAccessLogNameConfig = false; | 145 | bool g_hasClientAccessLogNameConfig = false; |
| 145 | std::string g_clientAccessLogNameConfig; | 146 | std::string g_clientAccessLogNameConfig; |
| 146 | } // namespace | 147 | } // 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 | + | ||
| 484 | void Logging::SetClientAccessLogName(const std::string &logName) | 502 | void 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 variable | 583 | // 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 datasystem | 656 | } // namespace datasystem |
| @@ -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. |
| @@ -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. |
| @@ -44,7 +44,7 @@ void SignalHandler(int signum) | |||
| 44 | int main(int argc, char **argv) | 44 | int 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; |
| @@ -26,6 +26,7 @@ | |||
| 26 | 26 | ||
| 27 | 27 | ||
| 28 | 28 | ||
| 29 | + | ||
| 29 | 30 | ||
| 30 | 31 | ||
| 31 | 32 | ||
| @@ -37,6 +38,7 @@ DS_DECLARE_uint32(max_log_size); | |||
| 37 | DS_DECLARE_uint32(log_async_queue_size); | 38 | DS_DECLARE_uint32(log_async_queue_size); |
| 38 | DS_DECLARE_int32(v); | 39 | DS_DECLARE_int32(v); |
| 39 | DS_DECLARE_string(monitor_config_file); | 40 | DS_DECLARE_string(monitor_config_file); |
| 41 | +DS_DECLARE_string(log_filename); | ||
| 40 | 42 | ||
| 41 | namespace datasystem { | 43 | namespace datasystem { |
| 42 | namespace st { | 44 | namespace 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 | + | ||
| 218 | TEST_F(KVClientInitTest, EmptyMonitorConfigPathAllowsUpdateConfig) | 249 | TEST_F(KVClientInitTest, EmptyMonitorConfigPathAllowsUpdateConfig) |
| 219 | { | 250 | { |
| 220 | if (FLAGS_use_brpc) { | 251 | if (FLAGS_use_brpc) { |
| @@ -60,6 +60,7 @@ DS_DECLARE_uint32(log_retention_day); | |||
| 60 | DS_DECLARE_uint32(max_log_file_num); | 60 | DS_DECLARE_uint32(max_log_file_num); |
| 61 | DS_DECLARE_uint32(max_log_size); | 61 | DS_DECLARE_uint32(max_log_size); |
| 62 | DS_DECLARE_uint32(stderrthreshold); | 62 | DS_DECLARE_uint32(stderrthreshold); |
| 63 | +DS_DECLARE_string(log_filename); | ||
| 63 | DS_DECLARE_string(monitor_config_file); | 64 | DS_DECLARE_string(monitor_config_file); |
| 64 | 65 | ||
| 65 | namespace { | 66 | namespace { |
| @@ -282,6 +283,10 @@ private: | |||
| 282 | void ApplyKvClientLogConfigFromConfig(const KVClientConfig &clientConfig) | 283 | void 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 | } // namespace | 312 | } // 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 | + | ||
| 482 | TEST_F(ClientLogConfigPriorityTest, ExplicitEmptyClientAccessLogNameIsNotOverriddenByEnv) | 513 | TEST_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 | ||
| 645 | TEST_F(FlagsTest, KVClientConfigBuilderAcceptsEmptyMonitorConfigPath) | 695 | TEST_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 | ||
| 694 | TEST_F(FlagsTest, ParseCommandLineFlagsClearsErrorStateOnRetry) | 768 | TEST_F(FlagsTest, ParseCommandLineFlagsClearsErrorStateOnRetry) |
| @@ -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 | ) |
| @@ -54,6 +54,7 @@ | |||
| 54 | 54 | ||
| 55 | 55 | ||
| 56 | 56 | ||
| 57 | + | ||
| 57 | 58 | ||
| 58 | 59 | ||
| 59 | DS_DECLARE_bool(alsologtostderr); | 60 | DS_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 ut | 1093 | } // namespace ut |
| 1061 | } // namespace datasystem | 1094 | } // namespace datasystem |
| @@ -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 | + | ||
| 73 | TEST_F(ValidatorTest, TestValidateHostPortString) | 82 | TEST_F(ValidatorTest, TestValidateHostPortString) |
| 74 | { | 83 | { |
| 75 | EXPECT_TRUE(Validator::ValidateHostPortString("FlagName", "")); | 84 | EXPECT_TRUE(Validator::ValidateHostPortString("FlagName", "")); |