Skip to content

Commit 4ba8061

Browse files
committed
fix(logger): inject --verbose and --log-level as proper Stricli flags
Fix three bugs in the logging system introduced in #338: 1. `--verbose` rejected by all commands except `api` — Stricli throws "No flag registered for --verbose" because extractLogLevelFromArgs intentionally left it in argv for the api command. 2. `--log-level=debug` (equals form) not handled — argv.indexOf only matched the space-separated form, passing the flag through to Stricli. 3. `SENTRY_LOG_LEVEL` env var has no visible effect — consola withTag() creates independent instances that snapshot the level at creation time. Module-level scoped loggers never saw later setLogLevel() calls. Instead of pre-parsing flags from argv (bypassing Stricli), define them as proper hidden Stricli flags. buildCommand in src/lib/command.ts now wraps Stricli to inject hidden --log-level (enum) and --verbose (boolean) flags into every command, intercept them, apply setLogLevel(), then strip them before calling the original func. When a command already defines its own --verbose (e.g. api uses it for HTTP output), the injected one is skipped — the command own value still triggers debug-level logging as a side-effect. setLogLevel() now propagates to all withTag() children via a registry, fixing the env var and flag having no effect on scoped loggers. Document SENTRY_LOG_LEVEL, --log-level, and --verbose in configuration docs.
1 parent 22071f1 commit 4ba8061

File tree

12 files changed

+655
-280
lines changed

12 files changed

+655
-280
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ mock.module("./some-module", () => ({
686686
* **Shared pagination infrastructure: buildPaginationContextKey and parseCursorFlag**: List commands with cursor pagination use \`buildPaginationContextKey(type, identifier, flags)\` for composite context keys and \`parseCursorFlag(value)\` accepting \`"last"\` magic value. Critical: \`resolveCursor()\` must be called inside the \`org-all\` override closure, not before \`dispatchOrgScopedList\` — otherwise cursor validation errors fire before the correct mode-specific error.
687687
688688
<!-- lore:019cbd5f-ec35-7e2d-8386-6d3a67adf0cf -->
689-
* **Telemetry instrumentation pattern: withTracingSpan + captureException for handled errors**: For graceful-fallback operations, use \`withTracingSpan\` from \`src/lib/telemetry.ts\` for child spans and \`captureException\` from \`@sentry/bun\` (named import — Biome forbids namespace imports) with \`level: 'warning'\` for non-fatal errors. \`withTracingSpan\` uses \`onlyIfParent: true\` so it's a no-op without active transaction. When returning \`withTracingSpan(...)\` directly, drop \`async\` and use \`Promise.resolve(null)\` for early returns. User-visible fallbacks should use \`log.warn()\` not \`log.debug()\` — debug is invisible at default level. Also: several commands bypass telemetry by importing \`buildCommand\` from \`@stricli/core\` directly instead of \`../../lib/command.js\`. Affected: trace/list, trace/view, log/view, api.ts, help.ts.
689+
* **Telemetry instrumentation pattern: withTracingSpan + captureException for handled errors**: For graceful-fallback operations, use \`withTracingSpan\` from \`src/lib/telemetry.ts\` for child spans and \`captureException\` from \`@sentry/bun\` (named import — Biome forbids namespace imports) with \`level: 'warning'\` for non-fatal errors. \`withTracingSpan\` uses \`onlyIfParent: true\` so it's a no-op without active transaction. When returning \`withTracingSpan(...)\` directly, drop \`async\` and use \`Promise.resolve(null)\` for early returns. User-visible fallbacks should use \`log.warn()\` not \`log.debug()\` — debug is invisible at default level.
690690
691691
### Preference
692692

docs/src/content/docs/commands/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ All commands support the following global options:
2525

2626
- `--help` - Show help for the command
2727
- `--version` - Show CLI version
28+
- `--log-level <level>` - Set log verbosity (`error`, `warn`, `log`, `info`, `debug`, `trace`). Overrides `SENTRY_LOG_LEVEL`
29+
- `--verbose` - Shorthand for `--log-level debug`
2830

2931
## JSON Output
3032

docs/src/content/docs/configuration.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ Disable CLI telemetry (error tracking for the CLI itself). The CLI sends anonymi
8989
export SENTRY_CLI_NO_TELEMETRY=1
9090
```
9191

92+
### `SENTRY_LOG_LEVEL`
93+
94+
Controls the verbosity of diagnostic output. Defaults to `info`.
95+
96+
Valid values: `error`, `warn`, `log`, `info`, `debug`, `trace`
97+
98+
```bash
99+
export SENTRY_LOG_LEVEL=debug
100+
```
101+
102+
Equivalent to passing `--log-level debug` on the command line. CLI flags take precedence over the environment variable.
103+
92104
### `SENTRY_CLI_NO_UPDATE_CHECK`
93105

94106
Disable the automatic update check that runs periodically in the background.
@@ -97,6 +109,33 @@ Disable the automatic update check that runs periodically in the background.
97109
export SENTRY_CLI_NO_UPDATE_CHECK=1
98110
```
99111

112+
## Global Options
113+
114+
These flags are accepted by every command. They are not shown in individual command `--help` output, but are always available.
115+
116+
### `--log-level <level>`
117+
118+
Set the log verbosity level. Accepts: `error`, `warn`, `log`, `info`, `debug`, `trace`.
119+
120+
```bash
121+
sentry issue list --log-level debug
122+
sentry --log-level=trace cli upgrade
123+
```
124+
125+
Overrides `SENTRY_LOG_LEVEL` when both are set.
126+
127+
### `--verbose`
128+
129+
Shorthand for `--log-level debug`. Enables debug-level diagnostic output.
130+
131+
```bash
132+
sentry issue list --verbose
133+
```
134+
135+
:::note
136+
The `sentry api` command also uses `--verbose` to show full HTTP request/response details. When used with `sentry api`, it serves both purposes (debug logging + HTTP output).
137+
:::
138+
100139
## Credential Storage
101140

102141
Credentials are stored in a SQLite database at `~/.sentry/` (or the path set by `SENTRY_CONFIG_DIR`) with restricted file permissions (mode 600) for security. The database also caches:

plugins/sentry-cli/skills/sentry-cli/SKILL.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,15 @@ Show the currently authenticated user
721721
**Flags:**
722722
- `--json - Output as JSON`
723723

724+
## Global Options
725+
726+
All commands support the following global options:
727+
728+
- `--help` - Show help for the command
729+
- `--version` - Show CLI version
730+
- `--log-level <level>` - Set log verbosity (`error`, `warn`, `log`, `info`, `debug`, `trace`). Overrides `SENTRY_LOG_LEVEL`
731+
- `--verbose` - Shorthand for `--log-level debug`
732+
724733
## Output Formats
725734

726735
### JSON Output

script/generate-skill.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type FlagDef = {
7575
optional?: boolean;
7676
variadic?: boolean;
7777
placeholder?: string;
78+
hidden?: boolean;
7879
};
7980

8081
// ─────────────────────────────────────────────────────────────────────────────
@@ -376,6 +377,7 @@ async function loadCommandExamples(
376377
* Load supplementary content from commands/index.md
377378
*/
378379
async function loadCommandsOverview(): Promise<{
380+
globalOptions: string;
379381
jsonOutput: string;
380382
webFlag: string;
381383
} | null> {
@@ -385,10 +387,12 @@ async function loadCommandsOverview(): Promise<{
385387
return null;
386388
}
387389

390+
const globalSection = extractSection(content, "Global Options");
388391
const jsonSection = extractSection(content, "JSON Output");
389392
const webSection = extractSection(content, "Opening in Browser");
390393

391394
return {
395+
globalOptions: globalSection || "",
392396
jsonOutput: jsonSection || "",
393397
webFlag: webSection || "",
394398
};
@@ -423,6 +427,7 @@ type FlagInfo = {
423427
default?: unknown;
424428
optional: boolean;
425429
variadic: boolean;
430+
hidden: boolean;
426431
};
427432

428433
type RouteInfo = {
@@ -468,6 +473,7 @@ function extractFlags(flags: Record<string, FlagDef> | undefined): FlagInfo[] {
468473
default: def.default,
469474
optional: def.optional ?? def.kind === "boolean",
470475
variadic: def.variadic ?? false,
476+
hidden: def.hidden ?? false,
471477
}));
472478
}
473479

@@ -616,9 +622,9 @@ function generateCommandDoc(cmd: CommandInfo): string {
616622
lines.push("");
617623
lines.push(cmd.brief);
618624

619-
// Flags section
625+
// Flags section — exclude help flags and hidden global flags (e.g. --log-level, --verbose)
620626
const visibleFlags = cmd.flags.filter(
621-
(f) => f.name !== "help" && f.name !== "helpAll"
627+
(f) => !f.hidden && f.name !== "help" && f.name !== "helpAll"
622628
);
623629

624630
if (visibleFlags.length > 0) {
@@ -707,12 +713,22 @@ function generateCommandsSection(routeInfos: RouteInfo[]): string {
707713
}
708714

709715
/**
710-
* Generate the Output Formats section from docs
716+
* Generate supplementary sections (Global Options, Output Formats) from docs
711717
*/
712-
async function generateOutputFormatsSection(): Promise<string> {
718+
async function generateSupplementarySections(): Promise<string> {
713719
const overview = await loadCommandsOverview();
714720

715721
const lines: string[] = [];
722+
723+
// Global Options section
724+
if (overview?.globalOptions) {
725+
lines.push("## Global Options");
726+
lines.push("");
727+
lines.push(overview.globalOptions);
728+
lines.push("");
729+
}
730+
731+
// Output Formats section
716732
lines.push("## Output Formats");
717733
lines.push("");
718734

@@ -747,7 +763,7 @@ async function generateOutputFormatsSection(): Promise<string> {
747763
async function generateSkillMarkdown(routeMap: RouteMap): Promise<string> {
748764
const routeInfos = await extractRoutes(routeMap);
749765
const prerequisites = await loadPrerequisites();
750-
const outputFormats = await generateOutputFormatsSection();
766+
const supplementary = await generateSupplementarySections();
751767

752768
const sections = [
753769
generateFrontMatter(),
@@ -759,7 +775,7 @@ async function generateSkillMarkdown(routeMap: RouteMap): Promise<string> {
759775
prerequisites,
760776
"",
761777
generateCommandsSection(routeInfos),
762-
outputFormats,
778+
supplementary,
763779
"",
764780
];
765781

src/bin.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ import { buildContext } from "./context.js";
55
import { AuthError, formatError, getExitCode } from "./lib/errors.js";
66
import { error } from "./lib/formatters/colors.js";
77
import { runInteractiveLogin } from "./lib/interactive-login.js";
8-
import {
9-
extractLogLevelFromArgs,
10-
getEnvLogLevel,
11-
setLogLevel,
12-
} from "./lib/logger.js";
8+
import { getEnvLogLevel, setLogLevel } from "./lib/logger.js";
139
import { withTelemetry } from "./lib/telemetry.js";
1410
import { startCleanupOldBinary } from "./lib/upgrade.js";
1511
import {
@@ -94,22 +90,14 @@ async function main(): Promise<void> {
9490

9591
const args = process.argv.slice(2);
9692

97-
// Apply SENTRY_LOG_LEVEL env var first (lazy read, not at module load time).
98-
// CLI flags below override this if present.
93+
// Apply SENTRY_LOG_LEVEL env var early (lazy read, not at module load time).
94+
// CLI flags (--log-level, --verbose) are handled by Stricli via
95+
// buildCommand and take priority when present.
9996
const envLogLevel = getEnvLogLevel();
10097
if (envLogLevel !== null) {
10198
setLogLevel(envLogLevel);
10299
}
103100

104-
// Extract global log-level flags before Stricli parses args.
105-
// --log-level is consumed (removed); --verbose is read but left in place
106-
// because some commands (e.g., `api`) define their own --verbose flag.
107-
// CLI flags take priority over SENTRY_LOG_LEVEL env var.
108-
const logLevel = extractLogLevelFromArgs(args);
109-
if (logLevel !== null) {
110-
setLogLevel(logLevel);
111-
}
112-
113101
const suppressNotification = shouldSuppressNotification(args);
114102

115103
// Start background update check (non-blocking)

src/commands/help.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* - `sentry help <command>`: Shows Stricli's detailed help (--helpAll) for that command
77
*/
88

9-
import { buildCommand, run } from "@stricli/core";
9+
import { run } from "@stricli/core";
1010
import type { SentryContext } from "../context.js";
11+
import { buildCommand } from "../lib/command.js";
1112
import { printCustomHelp } from "../lib/help.js";
1213

1314
export const helpCommand = buildCommand({

0 commit comments

Comments
 (0)