-
Notifications
You must be signed in to change notification settings - Fork 12
Add request phase timing: Server-Timing subtimings and access telemetry #1074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
cf16418
052eada
7505fb8
4ce413f
f83092a
4b19c44
ce295f1
c222ae2
8b028bb
48acd81
72d5755
c50be03
0aead79
e9cf5b9
7942c74
d8fcb5e
7cf7d86
600746f
3d7e697
38043d7
2ef7635
082461d
f3ee473
46b3c7f
a0a3af5
c6235b9
6f133cc
bcc1475
a152367
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| use core::future::Future; | ||
| use std::sync::Arc; | ||
|
|
||
| use edgezero_adapter_axum::service::EdgeZeroAxumService; | ||
| use edgezero_core::app::Hooks; | ||
| use edgezero_core::context::RequestContext; | ||
| use edgezero_core::error::EdgeError; | ||
|
|
@@ -565,15 +566,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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 thinking — Latent rather than live, since |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -594,6 +587,44 @@ impl TrustedServerApp { | |
| let state = build_state_with_settings(settings)?; | ||
| Ok(build_router(&state)) | ||
| } | ||
|
|
||
| /// The dev server's fully configured tower service: the application | ||
| /// router wrapped in the terminal timing layer | ||
| /// ([`crate::timing::TimingService`]), with `server_timing_enabled` | ||
| /// read from the same settings snapshot that built the router. | ||
| /// | ||
| /// This is the standard construction path for serving this adapter. | ||
| /// [`Hooks::routes`] satisfies the `Hooks` trait contract and returns | ||
| /// the bare router without the timing layer; callers who serve traffic | ||
| /// should use this instead so `server_timing_enabled` is never | ||
| /// silently discarded. | ||
| #[must_use] | ||
| pub fn dev_server_service() -> crate::timing::TimingService<EdgeZeroAxumService> { | ||
| let (router, server_timing_enabled) = Self::routes_with_server_timing_flag(); | ||
| crate::timing::TimingService::new(EdgeZeroAxumService::new(router), server_timing_enabled) | ||
| } | ||
|
|
||
| /// 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] | ||
| 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; | ||
|
aram356 marked this conversation as resolved.
|
||
| (build_router(&state), server_timing_enabled) | ||
| } | ||
| } | ||
|
|
||
| fn build_router(state: &Arc<AppState>) -> RouterService { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,15 @@ | ||||||||||||||||
| 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 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() { | ||||||||||||||||
|
|
@@ -20,13 +29,63 @@ fn main() { | |||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| log::info!("Listening on http://{}", config.addr); | ||||||||||||||||
| let router = TrustedServerApp::routes(); | ||||||||||||||||
| if let Err(err) = AxumDevServer::with_config(router, config).run() { | ||||||||||||||||
| let service = TrustedServerApp::dev_server_service(); | ||||||||||||||||
| if let Err(err) = run(service, 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 | ||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📌 out of scope — I compared this against The cost is ownership rather than correctness: this fork exists only because upstream has no seam for an outer Worth an upstream issue asking for a layer/service hook on |
||||||||||||||||
| /// ([`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( | ||||||||||||||||
| service: TimingService<EdgeZeroAxumService>, | ||||||||||||||||
| config: AxumDevServerConfig, | ||||||||||||||||
| ) -> std::io::Result<()> { | ||||||||||||||||
| let runtime = RuntimeBuilder::new_multi_thread().enable_all().build()?; | ||||||||||||||||
| runtime.block_on(serve(service, config)) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| async fn serve( | ||||||||||||||||
| service: TimingService<EdgeZeroAxumService>, | ||||||||||||||||
| config: AxumDevServerConfig, | ||||||||||||||||
| ) -> std::io::Result<()> { | ||||||||||||||||
| let listener = TcpListener::bind(config.addr).await?; | ||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⛏ nitpick — The upstream helper this replaces wrapped the bind with
Suggested change
(verified with |
||||||||||||||||
|
|
||||||||||||||||
| 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 | ||||||||||||||||
|
|
||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.