Skip to content

Commit 61805fd

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 injected by a buildSentryCommand() wrapper: - New `buildSentryCommand()` in src/lib/command.ts wraps buildCommand to inject hidden `--log-level` (enum) and `--verbose` (boolean) flags into every command. The wrapper intercepts them, applies setLogLevel(), then strips them before calling the original func. - The `api` command keeps its own `--verbose` (HTTP semantics) and adds only `--log-level` via LOG_LEVEL_FLAG manually. - 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 61805fd

32 files changed

Lines changed: 618 additions & 306 deletions

AGENTS.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,10 @@ 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.
690+
691+
<!-- lore:019cbfaa-0001-7000-a000-000000000001 -->
692+
* **buildSentryCommand injects hidden --verbose and --log-level flags into every command**: Stricli has no global flags (bloomberg/stricli#106 closed as not planned). The fix: \`buildSentryCommand\` in \`src/lib/command.ts\` wraps Stricli's \`buildCommand\` to inject hidden \`--log-level\` (enum) and \`--verbose\` (boolean) flags into every command's parameters. The wrapper intercepts these flags before calling the original func, applies them via \`setLogLevel()\`, then strips them from the flags object. The \`api\` command uses \`buildCommand\` directly (not \`buildSentryCommand\`) because it has its own \`--verbose\` with HTTP semantics — it adds \`LOG\_LEVEL\_FLAG\` manually. Consola's \`withTag()\` creates independent instances — \`setLogLevel()\` propagates to all children via a registry.
690693
691694
### Preference
692695

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:

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+
// buildSentryCommand 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/api.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77

88
import type { SentryContext } from "../context.js";
99
import { rawApiRequest } from "../lib/api-client.js";
10-
import { buildCommand } from "../lib/command.js";
10+
import {
11+
applyLoggingFlags,
12+
buildCommand,
13+
LOG_LEVEL_FLAG,
14+
} from "../lib/command.js";
1115
import { ValidationError } from "../lib/errors.js";
16+
import type { LogLevelName } from "../lib/logger.js";
1217
import type { Writer } from "../types/index.js";
1318

1419
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
@@ -23,6 +28,7 @@ type ApiFlags = {
2328
readonly include: boolean;
2429
readonly silent: boolean;
2530
readonly verbose: boolean;
31+
readonly "log-level"?: LogLevelName;
2632
};
2733

2834
// Request Parsing
@@ -1085,6 +1091,7 @@ export const apiCommand = buildCommand({
10851091
brief: "Include full HTTP request and response in the output",
10861092
default: false,
10871093
},
1094+
"log-level": LOG_LEVEL_FLAG,
10881095
},
10891096
aliases: {
10901097
X: "method",
@@ -1102,6 +1109,10 @@ export const apiCommand = buildCommand({
11021109
): Promise<void> {
11031110
const { stdout, stderr, stdin } = this;
11041111

1112+
// Apply logging flags. The api command's --verbose has its own HTTP
1113+
// semantics, but we also treat it as debug-level logging.
1114+
applyLoggingFlags(flags["log-level"], flags.verbose);
1115+
11051116
// Normalize endpoint to ensure trailing slash (Sentry API requirement)
11061117
const normalizedEndpoint = normalizeEndpoint(endpoint);
11071118

src/commands/auth/login.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { SentryContext } from "../../context.js";
22
import { getCurrentUser, getUserRegions } from "../../lib/api-client.js";
3-
import { buildCommand, numberParser } from "../../lib/command.js";
3+
import { buildSentryCommand, numberParser } from "../../lib/command.js";
44
import { clearAuth, isAuthenticated, setAuthToken } from "../../lib/db/auth.js";
55
import { getDbPath } from "../../lib/db/index.js";
66
import { setUserInfo } from "../../lib/db/user.js";
@@ -14,7 +14,7 @@ type LoginFlags = {
1414
readonly timeout: number;
1515
};
1616

17-
export const loginCommand = buildCommand({
17+
export const loginCommand = buildSentryCommand({
1818
docs: {
1919
brief: "Authenticate with Sentry",
2020
fullDescription:

src/commands/auth/logout.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
*/
66

77
import type { SentryContext } from "../../context.js";
8-
import { buildCommand } from "../../lib/command.js";
8+
import { buildSentryCommand } from "../../lib/command.js";
99
import { clearAuth, isAuthenticated } from "../../lib/db/auth.js";
1010
import { getDbPath } from "../../lib/db/index.js";
1111
import { success } from "../../lib/formatters/colors.js";
1212

13-
export const logoutCommand = buildCommand({
13+
export const logoutCommand = buildSentryCommand({
1414
docs: {
1515
brief: "Log out of Sentry",
1616
fullDescription:

src/commands/auth/refresh.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import type { SentryContext } from "../../context.js";
8-
import { buildCommand } from "../../lib/command.js";
8+
import { buildSentryCommand } from "../../lib/command.js";
99
import { getAuthConfig, refreshToken } from "../../lib/db/auth.js";
1010
import { AuthError } from "../../lib/errors.js";
1111
import { success } from "../../lib/formatters/colors.js";
@@ -24,7 +24,7 @@ type RefreshOutput = {
2424
expiresAt?: string;
2525
};
2626

27-
export const refreshCommand = buildCommand({
27+
export const refreshCommand = buildSentryCommand({
2828
docs: {
2929
brief: "Refresh your authentication token",
3030
fullDescription: `

src/commands/auth/status.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import type { SentryContext } from "../../context.js";
88
import { listOrganizations } from "../../lib/api-client.js";
9-
import { buildCommand } from "../../lib/command.js";
9+
import { buildSentryCommand } from "../../lib/command.js";
1010
import {
1111
type AuthConfig,
1212
getAuthConfig,
@@ -117,7 +117,7 @@ async function verifyCredentials(
117117
}
118118
}
119119

120-
export const statusCommand = buildCommand({
120+
export const statusCommand = buildSentryCommand({
121121
docs: {
122122
brief: "View authentication status",
123123
fullDescription:

src/commands/auth/token.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
*/
77

88
import type { SentryContext } from "../../context.js";
9-
import { buildCommand } from "../../lib/command.js";
9+
import { buildSentryCommand } from "../../lib/command.js";
1010
import { getAuthToken } from "../../lib/db/auth.js";
1111
import { AuthError } from "../../lib/errors.js";
1212

13-
export const tokenCommand = buildCommand({
13+
export const tokenCommand = buildSentryCommand({
1414
docs: {
1515
brief: "Print the stored authentication token",
1616
fullDescription:

src/commands/auth/whoami.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import type { SentryContext } from "../../context.js";
1010
import { getCurrentUser } from "../../lib/api-client.js";
11-
import { buildCommand } from "../../lib/command.js";
11+
import { buildSentryCommand } from "../../lib/command.js";
1212
import { isAuthenticated } from "../../lib/db/auth.js";
1313
import { setUserInfo } from "../../lib/db/user.js";
1414
import { AuthError } from "../../lib/errors.js";
@@ -18,7 +18,7 @@ type WhoamiFlags = {
1818
readonly json: boolean;
1919
};
2020

21-
export const whoamiCommand = buildCommand({
21+
export const whoamiCommand = buildSentryCommand({
2222
docs: {
2323
brief: "Show the currently authenticated user",
2424
fullDescription:

0 commit comments

Comments
 (0)