Skip to content
Agentmart
Submit a tool

flutter-logging-strategy

Establish, audit, and enforce a consistent logging strategy in any Flutter/Dart project using package:logging — installs the dependency if missing, builds a…

Skill by ersanyamarya

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/ersanyamarya-flutter-logging-strategy

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Establish, audit, and enforce a consistent logging strategy in any Flutter/Dart project using package:logging — installs the dependency if missing, builds a named-logger hierarchy with a documented level budget and redaction policy, migrates ad-hoc print/debugPrint/logger calls, finds silent catch blocks that destroy diagnostics, and audits project docs and skills so the documented strategy matches the code. Use this whenever the user mentions logging, log levels, debugPrint, print statements, diagnostics, observability, tracing, noisy logs, missing logs, "I can't tell why this failed", redacting secrets or tokens from logs, or asks to set up, clean up, standardize, or review logging — even if they never name a package or say the word "strategy".

Use cases: documents, security, marketing, finance

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Python package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/flutter-logging-strategy

What's inside

Skill name
flutter-logging-strategy
Description
Establish, audit, and enforce a consistent logging strategy in any Flutter/Dart project using package:logging — installs the dependency if missing, builds a named-logger hierarchy with a documented level budget and redaction policy, migrates ad-hoc print/debugPrint/logger calls, finds silent catch blocks that destroy diagnostics, and audits project docs and skills so the documented strategy matches the code. Use this whenever the user mentions logging, log levels, debugPrint, print statements, diagnostics, observability, tracing, noisy logs, missing logs, "I can't tell why this failed", redacting secrets or tokens from logs, or asks to set up, clean up, standardize, or review logging — even if they never name a package or say the word "strategy".
License
MIT
Instructions
189 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (6)

  • SKILL.md
  • app_logger.dart.template · assets/
  • evals.json · evals/
  • level-budget.md · references/
  • migration.md · references/
  • audit_logging.py · scripts/

SKILL.md (first lines)

# Flutter logging strategy

> The Flutter app lives in `apps/<app>/`. Run `flutter`/`dart` commands from that directory; `lib/`, `test/`, and `assets/` paths below are relative to it; shell scripts live in the repo-root `scripts/`. `docs/` stays at the repo root.

Most Flutter projects don't lack logging — they lack a *strategy*. The typical state is a few dozen `debugPrint` calls with no levels, no way to turn them down, no rule about what is safe to print, and a handful of `catch (_)` blocks quietly destroying the one error message that would have explained a bug. Adding a logging package doesn't fix that. What fixes it is a level budget people can actually apply, a single sink, and an audit that finds where diagnostics are being thrown away.

This skill does four things, in order: **survey** what's there, **establish** the logger, **migrate and close holes**, then **align the docs** so the written strategy and the code agree.

Work through the phases in order. Phase 0 is not optional — the survey is what tells you whether this is a greenfield install or a cleanup, and those need different amounts of work.

## Phase 0 — Survey before you touch anything

Run the bundled audit script. It has no dependencies beyond Python 3 and reports everything the later phases need:

```bash
python3 ${CLAUDE_SKILL_DIR}/scripts/audit_logging.py <project-root> [extra-docs-root ...]
```

In this monorepo: `python3 ${CLAUDE_SKILL_DIR}/scripts/audit_logging.py apps/<app> docs .claude/skills` (run from the repo root, so the root `docs/` and the skills are also checked for stale logging claims).

It reports: whether `logging` is already a direct or transitive dependency, which other logging packages are present, every ad-hoc log site, eagerly-interpolated gated calls, silent `catch` blocks, which global error handlers are wired, whether the expensive knobs have been touched, log lines that may expose secrets, and doc/skill files whose logging claims may now be stale.

Read the report before proposing anything. Two things you learn here change the plan:

- **`logging` may already be in `pubspec.lock` as a transitive dependency.** Many common packages pull it in. If so, adding it as a direct dependency costs *zero* new packages and cannot cause version churn — worth saying out loud, because "add another dependency" is the objection you'll otherwise get.
- **The project may already use a different logger** (`logger`, `talker`, `loggy`). This skill standardizes on `package:logging`, so that means a migration — see `references/migration.md`. Tell the user the scope before you start; a logger swap touches every call site and they may reasonably want to stage it.

## Phase 1 — Install

```bash
flutter pub add logging
```

Then `flutter pub get` if the project's tooling needs it separately (`pub add` normally runs resolution itself).

Two things to check after, because both fail the build later rather than here:

- If the project's analysis options enable `sort_pub_dependencies`, the new entry must sit in alphabetical position. `pub add` normally handles this; `flutter analyze` is the check.
- If `logging` was previously transitive, confirm the output says it moved from transitive to direct rather than resolving a *new* version. A surprise version bump means something else constrained it.

## Phase 2 — One file owns the whole surface

Create a single module — `lib/core/logging/app_logger.dart` or wherever the project's shared infrastructure lives. Copy `assets/app_logger.dart.template` and adapt the logger names to the project's actual subsystems.

Three properties make this file work, and all three are worth preserving when you adapt it:

**It is the only file that imports `package:logging`.** Everything else imports *this* file and gets a ready-made logger. That keeps the level policy, the sink format, and the redaction rule in one reviewable place instead of spread across every feature.

**It is the only remaining legal `print`/`debugPrint` site, because it *is* the sink.** This sounds contradictory until you state it plainly: the ban applies to call sites, not to the implementation of the sink. Say so in the file, or the next person will "fix" it. Prefer `debugPrint` over `print` — it throttles output, so Android's logcat doesn't drop lines under bursty logging, and it satisfies the `avoid_print` lint that most strict lint sets enable.

**The sink must format `error` and `stackTrace` itself.** This is the trap worth knowing about, because the broken version looks finished: `LogRecord.toString()` renders only `[LEVEL] loggerName: message`. It does **not** include the error or the stack. So a sink written as `onRecord.listen((r) => debugPrint('$r'))` silently discards both — every call site can be passing its exception correctly and nothing ever reaches the console. If someone reports "we log errors but there's never anything useful in there", check the sink before checking the call sites.

**Named loggers form a dotted hierarchy, one per subsystem.** `logging` uses dot-separated names as its hierarchy convention. Give the project a single root prefix (its name) plus one child per genuine subsystem — networking, persistence, auth, and whatever the app's core domain is. Keep the root logger too: global error handlers log things that belong to no subsystem.

Pick subsystems by *where you'd look during an incident*, not by folder structure. Five or six is plenty. A logger per feature folder is the failure mode — it produces names nobody remembers and no filtering benefit.

### `initLogging()`

The init function sets the root level and attaches exactly one listener. Two details matter:

**Guard against double-initialization.** A second call would attach a second listener and duplicate every line thereafter. A private bool and an early return is enough — and it makes the function safe to call from tests.

**Set the level from the build mode, not a constant.** Keep `WARNING` and above in release builds. This is the choice that pays off when a user reports a bug from a real device: `adb logcat` or Console.app will still carry the warnings and errors, while the chatty levels cost one integer comparison each. Turning logging fully off in release means field reports arrive with no diagnostic content at all.

## Phase 3 — Wire the bootstrap first, literally

`initLogging()` goes at the **top** of `main()`, before every other bootstrap step.

This ordering is load-bearing, not tidiness. Bootstrap code is exactly where destructive recovery lives — cache resets, corrupt-database deletion, migration fallbacks. Those paths log the only explanation the user will ever get for missing data, and they run *early*. A logger installed after them captures nothing. Find the project's bootstrap steps that can destroy or discard data on failure, and make sure the logger is installed before them.

Then wire the two global handlers, because most projects have neither or only one:

- **`FlutterError.onError`** — synchronous framework errors. Log at `SEVERE`, then delegate to `FlutterError.presentError` so the debug red screen still appears. Skip the delegation and you've made debugging worse, not better.
- **`PlatformDispatcher.instance.onError`** — uncaught async errors. If the project already has one that swallows certain error types deliberately, *keep the swallowing* and add the logging. Check whether it binds a stack trace it then discards; that is a common and free improvement.

## Phase 4 — The level budget

This is the part that decides whether the logs stay useful. Put this table in the logger file's doc comment so it is readable at the point of use, and in the project's contributor docs.

| Level | Means | Rough budget |
|---|---|---|
| `SEVERE` | A user action failed and did **not** recover; data may be lost | Rare. Carries the error object *and* a stack. |
| `WARNING` | Degraded but recovered — will retry, fell back, or was refused | Per failed operation. Survives into release. |
| `INFO` | Coarse lifecycle, once per *logical* operation | The ceiling for "leave it on" |
| `FINE` | Per-operation detail; dev builds only | State transitions |
| `FINER` | Per-row / per-message firehose | Bulk iteration, stream traffic |
| `FINEST` | Reserve it. An unused level is a feature — it leaves headroom | — |

**The one rule to carry:** *if a line would print more than once per user action, it belongs at `FINE` or below.* This is a better test than arguing about severity, because it is checkable. Also: never log inside `build()`, and never per frame.

`WARNING` vs `SEVERE` trips people up. The question is not "how bad does this feel" but **did the operation recover?** A failed network push that stays queued for retry is `WARNING` — the system handled it. A cache reset that deleted data is `SEVERE` — nothing recovers that.

See `references/level-budget.md` for the harder calls and worked examples.

## Phase 5 — Efficiency: four rules, one of which is the whole game

`Logger.log` checks `isLoggable(level)` before doing anything else, so a call at a disabled level costs one comparison. That gives you four rules:

1. **Pass a closure for any message that interpolates.** `log.fine(() => 'resolved $n of $m')`, never `log.fine('resolved $n of $m')`. Dart evaluates arguments eagerly, so the plain form builds that string on every call *even when the level is off* — which is exactly the cost gating is supposed to avoid. `logging` invokes a function message only after `isLoggable` passes. Plain strings are fine for `warning`/`severe`: they're rare and usually enabled.

2. **Move work that exists only for the log message inside the closure.** This is the one people miss, and it's often the bigger win. If a log line needs a lookup, a count, or a `join` to build its message, that work runs unconditionally when it sits above the log call. Inside the closure it runs only when the level is on. Look for this specifically during the audit — computed-for-logging-only locals are common in code that was instrumented under pressure.

3. **Leave `recordStackTraceAtLevel` at its `Level.OFF` default.** It is a *top-level* variable in the logging package, not a static, so it's easy to set casually. Lowering it makes every record at or above that level call `StackTrace.current`; the package's own comment says "Because this is expensive, this is off by default." Pass stacks explicitly at the few sites that have one.

4. **Leave `hierarchicalLoggingEnabled` at `false` unless per-subsystem runtime control is genuinely needed.** At `false` each record publishes once to the root. At `true` a record walks the parent chain publishing at each level, and non-root loggers become individually settable. Turn it on only if the project actually wants "crank one subsystem to FINEST, leave the rest alone" — and if it does, that's a real feature worth the cost.

Pass errors and stacks as the **second and third arguments**, not folded into the message string: `log.warning('Push failed for $id', error, stackTrace)`. The sink formats them, so the output stays consistent and the error object survives as an object.

## Phase 6 — Audit the code

The script gives you findings. Your job is triage, because **not every silent path is a bug.** Present findings grouped by severity and let the user choose before editing — auto-instrumenting everything produces exactly the noise this strategy exists to prevent.

Rank findings like this:

**Highest value: errors that are caught and discarded.** `catch (_)`, or `on SomeException catch` with no variable bound, in a path a user can reach. These are worse than missing logs — the information existed and was deliberately thrown away. The pattern to look for hardest: a layer that throws a *deliberate, informative* exception, and a caller that swallows it and reports a generic failure state. The user sees "failed" and the reason exists nowhere. Fix by binding the error and logging it; leave the control flow exactly as it was.

**High value: silent drops with no signal to the caller.** A function that returns early when a precondition fails, returning `void` or a default, gives the caller nothing to report. If a user action can trigger it, that early return needs a log.

**High value: the one place a reason is readable.** Some failures are only explicable at a specific line — a status object populated after a call returns, a response code available before it's discarded. Log *there*, even if the failure is handled elsewhere.

From ersanyamarya/agentry-labs, MIT.

More from ersanyamarya/agentry-labs

Signals

Stars
0
Forks
0
Open issues
0
Last commit (this folder)
Oct 18, 2026 (10 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from ersanyamarya

Cite this page

flutter-logging-strategy skill. Agent Mart. https://agentmart.dev/skills/ersanyamarya-flutter-logging-strategy (last updated 2026-10-24)