fix(ci): rewrite ci_trace_report.py in English to fix encoding issues
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been cancelled
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m5s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m23s

This commit is contained in:
2026-07-20 09:46:19 +08:00
parent 92a1c25828
commit e7dc4aac86
+66 -48
View File
@@ -1,24 +1,27 @@
#!/usr/bin/env python3
"""
CI Trace 涓婃姤鑴氭湰 - 鐢ㄤ簬 Gitea Actions workflow 涓笂鎶?Trace 鏁版嵁鍒?AgentLoop
CI Trace Report Script - Reports CI Trace data to AgentLoop from Gitea Actions workflows.
鍦?CI workflow 鐨勬瘡涓?job 涓皟鐢細
- 寮€濮嬫椂锛歱ython3 .gitea/scripts/ci_trace_report.py --status running
- 缁撴潫鏃讹細python3 .gitea/scripts/ci_trace_report.py --status ok --start-time $CI_TRACE_START_TIME
Usage in CI workflow jobs:
- At start: python3 scripts/ci/ci_trace_report.py --status running
- At end: python3 scripts/ci/ci_trace_report.py --status ok --start-time $CI_TRACE_START_TIME
鐜鍙橀噺锛圙itea Actions 鍐呯疆锛夛細
GITEA_REPOSITORY / GITHUB_REPOSITORY - 浠撳簱鍚?(owner/repo)
GITEA_WORKFLOW / GITHUB_WORKFLOW - workflow 鍚嶇О
Environment variables (built-in Gitea Actions):
GITEA_REPOSITORY / GITHUB_REPOSITORY - repository (owner/repo)
GITEA_WORKFLOW / GITHUB_WORKFLOW - workflow name
GITEA_JOB / GITHUB_JOB - job ID
GITEA_SHA / GITHUB_SHA - commit SHA
GITEA_REF_NAME / GITHUB_REF_NAME - 鍒嗘敮鍚? GITEA_RUN_ID / GITHUB_RUN_ID - run ID
GITEA_ACTOR / GITHUB_ACTOR - 瑙﹀彂鑰? GITEA_EVENT_NAME / GITHUB_EVENT_NAME - 浜嬩欢绫诲瀷
PR_NUMBER / GITEA_PR_NUMBER - PR 鍙凤紙濡傛灉鏄?PR 瑙﹀彂锛?
AgentLoop 閰嶇疆锛堥€氳繃 Secrets 娉ㄥ叆锛夛細
AGENTLOOP_LICENSE_KEY - LicenseKey锛堝繀濉級
AGENTLOOP_ENDPOINT - Trace 涓婃姤鍦板潃锛堝彲閫夛紝鏈夐粯璁ゅ€硷級
AGENTLOOP_PROJECT - SLS Project 鍚嶏紙鍙€夛級
AGENTLOOP_WORKSPACE - CMS Workspace 鍚嶏紙鍙€夛級
GITEA_REF_NAME / GITHUB_REF_NAME - branch name
GITEA_RUN_ID / GITHUB_RUN_ID - run ID
GITEA_ACTOR / GITHUB_ACTOR - trigger actor
GITEA_EVENT_NAME / GITHUB_EVENT_NAME - event type
PR_NUMBER / GITEA_PR_NUMBER - PR number (if PR triggered)
AgentLoop configuration (injected via Secrets):
AGENTLOOP_LICENSE_KEY - LicenseKey (required)
AGENTLOOP_ENDPOINT - Trace endpoint (optional, has default)
AGENTLOOP_PROJECT - SLS Project name (optional)
AGENTLOOP_WORKSPACE - CMS Workspace name (optional)
"""
import os
@@ -31,13 +34,13 @@ import urllib.request
import urllib.error
# ========== 榛樿閰嶇疆 ==========
# ========== Default Configuration ==========
DEFAULT_ENDPOINT = "https://proj-xtrace-495e81719a1fd9a2c5fd671eefafbe-cn-hangzhou.cn-hangzhou.log.aliyuncs.com/apm/trace/opentelemetry/v1/traces"
DEFAULT_PROJECT = "proj-xtrace-495e81719a1fd9a2c5fd671eefafbe-cn-hangzhou"
DEFAULT_WORKSPACE = "agentloop-13b8d6efb7fde6e9b193eb982ade68e2"
# ========== OTLP Protobuf 鎵嬪姩缂栫爜 ==========
# ========== OTLP Protobuf Manual Encoding ==========
def _encode_varint(value):
result = bytearray()
@@ -47,32 +50,40 @@ def _encode_varint(value):
result.append(value & 0x7F)
return bytes(result)
def _encode_tag(field_number, wire_type):
return _encode_varint((field_number << 3) | wire_type)
def _encode_string_field(field_number, value):
value_bytes = value.encode('utf-8')
value_bytes = value.encode("utf-8")
return _encode_tag(field_number, 2) + _encode_varint(len(value_bytes)) + value_bytes
def _encode_bytes_field(field_number, value_bytes):
return _encode_tag(field_number, 2) + _encode_varint(len(value_bytes)) + value_bytes
def _encode_int_field(field_number, value):
return _encode_tag(field_number, 0) + _encode_varint(value & 0xFFFFFFFFFFFFFFFF)
def _encode_message_field(field_number, message_bytes):
return _encode_tag(field_number, 2) + _encode_varint(len(message_bytes)) + message_bytes
def _encode_key_value(key, value_str):
any_value = _encode_string_field(1, value_str)
return _encode_string_field(1, key) + _encode_message_field(2, any_value)
def _encode_status(status_code, status_msg=""):
data = _encode_int_field(1, status_code)
if status_msg:
data += _encode_string_field(2, status_msg)
return data
def _encode_span(trace_id_bytes, span_id_bytes, parent_span_id_bytes,
name, start_time_unix_nano, end_time_unix_nano,
span_kind, attributes, status_code, status_msg=""):
@@ -92,6 +103,7 @@ def _encode_span(trace_id_bytes, span_id_bytes, parent_span_id_bytes,
data += _encode_message_field(12, status)
return data
def _encode_resource_spans(service_name, scope_spans_bytes):
svc_kv = _encode_key_value("service.name", service_name)
resource = _encode_message_field(1, svc_kv)
@@ -99,6 +111,7 @@ def _encode_resource_spans(service_name, scope_spans_bytes):
data += _encode_message_field(2, scope_spans_bytes)
return data
def _encode_scope_spans(scope_name, spans_bytes_list):
scope = _encode_string_field(1, scope_name)
data = _encode_message_field(1, scope)
@@ -106,6 +119,7 @@ def _encode_scope_spans(scope_name, spans_bytes_list):
data += _encode_message_field(2, span_bytes)
return data
def _encode_traces_data(resource_spans_bytes_list):
data = b""
for rs_bytes in resource_spans_bytes_list:
@@ -113,20 +127,21 @@ def _encode_traces_data(resource_spans_bytes_list):
return data
# ========== 杈呭姪鍑芥暟 ==========
# ========== Helper Functions ==========
def _gen_trace_id():
return uuid.uuid4().bytes
def _gen_span_id():
return uuid.uuid4().bytes[:8]
def _env(name, default=""):
"""鑾峰彇鐜鍙橀噺锛屾敮鎸?GITEA_ 鍜?GITHUB_ 鍓嶇紑"""
"""Get env var with GITEA_/GITHUB_ prefix fallback."""
val = os.getenv(name, "")
if val:
return val
# 灏濊瘯鍙︿竴绉嶅墠缂€
if name.startswith("GITEA_"):
alt = "GITHUB_" + name[6:]
return os.getenv(alt, default)
@@ -137,12 +152,11 @@ def _env(name, default=""):
def _get_pr_number():
"""浠庣幆澧冨彉閲忔垨浜嬩欢鏂囦欢涓幏鍙?PR 鍙?""
# 鐩存帴浠庣幆澧冨彉閲? pr = os.getenv("PR_NUMBER", "") or os.getenv("GITEA_PR_NUMBER", "")
"""Get PR number from environment or event file."""
pr = os.getenv("PR_NUMBER", "") or os.getenv("GITEA_PR_NUMBER", "")
if pr:
return pr
# 浠?GITHUB_EVENT_PATH 璇诲彇
event_path = os.getenv("GITHUB_EVENT_PATH", "") or os.getenv("GITEA_EVENT_PATH", "")
if event_path and os.path.isfile(event_path):
try:
@@ -157,7 +171,7 @@ def _get_pr_number():
def _get_ci_attributes():
"""?CI 鍙橀噺涓敹闆嗗睘鎬?""
"""Collect attributes from CI environment variables."""
attrs = {
"ci.repo": _env("GITEA_REPOSITORY") or _env("GITHUB_REPOSITORY") or "unknown",
"ci.workflow": _env("GITEA_WORKFLOW") or _env("GITHUB_WORKFLOW") or "unknown",
@@ -174,10 +188,10 @@ def _get_ci_attributes():
return attrs
# ========== 鏍稿績涓婃姤閫昏緫 ==========
# ========== Trace Building & Reporting ==========
def build_trace(service_name, trace_name, status, duration_ms, attributes=None):
"""鏋勫缓涓€鏉″畬鏁寸殑 Trace 鏁版嵁锛岃繑鍥?protobuf bytes銆?""
"""Build an OTLP trace payload (protobuf bytes). No external dependencies."""
trace_id = _gen_trace_id()
end_time = int(time.time() * 1e9)
start_time = end_time - int(duration_ms * 1e6)
@@ -214,8 +228,9 @@ def report_ci_trace(service_name, trace_name, status="ok", duration_ms=1000,
endpoint=None, license_key=None, project=None, workspace=None,
extra_attributes=None):
"""
涓婃姤 CI Trace 鏁版嵁銆傝繑鍥?(success: bool, message: str)
姘歌繙涓嶄細鎶涘嚭寮傚父锛屽け璐ヤ篃杩斿洖 False銆? """
Report CI Trace data. Returns (success: bool, message: str).
Never raises exceptions; returns False on failure.
"""
try:
endpoint = endpoint or os.getenv("AGENTLOOP_ENDPOINT", DEFAULT_ENDPOINT)
license_key = license_key or os.getenv("AGENTLOOP_LICENSE_KEY", "")
@@ -223,9 +238,9 @@ def report_ci_trace(service_name, trace_name, status="ok", duration_ms=1000,
workspace = workspace or os.getenv("AGENTLOOP_WORKSPACE", DEFAULT_WORKSPACE)
if not license_key:
return False, "[Trace] 璺宠繃涓婃姤锛氭湭閰嶇疆 AGENTLOOP_LICENSE_KEY"
return False, "[Trace] skipped: AGENTLOOP_LICENSE_KEY not configured"
# 鏀堕泦 CI 灞炴€? attrs = _get_ci_attributes()
attrs = _get_ci_attributes()
if extra_attributes:
attrs.update(extra_attributes)
@@ -254,39 +269,43 @@ def report_ci_trace(service_name, trace_name, status="ok", duration_ms=1000,
resp_body = e.read().decode("utf-8", errors="replace")
if status_code in (200, 202):
return True, f"[Trace] 鉁?涓婃姤鎴愬姛: {service_name} / {trace_name} ({status}, {duration_ms}ms)"
return True, (
f"[Trace] success: {service_name} / {trace_name} "
f"({status}, {duration_ms}ms)"
)
else:
return False, f"[Trace] 鈿?涓婃姤澶辫触: HTTP {status_code} - {resp_body[:200]}"
return False, (
f"[Trace] failed: HTTP {status_code} - {resp_body[:200]}"
)
except Exception as e:
return False, f"[Trace] 鈿?涓婃姤寮傚父: {type(e).__name__}: {str(e)}"
return False, f"[Trace] error: {type(e).__name__}: {str(e)}"
def main():
parser = argparse.ArgumentParser(description="CI AgentLoop Trace 涓婃姤")
parser = argparse.ArgumentParser(description="CI AgentLoop Trace Reporter")
parser.add_argument("--service", dest="service_name",
default=os.getenv("TRACE_SERVICE", ""),
help="鏈嶅姟鍚嶇О锛堜篃鍙€氳繃 TRACE_SERVICE 鐜鍙橀噺璁剧疆锛?)
help="Service name (also via TRACE_SERVICE env)")
parser.add_argument("--name", dest="trace_name",
default=os.getenv("TRACE_NAME", ""),
help="Trace 鍚嶇О锛堜篃鍙€氳繃 TRACE_NAME 鐜鍙橀噺璁剧疆锛?)
help="Trace name (also via TRACE_NAME env)")
parser.add_argument("--status", default=os.getenv("TRACE_STATUS", "ok"),
choices=["ok", "error", "running"],
help="鐘舵€? ok / error / running锛堥粯璁?ok锛?)
help="Status: ok / error / running (default ok)")
parser.add_argument("--start-time", dest="start_time",
default=os.getenv("TRACE_START_TIME", ""),
help="寮€濮嬫椂闂存埑锛堢锛夛紝鐢ㄤ簬璁$畻 duration锛涗笉濉垯鐢ㄩ粯璁?1s")
help="Start timestamp (seconds) for duration calculation")
parser.add_argument("--duration-ms", dest="duration_ms", type=int, default=0,
help="鐩存帴鎸囧畾鑰楁椂锛堟绉掞級锛屼紭鍏堢骇楂樹簬 --start-time")
help="Direct duration in ms; takes precedence over --start-time")
parser.add_argument("--attrs", default="",
help="闄勫姞灞炴€э紙JSON 瀛楃涓诧級")
help="Extra attributes (JSON string)")
args = parser.parse_args()
# 蹇呴』鍙傛暟妫€鏌? if not args.service_name:
print("[Trace] 鈿?璺宠繃涓婃姤锛氭湭鎸囧畾 service锛?-service 鎴?TRACE_SERVICE锛?)
if not args.service_name:
print("[Trace] skipped: no service specified (--service or TRACE_SERVICE)")
sys.exit(0)
# 璁$畻鑰楁椂
duration_ms = args.duration_ms
if duration_ms <= 0 and args.start_time:
try:
@@ -295,15 +314,15 @@ def main():
except (ValueError, TypeError):
duration_ms = 1000
if duration_ms <= 0:
duration_ms = 1000 # 榛樿 1 绉?
# 瑙f瀽闄勫姞灞炴€? extra_attrs = {}
duration_ms = 1000
extra_attrs = {}
if args.attrs:
try:
extra_attrs = json.loads(args.attrs)
except json.JSONDecodeError:
pass
# 鐢熸垚 trace_name锛氬鏋滄病鎸囧畾锛岀敤 workflow+job
trace_name = args.trace_name
if not trace_name:
wf = _env("GITEA_WORKFLOW") or _env("GITHUB_WORKFLOW") or "CI"
@@ -319,7 +338,6 @@ def main():
)
print(msg)
# 姘歌繙 exit 0锛屼笉褰卞搷 CI
sys.exit(0)