|
27 | 27 | FC_API_KEY, |
28 | 28 | FC_LOAD_API, |
29 | 29 | FC_QUERY_API, |
| 30 | + FC_QUERY_LOG_API, |
30 | 31 | FC_REGISTER_REWARD_API, |
31 | 32 | FC_REGISTER_ROLLOUT_API, |
32 | 33 | FC_REGISTER_GROUP_REWARD_API, |
@@ -1056,6 +1057,165 @@ async def verify_function( |
1056 | 1057 | error_code=2047, |
1057 | 1058 | ) from e |
1058 | 1059 |
|
| 1060 | + @staticmethod |
| 1061 | + def _extract_log_page( |
| 1062 | + result: Dict[str, Any], |
| 1063 | + ) -> Tuple[List[str], Optional[int]]: |
| 1064 | + """Extract log messages and total count from a log query response. |
| 1065 | +
|
| 1066 | + Expected response format:: |
| 1067 | +
|
| 1068 | + { |
| 1069 | + "success": true, |
| 1070 | + "code": null, |
| 1071 | + "message": null, |
| 1072 | + "data": { |
| 1073 | + "pageNumber": 1, |
| 1074 | + "pageSize": 50, |
| 1075 | + "totalCount": 128, |
| 1076 | + "logEntries": [ |
| 1077 | + {"instanceId": "...", "message": "...", |
| 1078 | + "timestamp": 1699051200} |
| 1079 | + ] |
| 1080 | + } |
| 1081 | + } |
| 1082 | +
|
| 1083 | + Only the "message" field of each log entry is kept; other fields |
| 1084 | + are ignored. |
| 1085 | + """ |
| 1086 | + data = result.get("data", {}) |
| 1087 | + if not isinstance(data, Dict): |
| 1088 | + return [], None |
| 1089 | + |
| 1090 | + entries = data.get("logEntries") or [] |
| 1091 | + logs = [ |
| 1092 | + entry.get("message", "") if isinstance(entry, Dict) else str(entry) |
| 1093 | + for entry in entries |
| 1094 | + ] |
| 1095 | + |
| 1096 | + total = data.get("totalCount") |
| 1097 | + if not isinstance(total, int): |
| 1098 | + total = None |
| 1099 | + |
| 1100 | + return logs, total |
| 1101 | + |
| 1102 | + @classmethod |
| 1103 | + async def query_function_instance_logs( |
| 1104 | + cls, |
| 1105 | + function_instance_id: str, |
| 1106 | + page_number: int = 1, |
| 1107 | + page_size: int = 100, |
| 1108 | + start_time: Optional[int] = None, |
| 1109 | + end_time: Optional[int] = None, |
| 1110 | + keywords: Optional[List[str]] = None, |
| 1111 | + ) -> Dict[str, Any]: |
| 1112 | + """Query logs of a function (faas) instance (single page). |
| 1113 | +
|
| 1114 | + Args: |
| 1115 | + function_instance_id: Target function instance ID. |
| 1116 | + page_number: Page number, starting from 1. |
| 1117 | + page_size: Number of log entries per page. |
| 1118 | + start_time: Optional start time filter (in seconds). |
| 1119 | + end_time: Optional end time filter (in seconds). |
| 1120 | + keywords: Optional keyword filters for log messages. |
| 1121 | +
|
| 1122 | + Returns: |
| 1123 | + Raw response dict of the log query API. |
| 1124 | + """ |
| 1125 | + if not function_instance_id: |
| 1126 | + raise InputError( |
| 1127 | + "No function instance ID available for log query", |
| 1128 | + error_code=2071, |
| 1129 | + ) |
| 1130 | + |
| 1131 | + url = f"{FC_QUERY_LOG_API}/{function_instance_id}" |
| 1132 | + request_data: Dict[str, Any] = { |
| 1133 | + "pageNumber": page_number, |
| 1134 | + "pageSize": page_size, |
| 1135 | + } |
| 1136 | + if start_time is not None: |
| 1137 | + request_data["startTime"] = start_time |
| 1138 | + if end_time is not None: |
| 1139 | + request_data["endTime"] = end_time |
| 1140 | + if keywords: |
| 1141 | + request_data["keywords"] = keywords |
| 1142 | + |
| 1143 | + result = await client_fc(FC_API_KEY, url, request_data) |
| 1144 | + status = result.get("status", {}) |
| 1145 | + if isinstance(status, Dict) and status.get("code", 200) != 200: |
| 1146 | + raise InstanceQueryError( |
| 1147 | + f"Log query failed: {result}", |
| 1148 | + error_code=2072, |
| 1149 | + instance_id=function_instance_id, |
| 1150 | + ) |
| 1151 | + if result.get("success") is False: |
| 1152 | + raise InstanceQueryError( |
| 1153 | + f"Log query failed: {result.get('message') or result}", |
| 1154 | + error_code=2073, |
| 1155 | + instance_id=function_instance_id, |
| 1156 | + ) |
| 1157 | + |
| 1158 | + logger.debug( |
| 1159 | + f"Log query completed | FunctionInstanceID: " |
| 1160 | + f"{function_instance_id} | " |
| 1161 | + f"Page: {page_number} | Size: {page_size}", |
| 1162 | + ) |
| 1163 | + return result |
| 1164 | + |
| 1165 | + @classmethod |
| 1166 | + async def query_all_function_instance_logs( |
| 1167 | + cls, |
| 1168 | + function_instance_id: str, |
| 1169 | + page_size: int = 100, |
| 1170 | + start_time: Optional[int] = None, |
| 1171 | + end_time: Optional[int] = None, |
| 1172 | + keywords: Optional[List[str]] = None, |
| 1173 | + max_pages: int = 100, |
| 1174 | + ) -> List[str]: |
| 1175 | + """Fetch all logs of a function (faas) instance with pagination. |
| 1176 | +
|
| 1177 | + Iterates pages starting from page 1 until all entries are |
| 1178 | + collected (based on the reported total count, an empty page, or |
| 1179 | + a short page). |
| 1180 | +
|
| 1181 | + Args: |
| 1182 | + function_instance_id: Target function instance ID. |
| 1183 | + page_size: Number of log entries per page. |
| 1184 | + start_time: Optional start time filter (in seconds). |
| 1185 | + end_time: Optional end time filter (in seconds). |
| 1186 | + keywords: Optional keyword filters for log messages. |
| 1187 | + max_pages: Safety limit on the number of pages to fetch. |
| 1188 | +
|
| 1189 | + Returns: |
| 1190 | + Aggregated list of log messages across all pages. |
| 1191 | + """ |
| 1192 | + all_logs: List[str] = [] |
| 1193 | + page_number = 1 |
| 1194 | + while page_number <= max_pages: |
| 1195 | + result = await cls.query_function_instance_logs( |
| 1196 | + function_instance_id=function_instance_id, |
| 1197 | + page_number=page_number, |
| 1198 | + page_size=page_size, |
| 1199 | + start_time=start_time, |
| 1200 | + end_time=end_time, |
| 1201 | + keywords=keywords, |
| 1202 | + ) |
| 1203 | + logs, total = cls._extract_log_page(result) |
| 1204 | + all_logs.extend(logs) |
| 1205 | + |
| 1206 | + if total is not None and len(all_logs) >= total: |
| 1207 | + break |
| 1208 | + if len(logs) < page_size: |
| 1209 | + break |
| 1210 | + page_number += 1 |
| 1211 | + |
| 1212 | + logger.debug( |
| 1213 | + f"All logs fetched | FunctionInstanceID: " |
| 1214 | + f"{function_instance_id} | " |
| 1215 | + f"Pages: {page_number} | Entries: {len(all_logs)}", |
| 1216 | + ) |
| 1217 | + return all_logs |
| 1218 | + |
1059 | 1219 |
|
1060 | 1220 | class RolloutFunctionComponent(AgenticRLFunctionComponent): |
1061 | 1221 | """Rollout function component with type fixed as ROLLOUT.""" |
|
0 commit comments