Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/cli-docs/src/content/docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ toolkit/
│ │ ├── commands/ # CLI commands
│ │ │ ├── agent-conversation/# list, view
│ │ │ ├── alert/ # create, delete, edit, list, view
│ │ │ ├── attribute/ # list
│ │ │ ├── auth/ # list, login, logout, refresh, status, switch, token, whoami
│ │ │ ├── build/ # download, upload
│ │ │ ├── cli/ # completion, defaults, feedback, fix, import, setup, uninstall, upgrade
Expand Down
9 changes: 9 additions & 0 deletions apps/cli-docs/src/fragments/commands/attribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@


## Using Attribute Keys

Attributes are the typed key-value data attached to spans, logs, and metric samples. Their keys can be passed to other commands to filter, add columns, or group results.

Custom non-string attributes include their type in the key, e.g. `tags[cart.total,number]`. Search for the name (`cart.total`), then use the returned key in queries and fields. Quote these keys in the shell.

The endpoint returns discovery candidates, including built-in and virtual keys even when no matching item was observed. It does not provide an exhaustive inventory or proof that every candidate co-occurs with the `has:` keys.
2 changes: 2 additions & 0 deletions packages/cli/grammar/field-list.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Split output fields at top-level commas, preserving nested expressions. */
export declare function parse(input: string): string[];
23 changes: 23 additions & 0 deletions packages/cli/grammar/field-list.pegjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Output fields are paths or API expressions, not search filters. Preserve
// their spelling and split only at commas outside balanced groups or quotes.
fields
= head:field tail:("," field)* {
return [head, ...tail.map((part) => part[1])].filter(Boolean);
}

field
= value:$(part*) { return value.trim(); }

// Unmatched punctuation can be part of a literal JSON key. This grammar
// separates fields without validating the API expressions themselves.
part
= brackets / parentheses / quoted / [^,]

brackets
= "[" (brackets / parentheses / quoted / [^()[\]"])* "]"

parentheses
= "(" (brackets / parentheses / quoted / [^()[\]"])* ")"

quoted
= '"' ("\\" . / [^"\\])* '"'
8 changes: 8 additions & 0 deletions packages/cli/plugins/sentry-cli/skills/sentry-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,14 @@ Manage Sentry alert rules

→ Full flags and examples: `references/alert.md`

### Attribute

Explore span, log, and metric attributes

- `sentry attribute list [<target>] [<filter>]` — List attribute keys for spans, logs, or metrics

→ Full flags and examples: `references/attribute.md`

### Build

Manage mobile build artifacts
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
name: sentry-cli-attribute
version: 0.48.0-dev.0
description: Explore span, log, and metric attributes
requires:
bins: ["sentry"]
auth: true
---

# Attribute Commands

Explore span, log, and metric attributes

### `sentry attribute list [<target>] [<filter>]`

List attribute keys for spans, logs, or metrics

**Arguments:**

| Argument | Description |
|----------|-------------|
| `[<target>]` | &lt;org&gt;/ (all projects), &lt;org&gt;/&lt;project&gt;, or &lt;project&gt; (search) |
| `[<filter>]` | Text in attribute names and/or has:&lt;key&gt; terms, e.g. 'gen_ai has:http.method' |

**Flags:**
- `-d, --dataset <value> - Dataset to list attributes for (spans, logs, metrics) - (default: "spans")`
- `--type <value>... - Find attributes of these types (string, number, boolean, array; repeatable, comma-separated)`
- `-t, --period <value> - Time range: "7d", "2024-01-01..2024-02-01", ">=2024-01-01" - (default: "7d")`
- `-n, --limit <value> - Maximum number of attributes to list - (default: "25")`
- `--context - Include each attribute's description, examples, and deprecation info (experimental API)`
- `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data`
- `-c, --cursor <value> - Navigate pages: "next", "prev", "first" (or raw cursor string)`

**Examples:**

```bash
# List span attribute keys for a project
sentry attribute list my-org/my-project

# List log attribute keys across the whole organization
sentry attribute list my-org/ --dataset logs

# String attributes whose names contain gen_ai (case-sensitive)
sentry attribute list my-org/ gen_ai --type string

# Discover candidates alongside an attribute
sentry attribute list my-org/ has:gen_ai.request.model

# Discover candidates alongside several keys (one quoted argument)
sentry attribute list my-org/ 'has:gen_ai.request.model has:gen_ai.usage.input_tokens'

# Text and has: filters together
sentry attribute list my-org/ 'gen_ai has:http.method'

# Discover candidates using the last 24 hours (includes built-in and virtual keys)
sentry attribute list my-org/ --period 24h

# Show descriptions, example values, and deprecation info
sentry attribute list my-org/ --context

# Show more than the default 25
sentry attribute list my-org/my-project -n 100

# Show the next page of the previous listing (same flags)
sentry attribute list my-org/my-project -n 100 -c next

# Filter spans that have a discovered key
sentry span list my-org/my-project -q 'has:gen_ai.request.model'

# Find a custom numeric attribute by name
sentry attribute list my-org/my-project cart.total --type number

# Show a discovered typed key as a span column
sentry span list my-org/my-project --fields 'tags[cart.total,number]'

# Group a metric by a discovered attribute
sentry explore my-org/ --dataset metrics -m llm.token_usage -F gen_ai.request.model

# Select fields as JSON
sentry attribute list my-org/ --json --fields key,type,source
```

All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags.
4 changes: 2 additions & 2 deletions packages/cli/script/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ const VERSION: string = pkg.version;
/** Node version for SEA binaries. "lts" resolves to the latest LTS via fossilize. */
const NODE_VERSION = "lts";

/** Files that use _require() for lazy relative imports (circular dep breaking). */
/** Files whose lazy relative imports must be included in the native bundle. */
const REQUIRE_ALIAS_FILTER =
/(?:db[\\/](?:index|schema)|list-command|telemetry)\.ts$/;
/(?:db[\\/](?:index|schema)|formatters[\\/]json|list-command|telemetry)\.ts$/;
const REQUIRE_ALIAS_RE = /\b_require\(/g;

/** Build-time constants injected into the binary */
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/script/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,10 @@ const sentrySourcemapPlugin: Plugin = {
* it stays a runtime `require()` that fails in a real install (the driver is
* a devDependency, not shipped). `node:sqlite` in the same file stays a
* builtin and is left external by esbuild regardless.
* `formatters/json` loads its generated parser only when --fields is parsed.
*/
const REQUIRE_ALIAS_FILTER =
/(?:db[\\/](?:index|schema|sqlite)|list-command|telemetry)\.ts$/;
/(?:db[\\/](?:index|schema|sqlite)|formatters[\\/]json|list-command|telemetry)\.ts$/;
const REQUIRE_ALIAS_RE = /\b_require\(/g;
const IDENTIFIER_FILTER_RE = /^[A-Za-z_$][\w$]*$/;
// Match both the prefixed and bare specifiers — bundled dependencies (e.g.
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/script/ensure-generated.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const SCHEMA = "src/generated/api-schema.json";
const GRAMMAR_OUTPUTS = [
"src/generated/org-project-target-grammar.js",
"src/generated/org-project-target-grammar.d.ts",
"src/generated/field-list-grammar.js",
"src/generated/field-list-grammar.d.ts",
];
/** Produced by `generate:docs`; imported at runtime by `src/`. */
const DOCS_OUTPUTS = [
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { conversationRoute } from "./commands/agent-conversation/index.js";
import { listCommand as conversationListCommand } from "./commands/agent-conversation/list.js";
import { alertRoute } from "./commands/alert/index.js";
import { apiCommand } from "./commands/api.js";
import { attributeRoute } from "./commands/attribute/index.js";
import { listCommand as attributeListCommand } from "./commands/attribute/list.js";
import { authRoute } from "./commands/auth/index.js";
import { whoamiCommand } from "./commands/auth/whoami.js";
import { bashHookCommand } from "./commands/bash-hook.js";
Expand Down Expand Up @@ -97,6 +99,7 @@ import { buildRouteMap } from "./lib/route-map.js";
*/
const PLURAL_TO_SINGULAR: Record<string, string> = {
"agent-conversations": "agent-conversation",
attributes: "attribute",
dashboards: "dashboard",
dsns: "dsn",
events: "event",
Expand All @@ -122,6 +125,7 @@ export const routes = buildRouteMap({
routes: {
help: helpCommand,
alert: alertRoute,
attribute: attributeRoute,
auth: authRoute,
build: buildRoute,
cli: cliRoute,
Expand Down Expand Up @@ -168,6 +172,7 @@ export const routes = buildRouteMap({
"send-envelope": sendEnvelopeCommand,
"bash-hook": bashHookCommand,
"agent-conversations": conversationListCommand,
attributes: attributeListCommand,
dashboards: dashboardListCommand,
dsns: dsnListCommand,
integrations: integrationListCommand,
Expand Down Expand Up @@ -195,6 +200,7 @@ export const routes = buildRouteMap({
hideRoute: {
dsns: true,
"agent-conversations": true,
attributes: true,
dashboards: true,
events: true,
integrations: true,
Expand Down
17 changes: 17 additions & 0 deletions packages/cli/src/commands/attribute/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** Trace item attribute commands. */

import { buildRouteMap } from "../../lib/route-map.js";
import { listCommand } from "./list.js";

export const attributeRoute = buildRouteMap({
routes: { list: listCommand },
docs: {
brief: "Explore span, log, and metric attributes",
fullDescription:
"List the attribute keys available on spans, logs, and trace metrics.\n\n" +
"Commands:\n" +
" list List attribute keys with their type and source\n\n" +
"Alias: `sentry attributes` → `sentry attribute list`",
hideRoute: {},
},
});
Loading
Loading