mcp-dbutils 0.23.0__py3-none-any.whl → 1.0.0__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 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)