@@ -44,70 +44,51 @@ def _get_cs_client(ctx: Context, region_id: str):
4444 return cs_client_factory (region_id , config )
4545
4646
47- def _parse_time (time_str : str ) -> int :
48- """解析时间字符串为 Unix 时间戳(秒级)。
49-
50- 支持格式:
51- - ISO 8601: "2025-09-16T08:09:44Z" 或 "2025-09-16T08:09:44+08:00"
52- - 相对时间: "30m", "1h", "24h", "7d"
53- - Unix 时间戳: "1758037055" (秒级) 或 "1758037055000" (毫秒级,自动转换为秒级)
47+ def _parse_single_time (time_str : Optional [str ], default_hours : int = 24 ) -> datetime :
48+ """参考 ack_audit_log_handler 的实现:
49+ 支持相对时间后缀(s/m/h/d/w)与 ISO 8601(允许 Z),返回 datetime。
50+ 兼容纯数字的 unix 秒/毫秒。
5451 """
55- if not time_str :
56- return int (datetime .now ().timestamp ())
57-
58- # 检查是否是纯数字(Unix 时间戳)
59- if time_str .isdigit ():
60- timestamp = int (time_str )
61- # 如果是毫秒级时间戳(13位数字),转换为秒级
62- if timestamp > 1e12 : # 大于 2001-09-09 的时间戳,可能是毫秒级
63- return timestamp // 1000
64- else :
65- return timestamp
66-
67- # 处理相对时间格式
68- relative_pattern = r'^(\d+)([smhd])$'
69- match = re .match (relative_pattern , time_str .lower ())
70- if match :
71- value = int (match .group (1 ))
72- unit = match .group (2 )
73- now = datetime .now ()
74-
75- if unit == 's' : # 秒
76- delta = timedelta (seconds = value )
77- elif unit == 'm' : # 分钟
78- delta = timedelta (minutes = value )
79- elif unit == 'h' : # 小时
80- delta = timedelta (hours = value )
81- elif unit == 'd' : # 天
82- delta = timedelta (days = value )
83- else :
84- raise ValueError (f"Unsupported time unit: { unit } " )
52+ from datetime import datetime
8553
86- return int ((now - delta ).timestamp ())
54+ if not time_str :
55+ return datetime .now () - timedelta (hours = default_hours )
56+
57+ ts = str (time_str ).strip ()
58+ if ts .isdigit ():
59+ iv = int (ts )
60+ if iv > 1e12 : # 毫秒
61+ iv //= 1000
62+ return datetime .fromtimestamp (iv )
63+
64+ ts_lower = ts .lower ()
65+ if ts_lower .endswith ('h' ):
66+ return datetime .now () - timedelta (hours = int (ts_lower [:- 1 ]))
67+ if ts_lower .endswith ('d' ):
68+ return datetime .now () - timedelta (days = int (ts_lower [:- 1 ]))
69+ if ts_lower .endswith ('m' ):
70+ return datetime .now () - timedelta (minutes = int (ts_lower [:- 1 ]))
71+ if ts_lower .endswith ('s' ):
72+ return datetime .now () - timedelta (seconds = int (ts_lower [:- 1 ]))
73+ if ts_lower .endswith ('w' ):
74+ return datetime .now () - timedelta (weeks = int (ts_lower [:- 1 ]))
8775
88- # ISO 8601 格式
8976 try :
90- # 检查是否包含时区信息
91- if not ('Z' in time_str or '+' in time_str or time_str .count ('-' ) > 2 ):
92- raise ValueError ("ISO 8601 format must include timezone information (Z, +HH:MM, or -HH:MM)" )
93-
94- # 处理 Z 后缀(UTC时间)
95- if time_str .endswith ('Z' ):
96- time_str = time_str [:- 1 ] + '+00:00'
97-
98- dt = datetime .fromisoformat (time_str )
99- # 确保转换为UTC时间戳
100- if dt .tzinfo is None :
101- # 如果没有时区信息,假设为UTC
102- dt = dt .replace (tzinfo = None )
103- return int (dt .timestamp ())
104- else :
105- return int (dt .timestamp ())
106- except ValueError as e :
107- if "timezone information" in str (e ):
108- raise e
109- raise ValueError (
110- f"Invalid time format: { time_str } . Expected ISO 8601 format (e.g., 2025-09-16T08:09:44Z), relative time (e.g., 24h), or Unix timestamp." )
77+ iso_str = ts
78+ if iso_str .endswith ('Z' ):
79+ iso_str = iso_str .replace ('Z' , '+00:00' )
80+ return datetime .fromisoformat (iso_str )
81+ except ValueError :
82+ return datetime .now () - timedelta (hours = default_hours )
83+
84+
85+ def _parse_time_params (start_time : Optional [str ], end_time : Optional [str ]) -> tuple [int , int ]:
86+ """与审计日志对齐:返回秒级 unix,确保开始时间 < 结束时间。"""
87+ start_dt = _parse_single_time (start_time or "24h" , default_hours = 24 )
88+ end_dt = _parse_single_time (end_time , default_hours = 0 ) if end_time else datetime .now ()
89+ if start_dt >= end_dt :
90+ end_dt = datetime .now ()
91+ return int (start_dt .timestamp ()), int (end_dt .timestamp ())
11192
11293
11394def _build_controlplane_log_query (
@@ -233,7 +214,19 @@ def __init__(self, server: FastMCP, settings: Optional[Dict[str, Any]] = None):
233214 # Register tools
234215 self .server .tool (
235216 name = "query_controlplane_logs" ,
236- description = "查询ACK集群的控制面组件日志。先查询控制面日志配置,验证组件是否启用,然后查询对应的SLS日志。"
217+ description = """Query ACK cluster control plane component logs.
218+
219+ Function Description:
220+ - Queries control plane component logs from ACK clusters.
221+ - Supports multiple time formats (ISO 8601 and relative time).
222+ - Supports additional filter patterns for log filtering.
223+ - Validates component availability before querying.
224+ - Provides detailed parameter validation and error messages.
225+
226+ Usage Suggestions:
227+ - You can use the list_clusters() tool to view available clusters and their IDs.
228+ - By default, it queries the control plane logs for the last 24 hours. The number of returned records is limited to 10 by default.
229+ - Supported components: apiserver, kcm, scheduler, ccm, etcd, kubelet, kube-proxy, kube-scheduler, kube-controller-manager, kube-apiserver."""
237230 )(self .query_controlplane_logs )
238231
239232 logger .info ("ACK Control Plane Log Handler initialized" )
@@ -272,14 +265,39 @@ def _get_cluster_region(self, cs_client, cluster_id: str) -> str:
272265 async def query_controlplane_logs (
273266 self ,
274267 ctx : Context ,
275- cluster_id : str = Field (..., description = "集群ID,例如 cxxxxx" ),
276- component_name : str = Field (..., description = "控制面组件的名称,如 apiserver, kcm, scheduler, ccm" ),
277- filter_pattern : Optional [str ] = Field (None , description = "额外过滤条件" ),
278- start_time : Optional [str ] = Field ("24h" ,
279- description = "查询开始时间,支持格式为 ISO 8601 (如: 2025-09-16T08:09:44Z)" ),
280- end_time : Optional [str ] = Field (None ,
281- description = "查询结束时间,支持格式为 ISO 8601 (如: 2025-09-16T08:09:44Z)" ),
282- limit : Optional [int ] = Field (10 , description = "结果限制,默认10,最大100" ),
268+ cluster_id : str = Field (..., description = "集群ID" ),
269+ component_name : str = Field (..., description = """控制面组件的名称,枚举值:
270+ apiserver: API Server组件
271+ kcm: KCM组件
272+ scheduler: Scheduler组件
273+ ccm: CCM组件
274+ """ ),
275+ filter_pattern : Optional [str ] = Field (None , description = """(Optional) Additional filter pattern.
276+ Example:
277+ - 查询pod相关的日志: "coredns-8bd9456cc-wck2l"
278+ Defaults to '*' (all logs)."""
279+ ),
280+ start_time : str = Field (
281+ "24h" ,
282+ description = """(Optional) Query start time.
283+ Formats:
284+ - ISO 8601: "2024-01-01T10:00:00Z"
285+ - Relative: "30m", "1h", "24h", "7d"
286+ Defaults to 24h."""
287+ ),
288+ end_time : Optional [str ] = Field (
289+ None ,
290+ description = """(Optional) Query end time.
291+ Formats:
292+ - ISO 8601: "2024-01-01T10:00:00Z"
293+ - Relative: "30m", "1h", "24h", "7d"
294+ Defaults to current time."""
295+ ),
296+ limit : int = Field (
297+ 10 ,
298+ ge = 1 , le = 100 ,
299+ description = "(Optional) Result limit, defaults to 10. Maximum is 100."
300+ ),
283301 ) -> QueryControlPlaneLogsOutput :
284302 """查询ACK集群的控制面组件日志
285303
@@ -430,9 +448,8 @@ async def query_controlplane_logs(
430448 'annotation' ) else "24h"
431449 end_time_str = end_time if isinstance (end_time , str ) and not hasattr (end_time , 'annotation' ) else None
432450
433- # SLS API 需要秒级时间戳
434- start_timestamp_s = _parse_time (start_time_str )
435- end_timestamp_s = _parse_time (end_time_str ) if end_time_str else int (datetime .now ().timestamp ())
451+ # SLS API 需要秒级时间戳(与审计日志时间解析策略对齐)
452+ start_timestamp_s , end_timestamp_s = _parse_time_params (start_time_str , end_time_str )
436453
437454 # 步骤4: 构建SLS查询语句
438455 logger .info (f"Step 4: Building SLS query for component { component_name } " )
0 commit comments