|
4 | 4 | * Provides formatting utilities for displaying Sentry logs in the CLI. |
5 | 5 | */ |
6 | 6 |
|
7 | | -import type { DetailedSentryLog, SentryLog } from "../../types/index.js"; |
| 7 | +import type { |
| 8 | + DetailedSentryLog, |
| 9 | + SentryLog, |
| 10 | + TraceItemAttribute, |
| 11 | +} from "../../types/index.js"; |
8 | 12 | import { buildTraceUrl } from "../sentry-urls.js"; |
9 | 13 | import { |
10 | 14 | colorTag, |
@@ -281,19 +285,64 @@ function formatSeverityLabel(severity: string | null | undefined): string { |
281 | 285 | return tag ? colorTag(tag, label) : label; |
282 | 286 | } |
283 | 287 |
|
| 288 | +/** |
| 289 | + * Attribute names rendered by the fixed sections in formatLogDetails. |
| 290 | + * Used to deduplicate against the trace-items detail response so we don't show |
| 291 | + * attributes in Custom Attributes that are already displayed above. |
| 292 | + * Also includes internal/noisy fields mirroring Sentry UI's HiddenLogDetailFields. |
| 293 | + */ |
| 294 | +const SHOWN_IN_STANDARD_SECTIONS = new Set([ |
| 295 | + // Core section |
| 296 | + "sentry.item_id", |
| 297 | + "id", |
| 298 | + "timestamp", |
| 299 | + "timestamp_precise", |
| 300 | + "message", |
| 301 | + "severity", |
| 302 | + // Context section |
| 303 | + "trace", |
| 304 | + "project", |
| 305 | + "environment", |
| 306 | + "release", |
| 307 | + // SDK section |
| 308 | + "sdk.name", |
| 309 | + "sdk.version", |
| 310 | + // Trace section |
| 311 | + "span_id", |
| 312 | + // Source location section |
| 313 | + "code.function", |
| 314 | + "code.file.path", |
| 315 | + "code.line.number", |
| 316 | + // OTel section |
| 317 | + "sentry.otel.kind", |
| 318 | + "sentry.otel.status_code", |
| 319 | + "sentry.otel.instrumentation_scope.name", |
| 320 | + // Internal / always-hidden noise (mirrors Sentry UI HiddenLogDetailFields) |
| 321 | + "severity_number", |
| 322 | + "item_type", |
| 323 | + "organization_id", |
| 324 | + "project.id", |
| 325 | + "project_id", |
| 326 | + "sentry.timestamp_nanos", |
| 327 | + "sentry.observed_timestamp_nanos", |
| 328 | + "tags[sentry.trace_flags,number]", |
| 329 | +]); |
| 330 | + |
284 | 331 | /** |
285 | 332 | * Format detailed log entry for display as rendered markdown. |
286 | 333 | * Shows all available fields in a structured format. |
287 | 334 | * |
288 | 335 | * @param log - The detailed log entry to format |
289 | 336 | * @param orgSlug - Organization slug for building trace URLs |
290 | | - * @param extraFields - Custom fields requested via --fields, shown in Custom Attributes section |
| 337 | + * @param allAttributes - All attributes from the trace-items detail endpoint (shows custom attrs) |
| 338 | + * @param extraFields - Optional --fields filter: limits which custom attributes are shown |
291 | 339 | * @returns Rendered terminal string |
292 | 340 | */ |
293 | 341 | // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: log detail formatting requires multiple conditional sections |
294 | 342 | export function formatLogDetails( |
295 | 343 | log: DetailedSentryLog, |
296 | 344 | orgSlug: string, |
| 345 | + allAttributes?: TraceItemAttribute[], |
297 | 346 | extraFields?: string[] |
298 | 347 | ): string { |
299 | 348 | const logId = log["sentry.item_id"]; |
@@ -396,8 +445,26 @@ export function formatLogDetails( |
396 | 445 | lines.push(mdKvTable(otelRows, "OpenTelemetry")); |
397 | 446 | } |
398 | 447 |
|
399 | | - // Custom Attributes — fields explicitly requested via --fields |
400 | | - if (extraFields?.length) { |
| 448 | + // Custom Attributes — from trace-items detail endpoint (all non-standard attributes) |
| 449 | + if (allAttributes?.length) { |
| 450 | + let customAttrs = allAttributes.filter( |
| 451 | + (a) => !SHOWN_IN_STANDARD_SECTIONS.has(a.name) |
| 452 | + ); |
| 453 | + if (extraFields?.length) { |
| 454 | + const wanted = new Set(extraFields); |
| 455 | + customAttrs = customAttrs.filter((a) => wanted.has(a.name)); |
| 456 | + } |
| 457 | + if (customAttrs.length > 0) { |
| 458 | + lines.push(""); |
| 459 | + lines.push( |
| 460 | + mdKvTable( |
| 461 | + customAttrs.map((a) => [a.name, String(a.value)]), |
| 462 | + "Custom Attributes" |
| 463 | + ) |
| 464 | + ); |
| 465 | + } |
| 466 | + } else if (extraFields?.length) { |
| 467 | + // Fallback: no trace-items detail available, show only explicitly requested fields |
401 | 468 | const customRows: [string, string][] = extraFields |
402 | 469 | .filter((f) => log[f] !== null && log[f] !== undefined) |
403 | 470 | .map((f) => [f, String(log[f])]); |
|
0 commit comments