mcp-dbutils 0.23.1__py3-none-any.whl → 1.0.1__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.
- mcp_dbutils/audit.py +269 -0
- mcp_dbutils/base.py +498 -0
- mcp_dbutils/config.py +103 -1
- mcp_dbutils/mysql/config.py +57 -40
- mcp_dbutils/mysql/handler.py +60 -0
- mcp_dbutils/postgres/config.py +40 -22
- mcp_dbutils/postgres/handler.py +60 -0
- mcp_dbutils/sqlite/config.py +8 -1
- mcp_dbutils/sqlite/handler.py +53 -0
- {mcp_dbutils-0.23.1.dist-info → mcp_dbutils-1.0.1.dist-info}/METADATA +1 -1
- mcp_dbutils-1.0.1.dist-info/RECORD +23 -0
- mcp_dbutils-0.23.1.dist-info/RECORD +0 -22
- {mcp_dbutils-0.23.1.dist-info → mcp_dbutils-1.0.1.dist-info}/WHEEL +0 -0
- {mcp_dbutils-0.23.1.dist-info → mcp_dbutils-1.0.1.dist-info}/entry_points.txt +0 -0
- {mcp_dbutils-0.23.1.dist-info → mcp_dbutils-1.0.1.dist-info}/licenses/LICENSE +0 -0
mcp_dbutils/audit.py
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
"""审计日志系统
|
2
|
+
|
3
|
+
记录所有数据库写操作,提供审计和追踪功能。
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
import logging
|
8
|
+
import os
|
9
|
+
import time
|
10
|
+
from datetime import datetime
|
11
|
+
from pathlib import Path
|
12
|
+
from typing import Any, Dict, List, Optional, Set, Union
|
13
|
+
|
14
|
+
# 设置审计日志记录器
|
15
|
+
audit_logger = logging.getLogger("mcp_dbutils.audit")
|
16
|
+
audit_logger.setLevel(logging.INFO)
|
17
|
+
|
18
|
+
# 内存缓冲区,保存最近的日志记录
|
19
|
+
_memory_buffer: List[Dict[str, Any]] = []
|
20
|
+
_memory_buffer_size = 1000 # 默认缓冲区大小
|
21
|
+
|
22
|
+
# 审计日志配置
|
23
|
+
_audit_config = {
|
24
|
+
"enabled": True,
|
25
|
+
"file_storage": {
|
26
|
+
"enabled": True,
|
27
|
+
"path": "logs/audit",
|
28
|
+
"max_file_size": 10 * 1024 * 1024, # 10MB
|
29
|
+
"backup_count": 10
|
30
|
+
},
|
31
|
+
"content": {
|
32
|
+
"sanitize_sql": True,
|
33
|
+
"include_user_context": True
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
def configure_audit_logging(config: Dict[str, Any]) -> None:
|
39
|
+
"""配置审计日志系统
|
40
|
+
|
41
|
+
Args:
|
42
|
+
config: 审计日志配置
|
43
|
+
"""
|
44
|
+
global _audit_config, _memory_buffer_size
|
45
|
+
|
46
|
+
if config:
|
47
|
+
_audit_config.update(config)
|
48
|
+
|
49
|
+
# 更新内存缓冲区大小
|
50
|
+
if "memory_buffer" in config and "size" in config["memory_buffer"]:
|
51
|
+
_memory_buffer_size = config["memory_buffer"]["size"]
|
52
|
+
|
53
|
+
# 配置文件处理器
|
54
|
+
if _audit_config["enabled"] and _audit_config["file_storage"]["enabled"]:
|
55
|
+
_setup_file_handler()
|
56
|
+
|
57
|
+
|
58
|
+
def _setup_file_handler() -> None:
|
59
|
+
"""设置文件日志处理器"""
|
60
|
+
log_dir = _audit_config["file_storage"]["path"]
|
61
|
+
os.makedirs(log_dir, exist_ok=True)
|
62
|
+
|
63
|
+
# 创建文件处理器
|
64
|
+
file_handler = logging.FileHandler(
|
65
|
+
f"{log_dir}/dbutils-audit.log",
|
66
|
+
mode="a",
|
67
|
+
encoding="utf-8"
|
68
|
+
)
|
69
|
+
|
70
|
+
# 设置格式
|
71
|
+
formatter = logging.Formatter("%(asctime)s - %(message)s")
|
72
|
+
file_handler.setFormatter(formatter)
|
73
|
+
|
74
|
+
# 添加到记录器
|
75
|
+
audit_logger.addHandler(file_handler)
|
76
|
+
|
77
|
+
|
78
|
+
def _sanitize_sql(sql: str) -> str:
|
79
|
+
"""对SQL语句进行脱敏处理
|
80
|
+
|
81
|
+
Args:
|
82
|
+
sql: SQL语句
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
脱敏后的SQL语句
|
86
|
+
"""
|
87
|
+
if not _audit_config["content"]["sanitize_sql"]:
|
88
|
+
return sql
|
89
|
+
|
90
|
+
# 简单的脱敏处理,替换VALUES子句中的值
|
91
|
+
import re
|
92
|
+
|
93
|
+
# 替换INSERT语句中的VALUES
|
94
|
+
sanitized = re.sub(
|
95
|
+
r"VALUES\s*\((.*?)\)",
|
96
|
+
"VALUES (?)",
|
97
|
+
sql,
|
98
|
+
flags=re.IGNORECASE | re.DOTALL
|
99
|
+
)
|
100
|
+
|
101
|
+
# 替换WHERE子句中的值
|
102
|
+
sanitized = re.sub(
|
103
|
+
r"(WHERE\s+\w+\s*=\s*)('[^']*'|\d+)",
|
104
|
+
r"\1?",
|
105
|
+
sanitized,
|
106
|
+
flags=re.IGNORECASE
|
107
|
+
)
|
108
|
+
|
109
|
+
return sanitized
|
110
|
+
|
111
|
+
|
112
|
+
def _get_user_context() -> Optional[str]:
|
113
|
+
"""获取用户上下文
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
用户上下文信息
|
117
|
+
"""
|
118
|
+
if not _audit_config["content"]["include_user_context"]:
|
119
|
+
return None
|
120
|
+
|
121
|
+
# 这里可以添加获取用户上下文的逻辑
|
122
|
+
# 例如从环境变量、请求头等获取
|
123
|
+
return None
|
124
|
+
|
125
|
+
|
126
|
+
def log_write_operation(
|
127
|
+
connection_name: str,
|
128
|
+
table_name: str,
|
129
|
+
operation_type: str,
|
130
|
+
sql: str,
|
131
|
+
affected_rows: int,
|
132
|
+
execution_time: float,
|
133
|
+
status: str = "SUCCESS",
|
134
|
+
error_message: Optional[str] = None
|
135
|
+
) -> None:
|
136
|
+
"""记录写操作到审计日志
|
137
|
+
|
138
|
+
Args:
|
139
|
+
connection_name: 数据库连接名称
|
140
|
+
table_name: 表名
|
141
|
+
operation_type: 操作类型(INSERT、UPDATE、DELETE)
|
142
|
+
sql: SQL语句
|
143
|
+
affected_rows: 影响的行数
|
144
|
+
execution_time: 执行时间(毫秒)
|
145
|
+
status: 操作结果(SUCCESS、FAILED)
|
146
|
+
error_message: 错误信息(如果失败)
|
147
|
+
"""
|
148
|
+
if not _audit_config["enabled"]:
|
149
|
+
return
|
150
|
+
|
151
|
+
# 创建日志记录
|
152
|
+
log_entry = {
|
153
|
+
"timestamp": datetime.now().isoformat(),
|
154
|
+
"connection_name": connection_name,
|
155
|
+
"table_name": table_name,
|
156
|
+
"operation_type": operation_type,
|
157
|
+
"sql_statement": _sanitize_sql(sql),
|
158
|
+
"affected_rows": affected_rows,
|
159
|
+
"status": status,
|
160
|
+
"execution_time": execution_time,
|
161
|
+
"user_context": _get_user_context()
|
162
|
+
}
|
163
|
+
|
164
|
+
if error_message:
|
165
|
+
log_entry["error_message"] = error_message
|
166
|
+
|
167
|
+
# 添加到内存缓冲区
|
168
|
+
_memory_buffer.append(log_entry)
|
169
|
+
if len(_memory_buffer) > _memory_buffer_size:
|
170
|
+
_memory_buffer.pop(0) # 移除最旧的记录
|
171
|
+
|
172
|
+
# 写入日志文件
|
173
|
+
if _audit_config["file_storage"]["enabled"]:
|
174
|
+
audit_logger.info(json.dumps(log_entry))
|
175
|
+
|
176
|
+
|
177
|
+
def get_logs(
|
178
|
+
connection_name: Optional[str] = None,
|
179
|
+
table_name: Optional[str] = None,
|
180
|
+
start_time: Optional[str] = None,
|
181
|
+
end_time: Optional[str] = None,
|
182
|
+
operation_type: Optional[str] = None,
|
183
|
+
status: Optional[str] = None,
|
184
|
+
limit: int = 100
|
185
|
+
) -> List[Dict[str, Any]]:
|
186
|
+
"""获取审计日志
|
187
|
+
|
188
|
+
Args:
|
189
|
+
connection_name: 数据库连接名称
|
190
|
+
table_name: 表名
|
191
|
+
start_time: 开始时间(ISO 8601格式)
|
192
|
+
end_time: 结束时间(ISO 8601格式)
|
193
|
+
operation_type: 操作类型(INSERT、UPDATE、DELETE)
|
194
|
+
status: 操作状态(SUCCESS、FAILED)
|
195
|
+
limit: 返回记录数量限制
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
审计日志记录列表
|
199
|
+
"""
|
200
|
+
# 从内存缓冲区获取日志
|
201
|
+
logs = _memory_buffer.copy()
|
202
|
+
|
203
|
+
# 应用过滤条件
|
204
|
+
filtered_logs = []
|
205
|
+
for log in logs:
|
206
|
+
# 连接名称过滤
|
207
|
+
if connection_name and log["connection_name"] != connection_name:
|
208
|
+
continue
|
209
|
+
|
210
|
+
# 表名过滤
|
211
|
+
if table_name and log["table_name"] != table_name:
|
212
|
+
continue
|
213
|
+
|
214
|
+
# 时间范围过滤
|
215
|
+
if start_time and log["timestamp"] < start_time:
|
216
|
+
continue
|
217
|
+
if end_time and log["timestamp"] > end_time:
|
218
|
+
continue
|
219
|
+
|
220
|
+
# 操作类型过滤
|
221
|
+
if operation_type and log["operation_type"] != operation_type:
|
222
|
+
continue
|
223
|
+
|
224
|
+
# 状态过滤
|
225
|
+
if status and log["status"] != status:
|
226
|
+
continue
|
227
|
+
|
228
|
+
filtered_logs.append(log)
|
229
|
+
|
230
|
+
# 应用限制
|
231
|
+
if len(filtered_logs) >= limit:
|
232
|
+
break
|
233
|
+
|
234
|
+
return filtered_logs
|
235
|
+
|
236
|
+
|
237
|
+
def format_logs(logs: List[Dict[str, Any]]) -> str:
|
238
|
+
"""格式化审计日志
|
239
|
+
|
240
|
+
Args:
|
241
|
+
logs: 审计日志记录列表
|
242
|
+
|
243
|
+
Returns:
|
244
|
+
格式化后的审计日志
|
245
|
+
"""
|
246
|
+
if not logs:
|
247
|
+
return "No audit logs found."
|
248
|
+
|
249
|
+
formatted = ["Audit Logs:", "==========="]
|
250
|
+
|
251
|
+
for log in logs:
|
252
|
+
formatted.extend([
|
253
|
+
f"\nTimestamp: {log['timestamp']}",
|
254
|
+
f"Connection: {log['connection_name']}",
|
255
|
+
f"Table: {log['table_name']}",
|
256
|
+
f"Operation: {log['operation_type']}",
|
257
|
+
f"Status: {log['status']}",
|
258
|
+
f"Affected Rows: {log['affected_rows']}",
|
259
|
+
f"Execution Time: {log['execution_time']:.2f}ms",
|
260
|
+
f"SQL: {log['sql_statement']}"
|
261
|
+
])
|
262
|
+
|
263
|
+
if "error_message" in log:
|
264
|
+
formatted.append(f"Error: {log['error_message']}")
|
265
|
+
|
266
|
+
if log.get("user_context"):
|
267
|
+
formatted.append(f"User Context: {log['user_context']}")
|
268
|
+
|
269
|
+
return "\n".join(formatted)
|