aury-boot 0.0.29__py3-none-any.whl → 0.0.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +2 -4
- aury/boot/application/app/base.py +126 -2
- aury/boot/application/app/components.py +226 -1
- aury/boot/application/config/settings.py +201 -3
- aury/boot/application/constants/components.py +3 -0
- aury/boot/application/middleware/logging.py +45 -6
- aury/boot/commands/docs.py +40 -0
- aury/boot/commands/init.py +2 -0
- aury/boot/commands/templates/project/AGENTS.md.tpl +59 -0
- aury/boot/commands/templates/project/alert_rules.example.yaml.tpl +85 -0
- aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +3 -0
- aury/boot/commands/templates/project/aury_docs/17-alerting.md.tpl +210 -0
- aury/boot/commands/templates/project/env_templates/messaging.tpl +21 -13
- aury/boot/commands/templates/project/env_templates/monitoring.tpl +63 -0
- aury/boot/common/logging/context.py +17 -1
- aury/boot/common/logging/format.py +4 -0
- aury/boot/infrastructure/__init__.py +4 -8
- aury/boot/infrastructure/channel/__init__.py +9 -8
- aury/boot/infrastructure/channel/backends/__init__.py +2 -6
- aury/boot/infrastructure/channel/backends/broadcaster.py +141 -0
- aury/boot/infrastructure/channel/base.py +11 -4
- aury/boot/infrastructure/channel/manager.py +25 -24
- aury/boot/infrastructure/database/query_tools/__init__.py +3 -5
- aury/boot/infrastructure/events/__init__.py +4 -6
- aury/boot/infrastructure/events/backends/__init__.py +2 -4
- aury/boot/infrastructure/events/backends/broadcaster.py +189 -0
- aury/boot/infrastructure/events/base.py +9 -4
- aury/boot/infrastructure/events/manager.py +24 -20
- aury/boot/infrastructure/monitoring/__init__.py +210 -6
- aury/boot/infrastructure/monitoring/alerting/__init__.py +50 -0
- aury/boot/infrastructure/monitoring/alerting/aggregator.py +193 -0
- aury/boot/infrastructure/monitoring/alerting/events.py +141 -0
- aury/boot/infrastructure/monitoring/alerting/manager.py +430 -0
- aury/boot/infrastructure/monitoring/alerting/notifiers/__init__.py +16 -0
- aury/boot/infrastructure/monitoring/alerting/notifiers/base.py +60 -0
- aury/boot/infrastructure/monitoring/alerting/notifiers/feishu.py +209 -0
- aury/boot/infrastructure/monitoring/alerting/notifiers/webhook.py +110 -0
- aury/boot/infrastructure/monitoring/alerting/rules.py +179 -0
- aury/boot/infrastructure/monitoring/health/__init__.py +231 -0
- aury/boot/infrastructure/monitoring/tracing/__init__.py +55 -0
- aury/boot/infrastructure/monitoring/tracing/context.py +43 -0
- aury/boot/infrastructure/monitoring/tracing/logging.py +73 -0
- aury/boot/infrastructure/monitoring/tracing/processor.py +357 -0
- aury/boot/infrastructure/monitoring/tracing/provider.py +322 -0
- aury/boot/infrastructure/monitoring/tracing/tracing.py +235 -0
- {aury_boot-0.0.29.dist-info → aury_boot-0.0.31.dist-info}/METADATA +14 -1
- {aury_boot-0.0.29.dist-info → aury_boot-0.0.31.dist-info}/RECORD +50 -33
- aury/boot/infrastructure/channel/backends/memory.py +0 -126
- aury/boot/infrastructure/channel/backends/redis.py +0 -130
- aury/boot/infrastructure/events/backends/memory.py +0 -86
- aury/boot/infrastructure/events/backends/redis.py +0 -169
- {aury_boot-0.0.29.dist-info → aury_boot-0.0.31.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.29.dist-info → aury_boot-0.0.31.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# 告警系统
|
|
2
|
+
|
|
3
|
+
本文档介绍 {project_name} 项目中的告警系统配置和使用方法。
|
|
4
|
+
|
|
5
|
+
## 快速开始(简版配置)
|
|
6
|
+
|
|
7
|
+
所有告警发送到同一个飞书群:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# .env
|
|
11
|
+
ALERT__ENABLED=true
|
|
12
|
+
ALERT__NOTIFIERS__DEFAULT__TYPE=feishu
|
|
13
|
+
ALERT__NOTIFIERS__DEFAULT__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/xxx
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
框架会自动创建默认规则,检测慢请求、慢 SQL、异常并发送告警。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 完整版配置(分群告警)
|
|
21
|
+
|
|
22
|
+
不同类型的告警发送到不同的飞书群,避免一个群接收所有消息。
|
|
23
|
+
|
|
24
|
+
### 1. 环境变量
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# .env
|
|
28
|
+
|
|
29
|
+
# ============ 告警系统 ============
|
|
30
|
+
ALERT__ENABLED=true
|
|
31
|
+
ALERT__RULES_FILE=alert_rules.yaml
|
|
32
|
+
|
|
33
|
+
# 性能群(慢请求、慢SQL)
|
|
34
|
+
ALERT__NOTIFIERS__PERF_GROUP__TYPE=feishu
|
|
35
|
+
ALERT__NOTIFIERS__PERF_GROUP__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/perf-xxx
|
|
36
|
+
ALERT__NOTIFIERS__PERF_GROUP__SECRET=your-secret # 可选
|
|
37
|
+
|
|
38
|
+
# 错误群(异常)
|
|
39
|
+
ALERT__NOTIFIERS__ERROR_GROUP__TYPE=feishu
|
|
40
|
+
ALERT__NOTIFIERS__ERROR_GROUP__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/error-xxx
|
|
41
|
+
|
|
42
|
+
# 运维群(任务失败、超时)
|
|
43
|
+
ALERT__NOTIFIERS__OPS_GROUP__TYPE=feishu
|
|
44
|
+
ALERT__NOTIFIERS__OPS_GROUP__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/ops-xxx
|
|
45
|
+
|
|
46
|
+
# 慢操作阈值
|
|
47
|
+
ALERT__SLOW_REQUEST_THRESHOLD=1.0
|
|
48
|
+
ALERT__SLOW_SQL_THRESHOLD=0.5
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. 规则文件
|
|
52
|
+
|
|
53
|
+
生成规则模板:`aury docs alert-rules`
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
# alert_rules.yaml
|
|
57
|
+
defaults:
|
|
58
|
+
slow_request_threshold: 1.0
|
|
59
|
+
slow_sql_threshold: 0.5
|
|
60
|
+
aggregate_window: 10
|
|
61
|
+
suppress_seconds: 300
|
|
62
|
+
|
|
63
|
+
rules:
|
|
64
|
+
# 慢请求 → 性能群
|
|
65
|
+
- name: slow_request
|
|
66
|
+
event_types: [slow_request]
|
|
67
|
+
aggregate_threshold: 5
|
|
68
|
+
notifiers: [perf_group]
|
|
69
|
+
|
|
70
|
+
# 慢 SQL → 性能群
|
|
71
|
+
- name: slow_sql
|
|
72
|
+
event_types: [slow_sql]
|
|
73
|
+
aggregate_threshold: 10
|
|
74
|
+
notifiers: [perf_group]
|
|
75
|
+
|
|
76
|
+
# 异常 → 错误群(立即告警)
|
|
77
|
+
- name: exception
|
|
78
|
+
event_types: [exception]
|
|
79
|
+
aggregate_threshold: 1
|
|
80
|
+
suppress_seconds: 60
|
|
81
|
+
notifiers: [error_group]
|
|
82
|
+
|
|
83
|
+
# 任务失败/超时 → 运维群
|
|
84
|
+
- name: task_issues
|
|
85
|
+
event_types: [task_failure, task_timeout]
|
|
86
|
+
aggregate_threshold: 1
|
|
87
|
+
notifiers: [ops_group]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. 效果
|
|
91
|
+
|
|
92
|
+
| 事件类型 | 目标群 | 触发条件 |
|
|
93
|
+
|---------|--------|----------|
|
|
94
|
+
| 慢请求(>1s) | 性能群 | 60秒内累计5次 |
|
|
95
|
+
| 慢 SQL(>0.5s) | 性能群 | 60秒内累计10次 |
|
|
96
|
+
| 异常 | 错误群 | 立即告警 |
|
|
97
|
+
| 任务失败/超时 | 运维群 | 立即告警 |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 代码中手动发送告警
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from aury.boot.infrastructure.monitoring.alerting import emit_alert, AlertEventType, AlertSeverity
|
|
105
|
+
|
|
106
|
+
# 发送自定义告警
|
|
107
|
+
await emit_alert(
|
|
108
|
+
AlertEventType.CUSTOM,
|
|
109
|
+
"订单支付超时",
|
|
110
|
+
severity=AlertSeverity.WARNING,
|
|
111
|
+
order_id="12345",
|
|
112
|
+
user_id="u001",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# 发送慢 SQL 告警(通常由框架自动触发)
|
|
116
|
+
await emit_alert(
|
|
117
|
+
AlertEventType.SLOW_SQL,
|
|
118
|
+
"慢查询告警",
|
|
119
|
+
duration=2.5,
|
|
120
|
+
sql="SELECT * FROM orders WHERE ...",
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 通知器类型
|
|
127
|
+
|
|
128
|
+
### 飞书(feishu)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
ALERT__NOTIFIERS__XXX__TYPE=feishu
|
|
132
|
+
ALERT__NOTIFIERS__XXX__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/xxx
|
|
133
|
+
ALERT__NOTIFIERS__XXX__SECRET=xxx # 可选,签名密钥
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 通用 Webhook
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
ALERT__NOTIFIERS__XXX__TYPE=webhook
|
|
140
|
+
ALERT__NOTIFIERS__XXX__URL=https://your-system.com/alert
|
|
141
|
+
ALERT__NOTIFIERS__XXX__METHOD=POST
|
|
142
|
+
ALERT__NOTIFIERS__XXX__HEADERS='{{"Authorization": "Bearer xxx"}}'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 自定义通知器
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from aury.boot.infrastructure.monitoring.alerting import AlertNotifier, AlertManager
|
|
149
|
+
|
|
150
|
+
class DingTalkNotifier(AlertNotifier):
|
|
151
|
+
@classmethod
|
|
152
|
+
def from_config(cls, config: dict) -> "DingTalkNotifier":
|
|
153
|
+
return cls(webhook=config["webhook"])
|
|
154
|
+
|
|
155
|
+
async def send(self, notification) -> bool:
|
|
156
|
+
# 实现发送逻辑
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
# 注册
|
|
160
|
+
AlertManager.register_notifier_class("dingtalk", DingTalkNotifier)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
然后在环境变量中使用:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
ALERT__NOTIFIERS__DING__TYPE=dingtalk
|
|
167
|
+
ALERT__NOTIFIERS__DING__WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=xxx
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 告警事件类型
|
|
173
|
+
|
|
174
|
+
| 类型 | 说明 | 自动触发 |
|
|
175
|
+
|-----|------|----------|
|
|
176
|
+
| `slow_request` | 慢 HTTP 请求 | 框架自动检测 |
|
|
177
|
+
| `slow_sql` | 慢 SQL 查询 | 框架自动检测 |
|
|
178
|
+
| `exception` | 异常/错误 | 框架自动检测 |
|
|
179
|
+
| `task_failure` | 任务执行失败 | 任务系统触发 |
|
|
180
|
+
| `task_timeout` | 任务执行超时 | 任务系统触发 |
|
|
181
|
+
| `custom` | 自定义告警 | 手动调用 emit_alert |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 告警抑制与聚合
|
|
186
|
+
|
|
187
|
+
- **聚合窗口**:在窗口时间内累计触发次数,达到阈值才发送告警
|
|
188
|
+
- **抑制时间**:同一告警在抑制时间内不会重复发送
|
|
189
|
+
- **示例**:`aggregate_window=60, aggregate_threshold=5` 表示 60 秒内触发 5 次才告警
|
|
190
|
+
|
|
191
|
+
这样可以避免告警风暴,同时不遗漏重要问题。
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 环境变量参考
|
|
196
|
+
|
|
197
|
+
| 变量 | 说明 | 默认值 |
|
|
198
|
+
|------|------|--------|
|
|
199
|
+
| `ALERT__ENABLED` | 是否启用告警 | `false` |
|
|
200
|
+
| `ALERT__RULES_FILE` | 规则文件路径 | - |
|
|
201
|
+
| `ALERT__SLOW_REQUEST_THRESHOLD` | 慢请求阈值(秒) | `1.0` |
|
|
202
|
+
| `ALERT__SLOW_SQL_THRESHOLD` | 慢 SQL 阈值(秒) | `0.5` |
|
|
203
|
+
| `ALERT__ALERT_ON_SLOW_REQUEST` | 是否对慢请求告警 | `true` |
|
|
204
|
+
| `ALERT__ALERT_ON_SLOW_SQL` | 是否对慢 SQL 告警 | `true` |
|
|
205
|
+
| `ALERT__ALERT_ON_ERROR` | 是否对异常告警 | `true` |
|
|
206
|
+
| `ALERT__AGGREGATE_WINDOW` | 聚合窗口(秒) | `10` |
|
|
207
|
+
| `ALERT__SLOW_REQUEST_AGGREGATE` | 慢请求触发阈值(窗口内次数) | `5` |
|
|
208
|
+
| `ALERT__SLOW_SQL_AGGREGATE` | 慢 SQL 触发阈值 | `10` |
|
|
209
|
+
| `ALERT__EXCEPTION_AGGREGATE` | 异常触发阈值 | `1` |
|
|
210
|
+
| `ALERT__SUPPRESS_SECONDS` | 抑制时间(秒) | `300` |
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
# =============================================================================
|
|
3
3
|
# 流式通道配置 (CHANNEL__) - SSE/实时通信
|
|
4
4
|
# =============================================================================
|
|
5
|
+
# 基于 Broadcaster 库,通过 URL scheme 切换后端
|
|
6
|
+
# 支持: memory:// | redis://host:port/db | kafka://host:port | postgres://...
|
|
7
|
+
#
|
|
5
8
|
# 单实例配置:
|
|
6
|
-
# CHANNEL__BACKEND=
|
|
7
|
-
#
|
|
8
|
-
# CHANNEL__URL=redis://localhost:6379/3
|
|
9
|
+
# CHANNEL__BACKEND=broadcaster
|
|
10
|
+
# CHANNEL__URL=memory://
|
|
11
|
+
# 分布式: CHANNEL__URL=redis://localhost:6379/3
|
|
9
12
|
#
|
|
10
13
|
# 多实例配置 (格式: CHANNEL__{{INSTANCE}}__{{FIELD}}):
|
|
11
|
-
# CHANNEL__SSE__BACKEND=
|
|
12
|
-
#
|
|
14
|
+
# CHANNEL__SSE__BACKEND=broadcaster
|
|
15
|
+
# CHANNEL__SSE__URL=memory://
|
|
16
|
+
# CHANNEL__NOTIFICATION__BACKEND=broadcaster
|
|
13
17
|
# CHANNEL__NOTIFICATION__URL=redis://localhost:6379/3
|
|
14
18
|
|
|
15
19
|
# =============================================================================
|
|
@@ -32,18 +36,22 @@
|
|
|
32
36
|
# =============================================================================
|
|
33
37
|
# 事件总线配置 (EVENT__)
|
|
34
38
|
# =============================================================================
|
|
39
|
+
# 基于 Broadcaster 库,通过 URL scheme 切换后端
|
|
40
|
+
# 支持: memory:// | redis://host:port/db | kafka://host:port | postgres://...
|
|
41
|
+
# RabbitMQ 需使用 backend=rabbitmq
|
|
42
|
+
#
|
|
35
43
|
# 单实例配置:
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
|
|
44
|
+
# EVENT__BACKEND=broadcaster
|
|
45
|
+
# EVENT__URL=memory://
|
|
46
|
+
# 分布式: EVENT__URL=redis://localhost:6379/5
|
|
47
|
+
#
|
|
39
48
|
# 多实例配置 (格式: EVENT__{{INSTANCE}}__{{FIELD}}):
|
|
40
|
-
# EVENT__DEFAULT__BACKEND=
|
|
41
|
-
#
|
|
49
|
+
# EVENT__DEFAULT__BACKEND=broadcaster
|
|
50
|
+
# EVENT__DEFAULT__URL=memory://
|
|
51
|
+
# EVENT__DISTRIBUTED__BACKEND=broadcaster
|
|
42
52
|
# EVENT__DISTRIBUTED__URL=redis://localhost:6379/5
|
|
43
|
-
# EVENT__DISTRIBUTED__KEY_PREFIX=events:
|
|
44
53
|
#
|
|
45
|
-
# RabbitMQ
|
|
54
|
+
# RabbitMQ 后端 (复杂消息场景):
|
|
46
55
|
# EVENT__DOMAIN__BACKEND=rabbitmq
|
|
47
56
|
# EVENT__DOMAIN__URL=amqp://guest:guest@localhost:5672/
|
|
48
57
|
# EVENT__DOMAIN__EXCHANGE_NAME=domain.events
|
|
49
|
-
# EVENT__DOMAIN__EXCHANGE_TYPE=topic
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# 监控与告警配置
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
# ---------- OpenTelemetry 遥测 ----------
|
|
6
|
+
# 启用后自动 instrument FastAPI、SQLAlchemy、httpx
|
|
7
|
+
TELEMETRY__ENABLED=false
|
|
8
|
+
# TELEMETRY__SAMPLING_RATE=1.0
|
|
9
|
+
|
|
10
|
+
# OTLP 导出(可选)
|
|
11
|
+
# TELEMETRY__TRACES_ENDPOINT=http://jaeger:4317
|
|
12
|
+
# TELEMETRY__LOGS_ENDPOINT=http://loki:3100
|
|
13
|
+
# TELEMETRY__METRICS_ENDPOINT=http://prometheus:9090
|
|
14
|
+
|
|
15
|
+
# ---------- 告警系统 ----------
|
|
16
|
+
ALERT__ENABLED=false
|
|
17
|
+
# 慢操作阈值
|
|
18
|
+
ALERT__SLOW_REQUEST_THRESHOLD=1.0
|
|
19
|
+
ALERT__SLOW_SQL_THRESHOLD=0.5
|
|
20
|
+
# 告警开关
|
|
21
|
+
ALERT__ALERT_ON_SLOW_REQUEST=true
|
|
22
|
+
ALERT__ALERT_ON_SLOW_SQL=true
|
|
23
|
+
ALERT__ALERT_ON_ERROR=true
|
|
24
|
+
# 慢请求排除路径(SSE/WebSocket 等长连接接口,支持 * 通配符)
|
|
25
|
+
# ALERT__SLOW_REQUEST_EXCLUDE_PATHS=["*/subscribe", "*/ws", "*/stream"]
|
|
26
|
+
# 聚合与抑制
|
|
27
|
+
ALERT__AGGREGATE_WINDOW=10
|
|
28
|
+
ALERT__SLOW_REQUEST_AGGREGATE=5
|
|
29
|
+
ALERT__SLOW_SQL_AGGREGATE=10
|
|
30
|
+
ALERT__EXCEPTION_AGGREGATE=1
|
|
31
|
+
ALERT__SUPPRESS_SECONDS=300
|
|
32
|
+
|
|
33
|
+
# ---------- 告警通知器(简版:单群)----------
|
|
34
|
+
# 所有告警发到同一个飞书群,取消注释并填写 Webhook 即可
|
|
35
|
+
# ALERT__NOTIFIERS__DEFAULT__TYPE=feishu
|
|
36
|
+
# ALERT__NOTIFIERS__DEFAULT__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/your-webhook-id
|
|
37
|
+
# ALERT__NOTIFIERS__DEFAULT__SECRET=your-secret
|
|
38
|
+
|
|
39
|
+
# ---------- 告警通知器(完整版:分群告警)----------
|
|
40
|
+
# 不同类型告警发到不同群,需配合 alert_rules.yaml 使用
|
|
41
|
+
# 生成规则模板:aury docs alert-rules
|
|
42
|
+
# ALERT__RULES_FILE=alert_rules.yaml
|
|
43
|
+
|
|
44
|
+
# 性能群(慢请求、慢SQL)
|
|
45
|
+
# ALERT__NOTIFIERS__PERF_GROUP__TYPE=feishu
|
|
46
|
+
# ALERT__NOTIFIERS__PERF_GROUP__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/perf-webhook-id
|
|
47
|
+
# ALERT__NOTIFIERS__PERF_GROUP__SECRET=perf-secret
|
|
48
|
+
|
|
49
|
+
# 错误群(异常)
|
|
50
|
+
# ALERT__NOTIFIERS__ERROR_GROUP__TYPE=feishu
|
|
51
|
+
# ALERT__NOTIFIERS__ERROR_GROUP__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/error-webhook-id
|
|
52
|
+
# ALERT__NOTIFIERS__ERROR_GROUP__SECRET=error-secret
|
|
53
|
+
|
|
54
|
+
# 运维群(任务失败、超时)
|
|
55
|
+
# ALERT__NOTIFIERS__OPS_GROUP__TYPE=feishu
|
|
56
|
+
# ALERT__NOTIFIERS__OPS_GROUP__WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/ops-webhook-id
|
|
57
|
+
# ALERT__NOTIFIERS__OPS_GROUP__SECRET=ops-secret
|
|
58
|
+
|
|
59
|
+
# 通用 Webhook(自定义系统)
|
|
60
|
+
# ALERT__NOTIFIERS__CUSTOM__TYPE=webhook
|
|
61
|
+
# ALERT__NOTIFIERS__CUSTOM__URL=https://your-system.com/api/alert
|
|
62
|
+
# ALERT__NOTIFIERS__CUSTOM__METHOD=POST
|
|
63
|
+
# ALERT__NOTIFIERS__CUSTOM__HEADERS={{"Authorization": "Bearer your-token"}}
|
|
@@ -58,8 +58,24 @@ def set_service_context(context: ServiceContext | str) -> None:
|
|
|
58
58
|
def get_trace_id() -> str:
|
|
59
59
|
"""获取当前链路追踪ID。
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
优先从 OpenTelemetry 获取(如果已启用),否则使用内置 trace_id。
|
|
62
|
+
如果都没有设置,则生成一个新的随机 ID。
|
|
62
63
|
"""
|
|
64
|
+
# 优先从 OTel 获取
|
|
65
|
+
try:
|
|
66
|
+
from opentelemetry import trace
|
|
67
|
+
|
|
68
|
+
span = trace.get_current_span()
|
|
69
|
+
if span and span.is_recording():
|
|
70
|
+
otel_trace_id = span.get_span_context().trace_id
|
|
71
|
+
if otel_trace_id:
|
|
72
|
+
return format(otel_trace_id, "032x")
|
|
73
|
+
except ImportError:
|
|
74
|
+
pass
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# 回退到内置实现
|
|
63
79
|
trace_id = _trace_id_var.get()
|
|
64
80
|
if not trace_id:
|
|
65
81
|
trace_id = str(uuid.uuid4())
|
|
@@ -309,7 +309,11 @@ def log_exception(
|
|
|
309
309
|
|
|
310
310
|
__all__ = [
|
|
311
311
|
"create_console_sink",
|
|
312
|
+
"format_exception_compact",
|
|
312
313
|
"format_exception_java_style",
|
|
313
314
|
"format_message",
|
|
314
315
|
"log_exception",
|
|
315
316
|
]
|
|
317
|
+
|
|
318
|
+
# 别名导出(保持内部使用 _ 前缀)
|
|
319
|
+
format_exception_compact = _format_exception_compact
|
|
@@ -27,12 +27,11 @@ from .cache import (
|
|
|
27
27
|
|
|
28
28
|
# 通道 (SSE/PubSub)
|
|
29
29
|
from .channel import (
|
|
30
|
+
BroadcasterChannel,
|
|
30
31
|
ChannelBackend,
|
|
31
32
|
ChannelManager,
|
|
32
33
|
ChannelMessage,
|
|
33
34
|
IChannel,
|
|
34
|
-
MemoryChannel,
|
|
35
|
-
RedisChannel,
|
|
36
35
|
)
|
|
37
36
|
|
|
38
37
|
# RabbitMQ 客户端
|
|
@@ -47,15 +46,14 @@ from .di import Container, Lifetime, Scope, ServiceDescriptor
|
|
|
47
46
|
|
|
48
47
|
# 事件总线
|
|
49
48
|
from .events import (
|
|
49
|
+
BroadcasterEventBus,
|
|
50
50
|
Event,
|
|
51
51
|
EventBackend,
|
|
52
52
|
EventBusManager,
|
|
53
53
|
EventHandler,
|
|
54
54
|
EventType,
|
|
55
55
|
IEventBus,
|
|
56
|
-
MemoryEventBus,
|
|
57
56
|
RabbitMQEventBus,
|
|
58
|
-
RedisEventBus,
|
|
59
57
|
)
|
|
60
58
|
|
|
61
59
|
# 消息队列
|
|
@@ -103,6 +101,7 @@ __all__ = [
|
|
|
103
101
|
"CacheFactory",
|
|
104
102
|
"CacheManager",
|
|
105
103
|
# 通道
|
|
104
|
+
"BroadcasterChannel",
|
|
106
105
|
"ChannelBackend",
|
|
107
106
|
"ChannelManager",
|
|
108
107
|
"ChannelMessage",
|
|
@@ -128,19 +127,16 @@ __all__ = [
|
|
|
128
127
|
"MQMessage",
|
|
129
128
|
"MemcachedCache",
|
|
130
129
|
"MemoryCache",
|
|
131
|
-
"MemoryChannel",
|
|
132
|
-
"MemoryEventBus",
|
|
133
130
|
"RabbitMQ",
|
|
134
131
|
# RabbitMQ 客户端
|
|
135
132
|
"RabbitMQClient",
|
|
136
133
|
"RabbitMQConfig",
|
|
137
134
|
"RabbitMQEventBus",
|
|
135
|
+
"BroadcasterEventBus",
|
|
138
136
|
"RedisCache",
|
|
139
|
-
"RedisChannel",
|
|
140
137
|
# Redis 客户端
|
|
141
138
|
"RedisClient",
|
|
142
139
|
"RedisConfig",
|
|
143
|
-
"RedisEventBus",
|
|
144
140
|
"RedisMQ",
|
|
145
141
|
"S3Storage",
|
|
146
142
|
# 调度器
|
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
提供发布/订阅模式的通道功能,用于 SSE、WebSocket 等实时通信场景。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- memory
|
|
7
|
-
- redis
|
|
5
|
+
支持的后端(通过 Broadcaster 库):
|
|
6
|
+
- memory:// - 内存通道(单进程,开发/测试用)
|
|
7
|
+
- redis:// - Redis Pub/Sub(多进程/分布式)
|
|
8
|
+
- kafka:// - Apache Kafka
|
|
9
|
+
- postgres:// - PostgreSQL LISTEN/NOTIFY
|
|
8
10
|
"""
|
|
9
11
|
|
|
10
|
-
from .backends import
|
|
12
|
+
from .backends import BroadcasterChannel
|
|
11
13
|
from .base import ChannelBackend, ChannelMessage, IChannel
|
|
12
14
|
from .manager import ChannelManager
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
15
17
|
# 接口和类型
|
|
16
18
|
"ChannelBackend",
|
|
17
|
-
# 管理器
|
|
18
|
-
"ChannelManager",
|
|
19
19
|
"ChannelMessage",
|
|
20
20
|
"IChannel",
|
|
21
|
+
# 管理器
|
|
22
|
+
"ChannelManager",
|
|
21
23
|
# 后端实现
|
|
22
|
-
"
|
|
23
|
-
"RedisChannel",
|
|
24
|
+
"BroadcasterChannel",
|
|
24
25
|
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Broadcaster 通道后端。
|
|
2
|
+
|
|
3
|
+
基于 Broadcaster 库实现,支持多种后端:
|
|
4
|
+
- memory:// - 内存(单进程,开发/测试用)
|
|
5
|
+
- redis:// - Redis Pub/Sub(多进程/分布式)
|
|
6
|
+
- kafka:// - Apache Kafka
|
|
7
|
+
- postgres:// - PostgreSQL LISTEN/NOTIFY
|
|
8
|
+
|
|
9
|
+
优势:
|
|
10
|
+
- 共享连接池,支持成千上万并发订阅
|
|
11
|
+
- 自动重连机制
|
|
12
|
+
- 统一 API,通过 URL scheme 切换后端
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from collections.abc import AsyncIterator
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
|
|
21
|
+
from broadcaster import Broadcast
|
|
22
|
+
|
|
23
|
+
from aury.boot.common.logging import logger
|
|
24
|
+
|
|
25
|
+
from ..base import ChannelMessage, IChannel
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BroadcasterChannel(IChannel):
|
|
29
|
+
"""Broadcaster 通道实现。
|
|
30
|
+
|
|
31
|
+
使用 Broadcaster 库统一处理多种后端,解决原生实现的连接池问题:
|
|
32
|
+
- 共享单个连接处理所有订阅
|
|
33
|
+
- 内部通过 asyncio.Queue 分发消息
|
|
34
|
+
- 支持成千上万并发订阅者
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, url: str) -> None:
|
|
38
|
+
"""初始化 Broadcaster 通道。
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
url: 连接 URL,支持的 scheme:
|
|
42
|
+
- memory:// - 内存后端
|
|
43
|
+
- redis://host:port/db - Redis Pub/Sub
|
|
44
|
+
- kafka://host:port - Apache Kafka
|
|
45
|
+
- postgres://user:pass@host:port/db - PostgreSQL
|
|
46
|
+
"""
|
|
47
|
+
self._url = url
|
|
48
|
+
self._broadcast = Broadcast(url)
|
|
49
|
+
self._connected = False
|
|
50
|
+
|
|
51
|
+
async def _ensure_connected(self) -> None:
|
|
52
|
+
"""确保已连接。"""
|
|
53
|
+
if not self._connected:
|
|
54
|
+
await self._broadcast.connect()
|
|
55
|
+
self._connected = True
|
|
56
|
+
logger.debug(f"Broadcaster 通道已连接: {self._mask_url(self._url)}")
|
|
57
|
+
|
|
58
|
+
def _mask_url(self, url: str) -> str:
|
|
59
|
+
"""URL 脱敏(隐藏密码)。"""
|
|
60
|
+
if "@" in url:
|
|
61
|
+
parts = url.split("@")
|
|
62
|
+
prefix = parts[0]
|
|
63
|
+
suffix = parts[1]
|
|
64
|
+
if ":" in prefix:
|
|
65
|
+
scheme_and_user = prefix.rsplit(":", 1)[0]
|
|
66
|
+
return f"{scheme_and_user}:***@{suffix}"
|
|
67
|
+
return url
|
|
68
|
+
|
|
69
|
+
async def publish(self, channel: str, message: ChannelMessage) -> None:
|
|
70
|
+
"""发布消息到通道。"""
|
|
71
|
+
await self._ensure_connected()
|
|
72
|
+
|
|
73
|
+
message.channel = channel
|
|
74
|
+
# 序列化消息
|
|
75
|
+
data = {
|
|
76
|
+
"data": message.data,
|
|
77
|
+
"event": message.event,
|
|
78
|
+
"id": message.id,
|
|
79
|
+
"channel": message.channel,
|
|
80
|
+
"timestamp": message.timestamp.isoformat(),
|
|
81
|
+
}
|
|
82
|
+
await self._broadcast.publish(channel=channel, message=json.dumps(data))
|
|
83
|
+
|
|
84
|
+
async def subscribe(self, channel: str) -> AsyncIterator[ChannelMessage]:
|
|
85
|
+
"""订阅通道。
|
|
86
|
+
|
|
87
|
+
Broadcaster 内部共享连接,每个订阅者不会创建新的连接。
|
|
88
|
+
"""
|
|
89
|
+
await self._ensure_connected()
|
|
90
|
+
|
|
91
|
+
async with self._broadcast.subscribe(channel=channel) as subscriber:
|
|
92
|
+
async for event in subscriber:
|
|
93
|
+
try:
|
|
94
|
+
data = json.loads(event.message)
|
|
95
|
+
message = ChannelMessage(
|
|
96
|
+
data=data.get("data"),
|
|
97
|
+
event=data.get("event"),
|
|
98
|
+
id=data.get("id"),
|
|
99
|
+
channel=data.get("channel") or channel,
|
|
100
|
+
timestamp=datetime.fromisoformat(data["timestamp"])
|
|
101
|
+
if data.get("timestamp")
|
|
102
|
+
else datetime.now(),
|
|
103
|
+
)
|
|
104
|
+
yield message
|
|
105
|
+
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
|
106
|
+
logger.warning(f"解析通道消息失败: {e}")
|
|
107
|
+
|
|
108
|
+
async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
|
|
109
|
+
"""模式订阅(通配符)。
|
|
110
|
+
|
|
111
|
+
注意:Broadcaster 目前不支持模式订阅,此方法会抛出 NotImplementedError。
|
|
112
|
+
如需模式订阅,请使用具体的 channel 名称。
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
pattern: 通道模式
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
NotImplementedError: Broadcaster 不支持模式订阅
|
|
119
|
+
"""
|
|
120
|
+
raise NotImplementedError(
|
|
121
|
+
"Broadcaster 后端不支持模式订阅 (psubscribe)。"
|
|
122
|
+
"请使用具体的 channel 名称。"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
async def unsubscribe(self, channel: str) -> None:
|
|
126
|
+
"""取消订阅通道。
|
|
127
|
+
|
|
128
|
+
注意:Broadcaster 的订阅通过上下文管理器自动处理,
|
|
129
|
+
退出 subscribe() 的 async for 循环即可取消订阅。
|
|
130
|
+
"""
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
async def close(self) -> None:
|
|
134
|
+
"""关闭通道。"""
|
|
135
|
+
if self._connected:
|
|
136
|
+
await self._broadcast.disconnect()
|
|
137
|
+
self._connected = False
|
|
138
|
+
logger.debug("Broadcaster 通道已关闭")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
__all__ = ["BroadcasterChannel"]
|
|
@@ -16,8 +16,11 @@ from typing import Any
|
|
|
16
16
|
class ChannelBackend(Enum):
|
|
17
17
|
"""通道后端类型。"""
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# Broadcaster 统一后端(支持 memory/redis/kafka/postgres,通过 URL scheme 区分)
|
|
20
|
+
BROADCASTER = "broadcaster"
|
|
21
|
+
# 未来扩展
|
|
22
|
+
RABBITMQ = "rabbitmq"
|
|
23
|
+
ROCKETMQ = "rocketmq"
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
@dataclass
|
|
@@ -31,7 +34,10 @@ class ChannelMessage:
|
|
|
31
34
|
timestamp: datetime = field(default_factory=datetime.now)
|
|
32
35
|
|
|
33
36
|
def to_sse(self) -> str:
|
|
34
|
-
"""转换为 SSE 格式。
|
|
37
|
+
"""转换为 SSE 格式。
|
|
38
|
+
|
|
39
|
+
SSE 规范要求每个消息以双换行符结束。
|
|
40
|
+
"""
|
|
35
41
|
lines = []
|
|
36
42
|
if self.id:
|
|
37
43
|
lines.append(f"id: {self.id}")
|
|
@@ -42,7 +48,8 @@ class ChannelMessage:
|
|
|
42
48
|
for line in data_str.split("\n"):
|
|
43
49
|
lines.append(f"data: {line}")
|
|
44
50
|
lines.append("") # 空行结束
|
|
45
|
-
|
|
51
|
+
# SSE 规范要求消息以双换行符结束
|
|
52
|
+
return "\n".join(lines) + "\n"
|
|
46
53
|
|
|
47
54
|
|
|
48
55
|
class IChannel(ABC):
|