# Ferron 3 architecture

> Source: https://ferron.sh/docs/module-development/concepts/architecture

Ferron builds on a small core. The core defines extension points and the server
wires modules through them. This page describes the flow from start-up to
request handling.

## Start-up flow

1. `ferron_entrypoint::init()` sets the global allocator and installs the panic
   hook.
2. `ferron_entrypoint::default_profile()` returns `Vec<Box<dyn ModuleLoader>>`
   with all built-in modules. For a custom binary you start from this list and
   add your loaders.
3. `ferron_entrypoint::main(profile)` parses CLI args (`run`, `validate`,
   `adapt`, `directives`), loads configuration via the selected
   `ConfigurationAdapter`, and runs validators.
4. Ferron builds the `Registry`: one `StageRegistry<C>` per context type and
   one `ProviderRegistry<P>` per provider type. `RegistryBuilder` provides
   `with_stage` and `with_provider` for registration.
5. `ModuleLoader::register_modules` creates `Module` instances and passes them
   the finalized `ServerConfiguration`.
6. `Module::start` receives `&mut Runtime` and spawns tasks.

If any validator or `register_modules` returns an error, Ferron exits before
it opens sockets.

## Pipeline and stages

A `Pipeline<C>` is an ordered list of `Stage<C>` objects. For HTTP, `C` is
`HttpContext`. The server builds the pipeline once per host (or per location)
from the `StageRegistry`.

Stages declare ordering with `StageConstraint::Before` and
`StageConstraint::After`. The registry sorts them via Kahn's algorithm
(topological sort). A cycle panics with a diagnostic that names the conflict.

Execution:

- `run` runs in order. `Ok(true)` continues, `Ok(false)` stops the forward
  pass gracefully (no error), `Err(PipelineError)` stops with an error.
- After the forward pass, `run_inverse` runs in reverse order for every stage
  that returned `Ok(true)` or `Ok(false)`. This is where stages modify the
  response or emit access logs. If any `run_inverse` returns `Err`, the pipeline
  stops.

Hooks (`StageHooks`) run before and after each stage. Ferron uses them to emit
per-stage trace spans without coupling `Pipeline` to observability code.

`is_applicable` controls whether a stage appears at all. Ferron calls it with
`Option<&ServerConfigurationBlock>` that merges all host blocks. If no block
uses the stage's directive, the stage is omitted. This keeps the pipeline lean.

## Providers

Providers are pluggable services discovered by type and name at runtime. They
implement `Provider<C>` and are registered with
`RegistryBuilder::with_provider`. At runtime a consumer calls
`registry.get_provider_registry::<C>()` and `registry.get("name")` to create an
instance via the factory.

Common provider families:

- `Provider<TlsContext>` / `TlsResolver`: certificate resolution for a hostname.
- `Provider<DnsContext>` / `DnsClient`: DNS record management for ACME.
- `Provider<ObservabilityContext>` / `EventSink`: log, metric, and trace sinks.
- `Provider<LogFormatterContext>`: access and application log formatting.

Providers do not depend on each other at compile time. The registry is the
only shared type, so any module can use a provider without importing its crate.

## Observability

Ferron has two observability channels that serve different purposes.

### Application logging macros

`ferron_core::log_info!`, `log_warn!`, `log_error!`, and `log_debug!` write to
stdout (or Windows Event Log). These are synchronous and unstructured. Use them
for server-infrastructure events: startup, shutdown, TLS configuration, file
rotation failures, and daemon lifecycle.

These macros check a global level guard before formatting. The lowest level is
`Debug`. There is no `Trace` level. The logger initializes once at startup via
`logging::init_stdio_logger(level)`.

### Structured event system

Request processing uses the structured event system in `types/observability`.
Modules emit `Event` values through `ctx.events` (`CompositeEventSink`) on
`HttpContext`. The `Event` enum has four variants:

- `Event::Access(Arc<dyn AccessEvent>)`: structured access log using a
  visitor pattern. The visitor receives typed fields (strings, integers,
  booleans) without allocating a formatted string.
- `Event::Log(LogEvent)`: a structured log with level, message, target,
  key-value attributes, and optional trace context.
- `Event::Metric(MetricEvent)`: a numeric metric (counter, gauge,
  up/down counter, or histogram) with attributes and optional unit.
- `Event::Trace(TraceEvent)`: a distributed trace span (start or end)
  with parent, attributes, links, and control plane metadata.

Each event carries an optional `EventTraceContext` that links the event to
a W3C traceparent. The trace sampler evaluates this context before
dispatch. Events not sampled are silently dropped.

## Dual runtime

Ferron uses two runtimes:

- **Primary runtime**: one zincio thread per CPU, pinned via `core_affinity`,
  optionally with `io_uring` on Linux (`RuntimeSettings::io_uring_enabled`).
  Use `spawn_primary_task` for TCP accept loops and connection handling.
  The factory closure is called once per primary thread, so you can hold
  thread-local state.
- **Secondary runtime**: standard tokio multi-thread pool
  (`available_parallelism / 2` threads, minimum 1). Use
  `spawn_secondary_task` for background work: metrics, cert renewal, custom
  servers that do not need per-CPU threads.

`Module::start` receives `&mut Runtime` and typically spawns one task of
either kind. Long-lived tasks should watch `SHUTDOWN_TOKEN` / `RELOAD_TOKEN`
for graceful stop.

## Configuration lifecycle

- Adapters (`ConfigurationAdapter`) load `ServerConfiguration` from a source
  (file, DB, API) and return a `ConfigurationWatcher` plus `ConfigurationMetadata`
  (hash, mtime, files). Ferron selects the adapter via `--config-adapter` or
  by file extension.
- Validators (`ConfigurationValidator`) run against each block. Scoped
  validators (`config_validator_scoped_key!(ns, name)`) run when a block
  selects a provider (e.g. `tls { provider selfsigned }` runs the
  `tls.selfsigned` validator).
- At runtime, handlers read `LayeredConfiguration`, which merges global + host + location blocks with child-over-parent semantics.

## See also

- `core/src/loader.rs`: `ModuleLoader` trait and call order.
- `core/src/pipeline.rs`: `Stage`, `Pipeline`, `StageHooks`.
- `core/src/registry.rs`: `Registry`, `StageRegistry`, `ProviderRegistry`.
- `core/src/runtime.rs`: `Runtime` and `RuntimeSettings`.
- `core/src/logging.rs`: application logging macros and `AppLogger`.
- `types/observability/src/sink.rs`: `EventSink` trait and `CompositeEventSink`.
- `types/observability/src/event.rs`: `Event` enum and event types.
- `types/observability/src/provider.rs`: `ObservabilityContext`.