stratify 1.1.0

Layered configuration and structured logging for Rust services: pluggable sources, priority merging, typed access, and a tracing facade
Documentation
# Changelog

All notable changes to this project are documented here.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0] — 2026-08-21

### Added

- `stratify::logging::tracing`, re-exporting the instrumentation API whole.
  `tracing` is how code emits — spans, events, `info!` — and this crate is how
  those records leave the process; re-exporting the former from the latter
  lets a service depend on stratify alone, with both halves guaranteed to
  agree on a version. `use stratify::logging::tracing;` at the top of a module
  keeps every existing `tracing::info!(...)` callsite compiling unchanged.
  The convention of depending on `tracing` directly remains available; this is
  for consumers who want one logging dependency, in the same spirit as the
  existing `EnvFilter` re-export.

## [1.0.1] — 2026-08-21

### Fixed

- Span fields no longer carry ANSI escape codes into files or colourless
  consoles. The escape codes were coming from span-field *caching*: the
  layer's own ANSI flag — which defaulted on — decides how a span's fields are
  rendered into the cache every event then prints verbatim, and the sinks only
  ever set the flag on the event format. Found live: a web service's
  per-request span (`http_request{method=…}`) arrived in its log file wrapped
  in italic/dim codes with colour configured off everywhere.

  The file sinks also get their own cache now (a distinct field-formatter
  type), so a *coloured* console beside a text file cannot bleed escape codes
  into it either — previously the two shared one cache and whichever layer
  formatted a span first decided what the other printed, which is why services
  had to switch console colour off to protect their files. Both regressions
  are pinned by tests that fail against the previous release.

## [1.0.0] — 2026-08-21

The stability release. From here, a breaking change to anything public is a
major version, so the 0.4-era caveat about minor bumps is retired along with
the number.

stratify absorbs the unpublished `loggingkit` crate. One crate, two namespaces:
`stratify::config` is the configuration half, `stratify::logging` is a
non-blocking `tracing` facade, and `stratify::init` stands both up in one call.
The logging half is behind the `logging` feature and a config-only build
compiles none of it — CI proves that on every push by failing if the default
dependency tree contains `tracing-subscriber`, `tracing-appender` or any
`opentelemetry` crate.

### Changed

- **Breaking:** the config API moved from the crate root into
  `stratify::config`, and the types shed their prefixes now that the module
  carries the name: `ConfigBuilder``config::Builder`, `ConfigError`  `config::Error`, `ConfigStore``config::Store`. `Source` and the source
  types live under `config::source`.
- The minimum supported Rust version is now declared: 1.88.0.

### Added

- `stratify::logging` (feature `logging`): console, JSON, file and syslog
  sinks, all non-blocking; per-sink filters; runtime filter reload; sampling
  and rate-limit gates; size- and time-based file rotation with retention;
  custom line formatters; redaction; panic capture; queue-depth and
  dropped-line accounting. Formerly the `loggingkit` facade, imported here
  without its history and with its legacy pre-facade API (`LogBuilder`,
  `LogStore`, the `sink` module) left behind.
- `logging::settings::Settings` (and per-sink `*Settings` blocks), a serde
  schema read from a config [`Store`] with `Settings::from_store`. The logging
  half does no parsing of its own — TOML, YAML, JSON and environment layering
  are the config half's job, in one place. The old `from_file`, and the `toml`
  dependency it carried, are gone.
- `stratify::init` and `init_with` (feature `logging`): read configuration
  (file < environment < `.env`), build logging from its `[logging]` block,
  install the subscriber, and return the `Bootstrap` pair. The first record
  the subscriber carries names the sources that resolved, so a wrong
  precedence stack is visible instead of silent.
- Features `appinsights` (Azure Application Insights export with trace
  correlation) and `compression` (gzip retired log files), both implying
  `logging`.
- Full settings parity: `[logging.filters]` (per-sink directives), `redact`,
  `capture_panics`, `[logging.global_fields]`, `[logging.syslog]` and
  `[logging.app_insights]` are all expressible in configuration, so "described
  rather than coded" covers the whole builder surface. The `app_insights`
  block names the *key* the connection string is found under rather than
  holding the value, so the file stays safe to commit; an explicit block whose
  secret is missing is a startup error, not a silently absent exporter. A bad
  `level`, an unknown syslog facility, or a block needing a feature that is
  not compiled in all fail loudly at startup naming the offending key —
  `Settings::to_builder` is fallible now for exactly that reason.
- `AppInsightsConfig::with_sample_rate`, bounding the fraction of traces
  exported (`0.0..=1.0`, parent-based so a trace is kept or dropped whole).
  Exporting every span is an Application Insights bill that grows linearly
  with traffic; this is the knob. Log records are not sampled — per-sink
  filters are the tool for those. Also settable as `sample_rate` in the
  `[logging.app_insights]` block.
- `init` reads `rust_log` from the *store* when no explicit level is set, so a
  `RUST_LOG` written in `.env` obeys the store's precedence instead of
  silently losing to a shell export — the builder's own fallback reads the
  process environment, which a `.env` loaded as a source never touches.
  Sourced from configuration it is validated strictly: a typo is a startup
  error naming the key.
- `AzureAppConfigSource::with_key_vault_resolution`, resolving Key Vault
  references into the secrets they point at, reusing the source's credential
  against the vault data plane. Off by default because resolution widens the
  source's reach from one store to every vault the references name; with it
  off, *encountering* a reference is an error naming the key — loud, rather
  than a JSON envelope masquerading as a configuration value.
- `stratify::logging::EnvFilter`, re-exported so `with_filter` and
  `reload_filter` can be called without taking `tracing-subscriber` as a
  direct dependency.

## [0.3.1] — 2026-08-20

### Added

- `EnvSource::with_keys` and `ConfigBuilder::env_keys`, capturing exactly the
  environment variables you name rather than everything matching a prefix.

  Some settings are named by convention rather than by application: `RUST_LOG`
  is read by `tracing-subscriber`, `AZURE_STORAGE_ACCOUNT` is what Azure
  injects. No prefix selects those and nothing else, and an empty prefix
  captures the whole environment — `PATH` and every other process's secrets
  along with it, which then sits in the merged configuration waiting to be
  logged.

  Names match case-insensitively and appear lowercased, so `RUST_LOG` is read as
  `rust_log`. The separator still applies.

## [0.3.0] — 2026-08-20

First public release. Continues the unpublished `configkit` under a name that is
available on crates.io and describes the crate rather than padding it.

### Added

- Azure App Configuration source, behind the `azure` feature. The caller supplies
  the credential rather than the crate choosing one, so a service can use a managed
  identity in Azure and a developer credential locally. That keeps `azure_identity`
  out of this crate's dependency tree and lets the source be tested against a fake.
  Supports `@nextLink` pagination, an optional label filter, and the `Database:Host`
  key convention.
- `ConfigBuilder::azure` for the same, in the fluent style.
- `#![forbid(unsafe_code)]`.

### Changed

- **Breaking:** `Source::load` is now `async`. Implementations declare
  `#[async_trait]`, and `ConfigBuilder::build` and `ConfigStore::refresh` are
  awaited. Network-backed sources should not block a runtime thread, and the
  synchronous trait left no way to use an async SDK without blocking inside a
  runtime, which panics.
- Replaced `serde_yaml` with `serde_norway`. `serde_yaml` is officially deprecated
  and should not be a dependency of a published crate.
- `toml` 0.8 to 1.
- Key nesting is shared between the environment and Azure sources rather than
  duplicated in each.

### Migration from `configkit` 0.2

```diff
-let store = ConfigBuilder::default().json("base.json", 100).build()?;
+let store = ConfigBuilder::default().json("base.json", 100).build().await?;
```

For a custom source, add the attribute and the keyword:

```diff
+#[async_trait::async_trait]
 impl Source for MySource {
-    fn load(&self) -> Result<Value, ConfigError> { ... }
+    async fn load(&self) -> Result<Value, ConfigError> { ... }
 }
```

[0.3.1]: https://github.com/LogicOcean/stratify/releases/tag/v0.3.1
[0.3.0]: https://github.com/LogicOcean/stratify/releases/tag/v0.3.0