Every function in my top-five list for tokio-rs/tracing falls into the ‘debt’ quadrant, not ‘fire’ — meaning the risk here isn’t code changing under your feet, it’s code that hasn’t moved in 546 days and will be expensive to touch when it finally has to be. The top result, parse in tracing-attributes/src/attr.rs, carries a cyclomatic complexity of 38 and a nesting depth of 13, giving it an activity-weighted risk score of 17.69 despite zero touches in the last 30 days and zero distinct authors in the last 90 days. Across 2,404 functions analyzed, 28 sit in the critical band and 116 sit in this same debt quadrant — so I’d treat this list as a refactoring backlog to schedule deliberately, not an incident to page someone for.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
parse | tracing-attributes/src/attr.rs | 17.7 | 38 | 13 | 7 |
from_fn | tracing-attributes/src/expand.rs | 14.7 | 27 | 5 | 6 |
format_event | tracing-subscriber/src/fmt/format/pretty.rs | 14.2 | 20 | 4 | 16 |
format_event | tracing-subscriber/src/fmt/format/mod.rs | 13.2 | 22 | 3 | 10 |
from_str | tracing-subscriber/src/filter/directive.rs | 12.1 | 12 | 3 | 7 |
<BandChart fire=‘0’ debt=‘116’ watch=‘3’ ok=‘2285’ />
Zero functions in tokio-rs/tracing currently fall into the ‘fire’ quadrant — nothing here is both complex and under active commit pressure right now. That’s a meaningfully different problem than most repos I look at. Instead, 116 functions sit in ‘debt’: structurally complex code that has gone quiet. The five functions below are the sharpest edges of that pile, all last changed 546 days ago, all in the critical band.
<PatternCloud patterns=‘exit_heavy:5,stale_complex:5,long_function:4,complex_branching:3,deeply_nested:2,god_function:2’ />
Every one of the top five hotspots is tagged ‘exit_heavy’ and ‘stale_complex’ — that combination is the story here. Multiple early-return paths mean a lot of branches to hold in your head, and none of them have been exercised by a recent commit, so nobody has recently had to re-verify all those paths still do what they say.
parse — tracing-attributes/src/attr.rs
<FunctionCard fn=‘parse’ file=‘tracing-attributes/src/attr.rs’ risk=‘17.69’ band=‘critical’ cc=‘38’ nd=‘13’ fo=‘7’ touches=‘0’ />
This is the parser for #[instrument] macro arguments — it walks through name, target, parent, follows_from, level, skip, skip_all, fields, err, and ret as a long if-else-if chain against lookahead1(). The nesting depth of 13 and cyclomatic complexity of 38 both come from that same structure: each keyword gets its own branch, and most branches have their own internal ‘is this already set’ guard that returns an error. That’s a lot of independent paths for what is conceptually a flat list of mutually exclusive options.
The source itself even flags one piece of accumulated awkwardness in a comment about supporting name as both a named argument and a bare string literal ‘because changing it is apparently breaking’ — a live signal that this function already carries API-compatibility constraints layered onto its complexity. It has three total commits on record and hasn’t been touched in 546 days, so nobody is actively wrestling with it, but a bug-fix rate of 0% and a review-comment density of 11 (the highest in this list) suggest past changes here drew a lot of scrutiny even when they didn’t touch defects.
Recommendation: this is a strong candidate for a match-based dispatch table (keyword symbol to handler closure) instead of the if-else-if chain — it would flatten the nesting and let each argument’s validation live in its own small function, easier to unit test in isolation than the current monolith.
from_fn — tracing-attributes/src/expand.rs
<FunctionCard fn=‘from_fn’ file=‘tracing-attributes/src/expand.rs’ risk=‘14.69’ band=‘critical’ cc=‘27’ nd=‘5’ fo=‘6’ touches=‘0’ />
This function detects whether an instrumented function body matches one of two known ‘manual async future’ shapes generated by different versions of async-trait — an immediately-invoked async fn pattern for async-trait <= 0.1.43, or a Box::pin(async move {...}) pattern for async-trait >= 0.1.44. The doc comment spells out exactly why this exists: to find the inner future that actually needs instrumenting, rather than the wrapper that just allocates it.
A cyclomatic complexity of 27 against a comparatively modest nesting depth of 5 tells me the complexity here is breadth, not depth — this reads as a sequence of early-return guard checks (is it async already, is the last statement an async block, is it a call, is it Box::pin) rather than deep conditional nesting. That matches the ‘exit_heavy’ tag: eight total commits, a bug-fix rate of 25% (two of those eight commits were bug fixes), and no changes in 546 days. Recommendation: extract each of the four pattern checks into a named helper returning an Option, chained with ? or and_then — that would let each async-trait version’s detection logic be tested and reasoned about independently of the others.
format_event — tracing-subscriber/src/fmt/format/pretty.rs and format/mod.rs
<FunctionCard fn=‘format_event’ file=‘tracing-subscriber/src/fmt/format/pretty.rs’ risk=‘14.24’ band=‘critical’ cc=‘20’ nd=‘4’ fo=‘16’ touches=‘0’ />
<FunctionCard fn=‘format_event’ file=‘tracing-subscriber/src/fmt/format/mod.rs’ risk=‘13.20’ band=‘critical’ cc=‘22’ nd=‘3’ fo=‘10’ touches=‘0’ />
I’m grouping these two because they’re the same function name solving the same problem in two formatter implementations — pretty-printing versus the default formatter — and both are tagged ‘god_function’. The pretty-printer variant has a fan-out of 16, meaning it directly calls sixteen distinct functions to assemble one line of output: timestamp formatting, level styling, target and line-number writing, a PrettyVisitor for field recording, and ANSI styling logic all get orchestrated from one place. That’s the widest fan-out in this list, and it’s the clearest blast-radius risk here — a change to any of those sixteen collaborators (say, how Style prefixes get computed) has this function as a likely call site to check. It hasn’t been changed in 546 days despite that wide surface area.
The non-pretty format_event in mod.rs trades some fan-out (10) for more branching (cyclomatic complexity of 22) and a notably high bug-fix rate of 62.5% across its 8 recorded commits — five of eight commits touching this function were tagged as bug fixes. That’s a meaningful historical signal, not proof the current code is broken, but it does mean this specific function’s history is worth reading before extending it further. Both functions are heavy on conditional writer configuration (thread name, thread ID, target, ANSI toggles) gated behind feature flags and boolean fields on self — a natural refactor is to extract a ‘build display options’ step ahead of the write calls, so the actual formatting logic isn’t interleaved with configuration branching.
from_str — tracing-subscriber/src/filter/directive.rs
<FunctionCard fn=‘from_str’ file=‘tracing-subscriber/src/filter/directive.rs’ risk=‘12.10’ band=‘critical’ cc=‘12’ nd=‘3’ fo=‘7’ touches=‘0’ />
This is the parser for filter directive syntax — strings like foo=trace, foo[{bar,baz}]=info, a bare trace, or a bare foo. At cyclomatic complexity 12 and nesting depth 3, it’s the mildest of the five by structural measure, but it’s still in the critical band because of the ‘exit_heavy’ pattern: I count at least five distinct early-return error paths in the excerpt (empty string, too many = signs, too many [{ groups, malformed field list, and the two final fallback arms). Two total commits, no bug-fix history recorded, and no review comments logged — this one looks more like inherent parsing complexity than accumulated patchwork. It hasn’t changed in 546 days. Recommendation: if this grows further, splitting the field-list parsing ([{bar,baz}]) into its own function would isolate one whole exit-heavy branch from the target/level parsing, cutting the combined complexity roughly in half.
For contrast, the context-only data shows what low-risk code looks like here: fmt in tracing-error/src/backtrace.rs has cyclomatic complexity 3, no nesting, and was touched within the current day — that’s a ‘watch’ function, active but structurally trivial, not a refactoring concern.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 5 |
stale_complex | 5 |
long_function | 4 |
complex_branching | 3 |
deeply_nested | 2 |
god_function | 2 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function, stale_complex.
Reproduce This Analysis
git clone https://github.com/tokio-rs/tracing
cd tracing
git checkout d9d4c542de10f5d3a711b7a45ffe450fd0666437
hotspots analyze . --mode snapshot --explain-patterns --force
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →