Hugh KaznowskiandClaude Sonnet 5 7a38d008fd Merge ticket 049 (inline profiles into settings.json) with auto-derived-param promotion
worktree-agent-a19b9ef959dcda6b2 (ticket 049) was branched from before "Auto
derived format" (1a481d3) landed on main, so its synchronous
persist_metric_declaration(_update) and ProfileStore::record_metric_declaration(_update)
didn't know about promoting auto-derived engine params into the persisted
profile. This merge reconciles the two:

- src/api.rs: kept the async persist_metric_declaration/_update methods and
  promoted_param_records helper from "Auto derived format", but switched their
  persistence from ProfileStore::load/record_metric_declaration to the new
  Settings-based settings.record_metric_declaration(_update), matching ticket
  049's single-settings.json model. Dropped the now-unused
  active_profile_location helper in favor of locking self.settings directly.
- src/profile.rs: removed the standalone ProfileStore struct/impl entirely
  (049 folds profiles into Settings), keeping load_legacy_profile_store for
  migrating a pre-049 profiles.json. Extended record_metric_declaration and
  record_metric_declaration_update (now methods on Settings) to keep taking
  promoted_params: &[ParamDeclarationRecord], persisting promoted auto-derived
  params alongside the metric via Settings::save. Moved the ticket 037/038
  legacy-shape migration (migrate_legacy_metric_params /
  migrate_duration_value_params) out of the removed ProfileStore::load into a
  new pub(crate) migrate_legacy_profile_shapes(profiles), invoked from
  Settings::load (via a new Settings::migrate_legacy_profile_shapes) so
  profiles inlined in settings.json still get migrated forward on every load.
  Rewrote the ProfileStore-based tests (legacy-shape migration tests, the
  no-op-migration test, and the auto-derived-param promotion round-trip test)
  to exercise Settings directly, including a write_legacy_settings_json helper
  that replaces the old write_legacy_profiles_json.
- src/api.rs tests: rewrote declaring_metric_against_auto_derived_param_promotes_it_into_the_profile
  to create/activate the profile via settings_handle.lock() and reload via
  Settings::load instead of ProfileStore.

Also fixed one clippy::collapsible_if warning introduced by the new
Settings::migrate_legacy_profile_shapes.

cargo build, cargo test (222 passed, 0 failed, 0 ignored), and
cargo clippy --all-targets all pass; no new clippy warnings beyond
pre-existing ones in files untouched by this merge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-06 02:03:22 +01:00
2026-09-06 01:20:17 +01:00
2026-09-06 01:20:17 +01:00
2026-09-05 21:41:11 +01:00

ottyl

Observability for any command's terminal output — no logging backend, no agents, no config to ship.

your_app --verbose | ottyl

Pipe a program's stdout into ottyl and turn it into a searchable log timeline, ad-hoc metrics extracted from the log lines themselves, and Grafana-style dashboards — served from a single local binary, for the lifetime of that process:

  • Search: A syntax-aware filter bar over your logs — regex matches and metric comparisons like latency_ms[value]<200, with relative (5h, -3m) and absolute datetimes.
  • Log timeline: A bucketed bar chart of message volume over time, Kibana-style. Drag across it to set the active time range.
  • Metrics from logs: Declare a metric by matching a regex against your log lines and extracting a value. It applies retroactively to everything already ingested, and to everything that follows.
  • Dashboards: Bar, line, and pie chart panels driven by PromQL-style queries over your declared metrics.
  • Mixed sources: Poll an existing Prometheus/OpenMetrics endpoint alongside log ingestion, in the same dashboard.
  • Profiles: Parsing rules and dashboards are saved per app, so they're there again next time that app runs through ottyl.

Use cases

  • Debugging a noisy CLI or long-running script, instead of scrolling a terminal buffer.
  • Getting metrics and charts out of a program that only ever prints text.
  • Local dashboards without standing up a metrics stack.
  • Combining scraped Prometheus metrics with log-derived ones during a debugging session.

Get started

Requires Rust and Node.js. The webapp is built and embedded into the binary, so the result is a single self-contained executable.

git clone <this-repo>
cd ottyl
make build
your_app --verbose | ottyl

Or point it at a log file instead of stdin, and mix in a Prometheus/OpenMetrics endpoint:

ottyl --file /var/log/your_app.log --metrics-url http://localhost:9090/metrics

ottyl prints the URL of the local web server it starts (an OS-assigned free port by default; pin one with --port). See ottyl --help for the rest, including where settings and profiles live (--settings-path).

Full documentation — filtering syntax, parameters, metrics, dashboards — is built into the app itself, under the nav menu's docs section.

Development

make dev    # frontend dev server with hot reload, proxying to a separately running `cargo run` backend
make test   # backend + frontend tests
make lint   # clippy + oxlint
S
Description
Observability dashboard from terminal output
Readme
1.2 MiB
0.1.0 release
Latest
2026-09-05 20:44:51 +00:00
Languages
Rust 70.4%
TypeScript 27.7%
CSS 1.8%
Makefile 0.1%