|
8 | 8 |
|
9 | 9 | import click |
10 | 10 |
|
| 11 | +JOURNAL_ENVELOPE_KEY = "nooaJournal" |
| 12 | +JOURNAL_FORMAT = "nooa.message_journal" |
| 13 | +JOURNAL_VERSION = 1 |
| 14 | + |
11 | 15 |
|
12 | 16 | def validate_endpoint(endpoint: str) -> None: |
13 | 17 | """Validate that the endpoint uses an HTTP(S) scheme.""" |
@@ -102,6 +106,57 @@ def post_annotations(endpoint: str, annotations: list[dict]) -> int: |
102 | 106 | return imported |
103 | 107 |
|
104 | 108 |
|
| 109 | +def get_journal_record(body: dict) -> dict | None: |
| 110 | + """Return a validated NOOA journal envelope, or ``None`` for other lines.""" |
| 111 | + record = body.get(JOURNAL_ENVELOPE_KEY) |
| 112 | + if not isinstance(record, dict): |
| 113 | + return None |
| 114 | + if record.get("format") != JOURNAL_FORMAT or record.get("version") != JOURNAL_VERSION: |
| 115 | + return None |
| 116 | + return record |
| 117 | + |
| 118 | + |
| 119 | +def post_journal_record(endpoint: str, record: dict, session_id: str) -> bool: |
| 120 | + """POST one portable-file journal record to the viewer. |
| 121 | +
|
| 122 | + ``session_id`` is authoritative so renaming a trace file and Harbor's |
| 123 | + trial-name remapping affect OTLP spans and their journal sideband equally. |
| 124 | + Manifest records are accepted as no-ops. |
| 125 | + """ |
| 126 | + record_type = record.get("type") |
| 127 | + if record_type == "manifest": |
| 128 | + return True |
| 129 | + if record_type == "blocks": |
| 130 | + payload = record.get("blocks") |
| 131 | + if not isinstance(payload, list): |
| 132 | + return False |
| 133 | + path = "/v1/journal/blocks" |
| 134 | + headers = {"Content-Type": "application/json", "X-Session-Id": session_id} |
| 135 | + elif record_type == "call": |
| 136 | + source = record.get("call") |
| 137 | + if not isinstance(source, dict): |
| 138 | + return False |
| 139 | + payload = dict(source) |
| 140 | + payload["session_id"] = session_id |
| 141 | + path = "/v1/journal/calls" |
| 142 | + headers = {"Content-Type": "application/json"} |
| 143 | + else: |
| 144 | + return False |
| 145 | + |
| 146 | + data = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8") |
| 147 | + request = urllib.request.Request( |
| 148 | + f"{endpoint.rstrip('/')}{path}", |
| 149 | + data=data, |
| 150 | + headers=headers, |
| 151 | + method="POST", |
| 152 | + ) |
| 153 | + try: |
| 154 | + with urllib.request.urlopen(request, timeout=30) as response: |
| 155 | + return response.status < 300 |
| 156 | + except Exception: |
| 157 | + return False |
| 158 | + |
| 159 | + |
105 | 160 | def session_exists(endpoint: str, session_id: str) -> bool: |
106 | 161 | """Check whether a session already exists in the viewer.""" |
107 | 162 | url = f"{endpoint.rstrip('/')}/api/trace-count?session_id={urllib.parse.quote(session_id)}" |
|
0 commit comments