Skip to content

Commit 3fc7487

Browse files
committed
feat(dashboard): warn on likely span attribute misuse in aggregate fields
Add warnUnknownAggregateFields() that logs a warning when an aggregate field doesn't match known aggregatable span fields (span.duration, span.self_time, http.*, cache.* measurements). Span attributes like dsn.files_collected or resolve.method are key-value metadata that cannot be aggregated — they should be used in --where or --group-by. Uses a warning (not error) because measurements are project-specific and the known-good list may not be exhaustive.
1 parent 6402331 commit 3fc7487

File tree

2 files changed

+57
-3
lines changed

2 files changed

+57
-3
lines changed

docs/src/content/docs/agent-guidance.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ When querying the Events API (directly or via `sentry api`), valid dataset value
198198

199199
- **Wrong dataset for custom metrics**: Use `--dataset tracemetrics` for custom metrics (`Sentry.metrics.distribution/gauge/count`). The query format is `aggregation(value,metric_name,metric_type,unit)` — see `sentry dashboard widget --help` for details.
200200
- **Wrong unit in tracemetrics queries**: The `unit` parameter must match the SDK emission. If no `unit` option is passed to `Sentry.metrics.*()`, use `none`. Check the SDK source for integrations — e.g., `nodeRuntimeMetricsIntegration` uses `byte` for memory, `second` for uptime, `none` for utilization ratios.
201-
- **Missing `--limit` with `--group-by`**: The Sentry API rejects grouped widgets without a limit. Always include `--limit` when using `--group-by`.
202-
- **`--sort` referencing a field not in `--query`**: The sort field must be one of the aggregate expressions in `--query`. If you sort by `-count` but only query `p50:span.duration`, the API returns 400.
203-
- **Span attributes are not aggregatable**: You cannot use `avg:dsn.files_collected` on span attributes. Span attributes are key-value metadata — use them in `--where` filters or `--group-by` columns, not as aggregate fields. Only `span.duration` and built-in measurements support aggregation.
201+
- **Missing `--limit` with `--group-by`**: Always include `--limit` when using `--group-by`. The CLI validates this before sending to the API.
202+
- **`--sort` referencing a field not in `--query`**: The sort field must be one of the aggregate expressions in `--query`. The CLI validates this before sending to the API.
203+
- **Span attributes are not aggregatable**: You cannot use `avg:dsn.files_collected` on span attributes. Span attributes are key-value metadata — use them in `--where` filters or `--group-by` columns, not as aggregate fields. The CLI warns when an aggregate field doesn't match known aggregatable fields (`span.duration`, `span.self_time`, etc.).
204204
- **Stale `--sort` after changing `--query`**: When editing a widget to change the query (e.g., p75→p50), also update `--sort` if it references the old aggregate.

src/commands/dashboard/resolve.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
ValidationError,
1919
} from "../../lib/errors.js";
2020
import { fuzzyMatch } from "../../lib/fuzzy.js";
21+
import { logger } from "../../lib/logger.js";
2122
import { resolveEffectiveOrg } from "../../lib/region.js";
2223
import { resolveOrg } from "../../lib/resolve-target.js";
2324
import { setOrgProjectContext } from "../../lib/telemetry.js";
@@ -368,6 +369,58 @@ export function validateGroupByRequiresLimit(
368369
}
369370
}
370371

372+
const log = logger.withTag("dashboard");
373+
374+
/**
375+
* Known aggregatable fields for the spans dataset.
376+
*
377+
* Span attributes (e.g., dsn.files_collected, resolve.method) are key-value
378+
* metadata and cannot be used as aggregate fields — only in --where or --group-by.
379+
* This set covers built-in numeric fields that support aggregation.
380+
* Measurements (http.*, cache.*, etc.) are project-specific and may not be
381+
* exhaustive — we warn instead of error for unknown fields.
382+
*/
383+
const KNOWN_SPAN_AGGREGATE_FIELDS = new Set([
384+
"span.duration",
385+
"span.self_time",
386+
"http.response_content_length",
387+
"http.decoded_response_content_length",
388+
"http.response_transfer_size",
389+
"cache.item_size",
390+
]);
391+
392+
/**
393+
* Warn when an aggregate argument looks like a span attribute rather than
394+
* an aggregatable field. No-arg functions (count(), epm()) are fine.
395+
* Only checks for the spans dataset.
396+
*/
397+
function warnUnknownAggregateFields(
398+
aggregates: string[],
399+
dataset: string | undefined
400+
): void {
401+
if (dataset && dataset !== "spans") {
402+
return;
403+
}
404+
for (const agg of aggregates) {
405+
const parenIdx = agg.indexOf("(");
406+
if (parenIdx < 0) {
407+
continue;
408+
}
409+
const inner = agg.slice(parenIdx + 1, -1);
410+
// No-arg functions like count(), epm() have empty inner — skip
411+
if (!inner) {
412+
continue;
413+
}
414+
if (!KNOWN_SPAN_AGGREGATE_FIELDS.has(inner)) {
415+
log.warn(
416+
`Aggregate field "${inner}" in "${agg}" is not a known aggregatable span field. ` +
417+
"Span attributes (custom tags) cannot be aggregated — use them in --where or --group-by instead. " +
418+
`Known fields: ${[...KNOWN_SPAN_AGGREGATE_FIELDS].join(", ")}`
419+
);
420+
}
421+
}
422+
}
423+
371424
export function buildWidgetFromFlags(opts: {
372425
title: string;
373426
display: string;
@@ -380,6 +433,7 @@ export function buildWidgetFromFlags(opts: {
380433
}): DashboardWidget {
381434
const aggregates = (opts.query ?? ["count"]).map(parseAggregate);
382435
validateAggregateNames(aggregates, opts.dataset);
436+
warnUnknownAggregateFields(aggregates, opts.dataset);
383437

384438
// Issue table widgets need at least one column or the Sentry UI shows "Columns: None".
385439
// Default to ["issue"] for table display only — timeseries (line/area/bar) don't use columns.

0 commit comments

Comments
 (0)