Skip to content

feat(usage): improve usage segment with debug logging, proxy support, and edge case fixes#92

Closed
neighbads wants to merge 0 commit intoHaleclipse:masterfrom
neighbads:master
Closed

feat(usage): improve usage segment with debug logging, proxy support, and edge case fixes#92
neighbads wants to merge 0 commit intoHaleclipse:masterfrom
neighbads:master

Conversation

@neighbads
Copy link

@neighbads neighbads commented Mar 3, 2026

Summary

  • Add --debug flag for logging support across the application
  • Add HTTP proxy support for usage API requests via environment variables (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY)
  • Fix five-hour only API response format parsing
  • Improve reset time display format
  • Add plain mode circle icons and debug logging for usage segment
  • Skip usage segment when custom ANTHROPIC_BASE_URL is set (avoids errors with non-standard API endpoints)

Commits

  • 2898c7b feat: add --debug logging support
  • b846f94 fix(usage): support five-hour only API response format
  • ca33bd8 feat(usage): add HTTP proxy support via environment variables
  • fae50fa fix(usage): improve reset time display format
  • d49b85e fix(usage): add plain mode circle icons and debug logging
  • ebb8765 fix(usage): skip usage segment when custom ANTHROPIC_BASE_URL is set

Test plan

  • Verify --debug flag enables logging output
  • Test usage segment with HTTP proxy configured
  • Test with five-hour only API response format
  • Verify usage segment is skipped when ANTHROPIC_BASE_URL is set
  • Verify plain mode displays circle icons correctly

Summary by Sourcery

Add configurable debug logging and improve the usage status segment’s robustness and display behavior.

New Features:

  • Introduce a global --debug CLI flag that enables file-based debug logging.
  • Add HTTP(S) proxy support for usage API requests via environment variables and settings configuration.
  • Provide distinct plain and nerd font usage icons based on UI style mode.

Bug Fixes:

  • Handle API responses that omit seven-day usage data while preserving functionality via optional fields and cache fallback.
  • Refine usage reset time formatting to a clearer date and time representation.
  • Skip the usage segment entirely when a custom ANTHROPIC_BASE_URL is configured to avoid hitting unsupported endpoints.

Enhancements:

  • Add detailed debug logging around credentials lookup, cache handling, proxy selection, and usage API calls to aid troubleshooting.
  • Improve usage segment metadata to include both plain and nerd icons and more informative utilization values.

Tests:

  • Update the usage preview component to reflect the new reset time display format.

Chores:

  • Introduce a lightweight logging utility with log rotation to cap debug log size.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 3, 2026

Reviewer's Guide

Implements a debug logging facility and CLI flag, enhances the usage segment with richer logging, proxy detection (env + settings), resilient handling of optional seven‑day usage and reset times, adds plain-mode circle icons, and skips usage when a custom ANTHROPIC_BASE_URL is configured.

Sequence diagram for usage segment collection with cache, proxy, and debug logging

sequenceDiagram
    actor User
    participant Cli
    participant Logger
    participant UsageSegment
    participant Credentials
    participant Config
    participant CacheStore as Cache
    participant HttpClient as ureqAgent
    participant UsageApi as AnthropicUsageAPI
    participant StatusLineGenerator as StatusLine

    User->>Cli: run ccline --debug
    Cli->>Logger: enable()
    Cli->>UsageSegment: collect(input)

    UsageSegment->>Logger: log_debug(usage, "check custom ANTHROPIC_BASE_URL")
    UsageSegment->>UsageSegment: is_custom_api_base()
    alt custom base configured
        UsageSegment->>Logger: log_debug(usage, "skipped: custom base")
        UsageSegment-->>Cli: None
        Cli-->>User: status line without usage
    else official API
        UsageSegment->>Credentials: get_oauth_token()
        alt token found
            Credentials->>Logger: log_debug(credentials, "token source details")
            Credentials-->>UsageSegment: Some(token)
        else token missing
            Credentials->>Logger: log_debug(credentials, "no token found")
            Credentials-->>UsageSegment: None
            UsageSegment->>Logger: log_debug(usage, "failed to get oauth token")
            UsageSegment-->>Cli: None
            Cli-->>User: status line without usage
        end

        UsageSegment->>Config: load()
        Config-->>UsageSegment: Config

        UsageSegment->>UsageSegment: load_cache()
        UsageSegment->>CacheStore: read cache file
        CacheStore-->>UsageSegment: Option<ApiUsageCache>

        alt cache exists
            UsageSegment->>Logger: log_debug(usage:cache, "loaded cache")
            UsageSegment->>UsageSegment: is_cache_valid(cache, cache_duration)
            alt cache valid
                UsageSegment->>Logger: log_debug(usage, "using cached data")
                UsageSegment-->>Cli: Some(SegmentData)
            else cache stale
                UsageSegment->>UsageSegment: fetch_api_usage(api_base_url, token, timeout)
                UsageSegment->>UsageSegment: get_proxy_url()
                UsageSegment->>Logger: log_debug(usage:proxy, "env/settings proxy")
                UsageSegment->>HttpClient: build Agent with optional Proxy
                HttpClient->>UsageApi: GET /api/usage
                UsageApi-->>HttpClient: HTTP 200 + JSON
                HttpClient-->>UsageSegment: Response
                UsageSegment->>Logger: log_debug(usage:api, "status and body")
                alt JSON parses
                    UsageSegment->>UsageSegment: save_cache(ApiUsageCache)
                    UsageSegment->>CacheStore: write cache file
                    CacheStore-->>UsageSegment: ok
                    UsageSegment-->>Cli: Some(SegmentData)
                else parse error
                    UsageSegment->>Logger: log_debug(usage:api, "deserialization error")
                    UsageSegment-->>Cli: Some(SegmentData from stale cache)
                end
            end
        else no cache
            UsageSegment->>Logger: log_debug(usage:cache, "no cache file found")
            UsageSegment->>UsageSegment: fetch_api_usage(api_base_url, token, timeout)
            UsageSegment->>UsageSegment: get_proxy_url()
            UsageSegment->>HttpClient: build Agent with optional Proxy
            HttpClient->>UsageApi: GET /api/usage
            UsageApi-->>HttpClient: HTTP response
            HttpClient-->>UsageSegment: Response
            alt HTTP 200 and valid JSON
                UsageSegment->>UsageSegment: save_cache(ApiUsageCache)
                UsageSegment-->>Cli: Some(SegmentData)
            else API error
                UsageSegment->>Logger: log_debug(usage:api, "request failed or parse error")
                UsageSegment-->>Cli: None
            end
        end

        Cli->>StatusLine: render_segment(usageConfig, SegmentData)
        StatusLine->>StatusLine: choose dynamic_icon_plain or dynamic_icon_nerd
        StatusLine-->>User: rendered usage segment
    end
Loading

Class diagram for updated usage, logging, and CLI structures

classDiagram
    class Cli {
        +bool debug
        +parse_args() Cli
    }

    class Logger {
        <<module>>
        +enable()
        +log_debug(tag: &str, msg: &str)
    }

    class UsageSegment {
        +new() UsageSegment
        +collect(input: &InputData) Option~SegmentData~
        -load_cache() Option~ApiUsageCache~
        -save_cache(cache: &ApiUsageCache)
        -is_cache_valid(cache: &ApiUsageCache, cache_duration: u64) bool
        -get_proxy_url() Option~String~
        -fetch_api_usage(api_base_url: &str, token: &str, timeout_secs: u64) Option~ApiUsageResponse~
        -is_custom_api_base() bool
        -get_circle_icon_nerd(utilization: f64) String
        -get_circle_icon_plain(utilization: f64) String
        -format_reset_time(reset_time_str: Option~&str~) String
    }

    class ApiUsageResponse {
        +five_hour: UsagePeriod
        +seven_day: Option~UsagePeriod~
    }

    class UsagePeriod {
        +utilization: f64
        +resets_at: Option~String~
    }

    class ApiUsageCache {
        +five_hour_utilization: f64
        +seven_day_utilization: Option~f64~
        +resets_at: Option~String~
        +cached_at: String
    }

    class StatusLineGenerator {
        -config: AppConfig
        +render_segment(config: &SegmentConfig, data: &SegmentData) String
        -get_icon(config: &SegmentConfig) String
    }

    class SegmentData {
        +primary: String
        +secondary: String
        +metadata: HashMap~String, String~
    }

    class Credentials {
        <<module>>
        +get_oauth_token() Option~String~
        -get_oauth_token_macos() Option~String~
        -get_oauth_token_file() Option~String~
        -get_credentials_path() Option~PathBuf~
        -read_token_from_path(path: &PathBuf) Option~String~
    }

    class CredentialsFile {
        +claude_ai_oauth: Option~ClaudeAiOauth~
    }

    class ClaudeAiOauth {
        +access_token: String
    }

    class Config {
        <<module>>
        +load() Result~Config, Error~
        +cache_duration_usage: Option~u64~
        +style: StyleConfig
    }

    class StyleConfig {
        +mode: StyleMode
    }

    class StyleMode {
        <<enum>>
        +Plain
        +Other
    }

    Cli ..> Logger : uses
    Cli ..> UsageSegment : drives

    UsageSegment ..> ApiUsageResponse : deserializes
    UsageSegment ..> UsagePeriod
    UsageSegment ..> ApiUsageCache
    UsageSegment ..> Logger : log_debug
    UsageSegment ..> Credentials : get_oauth_token
    UsageSegment ..> Config : load

    StatusLineGenerator ..> SegmentData
    StatusLineGenerator ..> StyleMode

    Credentials ..> CredentialsFile
    CredentialsFile ..> ClaudeAiOauth
    Credentials ..> Logger : log_debug
Loading

File-Level Changes

Change Details Files
Add debug logging infrastructure and CLI flag to control it
  • Introduce utils::logger module that writes bounded debug logs to ~/.claude/ccline/ccline.log with simple rotation
  • Add --debug flag to Cli and enable logger at startup when set
  • Wire logger into credentials lookup paths to trace token discovery and failures
src/utils/logger.rs
src/utils/mod.rs
src/cli.rs
src/main.rs
src/utils/credentials.rs
Improve usage API handling, caching, and logging with support for missing seven-day data
  • Make ApiUsageResponse.seven_day and ApiUsageCache.seven_day_utilization optional to support five-hour-only responses
  • Adjust cache validity checks and load/save to log cache presence, parsing issues, and age computations
  • When seven-day data is missing, fall back to five-hour resets_at and set secondary text to '· 5h only'
  • Log all major usage code paths (cache hit/miss, API result, fallbacks) for easier debugging
src/core/segments/usage.rs
Add HTTP proxy resolution for usage requests from env vars and settings.json
  • Replace get_proxy_from_settings with get_proxy_url that first consults HTTPS_PROXY/HTTP_PROXY (and lowercase variants), then ~/.claude/settings.json env.HTTPS_PROXY/HTTP_PROXY
  • Configure ureq::AgentBuilder with the resolved proxy and add debug logs describing which proxy source is used
src/core/segments/usage.rs
Skip usage segment when a custom ANTHROPIC_BASE_URL is configured
  • Introduce is_custom_api_base to check ANTHROPIC_BASE_URL in environment and ~/.claude/settings.json env
  • Short-circuit UsageSegment::collect when a custom base is detected and log that the segment was skipped
src/core/segments/usage.rs
Enhance usage segment visual output and reset time formatting
  • Rename nerd font icon helper to get_circle_icon_nerd and add get_circle_icon_plain for plain mode Unicode circles
  • Choose icon utilization based on seven-day usage when available, otherwise five-hour, and store both nerd/plain icons in metadata
  • Update reset time formatting to 'M.D HH:MM' and adjust preview mock data accordingly
  • Change seven_day_utilization metadata to a string that can be 'n/a' when not present
src/core/segments/usage.rs
src/core/statusline.rs
src/ui/components/preview.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The proxy resolution logic in get_proxy_url only checks HTTPS_PROXY/HTTP_PROXY (and lowercase variants) but not ALL_PROXY, which is mentioned in the PR description; consider including ALL_PROXY/all_proxy in the lookup order for consistency with the documented behavior.
  • The new is_custom_api_base and proxy helpers both reimplement home-directory resolution and ~/.claude/settings.json parsing; factoring this into a shared utility would reduce duplication and keep future config changes in one place.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The proxy resolution logic in `get_proxy_url` only checks `HTTPS_PROXY`/`HTTP_PROXY` (and lowercase variants) but not `ALL_PROXY`, which is mentioned in the PR description; consider including `ALL_PROXY`/`all_proxy` in the lookup order for consistency with the documented behavior.
- The new `is_custom_api_base` and proxy helpers both reimplement home-directory resolution and `~/.claude/settings.json` parsing; factoring this into a shared utility would reduce duplication and keep future config changes in one place.

## Individual Comments

### Comment 1
<location path="src/core/segments/usage.rs" line_range="161-164" />
<code_context>
-    fn get_proxy_from_settings() -> Option<String> {
+    fn get_proxy_url() -> Option<String> {
+        // 1. Environment variables first (HTTPS_PROXY, HTTP_PROXY, lowercase variants)
+        for var in &["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"] {
+            if let Ok(val) = std::env::var(var) {
+                if !val.is_empty() {
+                    log_debug("usage:proxy", &format!("using env var {}={}", var, val));
+                    return Some(val);
+                }
</code_context>
<issue_to_address>
**🚨 issue (security):** Avoid logging proxy URLs directly, as they may contain embedded credentials.

Since env-based proxy URLs can be of the form `http://user:pass@host:port`, this log line may write credentials to disk in plaintext. Instead, either log only that a proxy is being used (and which env var supplied it), or sanitize the URL before logging (e.g., mask the password).
</issue_to_address>

### Comment 2
<location path="src/core/segments/usage.rs" line_range="262-264" />
<code_context>
+    /// When set, the user is using a non-official API that lacks the usage endpoint.
+    fn is_custom_api_base() -> bool {
+        // 1. Check environment variables
+        if let Ok(val) = std::env::var("ANTHROPIC_BASE_URL") {
+            if !val.is_empty() {
+                log_debug("usage", &format!("ANTHROPIC_BASE_URL env set: {}", val));
+                return true;
+            }
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Consider redacting or shortening logged ANTHROPIC_BASE_URL values to avoid leaking potentially sensitive endpoints.

Even in a debug-only path, logging the full `ANTHROPIC_BASE_URL` (from env or settings.json) can reveal internal hostnames or infrastructure details. Prefer logging only that a custom base URL is set (optionally just the hostname) or redact path/query components.

Suggested implementation:

```rust
                // Avoid logging the full ANTHROPIC_BASE_URL to prevent leaking internal endpoints.
                log_debug("usage", "ANTHROPIC_BASE_URL env set (custom base URL configured via env)");

```

There may be similar logging for a custom base URL loaded from `settings.json` or other configuration sources elsewhere in `is_custom_api_base()` or related functions. Any log lines that include the full base URL value should be updated similarly to:
- Avoid printing the entire URL, especially path/query parts.
- Prefer a generic message (e.g., "custom base URL configured via settings") or at most log a heavily redacted form (e.g., just the scheme and hostname if truly needed).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +161 to +164
for var in &["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"] {
if let Ok(val) = std::env::var(var) {
if !val.is_empty() {
log_debug("usage:proxy", &format!("using env var {}={}", var, val));
Copy link

Choose a reason for hiding this comment

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

🚨 issue (security): Avoid logging proxy URLs directly, as they may contain embedded credentials.

Since env-based proxy URLs can be of the form http://user:pass@host:port, this log line may write credentials to disk in plaintext. Instead, either log only that a proxy is being used (and which env var supplied it), or sanitize the URL before logging (e.g., mask the password).

Comment on lines +262 to +264
if let Ok(val) = std::env::var("ANTHROPIC_BASE_URL") {
if !val.is_empty() {
log_debug("usage", &format!("ANTHROPIC_BASE_URL env set: {}", val));
Copy link

Choose a reason for hiding this comment

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

🚨 suggestion (security): Consider redacting or shortening logged ANTHROPIC_BASE_URL values to avoid leaking potentially sensitive endpoints.

Even in a debug-only path, logging the full ANTHROPIC_BASE_URL (from env or settings.json) can reveal internal hostnames or infrastructure details. Prefer logging only that a custom base URL is set (optionally just the hostname) or redact path/query components.

Suggested implementation:

                // Avoid logging the full ANTHROPIC_BASE_URL to prevent leaking internal endpoints.
                log_debug("usage", "ANTHROPIC_BASE_URL env set (custom base URL configured via env)");

There may be similar logging for a custom base URL loaded from settings.json or other configuration sources elsewhere in is_custom_api_base() or related functions. Any log lines that include the full base URL value should be updated similarly to:

  • Avoid printing the entire URL, especially path/query parts.
  • Prefer a generic message (e.g., "custom base URL configured via settings") or at most log a heavily redacted form (e.g., just the scheme and hostname if truly needed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant