mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-02 14:36:34 +08:00
fix: document telemetry opt-out and silence delivery failures (#2618)
* fix: document telemetry opt-out and silence delivery failures * style: format telemetry debug logging
This commit is contained in:
@@ -188,3 +188,7 @@ Thanks to all [contributors](https://github.com/langbot-app/LangBot/graphs/contr
|
||||
<a href="https://github.com/langbot-app/LangBot/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=langbot-app/LangBot" />
|
||||
</a>
|
||||
|
||||
## Optional telemetry
|
||||
|
||||
Telemetry is enabled by default. To opt out, set `space.disable_telemetry: true` in your instance’s `data/config.yaml`, preserve other settings, and restart the instance. This disables usage, heartbeat and execution telemetry. In Cloud, the instance operator controls this setting for every workspace in the instance. Delivery is best-effort in background tasks; connection failures, timeouts and server errors do not block normal operations. Failure details are logged only at DEBUG level.
|
||||
|
||||
@@ -212,3 +212,7 @@ LangBot **从设计上就对 Agent 友好** —— 你的编码 Agent(Claude C
|
||||
|
||||
If you're a code agent based on LLM, you should read [AGENTS.md](AGENTS.md) to understand the project structure and development process before you start any development.
|
||||
-->
|
||||
|
||||
## 可选遥测
|
||||
|
||||
遥测默认开启。可以在实例 `data/config.yaml` 中将 `space.disable_telemetry` 改为 `true`,保留其他配置,然后重启实例。此开关关闭使用统计、实例心跳和功能执行遥测;Cloud 由实例运维方配置,对该实例所有工作区生效。遥测在后台尽力发送,网络故障、超时或服务端错误不会阻断正常操作;失败详情仅记录在 DEBUG 日志中。
|
||||
|
||||
@@ -170,9 +170,7 @@ class TelemetryManager:
|
||||
|
||||
if resp.status_code >= 400:
|
||||
body = await httpclient.response_text(resp, max_chars=200)
|
||||
self.ap.logger.warning(
|
||||
f'Telemetry post to {url} returned status {resp.status_code} - {body}'
|
||||
)
|
||||
self.ap.logger.debug(f'Telemetry post to {url} returned status {resp.status_code} - {body}')
|
||||
else:
|
||||
# Detect application-level errors inside HTTP 200 responses
|
||||
app_err = False
|
||||
@@ -181,7 +179,7 @@ class TelemetryManager:
|
||||
app_code = j.get('code') if isinstance(j, dict) else None
|
||||
if app_code is not None and int(app_code) >= 400:
|
||||
app_err = True
|
||||
self.ap.logger.warning(
|
||||
self.ap.logger.debug(
|
||||
f'Telemetry post to {url} returned application error code {j.get("code")} - {j.get("msg")}'
|
||||
)
|
||||
except Exception:
|
||||
@@ -189,7 +187,7 @@ class TelemetryManager:
|
||||
|
||||
if app_err:
|
||||
body = await httpclient.response_text(resp, max_chars=200)
|
||||
self.ap.logger.warning(
|
||||
self.ap.logger.debug(
|
||||
f'Telemetry post to {url} returned app-level error - response: {body}'
|
||||
)
|
||||
else:
|
||||
@@ -200,19 +198,19 @@ class TelemetryManager:
|
||||
if not app_err:
|
||||
return True
|
||||
except asyncio.TimeoutError:
|
||||
self.ap.logger.warning(f'Telemetry post to {url} timed out')
|
||||
self.ap.logger.debug(f'Telemetry post to {url} timed out')
|
||||
except Exception as e:
|
||||
self.ap.logger.warning(f'Failed to post telemetry to {url}: {e}', exc_info=True)
|
||||
self.ap.logger.debug(f'Failed to post telemetry to {url}: {e}', exc_info=True)
|
||||
except Exception as e:
|
||||
try:
|
||||
self.ap.logger.warning(
|
||||
self.ap.logger.debug(
|
||||
f'Failed to create HTTP client for telemetry or sanitize payload: {e}', exc_info=True
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
# Never raise from telemetry; surface as warning for visibility
|
||||
# Never raise from telemetry; diagnostics are debug-only.
|
||||
try:
|
||||
self.ap.logger.warning(f'Unexpected telemetry error: {e}', exc_info=True)
|
||||
self.ap.logger.debug(f'Unexpected telemetry error: {e}', exc_info=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -426,5 +426,6 @@ space:
|
||||
# OAuth authorization page URL (user will be redirected here)
|
||||
oauth_authorize_url: 'https://space.langbot.app/auth/authorize'
|
||||
disable_models_service: false
|
||||
# Disable usage telemetry.
|
||||
# Optional telemetry is enabled by default. Set true to disable usage,
|
||||
# heartbeat and execution reporting; restart after changing this setting.
|
||||
disable_telemetry: false
|
||||
|
||||
@@ -431,6 +431,7 @@ class TestHTTPScenarios:
|
||||
with patch.object(httpx, 'AsyncClient', return_value=mock_client):
|
||||
await manager.send({'query_id': 'test'})
|
||||
|
||||
mock_app.logger.warning.assert_not_called()
|
||||
mock_app.logger.debug.assert_called()
|
||||
# Verify debug message contains URL and status
|
||||
debug_call_args = mock_app.logger.debug.call_args[0][0]
|
||||
@@ -459,8 +460,9 @@ class TestHTTPScenarios:
|
||||
with patch.object(httpx, 'AsyncClient', return_value=mock_client):
|
||||
await manager.send({'query_id': 'test'})
|
||||
|
||||
mock_app.logger.warning.assert_called()
|
||||
warning_call_args = mock_app.logger.warning.call_args[0][0]
|
||||
mock_app.logger.warning.assert_not_called()
|
||||
mock_app.logger.debug.assert_called()
|
||||
warning_call_args = mock_app.logger.debug.call_args[0][0]
|
||||
assert 'status 500' in warning_call_args
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -488,9 +490,9 @@ class TestHTTPScenarios:
|
||||
await manager.send({'query_id': 'test'})
|
||||
|
||||
# Source code calls warning twice for application errors
|
||||
assert mock_app.logger.warning.call_count >= 1
|
||||
assert mock_app.logger.debug.call_count >= 1
|
||||
# Check that one of the calls contains application error info
|
||||
all_warnings = [call[0][0] for call in mock_app.logger.warning.call_args_list]
|
||||
all_warnings = [call[0][0] for call in mock_app.logger.debug.call_args_list]
|
||||
assert any('400' in w for w in all_warnings), f'No warning contained error code 400: {all_warnings}'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -516,8 +518,9 @@ class TestHTTPScenarios:
|
||||
with patch.object(httpx, 'AsyncClient', return_value=mock_client):
|
||||
await manager.send({'query_id': 'test'})
|
||||
|
||||
mock_app.logger.warning.assert_called()
|
||||
warning_call_args = mock_app.logger.warning.call_args[0][0]
|
||||
mock_app.logger.warning.assert_not_called()
|
||||
mock_app.logger.debug.assert_called()
|
||||
warning_call_args = mock_app.logger.debug.call_args[0][0]
|
||||
assert 'timed out' in warning_call_args
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -542,7 +545,8 @@ class TestHTTPScenarios:
|
||||
# Should not raise exception
|
||||
await manager.send({'query_id': 'test'})
|
||||
|
||||
mock_app.logger.warning.assert_called()
|
||||
mock_app.logger.warning.assert_not_called()
|
||||
mock_app.logger.debug.assert_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_never_raises_exception(self):
|
||||
@@ -551,7 +555,7 @@ class TestHTTPScenarios:
|
||||
mock_app = Mock()
|
||||
# Even logger may fail
|
||||
mock_app.logger = Mock()
|
||||
mock_app.logger.warning = Mock(side_effect=Exception('Logger failed'))
|
||||
mock_app.logger.debug = Mock(side_effect=Exception('Logger failed'))
|
||||
|
||||
manager = telemetry.TelemetryManager(mock_app)
|
||||
manager.telemetry_config = {'url': 'https://example.com'}
|
||||
|
||||
Reference in New Issue
Block a user