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
611fab8
Add canary routing helpers: parse_rollout_pct, fnv1a_bucket, canary_r…
prk-Jr May 21, 2026
5718303
Add pinned FNV-1a test vector and debug_assert for canary bucket range
prk-Jr May 21, 2026
8ef14ec
Add edgezero_rollout_pct canary routing to Fastly entry point
prk-Jr May 21, 2026
8b8c303
Add edgezero_rollout_pct key to local Viceroy config store
prk-Jr May 21, 2026
76aee4a
Add EdgeZero canary rollout ops runbook
prk-Jr May 21, 2026
a1678dc
Warn when edgezero_rollout_pct key is absent (backward-compat full ro…
prk-Jr May 21, 2026
81d5179
Resolve review findings: canary preconditions, missing-IP log, runboo…
prk-Jr May 21, 2026
9c35fbf
Apply rustfmt formatting
prk-Jr May 21, 2026
951a35c
Merge branch 'feature/edgezero-pr19-spin-adapter' into feature/edgeze…
prk-Jr May 27, 2026
9743550
Demote absent edgezero_rollout_pct log to debug
prk-Jr Jun 13, 2026
7b420cc
Skip canary hashing for degenerate rollout and rename routing predicate
prk-Jr Jun 13, 2026
30002e2
Add unit tests for read_rollout_pct config-store branches
prk-Jr Jun 13, 2026
cbaa4a1
Clarify canary verification logs require debug-level logging
prk-Jr Jun 13, 2026
722c6dc
Extract canary dispatch decision into a testable helper
prk-Jr Jun 13, 2026
08168cb
Merge remote-tracking branch 'origin/feature/edgezero-pr19-spin-adapt…
prk-Jr Jun 13, 2026
3715cf5
Restore std::net::IpAddr import after merge with base
prk-Jr Jun 13, 2026
706b077
Make the canary runbook honest about production route observability
prk-Jr Jun 14, 2026
5f81e80
Make Fastly route-decision debug logs observable for local canary val…
prk-Jr Jun 15, 2026
17925a7
Make absent rollout_pct fail safe to legacy and fix canary docs
prk-Jr Jun 16, 2026
403de35
Align stale canary docs with fail-safe absent-key behavior
prk-Jr Jun 16, 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
65 changes: 64 additions & 1 deletion crates/trusted-server-adapter-fastly/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,52 @@ fn target_label(target: &str) -> &str {
}
}

/// Environment variable that overrides the Fastly logger's maximum level.
///
/// Production ships at `Info`; this override exists for local pre-production
/// validation under Viceroy, where raising the level to `debug` makes the
/// `should_route_to_edgezero` route-decision lines observable. Production Fastly
/// Compute does not surface arbitrary process environment variables, so the
/// override is effectively local-only and the level stays at the safe default
Comment thread
prk-Jr marked this conversation as resolved.
/// when the variable is unset or unparseable.
///
/// Fastly-only by design: this knob is safe here *because* Compute hides runtime
/// env vars. It must not be copied verbatim into the axum/spin/cloudflare
/// adapters, which run where env vars are readable — there it would reintroduce
/// the per-request production debug flood this path deliberately avoids.
const LOG_LEVEL_ENV: &str = "EDGEZERO_LOG_LEVEL";

/// Resolves the logger's maximum level from an optional configured value,
/// falling back to `Info` when it is absent or not a recognised level filter.
fn resolve_max_level(configured: Option<&str>) -> log::LevelFilter {
configured
.and_then(|value| value.trim().parse::<log::LevelFilter>().ok())
.unwrap_or(log::LevelFilter::Info)
}

/// Initialises the Fastly-backed `fern` logger and installs it as the global logger.
///
/// Log records are forwarded to the `tslog` Fastly endpoint and echoed to stdout.
/// Each line is prefixed with an RFC 3339 timestamp, level, and the final segment
/// of the record's target module path.
///
/// The maximum level defaults to `Info`. Setting the [`LOG_LEVEL_ENV`]
/// environment variable (e.g. `EDGEZERO_LOG_LEVEL=debug`) overrides it for local
/// Viceroy validation — the value is used as-is, so `error`/`off` lowers it just
/// as `debug` raises it; see [`resolve_max_level`].
///
/// # Panics
///
/// Panics if the Fastly logger cannot be built or if the global logger has already
/// been set.
pub(crate) fn init_logger() {
let configured = std::env::var(LOG_LEVEL_ENV).ok();

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.

🔧 wrench — EDGEZERO_LOG_LEVEL is not visible to Viceroy guest code.

std::env::var(LOG_LEVEL_ENV) reads the guest WASI environment, but fastly compute serve/Viceroy does not pass arbitrary shell environment variables like EDGEZERO_LOG_LEVEL into the Compute guest. That means the runbook command EDGEZERO_LOG_LEVEL=debug fastly compute serve still initializes at Info, so the route-decision debug! lines remain unavailable in the only documented branch-level validation path.

One minimal way to make the local validation path real is to key off the Fastly-provided local hostname and keep production at the safe default:

Suggested change
let configured = std::env::var(LOG_LEVEL_ENV).ok();
let configured = std::env::var(LOG_LEVEL_ENV).ok().or_else(|| {
(std::env::var("FASTLY_HOSTNAME").as_deref() == Ok("localhost"))
.then_some("debug".to_owned())
});

let max_level = resolve_max_level(configured.as_deref());

let logger = Logger::builder()
.default_endpoint("tslog")
.echo_stdout(true)
.max_level(log::LevelFilter::Info)
.max_level(max_level)
.build()
.expect("should build Logger");

Expand Down Expand Up @@ -77,4 +108,36 @@ mod tests {
"should strip separator when trailing segment is empty"
);
}

#[test]
fn resolve_max_level_defaults_to_info_when_unset() {
assert_eq!(
resolve_max_level(None),
log::LevelFilter::Info,
"should default to Info when the override is unset"
);
}

#[test]
fn resolve_max_level_raises_to_debug_for_viceroy_validation() {
assert_eq!(
resolve_max_level(Some("debug")),
log::LevelFilter::Debug,
"should raise to Debug so route-decision lines are observable locally"
);
assert_eq!(
resolve_max_level(Some(" DEBUG ")),
log::LevelFilter::Debug,
"should accept case-insensitive, surrounding-whitespace values"
);
}

#[test]
fn resolve_max_level_falls_back_to_info_on_unrecognised_value() {
assert_eq!(
resolve_max_level(Some("not-a-level")),
log::LevelFilter::Info,
"should keep the safe Info default for an unparseable override"
);
}
}
Loading
Loading