Skip to content

feat: add verbose/trace output for API latency debugging (-v to -vvvv)#56

Merged
gnapse merged 10 commits intoDoist:mainfrom
VariousForks:i40-verbose-trace-output
Feb 13, 2026
Merged

feat: add verbose/trace output for API latency debugging (-v to -vvvv)#56
gnapse merged 10 commits intoDoist:mainfrom
VariousForks:i40-verbose-trace-output

Conversation

@gwpl
Copy link
Contributor

@gwpl gwpl commented Feb 11, 2026

Summary

Adds stackable verbose output (-v, -vv, -vvv, -vvvv) to stderr for diagnosing API latency and behavior — especially useful for customer support tickets where users need to capture diagnostic traces.

Closes #40

Related to customer support ticket #916983.

Verbosity levels

Flag Level What's logged
-v INFO API endpoints called, HTTP status, total timing (duration_ms)
-vv DETAIL Request params, body sizes, pagination progress (per-page timing)
-vvv DEBUG Rate-limit headers, x-request-id, cf-ray, reference resolution steps
-vvvv TRACE All request/response headers (Authorization token always redacted)

Also activatable via TD_VERBOSE=1..4 environment variable.

Changes (14 files, +417 lines)

  • src/lib/logger.ts — new module: Logger class with 4 verbosity levels + verboseFetch() wrapper (drop-in fetch() replacement that logs timing/headers)
  • src/index.ts — register -v, --verbose global Commander option; initialize logger before parsing
  • src/lib/spinner.ts — auto-disable spinner when verbose mode is active
  • src/lib/api/core.ts — instrument SDK Proxy wrapper with per-call timing + executeSyncCommand() logging
  • src/lib/api/{workspaces,stats,reminders,filters,notifications,uploads,user-settings}.ts — replace fetch()verboseFetch() in all 7 direct-fetch sites
  • src/lib/refs.ts — log reference resolution strategy (ID lookup vs name search), match results
  • src/lib/pagination.ts — log per-page cursor progression and timing
  • src/lib/skills/content.ts — document new flag in agent skill reference

Example output

$ td today -vv 2>&1 | head -20
[td:info] 2026-02-11T09:46:14.778Z verbose logging enabled (level=2)
[td:detail] 2026-02-11T09:46:14.802Z initializing TodoistApi client
[td:info] 2026-02-11T09:46:14.810Z api.getTasks()
[td:detail] 2026-02-11T09:46:14.811Z api.getTasks() params | filter=today | overdue
[td:detail] 2026-02-11T09:46:14.812Z paginate started | limit=300 perPage=200
[td:info] 2026-02-11T09:46:25.953Z api.getTasks() response | duration_ms=11141 result_count=10
[td:detail] 2026-02-11T09:46:25.954Z paginate page 1 done | results=10 has_more=false duration_ms=11142
[td:detail] 2026-02-11T09:46:25.954Z paginate complete | total_results=10 pages_fetched=1

All output goes to stderr with [td:info/detail/debug/trace] prefixes — never pollutes stdout data pipes.

Test plan

  • All 675 existing tests pass (29 test files)
  • TypeScript build succeeds with zero errors
  • td --help shows the new -v, --verbose option
  • -v, -vv, -vvv, -vvvv flags produce expected verbosity levels
  • TD_VERBOSE=2 env var activates level 2
  • Verbose output goes to stderr only (stdout data unaffected)
  • Authorization tokens are redacted in trace output
  • Manual test with live Todoist API at all 4 verbosity levels

🤖 Generated with Claude Code

gwpl and others added 6 commits February 10, 2026 21:03
Add a verbose logger with 4 levels (-v to -vvvv) for debugging API
latency and CLI behavior. Output goes to stderr with timestamped
[td:info/detail/debug/trace] prefixes. Supports TD_VERBOSE env var.

Levels:
  -v     (1) INFO   : commands, endpoints, response status + timing
  -vv    (2) DETAIL : request params, response metadata, pagination
  -vvv   (3) DEBUG  : ref resolution, HTTP headers, rate-limit info
  -vvvv  (4) TRACE  : full headers, body sizes, connection details

Related to Doist#40 and customer support ticket #916983.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Register -v/--verbose as a Commander option (stackable: -v to -vvvv)
and initialize the verbose logger before command parsing. Also disable
the loading spinner when verbose mode is active, since spinner
animations conflict with stderr diagnostic output.

Related to Doist#40 and customer support ticket #916983.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add timing and verbose logging to the TodoistApi Proxy wrapper:
- Log every SDK API method call with params at -v level
- Log response duration_ms, result counts, pagination at -v level
- Log full request params at -vv level
- Log rate-limit and x-request-id headers at -vvv level
- Log all response headers at -vvvv level

Also instrument executeSyncCommand() with request/response logging
including HTTP status, timing, and header inspection.

Related to Doist#40 and customer support ticket #916983.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a verbose-aware fetch wrapper (verboseFetch) to the logger module
that logs request/response details at all 4 verbosity levels:
  -v    method + URL path + status + duration_ms
  -vv   full URL, request body size, content-length
  -vvv  rate-limit headers, x-request-id, cf-ray
  -vvvv all request/response headers (auth token redacted)

Replace all direct fetch() calls across the API layer:
  - core.ts (executeSyncCommand)
  - workspaces.ts, stats.ts, reminders.ts, filters.ts
  - notifications.ts, uploads.ts, user-settings.ts

This ensures every HTTP call made by `td` is visible in verbose mode,
making it easy to diagnose API latency and capture diagnostics for
support tickets.

Related to Doist#40 and customer support ticket #916983.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ging

Add verbose logging to refs.ts:
  - Log resolution strategy (direct ID lookup vs name search)
  - Log candidate counts, match types (exact/partial), results
  - Log auto-retry when raw ID fallback is triggered

Add verbose logging to pagination.ts:
  - Log each page fetch with page number, cursor, page size
  - Log per-page timing (duration_ms) and result counts
  - Log pagination summary (total results, pages fetched, has_more)

All logging at -vv (DETAIL) and -vvv (DEBUG) levels, helping to
diagnose whether latency is in individual API calls or pagination.

Related to Doist#40 and customer support ticket #916983.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update the agent skill content to document the new -v/--verbose
global option, keeping it in sync with the CLI.

Related to Doist#40 and customer support ticket #916983.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gwpl gwpl mentioned this pull request Feb 11, 2026
gwpl and others added 2 commits February 11, 2026 17:26
Ensure verbose output at all levels (-v to -vvvv) is safe to share
in support tickets and public gists:

- refs.ts: remove entity names (task content, project names) from
  debug logs; log only IDs and counts
- logger.ts verboseFetch: log request body keys instead of raw content
  at TRACE level (body may contain user data in sync commands)
- core.ts API proxy: redact 'query' and 'content' params to show
  only character length (e.g., "[16 chars]")
- logger.ts: convert Verbosity enum to const object (biome lint)

This allows users to capture -vvvv trace output and share it with
support without exposing private task/project data.

Related to Doist#40 and customer support ticket #916983.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the per-file verboseFetch() approach with a global fetch
patch that activates when verbose mode is enabled. This captures
HTTP-level details (status codes, timing, rate-limit headers,
x-request-id) for ALL fetch calls including those made internally
by @doist/todoist-api-typescript SDK.

Previously, SDK-internal HTTP calls were invisible to verbose output.
Now every HTTP request shows:
  -v    method, URL path, status, duration_ms
  -vv   full URL, body size, content-length
  -vvv  rate-limit headers, x-request-id, cf-ray
  -vvvv all headers (auth redacted), body keys

Remove verboseFetch() and its imports — no longer needed since the
global patch covers all call sites uniformly.

Related to Doist#40 and customer support ticket #916983.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gwpl gwpl mentioned this pull request Feb 11, 2026
Copy link
Collaborator

@gnapse gnapse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together. The verbose/trace idea could be useful for debugging latency, and the global fetch patch approach is right.

My main feedback: I think the PR does more than it needs to. The globalThis.fetch wrapper in logger.ts already captures timing, status, headers, and rate-limit info for every HTTP call the CLI makes. That single patch covers the stated goal ("diagnosing API latency") without touching any business logic.

But on top of that, the PR also manually instruments core.ts (+71 lines), pagination.ts (+28 lines), and refs.ts (+21 lines) with getLogger() calls — which largely duplicate what the fetch wrapper already logs. For example, the Proxy logging in core.ts that logs api.getTasks() with timing is restating what the fetch wrapper already reports as GET /rest/v2/tasks with the same duration_ms.

I'd suggest trimming this down to:

  • logger.ts — the Logger class + global fetch patch (the core of your PR, keep it)
  • index.ts — register the --verbose flag, call initializeLogger()
  • spinner.ts — disable spinner when verbose
  • content.ts — document the flag

And drop the changes to core.ts, pagination.ts, and refs.ts entirely. That gets us from +401/-5 across 7 files down to ~215 lines across 4 files, with zero changes to business logic, and the same user-facing functionality.

What do you think?

…refs.ts

Drop verbose logging from business logic files — the globalThis.fetch
patch in logger.ts already captures timing, status, and headers for
every HTTP call, making these per-method logs redundant.

Addresses review feedback on PR Doist#56.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor Author

@gwpl gwpl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Assistant:

Thanks for the thoughtful review @gnapse! You're absolutely right — the globalThis.fetch patch already captures timing, status, and headers for every HTTP call, making the manual instrumentation in core.ts, pagination.ts, and refs.ts redundant.

I've pushed dd6e608 which reverts all changes to those 3 files, bringing the PR down to 4 files with zero changes to business logic:

  • logger.ts — Logger class + global fetch patch (core of the PR)
  • index.ts--verbose flag registration + initializeLogger()
  • spinner.ts — disable spinner when verbose
  • content.ts — document the flag

All 675 tests still pass. Appreciate the guidance on keeping it focused!

@gnapse gnapse self-requested a review February 12, 2026 23:48
@gnapse gnapse added the 🙋 Ask PR PR must be reviewed before merging label Feb 12, 2026
Copy link
Collaborator

@gnapse gnapse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good. Thanks!

There's a pending issue: linting is not passing.

It's failing because of import sort order (Biome). Also, the eslint-disable comment doesn't do anything since this project uses Biome, not ESLint. And the import is actually used anyway so no disable needed.

Quick fix: npm run format

Or here's a patch if easier.

0001-fix-remove-useless-eslint-disable-and-fix-import-ord.patch

I tried contributing this commit myself, but the fork is not allowing push from me.

Move initializeLogger import to its alphabetically correct position
after ./commands/* imports. ES module imports are hoisted, so order
has no runtime effect — the initializeLogger() call site controls
actual execution timing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gwpl
Copy link
Contributor Author

gwpl commented Feb 13, 2026

Greg + AI Assistant:

Apologies for the lint issue! I (Greg) am primarily a Rust developer, not a JavaScript/TypeScript one — we're putting this PR together with the help of an AI coding assistant, doing our best to deliver useful debug instrumentation for support ticket #916983 and give some code/inspiration along the way.

The import ordering issue in src/index.ts is now fixed in 3d56f3e — the initializeLogger import is sorted to its correct alphabetical position. We verified locally that npm run format:check passes cleanly before pushing.

(The CI runs for the new commit show action_required — likely needs maintainer approval to run workflows on a fork PR.)

Really appreciate the thoughtful review feedback, @gnapse. It's helping us iterate toward a clean, focused contribution together!

@gwpl
Copy link
Contributor Author

gwpl commented Feb 13, 2026

Greg + AI Assistant:

Thanks @gnapse! Good catch on both the import order and the useless eslint-disable comment (we didn't realize the project uses Biome, not ESLint).

Both issues are already addressed in commit 3d56f3e which was pushed ~24 minutes after your review — timing was just unfortunate:

  • Import sort order fixed (moved initializeLogger to alphabetical position)
  • Removed the eslint-disable-next-line comment entirely

npm run format:check passes cleanly on our end. The CI for 3d56f3e shows action_required — it likely needs maintainer approval to run workflows on a fork PR.

Also thank you for trying to push the fix directly and for attaching the patch — really appreciate the collaborative spirit!

@gnapse gnapse merged commit 8d43e99 into Doist:main Feb 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Ask PR PR must be reviewed before merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--verbose flag

2 participants

Comments