lush-sentryx-core 0.1.0__tar.gz
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.
- lush_sentryx_core-0.1.0/PKG-INFO +31 -0
- lush_sentryx_core-0.1.0/README.md +21 -0
- lush_sentryx_core-0.1.0/pyproject.toml +220 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/__init__.py +105 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/py.typed +0 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/sdk/__init__.py +8 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/sdk/v2/__init__.py +89 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/sdk/v2/const.py +87 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/sdk/v2/filters.py +123 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/sdk/v2/scrubbers.py +145 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/sdk/v2/types.py +109 -0
- lush_sentryx_core-0.1.0/src/lush_sentryx_core/sdk/v2/utils.py +187 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: lush-sentryx-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core scrubbing and masking utilities for Sentry events, SDK-agnostic.
|
|
5
|
+
Author: straydragon
|
|
6
|
+
Requires-Dist: sentry-sdk>=2.0.0 ; extra == 'typing'
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Provides-Extra: typing
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# lush-sentryx-core
|
|
12
|
+
|
|
13
|
+
只做一件事: 对数据做脱敏/过滤.
|
|
14
|
+
|
|
15
|
+
它不依赖 `sentry-sdk`. 你可以把它接到 Sentry 的 `before_send` 上,也可以单独用在日志/审计/任务系统里.
|
|
16
|
+
|
|
17
|
+
## 例子
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from lush_sentryx_core import SENTRY_DEFAULT_DENYLIST, deep_scrub_sensitive_data
|
|
21
|
+
|
|
22
|
+
data = {"password": "secret", "profile": {"token": "xxx", "name": "demo"}}
|
|
23
|
+
deep_scrub_sensitive_data(data, SENTRY_DEFAULT_DENYLIST)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 开发
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv sync -p 3.10 --frozen
|
|
30
|
+
uv run -p 3.10 pytest
|
|
31
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# lush-sentryx-core
|
|
2
|
+
|
|
3
|
+
只做一件事: 对数据做脱敏/过滤.
|
|
4
|
+
|
|
5
|
+
它不依赖 `sentry-sdk`. 你可以把它接到 Sentry 的 `before_send` 上,也可以单独用在日志/审计/任务系统里.
|
|
6
|
+
|
|
7
|
+
## 例子
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from lush_sentryx_core import SENTRY_DEFAULT_DENYLIST, deep_scrub_sensitive_data
|
|
11
|
+
|
|
12
|
+
data = {"password": "secret", "profile": {"token": "xxx", "name": "demo"}}
|
|
13
|
+
deep_scrub_sensitive_data(data, SENTRY_DEFAULT_DENYLIST)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 开发
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv sync -p 3.10 --frozen
|
|
20
|
+
uv run -p 3.10 pytest
|
|
21
|
+
```
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lush-sentryx-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Core scrubbing and masking utilities for Sentry events, SDK-agnostic."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
authors = [{ name = "straydragon" }]
|
|
8
|
+
dependencies = [] # 无外部依赖,纯 Python 实现
|
|
9
|
+
|
|
10
|
+
[project.optional-dependencies]
|
|
11
|
+
typing = ["sentry-sdk>=2.0.0"]
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["uv_build>=0.10.12,<0.11.0"]
|
|
15
|
+
build-backend = "uv_build"
|
|
16
|
+
|
|
17
|
+
[tool.uv]
|
|
18
|
+
package = true
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = ["pytest>=8.4.1", "pytest-asyncio>=1.1.0", "pytest-cov>=6.2.1"]
|
|
22
|
+
|
|
23
|
+
[tool.pytest.ini_options]
|
|
24
|
+
addopts = "--import-mode=importlib --cov=lush_sentryx_core --cov-report=term-missing"
|
|
25
|
+
testpaths = ["tests"]
|
|
26
|
+
asyncio_mode = "auto"
|
|
27
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
28
|
+
asyncio_default_test_loop_scope = "function"
|
|
29
|
+
|
|
30
|
+
[tool.ruff]
|
|
31
|
+
line-length = 140
|
|
32
|
+
indent-width = 4
|
|
33
|
+
target-version = "py310"
|
|
34
|
+
|
|
35
|
+
[tool.ruff.lint]
|
|
36
|
+
fixable = ["F401", "ALL"]
|
|
37
|
+
unfixable = []
|
|
38
|
+
|
|
39
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
40
|
+
|
|
41
|
+
select = ["ALL"]
|
|
42
|
+
ignore = [
|
|
43
|
+
# === 代码质量与复杂性 ===
|
|
44
|
+
"C901", # 函数复杂度过高,影响可读性
|
|
45
|
+
"PLR0912", # 函数分支过多,建议重构
|
|
46
|
+
"PLR0913", # 函数参数过多,难以维护
|
|
47
|
+
"PLR0915", # 函数语句过多,建议拆分
|
|
48
|
+
"PLR0911", # 函数返回语句过多,逻辑复杂
|
|
49
|
+
"PLR2004", # 使用魔术数值,建议使用常量
|
|
50
|
+
"SIM108", # 更倾向于使用ifelse表达式,而不是if/else语句
|
|
51
|
+
|
|
52
|
+
# === 类型注解 ===
|
|
53
|
+
"ANN002", # 函数*args参数缺少类型注解
|
|
54
|
+
"ANN003", # 函数**kwargs参数缺少类型注解
|
|
55
|
+
"ANN401", # 使用Any类型,建议使用具体类型
|
|
56
|
+
|
|
57
|
+
# === 函数参数设计 ===
|
|
58
|
+
"FBT001", # 布尔类型位置参数,易混淆
|
|
59
|
+
"FBT002", # 布尔默认值位置参数,易误用
|
|
60
|
+
"N805", # 第一个方法参数命名问题, 先忽略
|
|
61
|
+
|
|
62
|
+
# === 异常处理 ===
|
|
63
|
+
"BLE001", # 捕获所有异常,隐藏问题
|
|
64
|
+
"TRY003", # 异常消息应在类中定义
|
|
65
|
+
"TRY004", # 类型检查应抛出TypeError
|
|
66
|
+
|
|
67
|
+
# === 代码简化 ===
|
|
68
|
+
"SIM102", # 可折叠的if语句,简化代码
|
|
69
|
+
|
|
70
|
+
# === 格式规范 ===
|
|
71
|
+
"COM812", # 缺少尾随逗号,影响diff
|
|
72
|
+
"E501", # 行长度过长,影响可读性
|
|
73
|
+
|
|
74
|
+
# === 日志记录 ===
|
|
75
|
+
"G004", # 日志使用f-string,影响性能
|
|
76
|
+
|
|
77
|
+
# === 类型检查优化 ===
|
|
78
|
+
"TC001", # 仅用于类型检查的导入,优化性能
|
|
79
|
+
"TC002", # 仅用于类型检查的导入,优化性能
|
|
80
|
+
"TC003", # 仅用于类型检查的导入,优化性能
|
|
81
|
+
|
|
82
|
+
# === 代码清理 ===
|
|
83
|
+
"ERA001", # 注释掉的代码,应删除
|
|
84
|
+
|
|
85
|
+
# === 私有成员访问 ===
|
|
86
|
+
"SLF001", # 访问私有成员,破坏封装
|
|
87
|
+
|
|
88
|
+
# === 日期时间处理 ===
|
|
89
|
+
"DTZ001", # datetime缺少时区信息
|
|
90
|
+
"DTZ005", # datetime.now()缺少时区
|
|
91
|
+
|
|
92
|
+
# === 调试代码 ===
|
|
93
|
+
"T201", # print语句调试代码,生产环境禁用
|
|
94
|
+
|
|
95
|
+
# === T O D O管理 ===
|
|
96
|
+
"TD002", # 缺少作者信息
|
|
97
|
+
"TD003", # 缺少问题链接
|
|
98
|
+
|
|
99
|
+
# === 其他 ===
|
|
100
|
+
"D", # 文档字符串相关规则
|
|
101
|
+
"EM", # 错误消息相关规则
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
[tool.ruff.lint.per-file-ignores]
|
|
105
|
+
"tests/**/*.py" = [
|
|
106
|
+
# === 测试代码质量 ===
|
|
107
|
+
"B011", # assert False应改为raise AssertionError
|
|
108
|
+
"B008", # 函数调用作为默认参数
|
|
109
|
+
"ARG001", # 未使用的函数参数
|
|
110
|
+
"ARG002", # 未使用的方法参数
|
|
111
|
+
"ARG005", # lambda中未使用的参数
|
|
112
|
+
"F841", # 未使用的变量
|
|
113
|
+
"B018", # 无用的表达式
|
|
114
|
+
"N806", # 函数中非小写变量名
|
|
115
|
+
"W293", # 空行包含空格
|
|
116
|
+
"N802", # 函数名不规范
|
|
117
|
+
"PERF401", # 性能相关
|
|
118
|
+
"PT", # assert 相关
|
|
119
|
+
|
|
120
|
+
# === 测试安全相关 ===
|
|
121
|
+
"S101", # assert语句(测试中使用)
|
|
122
|
+
"S105", # 硬编码密码字符串
|
|
123
|
+
"S201", # Flask debug=True
|
|
124
|
+
"S301", # 可疑的pickle使用
|
|
125
|
+
"S311", # 非加密安全的随机数
|
|
126
|
+
|
|
127
|
+
# === 测试异常处理 ===
|
|
128
|
+
"BLE001", # 捕获所有异常
|
|
129
|
+
"B017", # assertRaises捕获Exception
|
|
130
|
+
"PT011", # pytest.raises缺少match参数
|
|
131
|
+
"PT017", # except中的assert语句
|
|
132
|
+
"EM101", # 异常中的原始字符串
|
|
133
|
+
|
|
134
|
+
# === 测试代码复杂度 ===
|
|
135
|
+
"C901", # 函数复杂度过高
|
|
136
|
+
"PLR2004", # 魔术数值
|
|
137
|
+
|
|
138
|
+
# === 测试导入相关 ===
|
|
139
|
+
"PLC0415", # 函数内import语句
|
|
140
|
+
"ANN", # 类型注解(测试中宽松)
|
|
141
|
+
"TC", # 类型检查的导入
|
|
142
|
+
|
|
143
|
+
# === 测试参数设计 ===
|
|
144
|
+
"FBT001", # 布尔类型位置参数
|
|
145
|
+
"FBT002", # 布尔默认值位置参数
|
|
146
|
+
"FBT003", # 布尔位置参数调用
|
|
147
|
+
|
|
148
|
+
# === 测试异常处理 ===
|
|
149
|
+
"TRY003", # 异常消息定义
|
|
150
|
+
|
|
151
|
+
# === 测试代码简化 ===
|
|
152
|
+
"SIM117", # 多个连续的with语句
|
|
153
|
+
|
|
154
|
+
# === 测试格式规范 ===
|
|
155
|
+
"E501", # 行长度过长
|
|
156
|
+
|
|
157
|
+
# === 测试日志处理 ===
|
|
158
|
+
"G004", # 日志使用f-string
|
|
159
|
+
|
|
160
|
+
# === 测试调试代码 ===
|
|
161
|
+
"T201", # print语句
|
|
162
|
+
|
|
163
|
+
# === 测试文档相关 ===
|
|
164
|
+
"D", # 文档字符串
|
|
165
|
+
|
|
166
|
+
# === 测试私有访问 ===
|
|
167
|
+
"SLF001", # 私有成员访问
|
|
168
|
+
|
|
169
|
+
# === 测试Unicode ===
|
|
170
|
+
"RUF001", # 模糊的Unicode字符
|
|
171
|
+
|
|
172
|
+
# === 测试路径处理 ===
|
|
173
|
+
"PTH", # pathlib相关规则
|
|
174
|
+
|
|
175
|
+
# === 测试日期时间 ===
|
|
176
|
+
"DTZ001", # datetime缺少时区
|
|
177
|
+
"DTZ005", # datetime.now()缺少时区
|
|
178
|
+
|
|
179
|
+
# === 测试代码清理 ===
|
|
180
|
+
"ERA001", # 注释掉的代码
|
|
181
|
+
|
|
182
|
+
"RUF",
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
[tool.ruff.format]
|
|
186
|
+
quote-style = "double"
|
|
187
|
+
indent-style = "space"
|
|
188
|
+
skip-magic-trailing-comma = false
|
|
189
|
+
line-ending = "auto"
|
|
190
|
+
docstring-code-format = true
|
|
191
|
+
docstring-code-line-length = "dynamic"
|
|
192
|
+
|
|
193
|
+
# ============================================================================
|
|
194
|
+
# basedpyright 配置 - 独立包配置
|
|
195
|
+
# ============================================================================
|
|
196
|
+
[tool.basedpyright]
|
|
197
|
+
pythonVersion = "3.10"
|
|
198
|
+
|
|
199
|
+
reportUnannotatedClassAttribute = "none"
|
|
200
|
+
reportUnreachable = "none"
|
|
201
|
+
reportUnnecessaryIsInstance = "none"
|
|
202
|
+
reportAny = "none"
|
|
203
|
+
reportExplicitAny = "none"
|
|
204
|
+
reportConstantRedefinition = "none"
|
|
205
|
+
reportUnnecessaryComparison = "none"
|
|
206
|
+
|
|
207
|
+
[[tool.basedpyright.executionEnvironments]]
|
|
208
|
+
root = "tests"
|
|
209
|
+
reportUnusedCallResult = false
|
|
210
|
+
reportUnknownArgumentType = false
|
|
211
|
+
reportArgumentType = false
|
|
212
|
+
reportUnusedFunction = false
|
|
213
|
+
reportUnusedParameter = false
|
|
214
|
+
reportExplicitAny = "none"
|
|
215
|
+
reportAny = "none"
|
|
216
|
+
reportCallIssue = "none"
|
|
217
|
+
reportUnnecessaryTypeIgnoreComment = "none"
|
|
218
|
+
|
|
219
|
+
[[tool.basedpyright.executionEnvironments]]
|
|
220
|
+
root = "src"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Sentryx Core - Sentry 敏感数据过滤核心库
|
|
2
|
+
|
|
3
|
+
这是一个独立的敏感数据过滤和脱敏核心库,不依赖 sentry-sdk.
|
|
4
|
+
可被任何版本的 Sentry SDK (1.x 或 2.x) 使用,也可独立用于其他数据脱敏场景.
|
|
5
|
+
|
|
6
|
+
主要特性:
|
|
7
|
+
- 纯 Python 实现,无外部依赖
|
|
8
|
+
- 深度递归清理嵌套数据结构
|
|
9
|
+
- 支持自定义敏感字段列表
|
|
10
|
+
- 提供 URL 参数清理、邮箱脱敏等工具函数
|
|
11
|
+
- 兼容 Sentry SDK 1.x 和 2.x 的事件结构
|
|
12
|
+
- 提供类型定义用于类型检查
|
|
13
|
+
|
|
14
|
+
使用方式:
|
|
15
|
+
|
|
16
|
+
1. 推荐方式 - 通过版本命名空间导入 (明确 SDK 版本):
|
|
17
|
+
>>> from lush_sentryx_core.sdk.v2 import create_additional_filter, SENTRY_DEFAULT_DENYLIST
|
|
18
|
+
>>> from lush_sentryx_core.sdk.v2.types import Event, Hint
|
|
19
|
+
|
|
20
|
+
2. 简化方式 - 直接导入 (默认使用 v2):
|
|
21
|
+
>>> from lush_sentryx_core import create_additional_filter, SENTRY_DEFAULT_DENYLIST
|
|
22
|
+
|
|
23
|
+
3. 独立使用 (数据脱敏):
|
|
24
|
+
>>> from lush_sentryx_core import deep_scrub_sensitive_data, SENTRY_DEFAULT_DENYLIST
|
|
25
|
+
>>> data = {"password": "secret", "config": {"token": "xxx"}}
|
|
26
|
+
>>> deep_scrub_sensitive_data(data, SENTRY_DEFAULT_DENYLIST)
|
|
27
|
+
>>> data
|
|
28
|
+
{'password': '[Filtered]', 'config': {'token': '[Filtered]'}}
|
|
29
|
+
|
|
30
|
+
4. 配合 Sentry SDK 使用:
|
|
31
|
+
>>> from lush_sentryx_core.sdk.v2 import create_additional_filter, SENTRY_DEFAULT_DENYLIST
|
|
32
|
+
>>> import sentry_sdk
|
|
33
|
+
>>> sentry_sdk.init(
|
|
34
|
+
... dsn="...",
|
|
35
|
+
... before_send=create_additional_filter(SENTRY_DEFAULT_DENYLIST),
|
|
36
|
+
... )
|
|
37
|
+
|
|
38
|
+
Note:
|
|
39
|
+
- 当前默认导出的是 SDK v2 版本的实现
|
|
40
|
+
- 如果需要支持 SDK 1.x,可以在 sdk 目录下添加 v1 模块
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# 导入 sdk 命名空间
|
|
44
|
+
from lush_sentryx_core import sdk
|
|
45
|
+
|
|
46
|
+
# 从 v2 重新导出常用 API (保持向后兼容)
|
|
47
|
+
from lush_sentryx_core.sdk.v2 import (
|
|
48
|
+
# 常量
|
|
49
|
+
BUSINESS_SENSITIVE_FIELDS,
|
|
50
|
+
FILTERED_PLACEHOLDER,
|
|
51
|
+
SENSITIVE_URL_PATTERNS,
|
|
52
|
+
SENTRY_DEFAULT_DENYLIST,
|
|
53
|
+
# 类型 (类型检查时使用 sentry-sdk 原生类型)
|
|
54
|
+
Breadcrumb,
|
|
55
|
+
Event,
|
|
56
|
+
EventProcessor,
|
|
57
|
+
ExcInfo,
|
|
58
|
+
Hint,
|
|
59
|
+
SensitiveFields,
|
|
60
|
+
TransactionProcessor,
|
|
61
|
+
# 过滤器工厂 (返回 EventProcessor/TransactionProcessor 类型)
|
|
62
|
+
create_additional_filter,
|
|
63
|
+
create_transaction_filter,
|
|
64
|
+
# 工具函数
|
|
65
|
+
custom_repr,
|
|
66
|
+
# 数据清理函数
|
|
67
|
+
deep_scrub_sensitive_data,
|
|
68
|
+
mask_email_partially,
|
|
69
|
+
mask_string_partially,
|
|
70
|
+
mask_user_email_partially,
|
|
71
|
+
parameterize_request_urls,
|
|
72
|
+
scrub_dict_keys,
|
|
73
|
+
scrub_stacktrace_vars,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
__all__ = [ # noqa: RUF022
|
|
77
|
+
# SDK 命名空间
|
|
78
|
+
"sdk",
|
|
79
|
+
# 类型 (类型检查时使用 sentry-sdk 原生类型,确保与 sentry_sdk.init() 完全兼容)
|
|
80
|
+
"Breadcrumb",
|
|
81
|
+
"Event",
|
|
82
|
+
"EventProcessor",
|
|
83
|
+
"ExcInfo",
|
|
84
|
+
"Hint",
|
|
85
|
+
"SensitiveFields",
|
|
86
|
+
"TransactionProcessor",
|
|
87
|
+
# 常量
|
|
88
|
+
"BUSINESS_SENSITIVE_FIELDS",
|
|
89
|
+
"FILTERED_PLACEHOLDER",
|
|
90
|
+
"SENSITIVE_URL_PATTERNS",
|
|
91
|
+
"SENTRY_DEFAULT_DENYLIST",
|
|
92
|
+
# 过滤器工厂 (返回 EventProcessor/TransactionProcessor 类型)
|
|
93
|
+
"create_additional_filter",
|
|
94
|
+
"create_transaction_filter",
|
|
95
|
+
# 数据清理函数
|
|
96
|
+
"deep_scrub_sensitive_data",
|
|
97
|
+
"scrub_dict_keys",
|
|
98
|
+
"scrub_stacktrace_vars",
|
|
99
|
+
# 工具函数
|
|
100
|
+
"custom_repr",
|
|
101
|
+
"mask_email_partially",
|
|
102
|
+
"mask_string_partially",
|
|
103
|
+
"mask_user_email_partially",
|
|
104
|
+
"parameterize_request_urls",
|
|
105
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Sentryx Core SDK v2 - 适用于 Sentry SDK 2.x
|
|
2
|
+
|
|
3
|
+
此模块提供与 Sentry SDK 2.x 兼容的类型定义、过滤器和工具函数.
|
|
4
|
+
|
|
5
|
+
主要特性:
|
|
6
|
+
- 类型定义: Event, Hint, Breadcrumb 等类型别名
|
|
7
|
+
- 过滤器工厂: before_send, before_send_transaction 过滤器
|
|
8
|
+
- 数据清理: 深度递归清理敏感数据
|
|
9
|
+
- 工具函数: 邮箱脱敏、URL 清理等
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
基本使用:
|
|
13
|
+
>>> from lush_sentryx_core.sdk.v2 import create_additional_filter, SENTRY_DEFAULT_DENYLIST
|
|
14
|
+
>>> import sentry_sdk
|
|
15
|
+
>>> sentry_sdk.init(
|
|
16
|
+
... dsn="...",
|
|
17
|
+
... before_send=create_additional_filter(SENTRY_DEFAULT_DENYLIST),
|
|
18
|
+
... )
|
|
19
|
+
|
|
20
|
+
类型提示:
|
|
21
|
+
>>> from lush_sentryx_core.sdk.v2.types import Event, Hint
|
|
22
|
+
>>> def my_filter(event: Event, hint: Hint) -> Event | None:
|
|
23
|
+
... return event
|
|
24
|
+
|
|
25
|
+
Note:
|
|
26
|
+
- 此模块不依赖 sentry-sdk,但类型定义与 sentry-sdk 2.x 兼容
|
|
27
|
+
- 如果需要使用 sentry-sdk 的原生类型,可以在运行时导入
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from lush_sentryx_core.sdk.v2.const import (
|
|
31
|
+
BUSINESS_SENSITIVE_FIELDS,
|
|
32
|
+
FILTERED_PLACEHOLDER,
|
|
33
|
+
SENSITIVE_URL_PATTERNS,
|
|
34
|
+
SENTRY_DEFAULT_DENYLIST,
|
|
35
|
+
)
|
|
36
|
+
from lush_sentryx_core.sdk.v2.filters import (
|
|
37
|
+
create_additional_filter,
|
|
38
|
+
create_transaction_filter,
|
|
39
|
+
)
|
|
40
|
+
from lush_sentryx_core.sdk.v2.scrubbers import (
|
|
41
|
+
deep_scrub_sensitive_data,
|
|
42
|
+
scrub_dict_keys,
|
|
43
|
+
scrub_stacktrace_vars,
|
|
44
|
+
)
|
|
45
|
+
from lush_sentryx_core.sdk.v2.types import (
|
|
46
|
+
Breadcrumb,
|
|
47
|
+
Event,
|
|
48
|
+
EventProcessor,
|
|
49
|
+
ExcInfo,
|
|
50
|
+
Hint,
|
|
51
|
+
SensitiveFields,
|
|
52
|
+
TransactionProcessor,
|
|
53
|
+
)
|
|
54
|
+
from lush_sentryx_core.sdk.v2.utils import (
|
|
55
|
+
custom_repr,
|
|
56
|
+
mask_email_partially,
|
|
57
|
+
mask_string_partially,
|
|
58
|
+
mask_user_email_partially,
|
|
59
|
+
parameterize_request_urls,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
__all__ = [ # noqa: RUF022
|
|
63
|
+
# 类型 (类型检查时使用 sentry-sdk 原生类型)
|
|
64
|
+
"Breadcrumb",
|
|
65
|
+
"Event",
|
|
66
|
+
"EventProcessor",
|
|
67
|
+
"ExcInfo",
|
|
68
|
+
"Hint",
|
|
69
|
+
"SensitiveFields",
|
|
70
|
+
"TransactionProcessor",
|
|
71
|
+
# 常量
|
|
72
|
+
"BUSINESS_SENSITIVE_FIELDS",
|
|
73
|
+
"FILTERED_PLACEHOLDER",
|
|
74
|
+
"SENSITIVE_URL_PATTERNS",
|
|
75
|
+
"SENTRY_DEFAULT_DENYLIST",
|
|
76
|
+
# 过滤器工厂 (返回 EventProcessor/TransactionProcessor 类型)
|
|
77
|
+
"create_additional_filter",
|
|
78
|
+
"create_transaction_filter",
|
|
79
|
+
# 数据清理函数
|
|
80
|
+
"deep_scrub_sensitive_data",
|
|
81
|
+
"scrub_dict_keys",
|
|
82
|
+
"scrub_stacktrace_vars",
|
|
83
|
+
# 工具函数
|
|
84
|
+
"custom_repr",
|
|
85
|
+
"mask_email_partially",
|
|
86
|
+
"mask_string_partially",
|
|
87
|
+
"mask_user_email_partially",
|
|
88
|
+
"parameterize_request_urls",
|
|
89
|
+
]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Sentryx Core SDK v2 常量定义
|
|
2
|
+
|
|
3
|
+
包含敏感数据字段列表、URL 模式等常量.
|
|
4
|
+
此模块不依赖 sentry-sdk, 可被任何 Sentry SDK 版本使用.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from re import Pattern
|
|
9
|
+
from typing import Final
|
|
10
|
+
|
|
11
|
+
# Sentry SDK 2.x 默认的敏感字段列表
|
|
12
|
+
# 参考: https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/scrubber.py
|
|
13
|
+
#
|
|
14
|
+
# 这些字段在 EventScrubber 中使用子串匹配 (不区分大小写)
|
|
15
|
+
# 例如: 'token' 会匹配 'access_token', 'user_token', 'my_token_field' 等
|
|
16
|
+
SENTRY_DEFAULT_DENYLIST: Final[frozenset[str]] = frozenset(
|
|
17
|
+
{
|
|
18
|
+
# 认证相关
|
|
19
|
+
"password",
|
|
20
|
+
"passwd",
|
|
21
|
+
"secret",
|
|
22
|
+
"api_key",
|
|
23
|
+
"apikey",
|
|
24
|
+
"access_token",
|
|
25
|
+
"auth",
|
|
26
|
+
"credentials",
|
|
27
|
+
"token",
|
|
28
|
+
"api_secret",
|
|
29
|
+
"app_secret",
|
|
30
|
+
"client_secret",
|
|
31
|
+
"private_key",
|
|
32
|
+
"public_key",
|
|
33
|
+
"signing_key",
|
|
34
|
+
"encryption_key",
|
|
35
|
+
"session_key",
|
|
36
|
+
"session_id",
|
|
37
|
+
"sessionid",
|
|
38
|
+
# HTTP Headers
|
|
39
|
+
"authorization",
|
|
40
|
+
"cookie",
|
|
41
|
+
"set-cookie",
|
|
42
|
+
"x-api-key",
|
|
43
|
+
"x-auth-token",
|
|
44
|
+
"x-csrf-token",
|
|
45
|
+
"x-forwarded-for",
|
|
46
|
+
"x-real-ip",
|
|
47
|
+
# 个人身份信息
|
|
48
|
+
"email",
|
|
49
|
+
"phone",
|
|
50
|
+
"ssn",
|
|
51
|
+
"social_security",
|
|
52
|
+
"credit_card",
|
|
53
|
+
"card_number",
|
|
54
|
+
"cvv",
|
|
55
|
+
"pin",
|
|
56
|
+
# 数据库连接
|
|
57
|
+
"mysql_pwd",
|
|
58
|
+
"postgres_password",
|
|
59
|
+
"db_password",
|
|
60
|
+
"database_url",
|
|
61
|
+
"connection_string",
|
|
62
|
+
# 其他敏感信息
|
|
63
|
+
"jwt",
|
|
64
|
+
"bearer",
|
|
65
|
+
"oauth",
|
|
66
|
+
"refresh_token",
|
|
67
|
+
"id_token",
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# 业务特定的敏感数据字段列表
|
|
72
|
+
#
|
|
73
|
+
# 匹配规则: 使用 **子串匹配** (.*xxx.*, 不区分大小写)
|
|
74
|
+
# - 'token' 会匹配: access_token, user_token, my_token_field 等
|
|
75
|
+
# - 'secret' 会匹配: user_secret, api_secret, secret_key 等
|
|
76
|
+
# - 'corpid' 会匹配: corpid, CorpId, CORPID, my_corpid 等
|
|
77
|
+
#
|
|
78
|
+
# 这里添加业务特定的敏感字段,包括企业微信相关字段
|
|
79
|
+
BUSINESS_SENSITIVE_FIELDS: Final[frozenset[str]] = frozenset()
|
|
80
|
+
|
|
81
|
+
# URL中可能包含敏感信息的模式
|
|
82
|
+
SENSITIVE_URL_PATTERNS: Final[list[Pattern[str]]] = [
|
|
83
|
+
re.compile(r"[\?&](?:token|key|secret|password|api_key)=[\w\-\.]+", re.IGNORECASE),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# 默认的过滤替换值
|
|
87
|
+
FILTERED_PLACEHOLDER: Final[str] = "[Filtered]"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Sentryx Core SDK v2 事件过滤器
|
|
2
|
+
|
|
3
|
+
提供事件的额外过滤和清理功能.
|
|
4
|
+
此模块不依赖 sentry-sdk, 可被任何 Sentry SDK 版本使用.
|
|
5
|
+
|
|
6
|
+
类型兼容性:
|
|
7
|
+
- create_additional_filter 返回 EventProcessor 类型
|
|
8
|
+
- create_transaction_filter 返回 TransactionProcessor 类型
|
|
9
|
+
- 这两个类型在类型检查时与 sentry_sdk.init() 的参数类型完全匹配
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
from lush_sentryx_core.sdk.v2.const import SENSITIVE_URL_PATTERNS
|
|
15
|
+
from lush_sentryx_core.sdk.v2.scrubbers import deep_scrub_sensitive_data, scrub_stacktrace_vars
|
|
16
|
+
from lush_sentryx_core.sdk.v2.types import Event, EventProcessor, Hint, SensitiveFields, TransactionProcessor
|
|
17
|
+
from lush_sentryx_core.sdk.v2.utils import mask_user_email_partially, parameterize_request_urls
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_additional_filter(
|
|
21
|
+
sensitive_fields: SensitiveFields,
|
|
22
|
+
) -> EventProcessor:
|
|
23
|
+
"""创建轻量级事件过滤器,补充 EventScrubber 未覆盖的场景
|
|
24
|
+
|
|
25
|
+
EventScrubber 已自动处理标准的敏感字段清理,这个过滤器处理:
|
|
26
|
+
- URL 查询参数清理 (移除包含 token/key/secret 的查询参数)
|
|
27
|
+
- 用户邮箱脱敏处理 (保留部分信息用于识别)
|
|
28
|
+
- 深度递归清理嵌套数据结构 (extra, contexts 等)
|
|
29
|
+
- 其他 EventScrubber 无法自动处理的场景
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
sensitive_fields: 敏感字段名的集合 (用于深度清理)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
callable: before_send 过滤器函数,签名为 (Event, Hint) -> Event | None
|
|
36
|
+
|
|
37
|
+
Note:
|
|
38
|
+
- 这是一个工厂函数,返回实际的过滤器函数
|
|
39
|
+
- 兼容 Sentry SDK 2.x 的 before_send 回调
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> from lush_sentryx_core.sdk.v2 import create_additional_filter, SENTRY_DEFAULT_DENYLIST
|
|
43
|
+
>>> import sentry_sdk
|
|
44
|
+
>>> sentry_sdk.init(
|
|
45
|
+
... dsn="...",
|
|
46
|
+
... before_send=create_additional_filter(SENTRY_DEFAULT_DENYLIST),
|
|
47
|
+
... )
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def additional_filter(event: Event, hint: Hint) -> Event | None: # noqa: ARG001 # pyright: ignore[reportUnusedParameter]
|
|
51
|
+
"""轻量级事件过滤器:处理 EventScrubber 未覆盖的场景"""
|
|
52
|
+
try:
|
|
53
|
+
# 1. 清理 URL 中的敏感查询参数
|
|
54
|
+
request = event.get("request", {})
|
|
55
|
+
if request:
|
|
56
|
+
parameterize_request_urls(request)
|
|
57
|
+
|
|
58
|
+
# 2. 用户邮箱脱敏处理 (保留部分信息用于识别)
|
|
59
|
+
user = event.get("user", {})
|
|
60
|
+
if user:
|
|
61
|
+
mask_user_email_partially(user)
|
|
62
|
+
|
|
63
|
+
# 3. 深度清理 extra 数据 (补充 EventScrubber 的清理)
|
|
64
|
+
if "extra" in event:
|
|
65
|
+
deep_scrub_sensitive_data(event["extra"], sensitive_fields)
|
|
66
|
+
|
|
67
|
+
# 4. 深度清理 contexts 数据
|
|
68
|
+
if "contexts" in event:
|
|
69
|
+
deep_scrub_sensitive_data(event["contexts"], sensitive_fields)
|
|
70
|
+
|
|
71
|
+
# 5. 深度清理堆栈帧中的局部变量 (EventScrubber 不能递归处理嵌套对象)
|
|
72
|
+
scrub_stacktrace_vars(event, sensitive_fields)
|
|
73
|
+
|
|
74
|
+
except Exception:
|
|
75
|
+
# 出现异常时返回None,避免发送可能包含敏感数据的事件
|
|
76
|
+
return None
|
|
77
|
+
else:
|
|
78
|
+
return event
|
|
79
|
+
|
|
80
|
+
return additional_filter
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def create_transaction_filter() -> TransactionProcessor:
|
|
84
|
+
"""创建事务名称过滤器, 防止事务名称中包含敏感信息
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
callable: before_send_transaction 过滤器函数,签名为 (Event, Hint) -> Event | None
|
|
88
|
+
|
|
89
|
+
Note:
|
|
90
|
+
- 这是一个工厂函数,返回实际的过滤器函数
|
|
91
|
+
- 兼容 Sentry SDK 2.x 的 before_send_transaction 回调
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
>>> from lush_sentryx_core.sdk.v2 import create_transaction_filter
|
|
95
|
+
>>> import sentry_sdk
|
|
96
|
+
>>> sentry_sdk.init(
|
|
97
|
+
... dsn="...",
|
|
98
|
+
... before_send_transaction=create_transaction_filter(),
|
|
99
|
+
... )
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def transaction_filter(event: Event, hint: Hint) -> Event | None: # noqa: ARG001 # pyright: ignore[reportUnusedParameter]
|
|
103
|
+
"""过滤事务名称中的敏感信息"""
|
|
104
|
+
try:
|
|
105
|
+
transaction_name = event.get("transaction")
|
|
106
|
+
if transaction_name and isinstance(transaction_name, str):
|
|
107
|
+
for pattern in SENSITIVE_URL_PATTERNS:
|
|
108
|
+
if pattern.search(transaction_name):
|
|
109
|
+
transaction_name = re.sub(
|
|
110
|
+
r"[\?&](?:token|key|secret|password|api_key)=[\w\-\.]+",
|
|
111
|
+
r"?[Filtered]",
|
|
112
|
+
transaction_name,
|
|
113
|
+
flags=re.IGNORECASE,
|
|
114
|
+
)
|
|
115
|
+
event["transaction"] = transaction_name
|
|
116
|
+
break
|
|
117
|
+
|
|
118
|
+
except Exception:
|
|
119
|
+
return event
|
|
120
|
+
else:
|
|
121
|
+
return event
|
|
122
|
+
|
|
123
|
+
return transaction_filter
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Sentryx Core SDK v2 数据清理器
|
|
2
|
+
|
|
3
|
+
提供敏感数据清理功能,确保事件不包含敏感信息.
|
|
4
|
+
此模块不依赖 sentry-sdk, 可被任何 Sentry SDK 版本使用.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from lush_sentryx_core.sdk.v2.const import FILTERED_PLACEHOLDER
|
|
11
|
+
from lush_sentryx_core.sdk.v2.types import Event, SensitiveFields
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def deep_scrub_sensitive_data(
|
|
15
|
+
data: Any,
|
|
16
|
+
sensitive_fields: SensitiveFields,
|
|
17
|
+
max_depth: int = 10,
|
|
18
|
+
_current_depth: int = 0,
|
|
19
|
+
placeholder: str = FILTERED_PLACEHOLDER,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""深度递归清理敏感数据
|
|
22
|
+
|
|
23
|
+
此函数用于递归遍历数据结构,将包含敏感字段名的值替换为占位符.
|
|
24
|
+
适用于任何需要清理敏感数据的场景,不限于 Sentry.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
data: 要清理的数据(字典、列表或其他类型)
|
|
28
|
+
sensitive_fields: 敏感字段名的集合
|
|
29
|
+
max_depth: 最大递归深度,防止无限递归 (默认 10 层)
|
|
30
|
+
_current_depth: 当前递归深度(内部使用,不应手动设置)
|
|
31
|
+
placeholder: 替换敏感数据的占位符
|
|
32
|
+
|
|
33
|
+
Note:
|
|
34
|
+
- 仅处理 dict 和 list/tuple 类型,其他类型保持不变
|
|
35
|
+
- 使用子串不区分大小写匹配检查字段名
|
|
36
|
+
- 性能考虑: 限制最大递归深度避免栈溢出
|
|
37
|
+
- 会原地修改传入的数据结构
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> data = {"config": {"corpid": "ww123", "access_token": "secret", "normal": "value"}}
|
|
41
|
+
>>> fields = {"corpid", "access_token", "password"}
|
|
42
|
+
>>> deep_scrub_sensitive_data(data, fields)
|
|
43
|
+
>>> data
|
|
44
|
+
{'config': {'corpid': '[Filtered]', 'access_token': '[Filtered]', 'normal': 'value'}}
|
|
45
|
+
"""
|
|
46
|
+
if _current_depth >= max_depth:
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if isinstance(data, dict):
|
|
50
|
+
keys_to_scrub: list[str] = []
|
|
51
|
+
for key in list(data.keys()): # pyright: ignore[reportUnknownVariableType, reportUnknownArgumentType]
|
|
52
|
+
key_str = str(key).lower() # pyright: ignore[reportUnknownArgumentType]
|
|
53
|
+
is_sensitive = any(deny.lower() in key_str or key_str in deny.lower() for deny in sensitive_fields)
|
|
54
|
+
|
|
55
|
+
if is_sensitive:
|
|
56
|
+
keys_to_scrub.append(key) # pyright: ignore[reportUnknownArgumentType]
|
|
57
|
+
else:
|
|
58
|
+
deep_scrub_sensitive_data(data[key], sensitive_fields, max_depth, _current_depth + 1, placeholder)
|
|
59
|
+
|
|
60
|
+
for key in keys_to_scrub:
|
|
61
|
+
data[key] = placeholder
|
|
62
|
+
|
|
63
|
+
elif isinstance(data, (list, tuple)):
|
|
64
|
+
for item in data: # pyright: ignore[reportUnknownVariableType]
|
|
65
|
+
deep_scrub_sensitive_data(item, sensitive_fields, max_depth, _current_depth + 1, placeholder)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def scrub_stacktrace_vars(
|
|
69
|
+
event: Event,
|
|
70
|
+
sensitive_fields: SensitiveFields,
|
|
71
|
+
placeholder: str = FILTERED_PLACEHOLDER,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""清理堆栈帧中局部变量的嵌套敏感数据
|
|
74
|
+
|
|
75
|
+
此函数遍历 Sentry 事件中的所有堆栈帧,对每个局部变量进行深度清理.
|
|
76
|
+
适用于 Sentry SDK 2.x 的事件结构.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
event: Sentry 事件对象 (符合 SDK 2.x 结构)
|
|
80
|
+
sensitive_fields: 敏感字段名的集合
|
|
81
|
+
placeholder: 替换敏感数据的占位符
|
|
82
|
+
|
|
83
|
+
Note:
|
|
84
|
+
- 处理 exception 和 threads 中的 stacktrace
|
|
85
|
+
- 原地修改局部变量的值
|
|
86
|
+
- 保留变量结构,只过滤敏感字段
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
局部变量 wecom_config = {'corpid': 'ww123', 'name': 'test'}
|
|
90
|
+
会被处理为: {'corpid': '[Filtered]', 'name': 'test'}
|
|
91
|
+
"""
|
|
92
|
+
with contextlib.suppress(Exception): # 静默处理异常,避免影响主流程
|
|
93
|
+
# 处理异常堆栈帧中的局部变量
|
|
94
|
+
if "exception" in event:
|
|
95
|
+
exception_data = event["exception"]
|
|
96
|
+
values = exception_data.get("values", [])
|
|
97
|
+
for exception_value in values:
|
|
98
|
+
stacktrace = exception_value.get("stacktrace", {})
|
|
99
|
+
frames = stacktrace.get("frames", [])
|
|
100
|
+
for frame in frames:
|
|
101
|
+
if "vars" in frame and isinstance(frame["vars"], dict):
|
|
102
|
+
for _var_name, var_value in list(frame["vars"].items()):
|
|
103
|
+
if isinstance(var_value, (dict, list)):
|
|
104
|
+
deep_scrub_sensitive_data(var_value, sensitive_fields, placeholder=placeholder)
|
|
105
|
+
|
|
106
|
+
# 处理线程堆栈 (如果有)
|
|
107
|
+
if "threads" in event:
|
|
108
|
+
threads_data = event["threads"]
|
|
109
|
+
values = threads_data.get("values", [])
|
|
110
|
+
for thread_value in values:
|
|
111
|
+
stacktrace = thread_value.get("stacktrace", {})
|
|
112
|
+
frames = stacktrace.get("frames", [])
|
|
113
|
+
for frame in frames:
|
|
114
|
+
if "vars" in frame and isinstance(frame["vars"], dict):
|
|
115
|
+
for _var_name, var_value in list(frame["vars"].items()):
|
|
116
|
+
if isinstance(var_value, (dict, list)):
|
|
117
|
+
deep_scrub_sensitive_data(var_value, sensitive_fields, placeholder=placeholder)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def scrub_dict_keys(
|
|
121
|
+
data: dict[str, Any],
|
|
122
|
+
sensitive_fields: SensitiveFields,
|
|
123
|
+
placeholder: str = FILTERED_PLACEHOLDER,
|
|
124
|
+
) -> dict[str, Any]:
|
|
125
|
+
"""清理字典中的敏感字段 (非递归,仅顶层)
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
data: 要清理的字典
|
|
129
|
+
sensitive_fields: 敏感字段名的集合
|
|
130
|
+
placeholder: 替换敏感数据的占位符
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
清理后的字典副本
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> data = {"password": "secret", "username": "john"}
|
|
137
|
+
>>> scrub_dict_keys(data, {"password"})
|
|
138
|
+
{'password': '[Filtered]', 'username': 'john'}
|
|
139
|
+
"""
|
|
140
|
+
result = dict(data)
|
|
141
|
+
for key in result:
|
|
142
|
+
key_str = str(key).lower()
|
|
143
|
+
if any(deny.lower() in key_str or key_str in deny.lower() for deny in sensitive_fields):
|
|
144
|
+
result[key] = placeholder
|
|
145
|
+
return result
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Sentryx Core SDK v2 类型定义
|
|
2
|
+
|
|
3
|
+
定义与 Sentry SDK 2.x 兼容的类型别名.
|
|
4
|
+
|
|
5
|
+
类型导入策略:
|
|
6
|
+
- 类型检查时 (TYPE_CHECKING=True): 从 sentry-sdk 导入原生类型,获得完整的类型检查
|
|
7
|
+
- 运行时: 使用本模块定义的类型别名,无需依赖 sentry-sdk
|
|
8
|
+
|
|
9
|
+
这样设计的好处:
|
|
10
|
+
1. 类型检查器能获得与 sentry-sdk 完全匹配的类型定义
|
|
11
|
+
2. 运行时不强制依赖 sentry-sdk,保持核心库的独立性
|
|
12
|
+
3. 传递给 sentry_sdk.init() 的回调函数类型完全兼容
|
|
13
|
+
|
|
14
|
+
使用示例:
|
|
15
|
+
>>> from lush_sentryx_core.sdk.v2.types import Event, Hint
|
|
16
|
+
>>> def my_filter(event: Event, hint: Hint) -> Event | None:
|
|
17
|
+
... return event
|
|
18
|
+
|
|
19
|
+
Note:
|
|
20
|
+
- 需要在环境中安装 sentry-sdk>=2.0.0 才能获得正确的类型检查
|
|
21
|
+
- 运行时这些类型是 Any 的别名,不会影响代码执行
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import TYPE_CHECKING, Any, TypedDict
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
|
|
29
|
+
from sentry_sdk._types import (
|
|
30
|
+
Breadcrumb as SentryBreadcrumb,
|
|
31
|
+
)
|
|
32
|
+
from sentry_sdk._types import (
|
|
33
|
+
Event as SentryEvent,
|
|
34
|
+
)
|
|
35
|
+
from sentry_sdk._types import (
|
|
36
|
+
EventProcessor as SentryEventProcessor,
|
|
37
|
+
)
|
|
38
|
+
from sentry_sdk._types import (
|
|
39
|
+
ExcInfo as SentryExcInfo,
|
|
40
|
+
)
|
|
41
|
+
from sentry_sdk._types import (
|
|
42
|
+
Hint as SentryHint,
|
|
43
|
+
)
|
|
44
|
+
from sentry_sdk._types import (
|
|
45
|
+
TransactionProcessor as SentryTransactionProcessor,
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
from collections.abc import Callable
|
|
49
|
+
|
|
50
|
+
# region 基础类型别名
|
|
51
|
+
|
|
52
|
+
SensitiveFields = set[str] | frozenset[str]
|
|
53
|
+
"""敏感字段集合类型"""
|
|
54
|
+
|
|
55
|
+
# endregion
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# region 条件类型导出 (TYPE_CHECKING 时使用 sentry-sdk 原生类型)
|
|
59
|
+
|
|
60
|
+
if TYPE_CHECKING:
|
|
61
|
+
# 类型检查时: 使用 sentry-sdk 原生类型
|
|
62
|
+
# 这确保 create_additional_filter 等函数返回的类型与
|
|
63
|
+
# sentry_sdk.init(before_send=...) 期望的类型完全匹配
|
|
64
|
+
Event = SentryEvent
|
|
65
|
+
Hint = SentryHint
|
|
66
|
+
ExcInfo = SentryExcInfo
|
|
67
|
+
Breadcrumb = SentryBreadcrumb
|
|
68
|
+
EventProcessor = SentryEventProcessor
|
|
69
|
+
TransactionProcessor = SentryTransactionProcessor
|
|
70
|
+
else:
|
|
71
|
+
# 运行时: 使用 Any 别名,不需要 sentry-sdk 依赖
|
|
72
|
+
Event = dict[str, Any]
|
|
73
|
+
Hint = dict[str, Any]
|
|
74
|
+
ExcInfo = tuple[type[BaseException], BaseException, Any] | tuple[None, None, None]
|
|
75
|
+
Breadcrumb = dict[str, Any]
|
|
76
|
+
EventProcessor = Callable[[dict[str, Any], dict[str, Any]], dict[str, Any] | None]
|
|
77
|
+
TransactionProcessor = Callable[[dict[str, Any], dict[str, Any]], dict[str, Any] | None]
|
|
78
|
+
|
|
79
|
+
# endregion
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# region 内部辅助类型 (用于 utils.py 等模块的类型注解)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Request(TypedDict, total=False):
|
|
86
|
+
"""请求数据结构"""
|
|
87
|
+
|
|
88
|
+
url: str
|
|
89
|
+
method: str
|
|
90
|
+
query_string: str
|
|
91
|
+
data: dict[str, Any] | str
|
|
92
|
+
cookies: dict[str, str]
|
|
93
|
+
headers: dict[str, str]
|
|
94
|
+
env: dict[str, str]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class User(TypedDict, total=False):
|
|
98
|
+
"""用户数据结构"""
|
|
99
|
+
|
|
100
|
+
id: str
|
|
101
|
+
username: str
|
|
102
|
+
email: str
|
|
103
|
+
ip_address: str
|
|
104
|
+
name: str
|
|
105
|
+
geo: dict[str, Any]
|
|
106
|
+
data: dict[str, Any]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# endregion
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Sentryx Core SDK v2 工具函数
|
|
2
|
+
|
|
3
|
+
包含数据序列化、脱敏处理等辅助函数.
|
|
4
|
+
此模块不依赖 sentry-sdk, 可被任何 Sentry SDK 版本使用.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from lush_sentryx_core.sdk.v2.const import SENSITIVE_URL_PATTERNS
|
|
10
|
+
from lush_sentryx_core.sdk.v2.types import Request, User
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def custom_repr(value: Any) -> str | None:
|
|
14
|
+
"""创建自定义变量序列化函数,保持基本类型的清晰展示
|
|
15
|
+
|
|
16
|
+
Sentry SDK 默认使用 repr() 序列化局部变量,这会导致:
|
|
17
|
+
- 字符串 "hello" → "'hello'" (多了引号)
|
|
18
|
+
- 布尔值 False → "'False'" (变成字符串)
|
|
19
|
+
- 数字 123 → "'123'" (变成字符串)
|
|
20
|
+
|
|
21
|
+
此函数提供自定义序列化逻辑,对基本 JSON 可序列化类型保持清晰的原始格式,
|
|
22
|
+
同时让复杂对象继续使用默认的 repr() 表示.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
value: 需要序列化的任意值
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str | None: 序列化后的字符串,或 None (让调用者使用默认处理)
|
|
29
|
+
|
|
30
|
+
Note:
|
|
31
|
+
此函数用于 sentry_sdk.init() 的 custom_repr 参数 (SDK 2.12.0+)
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> custom_repr(True)
|
|
35
|
+
'True'
|
|
36
|
+
>>> custom_repr(123)
|
|
37
|
+
'123'
|
|
38
|
+
>>> custom_repr("hello")
|
|
39
|
+
'hello'
|
|
40
|
+
>>> custom_repr(None)
|
|
41
|
+
'None'
|
|
42
|
+
>>> custom_repr([1, 2])
|
|
43
|
+
None
|
|
44
|
+
"""
|
|
45
|
+
if isinstance(value, (dict, list, tuple, set)):
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
# 注意: bool 必须在 int 之前检查,因为 bool 是 int 的子类
|
|
49
|
+
if isinstance(value, bool):
|
|
50
|
+
return str(value)
|
|
51
|
+
if isinstance(value, (int, float)):
|
|
52
|
+
return str(value)
|
|
53
|
+
if isinstance(value, str):
|
|
54
|
+
return value
|
|
55
|
+
if value is None:
|
|
56
|
+
return "None"
|
|
57
|
+
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def mask_email_partially(email: str) -> str:
|
|
62
|
+
"""对邮箱进行部分脱敏,保留识别性
|
|
63
|
+
|
|
64
|
+
将 user@example.com 转换为 use***@example.com,
|
|
65
|
+
在保护隐私的同时保留一定的识别能力.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
email: 邮箱地址字符串
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
脱敏后的邮箱地址
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
>>> mask_email_partially("user@example.com")
|
|
75
|
+
'use***@example.com'
|
|
76
|
+
>>> mask_email_partially("ab@example.com")
|
|
77
|
+
'***@example.com'
|
|
78
|
+
>>> mask_email_partially("invalid-email")
|
|
79
|
+
'invalid-email'
|
|
80
|
+
"""
|
|
81
|
+
if "@" not in email:
|
|
82
|
+
return email
|
|
83
|
+
|
|
84
|
+
username, domain = email.split("@", 1)
|
|
85
|
+
if len(username) > 3:
|
|
86
|
+
return username[:3] + "***@" + domain
|
|
87
|
+
return "***@" + domain
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def mask_user_email_partially(user: User | dict[str, Any]) -> None:
|
|
91
|
+
"""对用户字典中的邮箱进行部分脱敏,保留识别性
|
|
92
|
+
|
|
93
|
+
将 user@example.com 转换为 use***@example.com,
|
|
94
|
+
在保护隐私的同时保留一定的识别能力.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
user: 用户数据字典 (符合 Sentry User 结构),可能包含 'email' 或 'mail' 字段
|
|
98
|
+
|
|
99
|
+
Note:
|
|
100
|
+
- 此函数会原地修改传入的 user 字典
|
|
101
|
+
- 如果邮箱格式无效或为空,保持不变
|
|
102
|
+
- 支持 'email' 和 'mail' 两种字段名
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
>>> user = {"email": "user@example.com", "id": "123"}
|
|
106
|
+
>>> mask_user_email_partially(user)
|
|
107
|
+
>>> user["email"]
|
|
108
|
+
'use***@example.com'
|
|
109
|
+
"""
|
|
110
|
+
for field_name in ["email", "mail"]:
|
|
111
|
+
if field_name not in user:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
value = user[field_name] # pyright: ignore[reportUnknownVariableType]
|
|
115
|
+
if not isinstance(value, str) or "@" not in value:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
user[field_name] = mask_email_partially(value)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def parameterize_request_urls(request: Request | dict[str, Any]) -> None:
|
|
122
|
+
"""清理请求 URL 中的敏感查询参数
|
|
123
|
+
|
|
124
|
+
将 /api/endpoint?token=secret123 转换为 /api/endpoint (移除查询参数),
|
|
125
|
+
防止敏感信息泄露到事件中.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
request: 请求数据字典 (符合 Sentry Request 结构)
|
|
129
|
+
|
|
130
|
+
Note:
|
|
131
|
+
- 此函数会原地修改传入的 request 字典
|
|
132
|
+
- 只有当 URL 包含敏感查询参数时才会修改
|
|
133
|
+
- 会同时移除 url 中的查询字符串和 query_string 字段
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> request = {"url": "https://api.example.com/users?token=secret123", "query_string": "token=secret123"}
|
|
137
|
+
>>> parameterize_request_urls(request)
|
|
138
|
+
>>> request["url"]
|
|
139
|
+
'https://api.example.com/users'
|
|
140
|
+
>>> "query_string" in request
|
|
141
|
+
False
|
|
142
|
+
"""
|
|
143
|
+
url = request.get("url", "")
|
|
144
|
+
if not isinstance(url, str):
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
for pattern in SENSITIVE_URL_PATTERNS:
|
|
148
|
+
if pattern.search(url):
|
|
149
|
+
_ = request.pop("query_string", None)
|
|
150
|
+
if "?" in url:
|
|
151
|
+
url = url.split("?")[0]
|
|
152
|
+
request["url"] = url
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def mask_string_partially(
|
|
157
|
+
value: str,
|
|
158
|
+
visible_prefix: int = 3,
|
|
159
|
+
visible_suffix: int = 0,
|
|
160
|
+
mask_char: str = "*",
|
|
161
|
+
min_mask_length: int = 3,
|
|
162
|
+
) -> str:
|
|
163
|
+
"""对字符串进行部分脱敏
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
value: 要脱敏的字符串
|
|
167
|
+
visible_prefix: 保留前缀的字符数
|
|
168
|
+
visible_suffix: 保留后缀的字符数
|
|
169
|
+
mask_char: 脱敏字符
|
|
170
|
+
min_mask_length: 最小脱敏字符数
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
脱敏后的字符串
|
|
174
|
+
|
|
175
|
+
Example:
|
|
176
|
+
>>> mask_string_partially("1234567890", visible_prefix=3, visible_suffix=2)
|
|
177
|
+
'123*****90'
|
|
178
|
+
>>> mask_string_partially("abc", visible_prefix=3)
|
|
179
|
+
'***'
|
|
180
|
+
"""
|
|
181
|
+
if len(value) <= visible_prefix + visible_suffix:
|
|
182
|
+
return mask_char * min_mask_length
|
|
183
|
+
|
|
184
|
+
masked_length = max(len(value) - visible_prefix - visible_suffix, min_mask_length)
|
|
185
|
+
prefix = value[:visible_prefix] if visible_prefix > 0 else ""
|
|
186
|
+
suffix = value[-visible_suffix:] if visible_suffix > 0 else ""
|
|
187
|
+
return prefix + (mask_char * masked_length) + suffix
|