diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-23 11:41:10 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-27 20:37:58 +0000 |
| commit | 73a366cbd7be4edf9c74194cd0891c80a15236a5 (patch) | |
| tree | c4a631a28496d48ad2bce6c2dfd4eff7ae0e4866 /src/nostr/builder.rs | |
| parent | 28cc7820953efeafb2bc4d41ebcf3d682da86711 (diff) | |
Add structured logging for migration analysis
- Add [PARSE_FAIL] logging when event parsing fails
- Add [PURGATORY_EXPIRED] logging when repos expire from purgatory
- Logs include: kind, event_id, repo, npub, reason
- Supports Phase 4 migration scripts (30-extract-*.sh)
- All 382 tests pass
Diffstat (limited to 'src/nostr/builder.rs')
| -rw-r--r-- | src/nostr/builder.rs | 135 |
1 files changed, 120 insertions, 15 deletions
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index 34014db..629c111 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs | |||
| @@ -98,6 +98,62 @@ impl Nip34WritePolicy { | |||
| 98 | self.ctx.set_local_relay(relay); | 98 | self.ctx.set_local_relay(relay); |
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | /// Extract repository identifier from event's 'd' tag. | ||
| 102 | /// | ||
| 103 | /// Used for structured logging when parsing fails - we try to extract | ||
| 104 | /// the identifier even if full parsing failed. | ||
| 105 | fn extract_identifier_from_event(event: &Event) -> String { | ||
| 106 | use nostr_relay_builder::prelude::TagKind; | ||
| 107 | event | ||
| 108 | .tags | ||
| 109 | .iter() | ||
| 110 | .find(|t| t.kind() == TagKind::d()) | ||
| 111 | .and_then(|t| t.content()) | ||
| 112 | .map(|s| s.to_string()) | ||
| 113 | .unwrap_or_else(|| "unknown".to_string()) | ||
| 114 | } | ||
| 115 | |||
| 116 | /// Extract ALL repository identifiers from PR event's 'a' tags. | ||
| 117 | /// | ||
| 118 | /// PR events can reference multiple repositories via multiple 'a' tags | ||
| 119 | /// (e.g., when there are multiple maintainers). Each tag has format | ||
| 120 | /// `30617:<owner_pubkey>:<identifier>`. | ||
| 121 | /// | ||
| 122 | /// Returns a vector of unique identifiers, or `["unknown"]` if none found. | ||
| 123 | fn extract_repos_from_pr_event(event: &Event) -> Vec<String> { | ||
| 124 | let repos: Vec<String> = event | ||
| 125 | .tags | ||
| 126 | .iter() | ||
| 127 | .filter_map(|tag| { | ||
| 128 | let tag_vec = tag.clone().to_vec(); | ||
| 129 | if tag_vec.len() >= 2 && tag_vec[0] == "a" && tag_vec[1].starts_with("30617:") { | ||
| 130 | // Format: 30617:<owner_pubkey>:<identifier> | ||
| 131 | let parts: Vec<&str> = tag_vec[1].split(':').collect(); | ||
| 132 | if parts.len() >= 3 { | ||
| 133 | Some(parts[2].to_string()) | ||
| 134 | } else { | ||
| 135 | None | ||
| 136 | } | ||
| 137 | } else { | ||
| 138 | None | ||
| 139 | } | ||
| 140 | }) | ||
| 141 | .collect(); | ||
| 142 | |||
| 143 | // Deduplicate while preserving order | ||
| 144 | let mut seen = std::collections::HashSet::new(); | ||
| 145 | let unique_repos: Vec<String> = repos | ||
| 146 | .into_iter() | ||
| 147 | .filter(|r| seen.insert(r.clone())) | ||
| 148 | .collect(); | ||
| 149 | |||
| 150 | if unique_repos.is_empty() { | ||
| 151 | vec!["unknown".to_string()] | ||
| 152 | } else { | ||
| 153 | unique_repos | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 101 | /// Handle repository announcement event | 157 | /// Handle repository announcement event |
| 102 | async fn handle_announcement(&self, event: &Event) -> WritePolicyResult { | 158 | async fn handle_announcement(&self, event: &Event) -> WritePolicyResult { |
| 103 | let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); | 159 | let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); |
| @@ -129,10 +185,18 @@ impl Nip34WritePolicy { | |||
| 129 | WritePolicyResult::Accept | 185 | WritePolicyResult::Accept |
| 130 | } | 186 | } |
| 131 | Err(e) => { | 187 | Err(e) => { |
| 188 | let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex()); | ||
| 189 | let event_id_short = &event.id.to_hex()[..12]; | ||
| 190 | // Try to extract repo identifier from 'd' tag even if parsing failed | ||
| 191 | let repo = Self::extract_identifier_from_event(event); | ||
| 192 | // Structured log for migration scripts | ||
| 132 | tracing::warn!( | 193 | tracing::warn!( |
| 133 | "Failed to parse repository announcement {}: {}", | 194 | "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}", |
| 134 | event_id_str, | 195 | event.kind.as_u16(), |
| 135 | e | 196 | event_id_short, |
| 197 | e, | ||
| 198 | repo, | ||
| 199 | npub | ||
| 136 | ); | 200 | ); |
| 137 | WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) | 201 | WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) |
| 138 | } | 202 | } |
| @@ -157,10 +221,18 @@ impl Nip34WritePolicy { | |||
| 157 | WritePolicyResult::Accept | 221 | WritePolicyResult::Accept |
| 158 | } | 222 | } |
| 159 | Err(e) => { | 223 | Err(e) => { |
| 224 | let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex()); | ||
| 225 | let event_id_short = &event.id.to_hex()[..12]; | ||
| 226 | // Try to extract repo identifier from 'd' tag even if parsing failed | ||
| 227 | let repo = Self::extract_identifier_from_event(event); | ||
| 228 | // Structured log for migration scripts | ||
| 160 | tracing::warn!( | 229 | tracing::warn!( |
| 161 | "Failed to parse maintainer announcement {}: {}", | 230 | "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}", |
| 162 | event_id_str, | 231 | event.kind.as_u16(), |
| 163 | e | 232 | event_id_short, |
| 233 | e, | ||
| 234 | repo, | ||
| 235 | npub | ||
| 164 | ); | 236 | ); |
| 165 | WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) | 237 | WritePolicyResult::reject(format!("Failed to parse announcement: {}", e)) |
| 166 | } | 238 | } |
| @@ -183,8 +255,6 @@ impl Nip34WritePolicy { | |||
| 183 | /// * `event` - The state event to validate | 255 | /// * `event` - The state event to validate |
| 184 | /// * `is_synced` - True if this event came from proactive sync (vs user-submitted) | 256 | /// * `is_synced` - True if this event came from proactive sync (vs user-submitted) |
| 185 | async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult { | 257 | async fn handle_state(&self, event: &Event, is_synced: bool) -> WritePolicyResult { |
| 186 | let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); | ||
| 187 | |||
| 188 | match self.state_policy.validate(event) { | 258 | match self.state_policy.validate(event) { |
| 189 | StateResult::Accept => { | 259 | StateResult::Accept => { |
| 190 | // Process state alignment asynchronously | 260 | // Process state alignment asynchronously |
| @@ -195,7 +265,19 @@ impl Nip34WritePolicy { | |||
| 195 | { | 265 | { |
| 196 | Ok(poilicy_result) => poilicy_result, | 266 | Ok(poilicy_result) => poilicy_result, |
| 197 | Err(e) => { | 267 | Err(e) => { |
| 198 | tracing::warn!("Failed to process state event {}: {}", event_id_str, e); | 268 | let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex()); |
| 269 | let event_id_short = &event.id.to_hex()[..12]; | ||
| 270 | // Try to extract repo identifier from 'd' tag even if parsing failed | ||
| 271 | let repo = Self::extract_identifier_from_event(event); | ||
| 272 | // Structured log for migration scripts | ||
| 273 | tracing::warn!( | ||
| 274 | "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}", | ||
| 275 | event.kind.as_u16(), | ||
| 276 | event_id_short, | ||
| 277 | e, | ||
| 278 | repo, | ||
| 279 | npub | ||
| 280 | ); | ||
| 199 | // reject if processing failed | 281 | // reject if processing failed |
| 200 | WritePolicyResult::Reject { | 282 | WritePolicyResult::Reject { |
| 201 | status: false, | 283 | status: false, |
| @@ -205,7 +287,19 @@ impl Nip34WritePolicy { | |||
| 205 | } | 287 | } |
| 206 | } | 288 | } |
| 207 | StateResult::Reject(reason) => { | 289 | StateResult::Reject(reason) => { |
| 208 | tracing::warn!("Rejected repository state {}: {}", event_id_str, reason); | 290 | let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex()); |
| 291 | let event_id_short = &event.id.to_hex()[..12]; | ||
| 292 | // Try to extract repo identifier from 'd' tag even if parsing failed | ||
| 293 | let repo = Self::extract_identifier_from_event(event); | ||
| 294 | // Structured log for migration scripts | ||
| 295 | tracing::warn!( | ||
| 296 | "[PARSE_FAIL] kind={} event_id={}... reason=\"{}\" repo={} npub={}", | ||
| 297 | event.kind.as_u16(), | ||
| 298 | event_id_short, | ||
| 299 | reason, | ||
| 300 | repo, | ||
| 301 | npub | ||
| 302 | ); | ||
| 209 | WritePolicyResult::reject(reason) | 303 | WritePolicyResult::reject(reason) |
| 210 | } | 304 | } |
| 211 | } | 305 | } |
| @@ -323,11 +417,22 @@ impl Nip34WritePolicy { | |||
| 323 | } | 417 | } |
| 324 | Err(e) => { | 418 | Err(e) => { |
| 325 | // Error checking git data - reject event | 419 | // Error checking git data - reject event |
| 326 | tracing::warn!( | 420 | let npub = event.pubkey.to_bech32().unwrap_or_else(|_| event.pubkey.to_hex()); |
| 327 | "Failed to check git data for PR event {}: {}", | 421 | let event_id_short = &event.id.to_hex()[..12]; |
| 328 | event_id_str, | 422 | // Extract ALL repo identifiers from 'a' tags for PR events |
| 329 | e | 423 | // (PR events can reference multiple repos when there are multiple maintainers) |
| 330 | ); | 424 | let repos = Self::extract_repos_from_pr_event(event); |
| 425 | // Structured log for migration scripts - log once per repo | ||
| 426 | for repo in &repos { | ||
| 427 | tracing::warn!( | ||
| 428 | "[PARSE_FAIL] kind={} event_id={}... reason=\"git data check failed: {}\" repo={} npub={}", | ||
| 429 | event.kind.as_u16(), | ||
| 430 | event_id_short, | ||
| 431 | e, | ||
| 432 | repo, | ||
| 433 | npub | ||
| 434 | ); | ||
| 435 | } | ||
| 331 | WritePolicyResult::reject(format!("Failed to check git data: {}", e)) | 436 | WritePolicyResult::reject(format!("Failed to check git data: {}", e)) |
| 332 | } | 437 | } |
| 333 | } | 438 | } |