Skip to content

Stats & logging

guard.stats (or parry.stats() for the active guard) returns a frozen ParryStats snapshot. It is cheap to take and safe to keep.

s = parry.stats()
print(s) # ParryStats(cache_hits=47, cache_misses=12, tokens_cached=8421, total_cost_usd=0.234000, ...)
print(s.summary()) # multi-line human-readable report
FieldMeaning
requestscalls the guard handled, including cache hits
cache_hits, cache_misses, cache_hit_ratelookups for non-streaming requests
tokens_cached, cost_saved_usdwhat the hits would have used and cost
total_cost_usd, input_tokens, output_tokenssession spend
by_model{"provider:model": {requests, input_tokens, output_tokens, cost_usd, ...}}, most expensive first
requests_blockedcalls that had to wait for a rate-limit slot
rate_limit_rejectionscalls refused with RateLimited
budget_rejectionscalls refused with BudgetExceeded
retries, errorsretry attempts, and calls that failed after all of them
unpriced_requestscalls to models missing from the pricing table
estimated_requestscalls whose usage was estimated (for example OpenAI streams without usage)
today_cost_usd, lifetime_cost_usdfrom SQLite storage; None without it
guard.reset_stats() # zero the counters and session spend (restarts the budget)

parry logs to the standard parry logger and is silent by default. It adds a NullHandler.

import logging
logging.basicConfig()
logging.getLogger("parry").setLevel(logging.DEBUG)
DEBUG:parry:parry: hooked openai.resources.chat.completions.completions.Completions.create
DEBUG:parry:parry: openai:gpt-4o-mini 10 in / 5 out tokens, $0.000005
DEBUG:parry:parry: cache hit openai:gpt-4o-mini
DEBUG:parry:parry: retrying after RateLimitError in 0.61s (attempt 2)
LevelWhat gets logged
DEBUGone line per call (model, tokens, cost, estimated or not), cache hits, retries, hooks applied
WARNINGunpriced models (once each), storage failures, bookkeeping errors
import threading, time, parry
def report(every=60):
while True:
time.sleep(every)
s = parry.stats()
print(f"[parry] ${s.total_cost_usd:.4f} spent, {s.cache_hit_rate:.0%} cache hits")
threading.Thread(target=report, daemon=True).start()

For a live, refreshing view of a SQLite ledger, see examples/cost_dashboard.py in the repository.