feat(logging): add startup log truncation env flag and sync docs

This commit is contained in:
rookiestar28
2026-02-28 22:52:07 +08:00
parent 06a933dcd0
commit 9d4965c5ac
19 changed files with 222 additions and 100 deletions
+9 -2
View File
@@ -15,7 +15,9 @@ ComfyUI-OpenClaw is a **security-first orchestration layer** for ComfyUI that co
This project is designed to make **ComfyUI a reliable automation target** with an explicit admin boundary and hardened defaults.
---
<center>
<img src="assets/adminMobileConsole.png" width="70%" />
</center>
## Security stance (how this project differs from convenience-first automation packs):
@@ -48,6 +50,8 @@ This project is designed to make **ComfyUI a reliable automation target** with a
Deployment profiles and hardening checklists:
- [Security Deployment Guide](docs/security_deployment_guide.md) (local / LAN / public templates + self-check command)
- [Security Key/Token Lifecycle SOP](docs/security_key_lifecycle_sop.md) (trust-root, secrets key, and bridge token rotation/revocation/disaster recovery)
- [Security Checklist](docs/security_checklist.md) (pre-exposure operational checklist for connector and ingress boundaries)
- [Runtime Hardening and Startup](docs/runtime_hardening_and_startup.md) (runtime profile, startup gate, and hardened baseline behaviors)
@@ -475,11 +479,13 @@ Remote admin actions are denied by default. If you understand the risk and need
- PowerShell (current session only):
- `$env:OPENCLAW_LLM_API_KEY="<YOUR_API_KEY>"`
- `$env:OPENCLAW_ADMIN_TOKEN="<YOUR_ADMIN_TOKEN>"`
- `$env:OPENCLAW_LOG_TRUNCATE_ON_START="1"` (optional: clear previous `openclaw.log` at startup)
- PowerShell (persistent; takes effect in new shells):
- `setx OPENCLAW_LLM_API_KEY "<YOUR_API_KEY>"`
- `setx OPENCLAW_ADMIN_TOKEN "<YOUR_ADMIN_TOKEN>"`
- `setx OPENCLAW_LOG_TRUNCATE_ON_START "1"` (optional)
- CMD (current session only): `set OPENCLAW_LLM_API_KEY=<YOUR_API_KEY>`
- Portable `.bat` launchers: add `set OPENCLAW_LLM_API_KEY=...` / `set OPENCLAW_ADMIN_TOKEN=...` before launching ComfyUI.
- Portable `.bat` launchers: add `set OPENCLAW_LLM_API_KEY=...` / `set OPENCLAW_ADMIN_TOKEN=...` (optionally `set OPENCLAW_LOG_TRUNCATE_ON_START=1`) before launching ComfyUI.
- ComfyUI Desktop: if env vars are not passed through reliably, prefer the Settings UI key store for localhost-only convenience, or set system-wide env vars.
## Remote Admin Console (Mobile UI)
@@ -933,6 +939,7 @@ Override:
Logs:
- `openclaw.log` (legacy `moltbot.log` is still supported)
- Optional startup truncation: set `OPENCLAW_LOG_TRUNCATE_ON_START=1` to clear the active log file once at process startup (useful to avoid stale-history noise in UI log views).
- Optional structured JSON logs for selected core paths:
- set `OPENCLAW_LOG_FORMAT=json` (or `OPENCLAW_STRUCTURED_LOGS=1`) before startup
- default behavior remains plain text logs (no structured log emission unless opt-in)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+35
View File
@@ -84,6 +84,40 @@ else:
DATA_DIR = os.path.join(PACK_DIR, "data")
LOG_FILE = os.path.join(DATA_DIR, "openclaw.log")
# IMPORTANT: startup log truncation must run once per process.
# Multiple module-level loggers call setup_logger(); repeated truncation would
# erase fresh logs emitted after the first logger initialization.
_LOG_TRUNCATE_APPLIED = False
def _is_env_enabled(*keys: str) -> bool:
for key in keys:
val = (os.environ.get(key) or "").strip().lower()
if val in {"1", "true", "yes", "on"}:
return True
return False
def _maybe_truncate_log_on_start(logger: logging.Logger) -> None:
global _LOG_TRUNCATE_APPLIED
if _LOG_TRUNCATE_APPLIED:
return
if not _is_env_enabled(
"OPENCLAW_LOG_TRUNCATE_ON_START", "MOLTBOT_LOG_TRUNCATE_ON_START"
):
return
try:
os.makedirs(DATA_DIR, exist_ok=True)
with open(LOG_FILE, "w", encoding="utf-8"):
pass
logger.info(
f"Startup log truncation applied for {LOG_FILE} "
"(OPENCLAW_LOG_TRUNCATE_ON_START=1)"
)
_LOG_TRUNCATE_APPLIED = True
except Exception as e:
logger.warning(f"Failed to truncate startup log file {LOG_FILE}: {e}")
class RedactedFormatter(logging.Formatter):
"""
@@ -136,6 +170,7 @@ def setup_logger(name: str = "ComfyUI-OpenClaw") -> logging.Logger:
# Only add handler if not already added to avoid duplicates on reload
if not logger.handlers:
_maybe_truncate_log_on_start(logger)
api_key = get_api_key()
sensitive = [api_key] if api_key else []
formatter = RedactedFormatter(
+1
View File
@@ -7,6 +7,7 @@ This guide covers optional, high-control features that are disabled by default.
- Remote registry sync uses quarantine and trust policy controls.
- Constrained transforms execute trusted Python modules with strict runtime limits.
- Both features are fail-closed when disabled.
- Optional operational log hygiene: `OPENCLAW_LOG_TRUNCATE_ON_START=1` clears stale `openclaw.log` at backend startup (once per process).
## Remote registry sync
+1
View File
@@ -45,6 +45,7 @@ Set the following environment variables (or put them in a `.env` file if you use
- `OPENCLAW_CONNECTOR_DEBUG`: Set to `1` for verbose logs.
- `OPENCLAW_CONNECTOR_ADMIN_USERS`: Comma-separated list of user IDs allowed to run admin commands (for example `/stop`, approvals, schedules). Admin users are also treated as trusted senders for `/run`.
- `OPENCLAW_CONNECTOR_ADMIN_TOKEN`: Admin token sent to OpenClaw (`X-OpenClaw-Admin-Token`).
- `OPENCLAW_LOG_TRUNCATE_ON_START`: Optional backend runtime flag. Set `1` to clear `openclaw.log` once at backend startup to avoid stale-history noise in UI log panels.
**Admin token behavior:**
+3
View File
@@ -40,6 +40,9 @@ OPENCLAW_ALLOW_REMOTE_ADMIN=1
# Require a token for Logs/Config viewing
OPENCLAW_OBSERVABILITY_TOKEN=observability-secret
# Optional startup log hygiene (avoid stale historical error lines in UI)
OPENCLAW_LOG_TRUNCATE_ON_START=1
```
### 3. Firewall Rules (Host)
+2
View File
@@ -40,6 +40,8 @@ No special configuration is required.
- Keep SSRF relax flags disabled:
- `OPENCLAW_ALLOW_ANY_PUBLIC_LLM_HOST=0`
- `OPENCLAW_ALLOW_INSECURE_BASE_URL=0`
- **Optional log hygiene**:
- `OPENCLAW_LOG_TRUNCATE_ON_START=1` clears stale `openclaw.log` content once at startup.
### 3. "Red Lines" (What NOT to do)
+1
View File
@@ -70,6 +70,7 @@ Only do this on trusted/private access planes and keep backend protection enable
- `OPENCLAW_ADMIN_TOKEN=<strong-secret>`
- `OPENCLAW_ALLOW_REMOTE_ADMIN=1`
- `OPENCLAW_LOG_TRUNCATE_ON_START=1` (optional, startup log hygiene)
Use one more auth boundary at proxy layer (IP allowlist, SSO, or basic auth), for example:
+3
View File
@@ -12,6 +12,9 @@ OPENCLAW_OBSERVABILITY_TOKEN=change-me-too
OPENCLAW_BRIDGE_ENABLED=0
# OPENCLAW_BRIDGE_DEVICE_TOKEN=
# Optional startup log hygiene (truncate openclaw.log once per process start)
# OPENCLAW_LOG_TRUNCATE_ON_START=1
# Network
# Bind to localhost by default
COMFYUI_LISTEN=127.0.0.1
+1
View File
@@ -46,6 +46,7 @@ To be safe:
1. Set `OPENCLAW_ADMIN_TOKEN` to a strong secret.
2. Enforce `OPENCLAW_OBSERVABILITY_TOKEN` if you plan to view logs remotely.
3. Optional: set `OPENCLAW_LOG_TRUNCATE_ON_START=1` to clear stale `openclaw.log` at startup.
### 4. "Red Lines"
+3
View File
@@ -14,6 +14,7 @@ To set OpenClaw security tokens in the portable version, edit your `run_nvidia_g
set OPENCLAW_ADMIN_TOKEN=my-secret-token
set OPENCLAW_ALLOW_REMOTE_ADMIN=1
set OPENCLAW_OBSERVABILITY_TOKEN=observability-token
set OPENCLAW_LOG_TRUNCATE_ON_START=1
:: Run ComfyUI
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build
@@ -25,6 +26,7 @@ pause
```powershell
$env:OPENCLAW_ADMIN_TOKEN="my-secret-token"
$env:OPENCLAW_ALLOW_REMOTE_ADMIN="1"
$env:OPENCLAW_LOG_TRUNCATE_ON_START="1"
./python_embeded/python.exe -s ComfyUI/main.py
```
@@ -35,6 +37,7 @@ If you want to open the standalone remote admin page from another device in your
```powershell
$env:OPENCLAW_ADMIN_TOKEN="my-secret-token"
$env:OPENCLAW_ALLOW_REMOTE_ADMIN="1"
$env:OPENCLAW_LOG_TRUNCATE_ON_START="1"
./python_embeded/python.exe -s ComfyUI/main.py --listen 0.0.0.0 --port 8188
```
+45 -98
View File
@@ -1,14 +1,24 @@
openapi: "3.0.3"
info:
title: "ComfyUI-OpenClaw API"
version: "1.0.2"
description: "Generated from docs/release/api_contract.md (v1.0.2 baseline)."
version: "1.0.0"
description: "Generated from docs/release/api_contract.md (R66 baseline)."
servers:
- url: "/openclaw"
description: "Direct OpenClaw prefix"
- url: "/api/openclaw"
description: "ComfyUI /api shim"
paths:
/admin:
get:
operationId: "get_admin"
summary: "Standalone remote admin console HTML shell (mobile-friendly)."
responses:
200:
description: "OK"
x-openclaw-auth: "None*"
x-openclaw-section: "1.0 UI Entry Points"
x-openclaw-legacy-path: "/moltbot/admin"
/health:
get:
operationId: "get_health"
@@ -31,17 +41,6 @@ paths:
x-openclaw-section: "1.1 Core Observability & System"
x-openclaw-legacy-path: "/moltbot/capabilities"
x-openclaw-auth-tier: "none"
/admin:
get:
operationId: "get_admin_console"
summary: "Standalone remote admin console HTML shell (mobile-friendly)."
responses:
200:
description: "OK"
x-openclaw-auth: "None*"
x-openclaw-section: "1.0 UI Entry Points"
x-openclaw-legacy-path: "/moltbot/admin"
x-openclaw-auth-tier: "none"
/logs/tail:
get:
operationId: "get_logs_tail"
@@ -260,7 +259,7 @@ paths:
- OpenClawAdminToken:
[]
x-openclaw-streaming: true
/llm/chat:
/chat:
post:
operationId: "post_chat"
summary: "Unified chat interface for assistant interactions."
@@ -274,7 +273,7 @@ paths:
security:
- OpenClawAdminToken:
[]
/llm/test:
/test:
post:
operationId: "post_test"
summary: "Test LLM connectivity and configuration."
@@ -288,7 +287,7 @@ paths:
security:
- OpenClawAdminToken:
[]
/llm/models:
/models:
get:
operationId: "get_models"
summary: "List available models from configured provider."
@@ -437,44 +436,32 @@ paths:
security:
- OpenClawAdminToken:
[]
/schedules:
/openclaw/schedules:
get:
operationId: "get_schedules"
operationId: "get_openclaw_schedules"
summary: "List all schedules."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
post:
operationId: "post_schedules"
operationId: "post_openclaw_schedules"
summary: "Create a new schedule."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
/schedules/{id}:
/openclaw/schedules/{id}:
get:
operationId: "get_schedules_id"
operationId: "get_openclaw_schedules_id"
summary: "Get schedule details."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
parameters:
- name: "id"
in: "path"
@@ -482,17 +469,13 @@ paths:
schema:
type: "string"
put:
operationId: "put_schedules_id"
operationId: "put_openclaw_schedules_id"
summary: "Update a schedule."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
parameters:
- name: "id"
in: "path"
@@ -500,106 +483,82 @@ paths:
schema:
type: "string"
delete:
operationId: "delete_schedules_id"
operationId: "delete_openclaw_schedules_id"
summary: "Delete a schedule."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
parameters:
- name: "id"
in: "path"
required: true
schema:
type: "string"
/schedules/{id}/run:
/openclaw/schedules/{id}/run:
post:
operationId: "post_schedules_id_run"
operationId: "post_openclaw_schedules_id_run"
summary: "Manually trigger a schedule."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
parameters:
- name: "id"
in: "path"
required: true
schema:
type: "string"
/schedules/{id}/runs:
/openclaw/schedules/{id}/runs:
get:
operationId: "get_schedules_id_runs"
operationId: "get_openclaw_schedules_id_runs"
summary: "Get run history for a schedule."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
parameters:
- name: "id"
in: "path"
required: true
schema:
type: "string"
/approvals:
/openclaw/approvals:
get:
operationId: "get_approvals"
operationId: "get_openclaw_approvals"
summary: "List pending approvals (includes pagination/scan diagnostics; bounded serialization scan on malformed records)."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
/approvals/{id}/approve:
/openclaw/approvals/{id}/approve:
post:
operationId: "post_approvals_id_approve"
operationId: "post_openclaw_approvals_id_approve"
summary: "Approve a pending request."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
parameters:
- name: "id"
in: "path"
required: true
schema:
type: "string"
/approvals/{id}/reject:
/openclaw/approvals/{id}/reject:
post:
operationId: "post_approvals_id_reject"
operationId: "post_openclaw_approvals_id_reject"
summary: "Reject a pending request."
responses:
200:
description: "OK"
x-openclaw-auth: "Admin"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.5 Schedules & Approvals"
x-openclaw-auth-tier: "admin"
security:
- OpenClawAdminToken:
[]
parameters:
- name: "id"
in: "path"
@@ -613,12 +572,8 @@ paths:
responses:
200:
description: "OK"
x-openclaw-auth: "Bridge Auth (Device Check)"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.6 Bridge (Sidecar)"
x-openclaw-auth-tier: "bridge"
security:
- OpenClawBridgeAuth:
[]
/bridge/submit:
post:
operationId: "post_bridge_submit"
@@ -626,12 +581,8 @@ paths:
responses:
200:
description: "OK"
x-openclaw-auth: "Bridge Auth (Device Check)"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.6 Bridge (Sidecar)"
x-openclaw-auth-tier: "bridge"
security:
- OpenClawBridgeAuth:
[]
/bridge/deliver:
post:
operationId: "post_bridge_deliver"
@@ -639,12 +590,8 @@ paths:
responses:
200:
description: "OK"
x-openclaw-auth: "Bridge Auth (Device Check)"
x-openclaw-auth: "Unknown"
x-openclaw-section: "1.6 Bridge (Sidecar)"
x-openclaw-auth-tier: "bridge"
security:
- OpenClawBridgeAuth:
[]
components:
securitySchemes:
OpenClawAdminToken:
+1
View File
@@ -98,6 +98,7 @@ Contractual limits to prevent resource exhaustion.
| Variable | Description |
| :--- | :--- |
| `OPENCLAW_STATE_DIR` | Directory for persistent state (DBs, history, logs). Default: `ComfyUI/user/default/openclaw` |
| `OPENCLAW_LOG_TRUNCATE_ON_START` | Set `1` to truncate active log file (`openclaw.log`) once at process startup before new handlers write records. |
| `OPENCLAW_DIAGNOSTICS` | Comma-separated list of subsystems to enable debug logging for (e.g. `webhook.*,templates`). Safe-redacted. |
| `OPENCLAW_CONNECTOR_DEBUG` | Set `1` to enable verbose debug logging in Connector. |
+1
View File
@@ -23,6 +23,7 @@ Users should audit these flags before deploying to a public or untrusted network
| `OPENCLAW_BRIDGE_ENABLED` | `0` | **High** | Enables the sidecar bridge for remote orchestration. Requires `OPENCLAW_BRIDGE_DEVICE_TOKEN` (and in public posture also mTLS + device allowlist controls). |
| `OPENCLAW_ALLOW_ANY_PUBLIC_LLM_HOST` | `0` | **High** | Bypasses the known-host allowlist for LLM `base_url`. Allows SSRF to public IPs. |
| `OPENCLAW_ALLOW_INSECURE_BASE_URL` | `0` | **Critical** | Allows HTTP (non-HTTPS) or private IP `base_url` for LLM. Risk of internal network scanning (SSRF). |
| `OPENCLAW_LOG_TRUNCATE_ON_START` | `0` | **Low** | Operational log hygiene toggle. If `1`, truncates active `openclaw.log` at startup (once per process). |
| `OPENCLAW_CONNECTOR_DISCORD_TOKEN` | *None* | **Medium** | Presence enables Discord Bot gateway. |
| `OPENCLAW_CONNECTOR_LINE_CHANNEL_SECRET` | *None* | **Medium** | Presence enables LINE webhook listener. Requires a public HTTPS endpoint. |
| `OPENCLAW_CONNECTOR_TELEGRAM_TOKEN` | *None* | **Low** | Presence enables Telegram long-polling. Outbound only. |
+2
View File
@@ -81,6 +81,8 @@ OPENCLAW_WEBHOOK_AUTH_MODE=hmac
OPENCLAW_WEBHOOK_HMAC_SECRET=replace-with-strong-secret
OPENCLAW_BRIDGE_ENABLED=1
OPENCLAW_BRIDGE_DEVICE_TOKEN=replace-with-bridge-device-token
# Optional: clear stale history in startup log views
OPENCLAW_LOG_TRUNCATE_ON_START=1
```
Then validate:
+1
View File
@@ -39,6 +39,7 @@
- [ ] `OPENCLAW_CONNECTOR_DEBUG=1` logs sensitive data — **disable in production**.
- [ ] Ensure no debug flags are set in production environment.
- [ ] Optional ops hygiene: if stale historical errors cause confusion in log viewers, use `OPENCLAW_LOG_TRUNCATE_ON_START=1` during controlled restart windows.
### 7. Tunnel / Reverse Proxy
+13
View File
@@ -55,6 +55,13 @@ Use `--strict-warnings` if you want warnings to fail the check in hardened pipel
python scripts/check_deployment_profile.py --profile public --strict-warnings
```
Optional operational log hygiene (all profiles):
```bash
# Clear active openclaw.log once at startup (useful to avoid stale UI log noise)
OPENCLAW_LOG_TRUNCATE_ON_START=1
```
## 3. Local (Single-user)
### 3.1 Pasteable config template
@@ -72,6 +79,8 @@ OPENCLAW_SECURITY_DANGEROUS_BIND_OVERRIDE=0
# Optional but recommended
OPENCLAW_ADMIN_TOKEN=change-this-local-admin-token
# Optional startup log hygiene
# OPENCLAW_LOG_TRUNCATE_ON_START=1
```
### 3.2 Checklist
@@ -107,6 +116,8 @@ OPENCLAW_ENABLE_TRANSFORMS=0
OPENCLAW_ALLOW_ANY_PUBLIC_LLM_HOST=0
OPENCLAW_ALLOW_INSECURE_BASE_URL=0
OPENCLAW_SECURITY_DANGEROUS_BIND_OVERRIDE=0
# Optional startup log hygiene
# OPENCLAW_LOG_TRUNCATE_ON_START=1
```
### 4.2 Checklist
@@ -156,6 +167,8 @@ OPENCLAW_ENABLE_TRANSFORMS=0
OPENCLAW_ALLOW_ANY_PUBLIC_LLM_HOST=0
OPENCLAW_ALLOW_INSECURE_BASE_URL=0
OPENCLAW_SECURITY_DANGEROUS_BIND_OVERRIDE=0
# Optional startup log hygiene
# OPENCLAW_LOG_TRUNCATE_ON_START=1
```
### 5.2 Checklist
+3
View File
@@ -19,6 +19,9 @@ All paths are relative to `OPENCLAW_STATE_DIR` (legacy fallback: `MOLTBOT_STATE_
| Encrypted secret store | `secrets.enc.json` | Encrypted provider secrets |
| Bridge token registry | `bridge_tokens.json` | Device token lifecycle state and audit trail |
Optional startup log hygiene for incident drills:
- Set `OPENCLAW_LOG_TRUNCATE_ON_START=1` before restart when you need a clean `openclaw.log` timeline.
## 2. Global Rules
1. Always take a timestamped backup before any lifecycle operation.
+97
View File
@@ -0,0 +1,97 @@
import logging
import os
import tempfile
import unittest
from unittest.mock import patch
import config
class TestLogTruncateOnStart(unittest.TestCase):
def setUp(self):
self._orig_log_file = config.LOG_FILE
self._orig_data_dir = config.DATA_DIR
self._orig_applied = getattr(config, "_LOG_TRUNCATE_APPLIED", False)
self.tmp = tempfile.TemporaryDirectory()
self.log_file = os.path.join(self.tmp.name, "openclaw.log")
self._logger_names: set[str] = set()
def tearDown(self):
for name in list(self._logger_names):
self._reset_logger(name)
config.LOG_FILE = self._orig_log_file
config.DATA_DIR = self._orig_data_dir
config._LOG_TRUNCATE_APPLIED = self._orig_applied
self.tmp.cleanup()
def _reset_logger(self, name: str) -> None:
logger = logging.getLogger(name)
for h in list(logger.handlers):
try:
h.close()
finally:
logger.removeHandler(h)
def _prepare_log_fixture(self, content: str) -> None:
os.makedirs(self.tmp.name, exist_ok=True)
with open(self.log_file, "w", encoding="utf-8") as f:
f.write(content)
config.LOG_FILE = self.log_file
config.DATA_DIR = self.tmp.name
config._LOG_TRUNCATE_APPLIED = False
def test_no_truncate_when_flag_disabled(self):
self._prepare_log_fixture("legacy-line\n")
logger_name = "test.log_truncate.disabled"
self._reset_logger(logger_name)
self._logger_names.add(logger_name)
with patch.dict(
os.environ, {"OPENCLAW_LOG_TRUNCATE_ON_START": "0"}, clear=False
):
config.setup_logger(logger_name)
with open(self.log_file, "r", encoding="utf-8") as f:
self.assertEqual(f.read(), "legacy-line\n")
def test_truncate_when_flag_enabled(self):
self._prepare_log_fixture("legacy-line\n")
logger_name = "test.log_truncate.enabled"
self._reset_logger(logger_name)
self._logger_names.add(logger_name)
with patch.dict(
os.environ, {"OPENCLAW_LOG_TRUNCATE_ON_START": "1"}, clear=False
):
config.setup_logger(logger_name)
with open(self.log_file, "r", encoding="utf-8") as f:
content = f.read()
self.assertNotIn("legacy-line", content)
self.assertEqual(content, "")
def test_truncate_applies_once_per_process(self):
self._prepare_log_fixture("legacy-line\n")
logger_name_a = "test.log_truncate.once.a"
logger_name_b = "test.log_truncate.once.b"
self._reset_logger(logger_name_a)
self._reset_logger(logger_name_b)
self._logger_names.update({logger_name_a, logger_name_b})
with patch.dict(
os.environ, {"OPENCLAW_LOG_TRUNCATE_ON_START": "1"}, clear=False
):
logger_a = config.setup_logger(logger_name_a)
logger_a.info("after-first-init")
logger_b = config.setup_logger(logger_name_b)
logger_b.info("after-second-init")
with open(self.log_file, "r", encoding="utf-8") as f:
content = f.read()
self.assertIn("after-first-init", content)
self.assertIn("after-second-init", content)
self.assertNotIn("legacy-line", content)
if __name__ == "__main__":
unittest.main()