Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cf16418
Add request phase timing design spec and implementation plan (#1069)
jevansnyc Aug 25, 2026
052eada
Add RequestTimings phase collection and Server-Timing rendering
jevansnyc Aug 25, 2026
7505fb8
Add observability settings and decouple tinybird access and auction e…
jevansnyc Aug 25, 2026
4ce413f
Add test coverage for the access_enabled without tinybird.enabled rej…
jevansnyc Aug 25, 2026
f83092a
Emit Server-Timing at the send freeze point on conclusively private r…
jevansnyc Aug 25, 2026
4b19c44
Record filter and geo spans and dedupe the per-request geo lookup
jevansnyc Aug 25, 2026
ce295f1
Record origin, template cache, and KV phase spans in core
jevansnyc Aug 25, 2026
c222ae2
Capture stream duration, auction wait placement, and response bytes
jevansnyc Aug 25, 2026
8b028bb
Add access telemetry snapshot, route classes, and coarse route templates
jevansnyc Aug 25, 2026
48acd81
Emit confirmed access telemetry rows after pull-sync post-send
jevansnyc Aug 25, 2026
72d5755
Extend access_logs_raw with phase columns and a non-null sorting key
jevansnyc Aug 25, 2026
c50be03
Widen time_elapsed_ms to nullable so dropped snapshots cannot quarant…
jevansnyc Aug 25, 2026
0aead79
Emit Server-Timing from the Axum terminal layer with adapter-specific…
jevansnyc Aug 25, 2026
e9cf5b9
Document the observability and access telemetry configuration surface
jevansnyc Aug 25, 2026
7942c74
Normalize telemetry method, guard zero sample rate, and mirror geo wr…
jevansnyc Aug 26, 2026
d8fcb5e
Add a local dev config envelope generator example
jevansnyc Aug 26, 2026
7cf7d86
Add JSONPaths and expression sorting key to the access datasource
jevansnyc Aug 26, 2026
600746f
Use web_time Instant on request timing paths
jevansnyc Aug 26, 2026
3d7e697
Reject opaque identifier segments in publisher route templates
jevansnyc Aug 26, 2026
38043d7
Address access telemetry review feedback
jevansnyc Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/trusted-server-adapter-axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ path = "src/main.rs"

[dependencies]
async-trait = { workspace = true }
axum = { workspace = true }
edgezero-adapter-axum = { workspace = true, features = ["axum"] }
edgezero-core = { workspace = true }
error-stack = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
reqwest = { workspace = true }
simple_logger = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "sync", "time"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "net", "signal", "sync", "time"] }
tower = { workspace = true, features = ["util"] }
trusted-server-core = { workspace = true }

[dev-dependencies]
axum = { workspace = true }
base64 = { workspace = true }
temp-env = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tower = { workspace = true, features = ["util"] }
32 changes: 23 additions & 9 deletions crates/trusted-server-adapter-axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,15 +565,7 @@ impl Hooks for TrustedServerApp {
}

fn routes() -> RouterService {
let state = match build_state() {
Ok(s) => s,
Err(ref e) => {
log::error!("failed to build application state: {:?}", e);
return startup_error_router(e);
}
};

build_router(&state)
Self::routes_with_server_timing_flag().0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ P3 — Do not discard timing configuration in the standard Axum application path

TimingService is not part of the returned RouterService, so callers using the existing Hooks::routes() or Hooks::build_app() interface get a different application from the shipped binary and silently ignore server_timing_enabled. The tuple is also an opaque public construction API whose Boolean is meaningful only to the custom runner.

The outer service is justified here chiefly by router-generated 404/405 responses, but those responses also bypass finalization and are not conclusively private; the 404 test has to manufacture a private response. Please consider using an outermost EdgeZero timing middleware. If the Tower wrapper is genuinely required, expose a named, fully configured application/service and make that the standard construction path rather than returning (RouterService, bool).

}
}

Expand All @@ -594,6 +586,28 @@ impl TrustedServerApp {
let state = build_state_with_settings(settings)?;
Ok(build_router(&state))
}

/// Build the router alongside whether `Server-Timing` emission is
/// enabled, read from the same settings snapshot used to build the
/// router.
///
/// The Axum dev server's terminal timing layer ([`crate::timing`]) needs
/// this flag once at startup: unlike the Fastly adapter, which rebuilds
/// `Settings` per request, the Axum dev server builds its application
/// state once and reuses the same [`RouterService`] for every request.
#[must_use]
pub fn routes_with_server_timing_flag() -> (RouterService, bool) {
let state = match build_state() {
Ok(s) => s,
Err(ref e) => {
log::error!("failed to build application state: {:?}", e);
return (startup_error_router(e), false);
}
};

let server_timing_enabled = state.settings.observability.server_timing_enabled;
(build_router(&state), server_timing_enabled)
}
}

fn build_router(state: &Arc<AppState>) -> RouterService {
Expand Down
3 changes: 3 additions & 0 deletions crates/trusted-server-adapter-axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ pub mod app;
pub mod middleware;
/// Platform-trait implementations backed by env vars and `reqwest`.
pub mod platform;
/// Terminal timing layer wrapping the Axum dev server's tower `Service`
/// boundary with the request-phase `Server-Timing` freeze point.
pub mod timing;
71 changes: 67 additions & 4 deletions crates/trusted-server-adapter-axum/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
use edgezero_adapter_axum::dev_server::{AxumDevServer, AxumDevServerConfig};
use edgezero_core::app::Hooks as _;
use std::net::SocketAddr;

use axum::Router;
use edgezero_adapter_axum::dev_server::AxumDevServerConfig;
use edgezero_adapter_axum::service::EdgeZeroAxumService;
use edgezero_core::router::RouterService;
use tokio::net::TcpListener;
use tokio::runtime::Builder as RuntimeBuilder;
use tokio::signal;
use tower::Service as _;
use tower::service_fn;
use trusted_server_adapter_axum::app::TrustedServerApp;
use trusted_server_adapter_axum::timing::TimingService;

#[allow(clippy::print_stderr)]
fn main() {
Expand All @@ -20,13 +30,66 @@ fn main() {
};

log::info!("Listening on http://{}", config.addr);
let router = TrustedServerApp::routes();
if let Err(err) = AxumDevServer::with_config(router, config).run() {
let (router, server_timing_enabled) = TrustedServerApp::routes_with_server_timing_flag();
if let Err(err) = run(router, server_timing_enabled, config) {
log::error!("trusted-server-adapter-axum failed: {err}");
std::process::exit(1);
}
}

/// Runs the Axum dev server with the request-phase timing terminal layer
/// ([`trusted_server_adapter_axum::timing::TimingService`]) wrapped around
/// `EdgeZeroAxumService`, ahead of `axum::serve`.
///
/// This does not use `edgezero_adapter_axum::dev_server::AxumDevServer::run`:
/// that helper only accepts a bare [`RouterService`] and builds its own
/// `EdgeZeroAxumService` and `axum::Router` internally, with no seam for an
/// outer service wrapper. Router-generated 404/405 responses bypass
/// `RouterBuilder::middleware` (see `trusted_server_adapter_axum::timing`),
/// so the freeze point has to wrap the tower `Service` boundary itself.
/// Driving `axum::serve` directly here mirrors that helper's own internal
/// bind/wrap/serve/shutdown sequence closely enough to keep behavior
/// identical for callers (`PORT` env var, ctrl-c graceful shutdown).
///
/// # Errors
///
/// Returns an error if the Tokio runtime fails to start, the listener fails
/// to bind, or the underlying serve loop errors.
fn run(
router: RouterService,
server_timing_enabled: bool,
config: AxumDevServerConfig,
) -> std::io::Result<()> {
let runtime = RuntimeBuilder::new_multi_thread().enable_all().build()?;
runtime.block_on(serve(router, server_timing_enabled, config))
}

async fn serve(
router: RouterService,
server_timing_enabled: bool,
config: AxumDevServerConfig,
) -> std::io::Result<()> {
let listener = TcpListener::bind(config.addr).await?;

let service = TimingService::new(EdgeZeroAxumService::new(router), server_timing_enabled);
let axum_router = Router::new().fallback_service(service_fn(move |req| {
let mut svc = service.clone();
async move { svc.call(req).await }
}));
let make_service = axum_router.into_make_service_with_connect_info::<SocketAddr>();

let server = axum::serve(listener, make_service);
if config.enable_ctrl_c {
server
.with_graceful_shutdown(async {
let _ctrl_c = signal::ctrl_c().await;
})
.await
} else {
server.await
}
}

/// Read a port number from the `PORT` environment variable.
///
/// Returns `None` when the variable is unset. Exits non-zero if the value
Expand Down
Loading
Loading