Ferron 3 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#
ferron_entrypoint::init()sets the global allocator and installs the panic hook.ferron_entrypoint::default_profile()returnsVec<Box<dyn ModuleLoader>>with all built-in modules. For a custom binary you start from this list and add your loaders.ferron_entrypoint::main(profile)parses CLI args (run,validate,adapt,directives), loads configuration via the selectedConfigurationAdapter, and runs validators.- Ferron builds the
Registry: oneStageRegistry<C>per context type and oneProviderRegistry<P>per provider type.RegistryBuilderprovideswith_stageandwith_providerfor registration. ModuleLoader::register_modulescreatesModuleinstances and passes them the finalizedServerConfiguration.Module::startreceives&mut Runtimeand 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:
runruns 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_inverseruns in reverse order for every stage that returnedOk(true)orOk(false). This is where stages modify the response or emit access logs. If anyrun_inversereturnsErr, 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 withio_uringon Linux (RuntimeSettings::io_uring_enabled). Usespawn_primary_taskfor 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 / 2threads, minimum 1). Usespawn_secondary_taskfor 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) loadServerConfigurationfrom a source (file, DB, API) and return aConfigurationWatcherplusConfigurationMetadata(hash, mtime, files). Ferron selects the adapter via--config-adapteror 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 thetls.selfsignedvalidator). - At runtime, handlers read
LayeredConfiguration, which merges global + host + location blocks with child-over-parent semantics.
See also#
core/src/loader.rs:ModuleLoadertrait and call order.core/src/pipeline.rs:Stage,Pipeline,StageHooks.core/src/registry.rs:Registry,StageRegistry,ProviderRegistry.core/src/runtime.rs:RuntimeandRuntimeSettings.core/src/logging.rs: application logging macros andAppLogger.types/observability/src/sink.rs:EventSinktrait andCompositeEventSink.types/observability/src/event.rs:Eventenum and event types.types/observability/src/provider.rs:ObservabilityContext.