Repository: candlecorp/wick (language: rust)

Issue:
Tracing inconsistent with jaeger endpoint
**Describe the bug**
The trace spans emitted to a jaeger endpoint are inconsistent and aren't reconcilable. This is an artifact of the move from logging to tracing and requires re-assessing how spans & events are used.




Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index f6c0c0d67..91bbb5a18 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -35,7 +35,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
 dependencies = [
  "cfg-if",
- "getrandom 0.2.9",
  "once_cell",
  "version_check",
 ]
@@ -1611,6 +1610,7 @@ dependencies = [
  "futures",
  "serde",
  "serde_json",
+ "tracing",
  "wick-interface-types",
  "wick-packet",
 ]
@@ -1859,16 +1859,6 @@ dependencies = [
  "version_check",
 ]
 
-[[package]]
-name = "gethostname"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
-dependencies = [
- "libc",
- "winapi",
-]
-
 [[package]]
 name = "getrandom"
 version = "0.1.16"
@@ -2015,31 +2005,6 @@ dependencies = [
  "hashbrown 0.12.3",
 ]
 
-[[package]]
-name = "headers"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
-dependencies = [
- "base64 0.13.1",
- "bitflags 1.3.2",
- "bytes",
- "headers-core",
- "http",
- "httpdate",
- "mime",
- "sha1",
-]
-
-[[package]]
-name = "headers-core"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
-dependencies = [
- "http",
-]
-
 [[package]]
 name = "heapless"
 version = "0.7.16"
@@ -2396,12 +2361,6 @@ dependencies = [
  "cfg-if",
 ]
 
-[[package]]
-name = "integer-encoding"
-version = "3.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
-
 [[package]]
 name = "integration"
 version = "0.1.0"
@@ -2417,7 +2376,7 @@ dependencies = [
  "test-logger",
  "tokio",
  "tokio-stream",
- "tonic",
+ "tonic 0.9.2",
  "tracing",
  "wick-component",
  "wick-component-cli",
@@ -3112,47 +3071,35 @@ dependencies = [
 ]
 
 [[package]]
-name = "opentelemetry-http"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1edc79add46364183ece1a4542592ca593e6421c60807232f5b8f7a31703825d"
-dependencies = [
- "async-trait",
- "bytes",
- "http",
- "hyper",
- "opentelemetry_api",
- "tokio",
-]
-
-[[package]]
-name = "opentelemetry-jaeger"
-version = "0.17.0"
+name = "opentelemetry-otlp"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e785d273968748578931e4dc3b4f5ec86b26e09d9e0d66b55adda7fce742f7a"
+checksum = "d1c928609d087790fc936a1067bdc310ae702bdf3b090c3f281b713622c8bbde"
 dependencies = [
  "async-trait",
  "futures",
- "futures-executor",
- "headers",
+ "futures-util",
  "http",
- "hyper",
- "once_cell",
  "opentelemetry",
- "opentelemetry-http",
- "opentelemetry-semantic-conventions",
+ "opentelemetry-proto",
+ "prost",
  "thiserror",
- "thrift",
  "tokio",
+ "tonic 0.8.3",
 ]
 
 [[package]]
-name = "opentelemetry-semantic-conventions"
-version = "0.10.0"
+name = "opentelemetry-proto"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b02e0230abb0ab6636d18e2ba8fa02903ea63772281340ccac18e0af3ec9eeb"
+checksum = "d61a2f56df5574508dd86aaca016c917489e589ece4141df1b5e349af8d66c28"
 dependencies = [
+ "futures",
+ "futures-util",
  "opentelemetry",
+ "prost",
+ "tonic 0.8.3",
+ "tonic-build 0.8.4",
 ]
 
 [[package]]
@@ -3205,15 +3152,6 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1800ff62dcf34d7de2c7b7c84e3107a19708578441bd998eaddadb1134a4b007"
 
-[[package]]
-name = "ordered-float"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7"
-dependencies = [
- "num-traits",
-]
-
 [[package]]
 name = "ordered-float"
 version = "2.10.0"
@@ -4162,7 +4100,7 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
 dependencies = [
- "ordered-float 2.10.0",
+ "ordered-float",
  "serde",
 ]
 
@@ -4926,28 +4864,6 @@ dependencies = [
  "once_cell",
 ]
 
-[[package]]
-name = "threadpool"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
-dependencies = [
- "num_cpus",
-]
-
-[[package]]
-name = "thrift"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09678c4cdbb4eed72e18b7c2af1329c69825ed16fcbac62d083fc3e2b0590ff0"
-dependencies = [
- "byteorder",
- "integer-encoding",
- "log",
- "ordered-float 1.1.1",
- "threadpool",
-]
-
 [[package]]
 name = "tiberius"
 version = "0.12.2"
@@ -5171,6 +5087,38 @@ dependencies = [
  "winnow",
 ]
 
+[[package]]
+name = "tonic"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum",
+ "base64 0.13.1",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-timeout",
+ "percent-encoding",
+ "pin-project",
+ "prost",
+ "prost-derive",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+ "tracing-futures",
+]
+
 [[package]]
 name = "tonic"
 version = "0.9.2"
@@ -5202,6 +5150,19 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "tonic-build"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4"
+dependencies = [
+ "prettyplease",
+ "proc-macro2",
+ "prost-build",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "tonic-build"
 version = "0.9.2"
@@ -5225,7 +5186,7 @@ dependencies = [
  "prost-types",
  "tokio",
  "tokio-stream",
- "tonic",
+ "tonic 0.9.2",
 ]
 
 [[package]]
@@ -5295,24 +5256,6 @@ dependencies = [
  "syn 2.0.15",
 ]
 
-[[package]]
-name = "tracing-bunyan-formatter"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25a348912d4e90923cb93343691d3be97e3409607363706c400fc935bb032fb0"
-dependencies = [
- "ahash 0.8.3",
- "gethostname",
- "log",
- "serde",
- "serde_json",
- "time 0.3.20",
- "tracing",
- "tracing-core",
- "tracing-log",
- "tracing-subscriber",
-]
-
 [[package]]
 name = "tracing-core"
 version = "0.1.30"
@@ -5358,16 +5301,6 @@ dependencies = [
  "tracing-subscriber",
 ]
 
-[[package]]
-name = "tracing-serde"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
-dependencies = [
- "serde",
- "tracing-core",
-]
-
 [[package]]
 name = "tracing-subscriber"
 version = "0.3.17"
@@ -5377,9 +5310,8 @@ dependencies = [
  "matchers",
  "nu-ansi-term",
  "once_cell",
+ "parking_lot 0.12.1",
  "regex",
- "serde",
- "serde_json",
  "sharded-slab",
  "smallvec",
  "thread_local",
@@ -5387,7 +5319,6 @@ dependencies = [
  "tracing",
  "tracing-core",
  "tracing-log",
- "tracing-serde",
 ]
 
 [[package]]
@@ -6402,7 +6333,7 @@ dependencies = [
  "thiserror",
  "tokio",
  "tokio-stream",
- "tonic",
+ "tonic 0.9.2",
  "tonic-reflection",
  "tracing",
  "uuid",
@@ -6509,6 +6440,7 @@ name = "wick-host"
 version = "0.4.0"
 dependencies = [
  "anyhow",
+ "derive_builder",
  "flow-component",
  "futures",
  "http",
@@ -6604,7 +6536,7 @@ dependencies = [
  "thiserror",
  "tokio",
  "tokio-stream",
- "tonic",
+ "tonic 0.9.2",
  "tracing",
  "wick-logger",
  "wick-packet",
@@ -6617,13 +6549,13 @@ version = "0.2.0"
 dependencies = [
  "ansi_term",
  "opentelemetry",
- "opentelemetry-jaeger",
+ "opentelemetry-otlp",
  "thiserror",
  "time 0.3.20",
  "tokio",
+ "tokio-stream",
  "tracing",
  "tracing-appender",
- "tracing-bunyan-formatter",
  "tracing-opentelemetry",
  "tracing-subscriber",
  "wick-xdg",
@@ -6717,8 +6649,8 @@ dependencies = [
  "thiserror",
  "tokio",
  "tokio-stream",
- "tonic",
- "tonic-build",
+ "tonic 0.9.2",
+ "tonic-build 0.9.2",
  "tracing",
  "uuid",
  "wick-interface-types",
@@ -6735,6 +6667,7 @@ dependencies = [
  "bytes",
  "chrono",
  "cron",
+ "derive_builder",
  "flow-component",
  "flow-graph",
  "flow-graph-interpreter",
diff --git a/Cargo.toml b/Cargo.toml
index 823ca6c18..6e78dab5d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -67,6 +67,7 @@ wasmrs-rx = { version = "0.11" }
 wasmrs-wasmtime = { version = "0.11" }
 #
 # Other
+ansi_term = "0.12"
 anyhow = "1.0"
 async-recursion = "1.0"
 async-stream = "0.3"
@@ -107,7 +108,7 @@ once_cell = "1.8"
 openssl = "0.10"
 option-utils = "0.1"
 opentelemetry = { version = "0.18.0" }
-opentelemetry-jaeger = { version = "0.17.0" }
+opentelemetry-otlp = { version = "0.11.0" }
 parking_lot = "0.12"
 paste = "1.0"
 postgres-openssl = "0.5"
@@ -140,10 +141,12 @@ structmeta = "0.2"
 structured-output = "0.1.1"
 tar = "0.4"
 flate2 = "1"
+tracing-appender = "0.2"
 test_bin = "0.4"
 test-log = "0.2"
 testanything = "0.4"
 thiserror = "1.0"
+time = "0.3"
 tokio = "1"
 tokio-postgres = "0.7"
 tokio-stream = "0.1"
diff --git a/crates/bins/wick/Cargo.toml b/crates/bins/wick/Cargo.toml
index 3c83fe60f..047ef7621 100644
--- a/crates/bins/wick/Cargo.toml
+++ b/crates/bins/wick/Cargo.toml
@@ -27,7 +27,7 @@ wick-oci-utils = { workspace = true }
 wick-package = { workspace = true }
 wick-logger = { workspace = true }
 seeded-random = { workspace = true }
-tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
+tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
 tracing = { workspace = true }
 serde_json = { workspace = true }
 anyhow = { workspace = true }
diff --git a/crates/bins/wick/src/commands/init.rs b/crates/bins/wick/src/commands/init.rs
index 0b8191a96..def5d4c62 100644
--- a/crates/bins/wick/src/commands/init.rs
+++ b/crates/bins/wick/src/commands/init.rs
@@ -17,18 +17,21 @@ pub(crate) struct InitCommand {
 }
 
 #[allow(clippy::field_reassign_with_default)]
-pub(crate) async fn handle(opts: InitCommand, _settings: wick_settings::Settings) -> Result<()> {
-  let files: Vec<(PathBuf, String)> = if opts.component {
-    info!("Initializing wick component project: {}", opts.name);
-    let mut config = ComponentConfiguration::default();
-    config.set_name(opts.name);
-    vec![("component.wick".into(), config.into_v1_yaml()?)]
-  } else {
-    info!("Initializing wick application: {}", opts.name);
-    let mut config = AppConfiguration::default();
-    config.set_name(opts.name);
-    vec![("app.wick".into(), config.into_v1_yaml()?)]
-  };
+pub(crate) async fn handle(opts: InitCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+  let files: Result<Vec<(PathBuf, String)>> = span.in_scope(|| {
+    if opts.component {
+      info!("Initializing wick component project: {}", opts.name);
+      let mut config = ComponentConfiguration::default();
+      config.set_name(opts.name);
+      Ok(vec![("component.wick".into(), config.into_v1_yaml()?)])
+    } else {
+      info!("Initializing wick application: {}", opts.name);
+      let mut config = AppConfiguration::default();
+      config.set_name(opts.name);
+      Ok(vec![("app.wick".into(), config.into_v1_yaml()?)])
+    }
+  });
+  let files = files?;
 
   for (file, _) in &files {
     if file.exists() {
@@ -37,7 +40,8 @@ pub(crate) async fn handle(opts: InitCommand, _settings: wick_settings::Settings
   }
 
   for (file, contents) in files {
-    info!("Writing file: {}", file.display());
+    span.in_scope(|| info!("Writing file: {}", file.display()));
+
     crate::io::write_bytes(file, contents.as_bytes()).await?;
   }
 
diff --git a/crates/bins/wick/src/commands/invoke.rs b/crates/bins/wick/src/commands/invoke.rs
index 2fdb2191c..f79033697 100644
--- a/crates/bins/wick/src/commands/invoke.rs
+++ b/crates/bins/wick/src/commands/invoke.rs
@@ -63,7 +63,7 @@ pub(crate) struct InvokeCommand {
   args: Vec<String>,
 }
 
-pub(crate) async fn handle(opts: InvokeCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(opts: InvokeCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
   // let mut logging = &mut opts.logging;
   // if !(opts.info || logging.trace || logging.debug) {
   //   logging.quiet = true;
@@ -90,9 +90,8 @@ pub(crate) async fn handle(opts: InvokeCommand, _settings: wick_settings::Settin
 
   let component = opts.operation;
 
-  let host_builder = ComponentHostBuilder::from_definition(config);
+  let mut host = ComponentHostBuilder::default().manifest(config).span(span).build()?;
 
-  let mut host = host_builder.build();
   host.start_engine(opts.seed.map(Seed::unsafe_new)).await?;
 
   let signature = host.get_signature()?;
@@ -154,7 +153,7 @@ pub(crate) async fn handle(opts: InvokeCommand, _settings: wick_settings::Settin
     for port in seen_ports {
       packets.push(Ok(Packet::done(port)));
     }
-    debug!(args= ?packets, "wick:invoke");
+    debug!(args= ?packets, "invoke");
     let stream = PacketStream::new(futures::stream::iter(packets));
 
     let stream = host.request(&component, stream, inherent_data).await?;
diff --git a/crates/bins/wick/src/commands/key/gen.rs b/crates/bins/wick/src/commands/key/gen.rs
index 4fdbdc67d..70298e4f5 100644
--- a/crates/bins/wick/src/commands/key/gen.rs
+++ b/crates/bins/wick/src/commands/key/gen.rs
@@ -16,7 +16,8 @@ pub(crate) struct KeyGenCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: KeyGenCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(opts: KeyGenCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+  let _enter = span.enter();
   debug!("Generating {} key", crate::keys::keypair_type_to_string(&opts.keytype));
 
   let kp = KeyPair::new(opts.keytype);
diff --git a/crates/bins/wick/src/commands/key/get.rs b/crates/bins/wick/src/commands/key/get.rs
index 3d631d39a..13beba56b 100644
--- a/crates/bins/wick/src/commands/key/get.rs
+++ b/crates/bins/wick/src/commands/key/get.rs
@@ -16,7 +16,8 @@ pub(crate) struct KeyGetCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: KeyGetCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(opts: KeyGetCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+  let _span = span.enter();
   println!("Reading key: {}\n", opts.path.to_string_lossy());
   let kp = crate::keys::get_key(opts.directory, opts.path).await?;
 
diff --git a/crates/bins/wick/src/commands/key/list.rs b/crates/bins/wick/src/commands/key/list.rs
index a33df8ca5..25390f685 100644
--- a/crates/bins/wick/src/commands/key/list.rs
+++ b/crates/bins/wick/src/commands/key/list.rs
@@ -14,7 +14,12 @@ pub(crate) struct KeyListCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: KeyListCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(
+  opts: KeyListCommand,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
+  let _enter = span.enter();
   debug!("Listing keys");
   let (dir, keys) = get_key_files(opts.directory)?;
   info!("Listing keys in {}", dir.to_string_lossy());
diff --git a/crates/bins/wick/src/commands/list.rs b/crates/bins/wick/src/commands/list.rs
index d2161e982..c78439bfc 100644
--- a/crates/bins/wick/src/commands/list.rs
+++ b/crates/bins/wick/src/commands/list.rs
@@ -20,7 +20,7 @@ pub(crate) struct ListCommand {
   pub(crate) json: bool,
 }
 
-pub(crate) async fn handle(opts: ListCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(opts: ListCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
   let fetch_options = wick_config::config::FetchOptions::new()
     .allow_latest(opts.oci.allow_latest)
     .allow_insecure(&opts.oci.insecure_registries);
@@ -35,10 +35,8 @@ pub(crate) async fn handle(opts: ListCommand, _settings: wick_settings::Settings
   // Disable everything but the mesh
   config.host_mut().set_rpc(None);
 
-  let host_builder = ComponentHostBuilder::from_definition(config);
+  let mut host = ComponentHostBuilder::default().manifest(config).span(span).build()?;
 
-  let mut host = host_builder.build();
-  // host.connect_to_mesh().await?;
   host.start_engine(None).await?;
   let signature = host.get_signature()?;
 
diff --git a/crates/bins/wick/src/commands/query.rs b/crates/bins/wick/src/commands/query.rs
index 2753ad5d7..e48e59805 100644
--- a/crates/bins/wick/src/commands/query.rs
+++ b/crates/bins/wick/src/commands/query.rs
@@ -47,7 +47,7 @@ impl FromStr for MarkupKind {
   }
 }
 
-pub(crate) async fn handle(opts: QueryCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(opts: QueryCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
   let input = if let Some(path) = opts.path {
     match opts.kind {
       None => Transcoder::from_path(&path)?.to_json()?,
@@ -73,6 +73,7 @@ pub(crate) async fn handle(opts: QueryCommand, _settings: wick_settings::Setting
       Some(MarkupKind::Yaml) => Transcoder::new(Format::yaml(&markup)?)?.to_json()?,
     }
   };
+  let _enter = span.enter();
 
   let filter = &opts.query;
 
diff --git a/crates/bins/wick/src/commands/registry/login.rs b/crates/bins/wick/src/commands/registry/login.rs
index 202ba58e3..64ea42074 100644
--- a/crates/bins/wick/src/commands/registry/login.rs
+++ b/crates/bins/wick/src/commands/registry/login.rs
@@ -15,7 +15,12 @@ pub(crate) struct RegistryLoginCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: RegistryLoginCommand, mut settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(
+  opts: RegistryLoginCommand,
+  mut settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
+  let _enter = span.enter();
   let creds = settings.credentials.iter_mut().find(|c| c.scope == opts.registry);
   println!(
     "Use your registry's UI (i.e. open {} in a browser) to retrieve a token and paste it below.\n",
diff --git a/crates/bins/wick/src/commands/registry/pull.rs b/crates/bins/wick/src/commands/registry/pull.rs
index 9591f26b1..4407fb5c4 100644
--- a/crates/bins/wick/src/commands/registry/pull.rs
+++ b/crates/bins/wick/src/commands/registry/pull.rs
@@ -2,6 +2,7 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use tracing::Instrument;
 use wick_oci_utils::OciOptions;
 
 use crate::options::get_auth_for_scope;
@@ -44,7 +45,11 @@ pub(crate) async fn pull(reference: String, oci_opts: OciOptions) -> Result<wick
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: RegistryPullCommand, settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(
+  opts: RegistryPullCommand,
+  settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
   let configured_creds = settings
     .credentials
     .iter()
@@ -64,9 +69,9 @@ pub(crate) async fn handle(opts: RegistryPullCommand, settings: wick_settings::S
     .overwrite(opts.force)
     .base_dir(opts.output);
 
-  debug!(options=?oci_opts, reference= opts.reference, "pulling reference");
+  span.in_scope(|| debug!(options=?oci_opts, reference= opts.reference, "pulling reference"));
 
-  let pull_result = pull(opts.reference, oci_opts).await;
+  let pull_result = pull(opts.reference, oci_opts).instrument(span.clone()).await;
 
   for file in pull_result?.list_files() {
     info!("Pulled file: {}", file.path().display());
diff --git a/crates/bins/wick/src/commands/registry/push.rs b/crates/bins/wick/src/commands/registry/push.rs
index 7feb222fb..e00139d1c 100644
--- a/crates/bins/wick/src/commands/registry/push.rs
+++ b/crates/bins/wick/src/commands/registry/push.rs
@@ -2,6 +2,7 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use tracing::Instrument;
 
 use crate::options::get_auth_for_scope;
 
@@ -20,13 +21,19 @@ pub(crate) struct RegistryPushCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: RegistryPushCommand, settings: wick_settings::Settings) -> Result<()> {
-  debug!("Push artifact");
+pub(crate) async fn handle(
+  opts: RegistryPushCommand,
+  settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
+  span.in_scope(|| debug!("Push artifact"));
 
-  let mut package = wick_package::WickPackage::from_path(&opts.source).await?;
+  let mut package = wick_package::WickPackage::from_path(&opts.source)
+    .instrument(span.clone())
+    .await?;
 
   let Some(registry) =  package.registry() else  {
-      error!("No registry provided in package");
+      span.in_scope(||error!("No registry provided in package"));
       return Err(anyhow!("No registry provided in package"));
   };
 
@@ -46,19 +53,21 @@ pub(crate) async fn handle(opts: RegistryPushCommand, settings: wick_settings::S
 
   let reference = package.registry_reference().unwrap(); // unwrap OK because we know we have a reg from above.
 
-  info!(reference, "pushing artifact");
-  debug!(options=?oci_opts, reference= &reference, "pushing reference");
+  span.in_scope(|| info!(reference, "pushing artifact"));
+  span.in_scope(|| debug!(options=?oci_opts, reference= &reference, "pushing reference"));
 
   let url = package.push(&reference, &oci_opts).await?;
-  info!(%url, "artifact pushed");
+  span.in_scope(|| info!(%url, "artifact pushed"));
+
   if opts.latest {
     let reference = package.tagged_reference("latest").unwrap();
 
-    info!(reference, "pushing artifact");
-    debug!(options=?oci_opts, reference= &reference, "pushing reference");
+    span.in_scope(|| info!(reference, "pushing artifact"));
+
+    span.in_scope(|| debug!(options=?oci_opts, reference= &reference, "pushing reference"));
 
     let url = package.push(&reference, &oci_opts).await?;
-    info!(%url, "artifact pushed");
+    span.in_scope(|| info!(%url, "artifact pushed"));
   }
 
   Ok(())
diff --git a/crates/bins/wick/src/commands/rpc/invoke.rs b/crates/bins/wick/src/commands/rpc/invoke.rs
index 602e7b8fa..cdfc01303 100644
--- a/crates/bins/wick/src/commands/rpc/invoke.rs
+++ b/crates/bins/wick/src/commands/rpc/invoke.rs
@@ -47,7 +47,11 @@ pub(crate) struct RpcInvokeCommand {
   args: Vec<String>,
 }
 
-pub(crate) async fn handle(opts: RpcInvokeCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(
+  opts: RpcInvokeCommand,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
   let mut client = wick_rpc::make_rpc_client(
     format!("http://{}:{}", opts.connection.address, opts.connection.port),
     opts.connection.pem,
@@ -105,10 +109,10 @@ pub(crate) async fn handle(opts: RpcInvokeCommand, _settings: wick_settings::Set
       tx.send(Packet::done(port))?;
     }
 
-    let invocation = Invocation::new(origin, target, inherent_data);
-    trace!("issuing invocation");
+    let invocation = Invocation::new(origin, target, inherent_data, &span);
+    span.in_scope(|| trace!("issuing invocation"));
     let stream = client.invoke(invocation, stream).await?;
-    trace!("server responsed");
+    span.in_scope(|| trace!("server responsed"));
     utils::print_stream_json(stream, &opts.filter, opts.short, opts.raw).await?;
   }
 
diff --git a/crates/bins/wick/src/commands/rpc/list.rs b/crates/bins/wick/src/commands/rpc/list.rs
index eb54dde54..418536f97 100644
--- a/crates/bins/wick/src/commands/rpc/list.rs
+++ b/crates/bins/wick/src/commands/rpc/list.rs
@@ -8,7 +8,12 @@ pub(crate) struct RpcListCommand {
   pub(crate) connection: super::ConnectOptions,
 }
 
-pub(crate) async fn handle(opts: RpcListCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(
+  opts: RpcListCommand,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
+  let _span = span.enter();
   let mut client = wick_rpc::make_rpc_client(
     format!("http://{}:{}", opts.connection.address, opts.connection.port),
     opts.connection.pem,
diff --git a/crates/bins/wick/src/commands/rpc/stats.rs b/crates/bins/wick/src/commands/rpc/stats.rs
index d02ba76a3..fd5617d15 100644
--- a/crates/bins/wick/src/commands/rpc/stats.rs
+++ b/crates/bins/wick/src/commands/rpc/stats.rs
@@ -10,7 +10,12 @@ pub(crate) struct RpcStatsCommand {
   pub(crate) connection: super::ConnectOptions,
 }
 
-pub(crate) async fn handle(opts: RpcStatsCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(
+  opts: RpcStatsCommand,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
+  let _span = span.enter();
   let mut client = wick_rpc::make_rpc_client(
     format!("http://{}:{}", opts.connection.address, opts.connection.port),
     opts.connection.pem,
diff --git a/crates/bins/wick/src/commands/run.rs b/crates/bins/wick/src/commands/run.rs
index 9665bf4dc..712eef9ee 100644
--- a/crates/bins/wick/src/commands/run.rs
+++ b/crates/bins/wick/src/commands/run.rs
@@ -1,5 +1,6 @@
 use anyhow::Result;
 use clap::Args;
+use tracing::Instrument;
 use wick_config::WickConfiguration;
 use wick_host::AppHostBuilder;
 
@@ -22,16 +23,22 @@ pub(crate) struct RunCommand {
   args: Vec<String>,
 }
 
-pub(crate) async fn handle(opts: RunCommand, _settings: wick_settings::Settings) -> Result<()> {
-  debug!(args = ?opts.args, "rest args");
+pub(crate) async fn handle(opts: RunCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
+  span.in_scope(|| trace!(args = ?opts.args, "rest args"));
   let app_config = WickConfiguration::fetch_all(&opts.path, opts.oci.into())
+    .instrument(span.clone())
     .await?
     .try_app_config()?;
 
-  let mut host = AppHostBuilder::from_definition(app_config.clone()).build();
+  let mut host = AppHostBuilder::default()
+    .manifest(app_config.clone())
+    .span(span.clone())
+    .build()?;
+
   host.start(opts.seed)?;
-  debug!("Waiting on triggers to finish or interrupt...");
-  host.wait_for_done().await?;
+  span.in_scope(|| debug!("Waiting on triggers to finish or interrupt..."));
+
+  host.wait_for_done().instrument(span.clone()).await?;
 
   Ok(())
 }
diff --git a/crates/bins/wick/src/commands/serve.rs b/crates/bins/wick/src/commands/serve.rs
index 3ecb934ed..b4149acd5 100644
--- a/crates/bins/wick/src/commands/serve.rs
+++ b/crates/bins/wick/src/commands/serve.rs
@@ -22,7 +22,7 @@ pub(crate) struct ServeCommand {
   pub(crate) location: String,
 }
 
-pub(crate) async fn handle(opts: ServeCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(opts: ServeCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
   let fetch_options = wick_config::config::FetchOptions::new()
     .allow_latest(opts.oci.allow_latest)
     .allow_insecure(&opts.oci.insecure_registries);
@@ -33,9 +33,7 @@ pub(crate) async fn handle(opts: ServeCommand, _settings: wick_settings::Setting
 
   let config = merge_config(&manifest, &opts.oci, Some(opts.cli));
 
-  let host_builder = ComponentHostBuilder::from_definition(config);
-
-  let mut host = host_builder.build();
+  let mut host = ComponentHostBuilder::default().manifest(config).span(span).build()?;
 
   host.start(Some(0)).await?;
   info!("Host started");
diff --git a/crates/bins/wick/src/commands/wasm/inspect.rs b/crates/bins/wick/src/commands/wasm/inspect.rs
index 669fcf6e6..c9b3537df 100644
--- a/crates/bins/wick/src/commands/wasm/inspect.rs
+++ b/crates/bins/wick/src/commands/wasm/inspect.rs
@@ -13,7 +13,12 @@ pub(crate) struct WasmInspectCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: WasmInspectCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(
+  opts: WasmInspectCommand,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
+  let _enter = span.enter();
   let mut file = File::open(opts.module)?;
   let mut buf = Vec::new();
   file.read_to_end(&mut buf)?;
diff --git a/crates/bins/wick/src/commands/wasm/sign.rs b/crates/bins/wick/src/commands/wasm/sign.rs
index 1a82755cd..01dfbd66c 100644
--- a/crates/bins/wick/src/commands/wasm/sign.rs
+++ b/crates/bins/wick/src/commands/wasm/sign.rs
@@ -4,6 +4,7 @@ use std::path::PathBuf;
 
 use anyhow::Result;
 use clap::Args;
+use tracing::Instrument;
 use wick_config::WickConfiguration;
 use wick_wascap::{sign_buffer_with_claims, ClaimsOptions};
 
@@ -36,11 +37,18 @@ pub(crate) struct WasmSignCommand {
 }
 
 #[allow(clippy::unused_async)]
-pub(crate) async fn handle(opts: WasmSignCommand, _settings: wick_settings::Settings) -> Result<()> {
-  debug!("Signing module");
+pub(crate) async fn handle(
+  opts: WasmSignCommand,
+  _settings: wick_settings::Settings,
+  span: tracing::Span,
+) -> Result<()> {
+  span.in_scope(|| {
+    debug!("Signing module");
+    debug!("Reading from {}", opts.interface);
+  });
 
-  debug!("Reading from {}", opts.interface);
   let interface = WickConfiguration::fetch(&opts.interface, wick_config::FetchOptions::default())
+    .instrument(span.clone())
     .await?
     .try_component_config()?;
 
@@ -56,12 +64,6 @@ pub(crate) async fn handle(opts: WasmSignCommand, _settings: wick_settings::Sett
   )
   .await?;
 
-  debug!(
-    "Signing module (orig: {} bytes) with interface : {:#?}",
-    buf.len(),
-    interface.signature()?
-  );
-
   let signed = sign_buffer_with_claims(
     &buf,
     interface.signature()?,
@@ -98,17 +100,17 @@ pub(crate) async fn handle(opts: WasmSignCommand, _settings: wick_settings::Sett
       }
     }
   };
-  debug!("Destination : {}", destination);
+  span.in_scope(|| debug!("Destination : {}", destination));
 
   let mut outfile = File::create(&destination).unwrap();
-  match outfile.write(&signed) {
+  span.in_scope(|| match outfile.write(&signed) {
     Ok(_) => {
       info!("Successfully signed {}", destination,);
     }
     Err(e) => {
       error!("Error signing: {}", e);
     }
-  }
+  });
 
   Ok(())
 }
diff --git a/crates/bins/wick/src/main.rs b/crates/bins/wick/src/main.rs
index 5f48ad5c3..f0fe8d207 100644
--- a/crates/bins/wick/src/main.rs
+++ b/crates/bins/wick/src/main.rs
@@ -99,6 +99,7 @@ mod io;
 mod keys;
 mod oci;
 mod options;
+mod panic;
 
 pub(crate) use options::LoggingOptions;
 
@@ -108,56 +109,92 @@ use self::options::apply_log_settings;
 static BIN_NAME: &str = "wick";
 static BIN_DESC: &str = "wick runtime executable";
 
-#[tokio::main]
-async fn main() -> Result<()> {
-  human_panic::setup_panic!();
+fn main() {
+  let runtime = tokio::runtime::Builder::new_multi_thread()
+    .enable_all()
+    .thread_name("wick")
+    .worker_threads(10)
+    .build()
+    .unwrap();
+  let result = runtime.block_on(async_start());
+  runtime.shutdown_background();
+
+  let code = match result {
+    Ok(_) => 0,
+    Err(e) => {
+      eprintln!("\n{} exited with error: {}", BIN_NAME, e);
+      eprintln!("Run with --info, --debug, or --trace for more information.");
+      1
+    }
+  };
+
+  std::process::exit(code);
+}
+
+async fn async_start() -> Result<()> {
+  #[cfg(debug_assertions)]
+  panic::setup(human_panic::PanicStyle::Debug);
+  #[cfg(not(debug_assertions))]
+  panic::setup(human_panic::PanicStyle::Human);
 
   let mut cli = Cli::parse();
-  let _guard = wick_logger::init(&cli.logging.name(BIN_NAME).into());
-  let settings = wick_settings::Settings::new();
+
+  // Do a preliminary logger init to catch any logs from the local settings.
+  let log_options: wick_logger::LoggingOptions = cli.logging.name(BIN_NAME).into();
+  let settings = wick_logger::with_default(&log_options, Box::new(wick_settings::Settings::new));
   apply_log_settings(&settings, &mut cli.logging);
 
-  let res = match cli.command {
-    CliCommand::Serve(cmd) => commands::serve::handle(cmd, settings).await,
-    CliCommand::List(cmd) => commands::list::handle(cmd, settings).await,
-    CliCommand::Run(cmd) => commands::run::handle(cmd, settings).await,
-    CliCommand::Invoke(cmd) => commands::invoke::handle(cmd, settings).await,
-    CliCommand::Test(cmd) => commands::test::handle(cmd, settings).await,
-    CliCommand::Init(cmd) => commands::init::handle(cmd, settings).await,
+  let mut logger_opts: wick_logger::LoggingOptions = cli.logging.name(BIN_NAME).into();
+  logger_opts.global = true;
+
+  // Initialize the global logger
+  let logger = wick_logger::init(&logger_opts);
+
+  let res = tokio::spawn(async_main(cli, settings)).await?;
+  tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+  match &res {
+    Ok(_) => {
+      info!("Done");
+    }
+    Err(e) => {
+      error!("Error: {}", e);
+    }
+  };
+  logger.flush();
+  res
+}
+
+async fn async_main(cli: Cli, settings: wick_settings::Settings) -> Result<()> {
+  let span = trace_span!(target:"cli","cli");
+
+  match cli.command {
+    CliCommand::Serve(cmd) => commands::serve::handle(cmd, settings, span).await,
+    CliCommand::List(cmd) => commands::list::handle(cmd, settings, span).await,
+    CliCommand::Run(cmd) => commands::run::handle(cmd, settings, span).await,
+    CliCommand::Invoke(cmd) => commands::invoke::handle(cmd, settings, span).await,
+    CliCommand::Test(cmd) => commands::test::handle(cmd, settings, span).await,
+    CliCommand::Init(cmd) => commands::init::handle(cmd, settings, span).await,
     CliCommand::Wasm(cmd) => match cmd {
-      commands::wasm::SubCommands::Sign(cmd) => commands::wasm::sign::handle(cmd, settings).await,
-      commands::wasm::SubCommands::Inspect(cmd) => commands::wasm::inspect::handle(cmd, settings).await,
+      commands::wasm::SubCommands::Sign(cmd) => commands::wasm::sign::handle(cmd, settings, span).await,
+      commands::wasm::SubCommands::Inspect(cmd) => commands::wasm::inspect::handle(cmd, settings, span).await,
     },
     CliCommand::Key(cmd) => match cmd {
-      commands::key::SubCommands::Get(cmd) => commands::key::get::handle(cmd, settings).await,
-      commands::key::SubCommands::Gen(cmd) => commands::key::gen::handle(cmd, settings).await,
-      commands::key::SubCommands::List(cmd) => commands::key::list::handle(cmd, settings).await,
+      commands::key::SubCommands::Get(cmd) => commands::key::get::handle(cmd, settings, span).await,
+      commands::key::SubCommands::Gen(cmd) => commands::key::gen::handle(cmd, settings, span).await,
+      commands::key::SubCommands::List(cmd) => commands::key::list::handle(cmd, settings, span).await,
     },
     CliCommand::Registry(cmd) => match cmd {
-      commands::registry::SubCommands::Push(cmd) => commands::registry::push::handle(cmd, settings).await,
-      commands::registry::SubCommands::Pull(cmd) => commands::registry::pull::handle(cmd, settings).await,
-      commands::registry::SubCommands::Login(cmd) => commands::registry::login::handle(cmd, settings).await,
+      commands::registry::SubCommands::Push(cmd) => commands::registry::push::handle(cmd, settings, span).await,
+      commands::registry::SubCommands::Pull(cmd) => commands::registry::pull::handle(cmd, settings, span).await,
+      commands::registry::SubCommands::Login(cmd) => commands::registry::login::handle(cmd, settings, span).await,
     },
     CliCommand::Rpc(cmd) => match cmd {
-      commands::rpc::SubCommands::Invoke(cmd) => commands::rpc::invoke::handle(cmd, settings).await,
-      commands::rpc::SubCommands::List(cmd) => commands::rpc::list::handle(cmd, settings).await,
-      commands::rpc::SubCommands::Stats(cmd) => commands::rpc::stats::handle(cmd, settings).await,
+      commands::rpc::SubCommands::Invoke(cmd) => commands::rpc::invoke::handle(cmd, settings, span).await,
+      commands::rpc::SubCommands::List(cmd) => commands::rpc::list::handle(cmd, settings, span).await,
+      commands::rpc::SubCommands::Stats(cmd) => commands::rpc::stats::handle(cmd, settings, span).await,
     },
-    CliCommand::Query(cmd) => commands::query::handle(cmd, settings).await,
-  };
-
-  std::process::exit(match res {
-    Ok(_) => {
-      info!("Done");
-      0
-    }
-    Err(e) => {
-      error!("Error: {}", e);
-      eprintln!("\n{} exited with error: {}", BIN_NAME, e);
-      eprintln!("Run with --info, --debug, or --trace for more information.");
-      1
-    }
-  });
+    CliCommand::Query(cmd) => commands::query::handle(cmd, settings, span).await,
+  }
 }
 
 #[cfg(test)]
diff --git a/crates/bins/wick/src/options.rs b/crates/bins/wick/src/options.rs
index edc86d127..6c402883c 100644
--- a/crates/bins/wick/src/options.rs
+++ b/crates/bins/wick/src/options.rs
@@ -21,8 +21,8 @@ pub(crate) struct LoggingOptions {
   pub(crate) trace: bool,
 
   /// The endpoint to send jaeger-format traces.
-  #[clap(long = "jaeger-endpoint", short = 'j', env = "OTEL_EXPORTER_JAEGER_ENDPOINT", action)]
-  pub(crate) jaeger_endpoint: Option<String>,
+  #[clap(long = "otlp", env = "OTLP_ENDPOINT", action)]
+  pub(crate) otlp_endpoint: Option<String>,
 
   /// The application doing the logging.
   #[clap(skip)]
@@ -31,52 +31,61 @@ pub(crate) struct LoggingOptions {
 
 impl LoggingOptions {
   /// Set the name of the application doing the logging.
-  pub(crate) fn name(&self, name: impl AsRef<str>) -> Self {
-    Self {
-      app_name: name.as_ref().to_owned(),
-      ..self.clone()
-    }
+  pub(crate) fn name(&mut self, name: impl AsRef<str>) -> &mut Self {
+    self.app_name = name.as_ref().to_owned();
+    self
   }
 }
 
-impl From<LoggingOptions> for wick_logger::LoggingOptions {
-  fn from(value: LoggingOptions) -> Self {
+impl From<&LoggingOptions> for wick_logger::LoggingOptions {
+  fn from(value: &LoggingOptions) -> Self {
     Self {
       verbose: value.verbose == 1,
       silly: value.verbose == 2,
       level: if value.quiet {
         wick_logger::LogLevel::Quiet
-      } else if value.debug {
-        wick_logger::LogLevel::Debug
       } else if value.trace {
         wick_logger::LogLevel::Trace
+      } else if value.debug {
+        wick_logger::LogLevel::Debug
       } else {
         wick_logger::LogLevel::Info
       },
       log_json: false,
       log_dir: None,
-      jaeger_endpoint: value.jaeger_endpoint,
-      app_name: value.app_name,
+      otlp_endpoint: value.otlp_endpoint.clone(),
+      app_name: value.app_name.clone(),
+      global: false,
     }
   }
 }
 
+impl From<&mut LoggingOptions> for wick_logger::LoggingOptions {
+  fn from(value: &mut LoggingOptions) -> Self {
+    let v: &LoggingOptions = value;
+    v.into()
+  }
+}
+
 pub(crate) fn apply_log_settings(settings: &wick_settings::Settings, options: &mut LoggingOptions) {
-  if settings.logging.level == wick_settings::LogLevel::Debug {
+  if settings.trace.level == wick_settings::LogLevel::Debug {
     options.debug = true;
   }
-  if settings.logging.level == wick_settings::LogLevel::Trace {
+  if settings.trace.level == wick_settings::LogLevel::Trace {
     options.trace = true;
   }
-  if settings.logging.level == wick_settings::LogLevel::Off {
+  if settings.trace.level == wick_settings::LogLevel::Off {
     options.quiet = true;
   }
-  if settings.logging.modifier == wick_settings::LogModifier::Verbose {
+  if settings.trace.modifier == wick_settings::LogModifier::Verbose {
     options.verbose = 1;
   }
-  if settings.logging.modifier == wick_settings::LogModifier::Silly {
+  if settings.trace.modifier == wick_settings::LogModifier::Silly {
     options.verbose = 2;
   }
+  if options.otlp_endpoint.is_none() {
+    options.otlp_endpoint = settings.trace.otlp.clone();
+  }
 }
 
 pub(crate) fn get_auth_for_scope(
diff --git a/crates/bins/wick/src/panic.rs b/crates/bins/wick/src/panic.rs
new file mode 100644
index 000000000..a50d96190
--- /dev/null
+++ b/crates/bins/wick/src/panic.rs
@@ -0,0 +1,19 @@
+#[allow(clippy::expect_used)]
+pub(super) fn setup(style: human_panic::PanicStyle) {
+  match style {
+    human_panic::PanicStyle::Debug => {}
+    human_panic::PanicStyle::Human => {
+      let meta = human_panic::Metadata {
+        name: crate::BIN_NAME.into(),
+        version: env!("CARGO_PKG_VERSION").into(),
+        authors: "wick authors".into(),
+        homepage: "https://github.com/candlecorp/wick".into(),
+      };
+
+      std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo| {
+        let file_path = human_panic::handle_dump(&meta, info);
+        human_panic::print_msg(file_path, &meta).expect("human-panic: printing error message to console failed");
+      }));
+    }
+  }
+}
diff --git a/crates/components/wick-http-client/src/component.rs b/crates/components/wick-http-client/src/component.rs
index 24a8c7846..70808f379 100644
--- a/crates/components/wick-http-client/src/component.rs
+++ b/crates/components/wick-http-client/src/component.rs
@@ -104,7 +104,7 @@ async fn handle(
         break 'outer;
       }
     };
-    trace!(inputs = ?inputs, "http:inputs");
+
     if inputs.values().all(|v| v.is_done()) {
       break 'outer;
     }
@@ -114,7 +114,7 @@ async fn handle(
         let v = v
           .deserialize_generic()
           .map_err(|e| {
-            warn!(port=%k,error=%e, "http:input:deserialize");
+            invocation.trace(|| warn!(port=%k,error=%e, "http:input:deserialize"));
             e
           })
           .unwrap_or(Value::Null);
@@ -127,7 +127,6 @@ async fn handle(
       }
     }
     let inputs = Value::Object(inputs);
-    trace!(inputs = ?inputs, "http:inputs");
 
     let body = match opdef.body() {
       Some(body) => match body.render(&inputs) {
@@ -150,7 +149,7 @@ async fn handle(
       }
     };
     let request_url = baseurl.join(&append_path).unwrap();
-    trace!(url= %request_url, "http:sending request");
+    invocation.trace(|| trace!(url= %request_url, "initiating request"));
 
     let request = match opdef.method() {
       HttpMethod::Get => Request::new(Method::GET, request_url),
@@ -182,8 +181,6 @@ async fn handle(
     let (client, request) = request_builder.build_split();
     let request = request.unwrap();
 
-    trace!(?request,body=?request.body().map(|b|b.as_bytes()), "http:client:request");
-
     let response = match client.execute(request).await {
       Ok(r) => r,
       Err(e) => {
@@ -312,7 +309,7 @@ fn output_task(
             return;
           }
         };
-        trace!(json = %json, "http:response");
+
         let _ = tx.send(Packet::encode("body", json));
         let _ = tx.send(Packet::done("body"));
       }
diff --git a/crates/components/wick-sqlx/src/component.rs b/crates/components/wick-sqlx/src/component.rs
index 61281258d..c3f170023 100644
--- a/crates/components/wick-sqlx/src/component.rs
+++ b/crates/components/wick-sqlx/src/component.rs
@@ -273,7 +273,7 @@ async fn exec(
   args: Vec<(TypeSignature, Packet)>,
   stmt: Arc<(String, String)>,
 ) -> Result<(), Error> {
-  debug!(stmt = %stmt.0, "executing  query");
+  debug!(stmt = %stmt.0, "executing query");
 
   let mut bound_args = Vec::new();
   for arg in def.arguments() {
diff --git a/crates/wick/flow-component/Cargo.toml b/crates/wick/flow-component/Cargo.toml
index 80247ddd2..04417767b 100644
--- a/crates/wick/flow-component/Cargo.toml
+++ b/crates/wick/flow-component/Cargo.toml
@@ -18,5 +18,6 @@ serde_json = { workspace = true }
 futures = { workspace = true }
 anyhow = { workspace = true }
 serde = { workspace = true, features = ["derive"] }
+tracing = { workspace = true }
 
 [dev-dependencies]
diff --git a/crates/wick/flow-component/src/traits.rs b/crates/wick/flow-component/src/traits.rs
index e9c5e4ea2..9618c48b5 100644
--- a/crates/wick/flow-component/src/traits.rs
+++ b/crates/wick/flow-component/src/traits.rs
@@ -34,6 +34,7 @@ pub trait Operation {
   type Config: std::fmt::Debug + DeserializeOwned + Serialize + Send + Sync + 'static;
   fn handle(
     &self,
+    invocation: Invocation,
     payload: PacketStream,
     context: Context<Self::Config>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>>;
@@ -51,13 +52,14 @@ pub type RuntimeCallback = dyn Fn(
     PacketStream,
     Option<InherentData>,
     Option<OperationConfig>,
+    &tracing::Span,
   ) -> BoxFuture<'static, Result<PacketStream, ComponentError>>
   + Send
   + Sync;
 
 #[must_use]
 pub fn panic_callback() -> Arc<RuntimeCallback> {
-  Arc::new(|_, _, _, _, _| {
+  Arc::new(|_, _, _, _, _, _| {
     Box::pin(async move {
       panic!("Panic callback invoked. This should never happen outside of tests.");
     })
diff --git a/crates/wick/flow-graph-interpreter/.vscode/launch.json b/crates/wick/flow-graph-interpreter/.vscode/launch.json
index da569b12c..b62e127f5 100644
--- a/crates/wick/flow-graph-interpreter/.vscode/launch.json
+++ b/crates/wick/flow-graph-interpreter/.vscode/launch.json
@@ -17,7 +17,6 @@
         ]
       },
       "env": {
-        "OTEL_EXPORTER_JAEGER_ENDPOINT": "nas.glhf.lan:6831"
       },
       "args": [],
       "cwd": "${workspaceFolder}"
diff --git a/crates/wick/flow-graph-interpreter/src/graph.rs b/crates/wick/flow-graph-interpreter/src/graph.rs
index 8228a3586..b8e1750d8 100644
--- a/crates/wick/flow-graph-interpreter/src/graph.rs
+++ b/crates/wick/flow-graph-interpreter/src/graph.rs
@@ -104,7 +104,7 @@ fn register_operation(
           .map(|component| component.add_input(to.port()));
 
         if to_port.is_none() {
-          println!("Missing downstream: instance {:?}", to);
+          error!("Missing downstream: instance {:?}", to);
           return Err(flow_graph::error::Error::MissingDownstream(
             to.instance().id().unwrap().to_owned(),
           ));
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter.rs b/crates/wick/flow-graph-interpreter/src/interpreter.rs
index 9027ad4c5..39876b036 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter.rs
@@ -12,6 +12,7 @@ use std::time::Duration;
 use flow_component::{Component, RuntimeCallback};
 use parking_lot::Mutex;
 use seeded_random::{Random, Seed};
+use tracing::{trace_span, Span};
 use tracing_futures::Instrument;
 use wick_interface_types::ComponentSignature;
 use wick_packet::{Entity, Invocation, OperationConfig, PacketStream};
@@ -44,6 +45,7 @@ pub struct Interpreter {
   namespace: Option<String>,
   callback: Arc<RuntimeCallback>,
   exposed_ops: HashMap<String, SharedHandler>, // A map from op name to the ns of the handler that exposes it.
+  span: Span,
 }
 
 impl std::fmt::Debug for Interpreter {
@@ -59,15 +61,17 @@ impl std::fmt::Debug for Interpreter {
 }
 
 impl Interpreter {
-  #[instrument(name="interpreter-init", skip_all, fields(namespace = %namespace.as_ref().map_or("n/a", String::as_str)))]
   pub fn new(
     seed: Option<Seed>,
     network: Network,
     namespace: Option<String>,
     components: Option<HandlerMap>,
     callback: Arc<RuntimeCallback>,
+    parent_span: &Span,
   ) -> Result<Self, Error> {
-    debug!("init");
+    let span = trace_span!("interpreter");
+    span.follows_from(parent_span);
+    let _guard = span.enter();
     let rng = seed.map_or_else(Random::new, Random::from_seed);
     let mut handlers = components.unwrap_or_default();
     debug!(handlers = ?handlers.keys(), "initializing interpreter");
@@ -111,6 +115,7 @@ impl Interpreter {
       operations = ?handled_opts,
       "operations handled by this interpreter"
     );
+    drop(_guard);
 
     Ok(Self {
       rng,
@@ -123,6 +128,7 @@ impl Interpreter {
       namespace,
       exposed_ops,
       callback,
+      span,
     })
   }
 
@@ -134,25 +140,26 @@ impl Interpreter {
     let cb_container = Arc::new(Mutex::new(None));
 
     let inner_cb = cb_container.clone();
-    let local_first_callback: Arc<RuntimeCallback> = Arc::new(move |compref, op, stream, inherent, config| {
+    let local_first_callback: Arc<RuntimeCallback> = Arc::new(move |compref, op, stream, inherent, config, span| {
       let internal_components = internal_components.clone();
       let inner_cb = inner_cb.clone();
       let outside_callback = outside_callback.clone();
       let self_component = self_component.clone();
+      let span = span.clone();
       Box::pin(async move {
-        trace!(op, %compref, "invoke:component reference");
+        span.in_scope(|| trace!(op, %compref, "invoke:component reference"));
         if compref.get_target_id() == NS_SELF {
-          trace!(op, %compref, "handling component invocation for self");
+          span.in_scope(|| trace!(op, %compref, "handling component invocation for self"));
           let cb = inner_cb.lock().clone().unwrap();
-          let invocation = compref.to_invocation(&op, inherent);
+          let invocation = compref.to_invocation(&op, inherent, &span);
           self_component.handle(invocation, stream, config, cb).await
         } else if let Some(handler) = internal_components.get(compref.get_target_id()) {
-          trace!(op, %compref, "handling component invocation internal to this interpreter");
+          span.in_scope(|| trace!(op, %compref, "handling component invocation internal to this interpreter"));
           let cb = inner_cb.lock().clone().unwrap();
-          let invocation = compref.to_invocation(&op, inherent);
+          let invocation = compref.to_invocation(&op, inherent, &span);
           handler.component().handle(invocation, stream, config, cb).await
         } else {
-          outside_callback(compref, op, stream, inherent, config).await
+          outside_callback(compref, op, stream, inherent, config, &span).await
         }
       })
     });
@@ -174,6 +181,7 @@ impl Interpreter {
           self.program.operations().iter().map(|s| s.name().to_owned()).collect(),
         )
       })?;
+    let span = self.span.clone();
 
     let executor = SchematicExecutor::new(op.clone(), dispatcher.clone());
     Ok(
@@ -186,7 +194,7 @@ impl Interpreter {
           self.self_component.clone(),
           self.get_callback(),
         )
-        .instrument(tracing::span::Span::current())
+        .instrument(span)
         .await?,
     )
   }
@@ -204,14 +212,14 @@ impl Interpreter {
       }
       hosted
     };
-    let span = trace_span!("invoke");
+    let span = trace_span!("invoke", tx_id = %invocation.tx_id);
     let cb = self.get_callback();
-    trace!(?invocation, "invoking");
+    span.in_scope(|| trace!(?invocation, "invoking"));
     let stream = match &invocation.target {
       Entity::Operation(ns, name) => {
         if ns == NS_SELF || ns == Entity::LOCAL || Some(ns) == self.namespace.as_ref() {
           if let Some(component) = self.exposed_ops.get(name) {
-            trace!(entity=%invocation.target, "invoke::exposed::operation");
+            span.in_scope(|| trace!(entity=%invocation.target, "invoke::exposed::operation"));
             return Ok(
               component
                 .handle(invocation, stream, config, cb)
@@ -220,10 +228,10 @@ impl Interpreter {
                 .map_err(ExecutionError::ComponentError)?,
             );
           }
-          trace!(entity=%invocation.target, "invoke::composite::operation");
+          span.in_scope(|| trace!(entity=%invocation.target, "invoke::composite::operation"));
           self.invoke_operation(invocation, stream).instrument(span).await?
         } else {
-          trace!(entity=%invocation.target, "invoke::instance::operation");
+          span.in_scope(|| trace!(entity=%invocation.target, "invoke::instance::operation"));
           self
             .components
             .get(ns)
@@ -253,21 +261,22 @@ impl Interpreter {
     self.event_loop.start(options.unwrap_or_default(), observer).await;
   }
 
-  #[instrument(skip(self))]
   pub async fn shutdown(&self) -> Result<(), Error> {
     let shutdown = self.event_loop.shutdown().await;
     if let Err(error) = &shutdown {
-      error!(%error,"error shutting down event loop");
+      self.span.in_scope(|| error!(%error,"error shutting down event loop"));
     };
     for (ns, components) in self.components.inner() {
-      debug!(namespace = %ns, "shutting down collection");
+      self
+        .span
+        .in_scope(|| debug!(namespace = %ns, "shutting down collection"));
       if let Err(error) = components
         .component
         .shutdown()
         .await
         .map_err(|e| Error::ComponentShutdown(e.to_string()))
       {
-        warn!(%error,"error during shutdown");
+        self.span.in_scope(|| warn!(%error,"error during shutdown"));
       };
     }
 
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components.rs
index 8d3e53398..5d6d404d5 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components.rs
@@ -51,7 +51,7 @@ impl HandlerMap {
   }
 
   pub(crate) fn add_core(&mut self, network: &Network) -> Result<(), InterpreterError> {
-    self.add(NamespaceHandler::new(NS_CORE, Box::new(CoreCollection::new(network))))
+    self.add(NamespaceHandler::new(NS_CORE, Box::new(CoreCollection::new(network)?)))
   }
 
   #[must_use]
@@ -74,7 +74,6 @@ impl HandlerMap {
   }
 
   pub fn add(&mut self, component: NamespaceHandler) -> Result<(), InterpreterError> {
-    trace!(namespace = %component.namespace, "adding component");
     if self.components.contains_key(&component.namespace) {
       return Err(InterpreterError::DuplicateNamespace(component.namespace));
     }
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection.rs
index f10c9064a..1858a9e95 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection.rs
@@ -21,8 +21,44 @@ pub(crate) struct CoreCollection {
   switch: switch::Op,
 }
 
+#[derive(Debug, thiserror::Error)]
+pub struct OpInitError {
+  error: ComponentError,
+  kind: DynamicOperation,
+}
+
+impl OpInitError {
+  fn new(error: ComponentError, kind: DynamicOperation) -> Self {
+    Self { error, kind }
+  }
+}
+
+impl std::fmt::Display for OpInitError {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    f.write_str("dynamic ")?;
+    self.kind.fmt(f)?;
+    f.write_str(" component failed to initialize: ")?;
+    self.error.fmt(f)
+  }
+}
+
+#[derive(Debug)]
+enum DynamicOperation {
+  Merge,
+  Switch,
+}
+
+impl std::fmt::Display for DynamicOperation {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    match self {
+      DynamicOperation::Merge => f.write_str("merge"),
+      DynamicOperation::Switch => f.write_str("switch"),
+    }
+  }
+}
+
 impl CoreCollection {
-  pub(crate) fn new(graph: &Network) -> Self {
+  pub(crate) fn new(graph: &Network) -> Result<Self, OpInitError> {
     let mut this = Self {
       signature: ComponentSignature::new(NS_CORE).version("0.0.0"),
       pluck: pluck::Op::new(),
@@ -41,49 +77,49 @@ impl CoreCollection {
           continue;
         }
 
-        match operation.cref().name() {
-          merge::Op::ID => {
-            let config = match merge::Op::decode_config(operation.data().clone()) {
-              Ok(c) => c,
-              Err(e) => {
-                error!("Configuration for dynamic merge component invalid: {}", e);
-                panic!()
-              }
-            };
-            let id = dyn_component_id(merge::Op::ID, schematic.name(), operation.id());
-            debug!(%id,"adding dynamic type signature for merge component");
-            let (op_sig, output_sig) = merge::Op::gen_signature(id, config);
-
-            this.signature.types.push(TypeDefinition::Struct(output_sig));
-            this.signature.operations.push(op_sig);
-          }
-          switch::Op::ID => {
-            let config = match switch::Op::decode_config(operation.data().clone()) {
-              Ok(c) => c,
-              Err(e) => {
-                error!("Configuration for dynamic switch component invalid: {}", e);
-                panic!();
-              }
-            };
-            let op_sig = this.switch.gen_signature(graph, config);
-
-            this.signature.operations.push(op_sig);
-          }
-          _ => {}
+        let result = match operation.cref().name() {
+          merge::Op::ID => match merge::Op::decode_config(operation.data().clone()) {
+            Ok(config) => {
+              let id = dyn_component_id(merge::Op::ID, schematic.name(), operation.id());
+              debug!(%id,"adding dynamic type signature for merge component");
+              let (op_sig, output_sig) = merge::Op::gen_signature(id, config);
+
+              this.signature.types.push(TypeDefinition::Struct(output_sig));
+              this.signature.operations.push(op_sig);
+              Ok(())
+            }
+            Err(e) => Err(OpInitError::new(e, DynamicOperation::Merge)),
+          },
+          switch::Op::ID => match switch::Op::decode_config(operation.data().clone()) {
+            Ok(config) => {
+              let op_sig = this.switch.gen_signature(graph, config);
+
+              this.signature.operations.push(op_sig);
+              Ok(())
+            }
+            Err(e) => Err(OpInitError::new(e, DynamicOperation::Switch)),
+          },
+          _ => Ok(()),
+        };
+        if let Err(error) = result {
+          error!(%error, "Failed to add dynamic signature");
+          return Err(error);
         }
       }
     }
 
     trace!(?this.signature, "core signature");
 
-    this
+    Ok(this)
   }
 }
 
 macro_rules! core_op {
-  ($type:ty, $name:expr, $stream:ident, $callback:expr, $data:ident, $seed:ident) => {{
+  ($type:ty, $inv:expr, $name:expr, $stream:ident, $callback:expr, $data:ident, $seed:ident) => {{
     let config = <$type>::decode_config($data)?;
-    $name.handle($stream, Context::new(config, $seed, $callback)).await
+    $name
+      .handle($inv, $stream, Context::new(config, $seed, $callback))
+      .await
   }};
 }
 
@@ -95,15 +131,15 @@ impl Component for CoreCollection {
     data: Option<wick_packet::OperationConfig>,
     callback: std::sync::Arc<RuntimeCallback>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
-    trace!(target = %invocation.target, namespace = NS_CORE);
+    invocation.trace(|| trace!(target = %invocation.target, namespace = NS_CORE));
     let seed = invocation.seed();
-    debug!("data: {:?}", data);
+
     let task = async move {
       match invocation.target.operation_id() {
-        sender::Op::ID => core_op! {sender::Op, self.sender, stream, callback, data, seed},
-        pluck::Op::ID => core_op! {pluck::Op, self.pluck, stream, callback, data, seed},
-        merge::Op::ID => core_op! {merge::Op, self.merge, stream, callback, data, seed},
-        switch::Op::ID => core_op! {switch::Op, self.switch, stream, callback, data, seed},
+        sender::Op::ID => core_op! {sender::Op, invocation, self.sender, stream, callback, data, seed},
+        pluck::Op::ID => core_op! {pluck::Op, invocation, self.pluck, stream, callback, data, seed},
+        merge::Op::ID => core_op! {merge::Op, invocation, self.merge, stream, callback, data, seed},
+        switch::Op::ID => core_op! {switch::Op, invocation, self.switch, stream, callback, data, seed},
         _ => {
           panic!("Core operation {} not handled.", invocation.target.operation_id());
         }
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/merge.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/merge.rs
index 1d0ec27b0..0671a6c06 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/merge.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/merge.rs
@@ -4,7 +4,7 @@ use flow_component::{ComponentError, Context, Operation};
 use futures::FutureExt;
 use wasmrs_rx::Observer;
 use wick_interface_types::{Field, OperationSignature, StructSignature, TypeSignature};
-use wick_packet::{Packet, PacketStream, StreamMap};
+use wick_packet::{Invocation, Packet, PacketStream, StreamMap};
 
 use crate::BoxFuture;
 pub(crate) struct Op {}
@@ -48,6 +48,7 @@ impl Operation for Op {
   type Config = Config;
   fn handle(
     &self,
+    _invocation: Invocation,
     stream: PacketStream,
     context: Context<Self::Config>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
@@ -100,7 +101,7 @@ mod test {
   use flow_component::panic_callback;
   use serde_json::json;
   use tokio_stream::StreamExt;
-  use wick_packet::packet_stream;
+  use wick_packet::{packet_stream, Entity};
 
   use super::*;
 
@@ -114,8 +115,9 @@ mod test {
     let config = serde_json::json!({ "inputs": inputs });
     let config = Op::decode_config(Some(config.try_into()?))?;
     let stream = packet_stream!(("input_a", "hello"), ("input_b", 1000));
+    let inv = Invocation::test(file!(), Entity::test("noop"), None)?;
     let mut packets = op
-      .handle(stream, Context::new(config, None, panic_callback()))
+      .handle(inv, stream, Context::new(config, None, panic_callback()))
       .await?
       .collect::<Vec<_>>()
       .await;
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/pluck.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/pluck.rs
index b840fe761..926063901 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/pluck.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/pluck.rs
@@ -1,7 +1,7 @@
 use flow_component::{ComponentError, Context, Operation};
 use futures::{FutureExt, StreamExt};
 use wick_interface_types::{operation, OperationSignature};
-use wick_packet::{Packet, PacketStream, StreamMap};
+use wick_packet::{Invocation, Packet, PacketStream, StreamMap};
 
 use crate::BoxFuture;
 pub(crate) struct Op {
@@ -39,6 +39,7 @@ impl Operation for Op {
   type Config = Config;
   fn handle(
     &self,
+    _invocation: Invocation,
     stream: PacketStream,
     context: Context<Self::Config>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
@@ -89,7 +90,7 @@ impl Operation for Op {
 mod test {
   use anyhow::Result;
   use flow_component::panic_callback;
-  use wick_packet::packet_stream;
+  use wick_packet::{packet_stream, Entity};
 
   use super::*;
 
@@ -107,8 +108,9 @@ mod test {
         "dont_pluck_this": "unused",
       })
     ));
+    let inv = Invocation::test(file!(), Entity::test("noop"), None)?;
     let mut packets = op
-      .handle(stream, Context::new(config, None, panic_callback()))
+      .handle(inv, stream, Context::new(config, None, panic_callback()))
       .await?
       .collect::<Vec<_>>()
       .await;
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/sender.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/sender.rs
index 66d49d0e5..41b78d1b8 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/sender.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/sender.rs
@@ -1,7 +1,7 @@
 use flow_component::{ComponentError, Context, Operation};
 use serde_json::Value;
 use wick_interface_types::{operation, OperationSignature};
-use wick_packet::{packet_stream, PacketStream};
+use wick_packet::{packet_stream, Invocation, PacketStream};
 
 use crate::BoxFuture;
 #[derive()]
@@ -33,6 +33,7 @@ impl Operation for Op {
   type Config = SenderData;
   fn handle(
     &self,
+    _invocation: Invocation,
     _payload: PacketStream,
     context: Context<Self::Config>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/switch.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/switch.rs
index 9d5c292dc..1d8aaa830 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/switch.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components/core_collection/switch.rs
@@ -4,9 +4,10 @@ use std::sync::Arc;
 use flow_component::{ComponentError, Context, Operation};
 use futures::{FutureExt, StreamExt};
 use parking_lot::Mutex;
+use serde_json::Value;
 use wasmrs_rx::Observer;
 use wick_interface_types::{Field, OperationSignature, TypeSignature};
-use wick_packet::{ComponentReference, Entity, PacketSender, PacketStream};
+use wick_packet::{ComponentReference, Entity, Invocation, PacketSender, PacketStream};
 
 use crate::constants::NS_CORE;
 use crate::graph::types::Network;
@@ -34,7 +35,7 @@ pub(crate) struct Config {
 
 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 pub(crate) struct SwitchCase {
-  case: String,
+  case: Value,
   #[serde(rename = "do")]
   case_do: String,
 }
@@ -48,7 +49,10 @@ fn gen_signature(id: &str, graph: &Network, config: Config) -> OperationSignatur
       "Invalid switch configuration: default operation '{}' not found.",
       default_op_path
     );
-    panic!();
+    panic!(
+      "Invalid switch configuration: default operation '{}' not found.",
+      default_op_path
+    );
   });
   let case_ops = config
     .cases
@@ -105,6 +109,7 @@ impl Operation for Op {
   #[allow(clippy::too_many_lines)]
   fn handle(
     &self,
+    invocation: Invocation,
     mut stream: PacketStream,
     context: Context<Self::Config>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
@@ -164,11 +169,12 @@ impl Operation for Op {
             &default
           },
           |case| {
-            trace!(case = case.case, op = case.case_do, "switch:case");
+            trace!(case = %case.case, op = case.case_do, "switch:case");
             &case.case_do
           },
         );
         trace!(operation = op, "core:switch:route");
+        let span = invocation.span.clone();
         let stream = router.entry(op.clone()).or_insert_with(|| {
           let target = path_to_entity(op).unwrap(); // unwrap ok because the config has been pre-validated.
           let op_id = target.operation_id().to_owned();
@@ -177,7 +183,7 @@ impl Operation for Op {
           let tx = tx.clone();
           let callback = callback.clone();
           tokio::spawn(async move {
-            match callback(link, op_id, route_rx, None, None).await {
+            match callback(link, op_id, route_rx, None, None, &span).await {
               Ok(mut call_rx) => {
                 while let Some(packet) = call_rx.next().await {
                   let _ = tx.send_result(packet);
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components/internal_collection.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components/internal_collection.rs
index e36c61576..0abeaa86e 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components/internal_collection.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components/internal_collection.rs
@@ -35,7 +35,7 @@ impl Component for InternalCollection {
     _config: Option<wick_packet::OperationConfig>,
     _callback: std::sync::Arc<RuntimeCallback>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
-    trace!(target = %invocation.target, id=%invocation.id,namespace = NS_INTERNAL);
+    invocation.trace(|| trace!(target = %invocation.target, id=%invocation.id,namespace = NS_INTERNAL));
     let op = invocation.target.operation_id().to_owned();
 
     let is_oneshot = op == SCHEMATIC_INPUT || op == INTERNAL_ID_INHERENT;
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components/null_component.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components/null_component.rs
index caa6d015e..a421a6e8e 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components/null_component.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components/null_component.rs
@@ -32,7 +32,7 @@ impl Component for NullComponent {
     _data: Option<wick_packet::OperationConfig>,
     _callback: std::sync::Arc<RuntimeCallback>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
-    trace!(target = %invocation.target, namespace = NS_CORE);
+    invocation.trace(|| trace!(target = %invocation.target, namespace = NS_CORE));
     async move { Ok(PacketStream::empty()) }.boxed()
   }
 
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/components/schematic_component.rs b/crates/wick/flow-graph-interpreter/src/interpreter/components/schematic_component.rs
index 7eebc9016..8fa3d4d23 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/components/schematic_component.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/components/schematic_component.rs
@@ -75,7 +75,7 @@ impl Component for SchematicComponent {
     _config: Option<wick_packet::OperationConfig>,
     callback: Arc<RuntimeCallback>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
-    trace!(target = %invocation.target, namespace = NS_SELF);
+    invocation.trace(|| trace!(target = %invocation.target, namespace = NS_SELF));
 
     let operation = invocation.target.operation_id().to_owned();
     let fut = self
@@ -93,7 +93,6 @@ impl Component for SchematicComponent {
         )
       })
       .ok_or_else(|| Error::SchematicNotFound(operation.clone()));
-    trace!(%operation, "operation");
 
     Box::pin(async move {
       let span = trace_span!("ns_self", name = %operation);
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/error.rs b/crates/wick/flow-graph-interpreter/src/interpreter/error.rs
index 36712aca0..d29d0a6ca 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/error.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/error.rs
@@ -1,5 +1,6 @@
 use wick_packet::Entity;
 
+use super::components::core_collection::OpInitError;
 use super::executor::error::ExecutionError;
 use super::program::validator::error::{OperationInvalid, ValidationError};
 
@@ -21,6 +22,8 @@ pub enum Error {
   Shutdown(String),
   #[error("Namespace '{0}' already exists, can not overwrite")]
   DuplicateNamespace(String),
+  #[error(transparent)]
+  OperationInit(#[from] OpInitError),
 }
 
 #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/event_loop.rs b/crates/wick/flow-graph-interpreter/src/interpreter/event_loop.rs
index 2763eb1e7..51e78fa91 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/event_loop.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/event_loop.rs
@@ -4,6 +4,7 @@ use std::time::Duration;
 
 use parking_lot::Mutex;
 use tokio::task::JoinHandle;
+use tracing::Span;
 use tracing_futures::Instrument;
 
 use super::channel::{Event, EventKind, InterpreterChannel, InterpreterDispatchChannel};
@@ -17,6 +18,7 @@ pub(crate) struct EventLoop {
   channel: Option<InterpreterChannel>,
   dispatcher: InterpreterDispatchChannel,
   task: Mutex<Option<JoinHandle<Result<(), ExecutionError>>>>,
+  span: Span,
 }
 
 impl EventLoop {
@@ -25,19 +27,20 @@ impl EventLoop {
 
   pub(super) fn new(channel: InterpreterChannel) -> Self {
     let dispatcher = channel.dispatcher();
+    let span = debug_span!("event_loop");
+    span.follows_from(Span::current());
     Self {
       channel: Some(channel),
       dispatcher,
       task: Mutex::new(None),
+      span,
     }
   }
 
   pub(super) async fn start(&mut self, options: InterpreterOptions, observer: Option<Box<dyn Observer + Send + Sync>>) {
-    trace!("starting event loop");
     let channel = self.channel.take().unwrap();
 
-    let span = trace_span!("event_loop");
-
+    let span = self.span.clone();
     let handle = tokio::spawn(async move { event_loop(channel, options, observer).instrument(span).await });
     let mut lock = self.task.lock();
     lock.replace(handle);
@@ -49,21 +52,21 @@ impl EventLoop {
   }
 
   pub(super) async fn shutdown(&self) -> Result<(), Error> {
-    trace!("shutting down event loop");
+    self.span.in_scope(|| trace!("shutting down event loop"));
     let task = self.steal_task();
     match task {
       Some(task) => {
         self.dispatcher.dispatch_close(None).await;
-        trace!("yielding to loop");
+
         let timeout = std::time::Duration::from_secs(2);
         let result = tokio::time::timeout(timeout, task).await;
         result
           .map_err(|e| Error::Shutdown(e.to_string()))?
           .map_err(|e| Error::Shutdown(e.to_string()))??;
-        debug!("event loop closed");
+        self.span.in_scope(|| debug!("event loop closed"));
       }
       None => {
-        warn!("shutdown called but no task running");
+        self.span.in_scope(|| warn!("shutdown called but no task running"));
       }
     }
 
@@ -73,7 +76,7 @@ impl EventLoop {
 
 impl Drop for EventLoop {
   fn drop(&mut self) {
-    trace!("dropping event loop");
+    self.span.in_scope(|| trace!("dropping event loop"));
     let lock = self.task.lock();
     if let Some(task) = &*lock {
       task.abort();
@@ -92,7 +95,8 @@ async fn event_loop(
   options: InterpreterOptions,
   observer: Option<Box<dyn Observer + Send + Sync>>,
 ) -> Result<(), ExecutionError> {
-  debug!(?options, "started");
+  let span = trace_span!("event_loop");
+  span.in_scope(|| debug!(?options, "started"));
   let mut state = State::new(channel.dispatcher());
 
   let mut num: usize = 0;
@@ -107,8 +111,8 @@ async fn event_loop(
           observer.on_event(num, &event);
         }
 
-        let span = trace_span!("event", otel.name = event.name(), i = num, %tx_id);
-        debug!(event = ?event, tx_id = ?tx_id, "iteration");
+        let evt_span = trace_span!(parent:&span,"event", otel.name = event.name(), i = num, %tx_id);
+        evt_span.in_scope(|| debug!(event = ?event, tx_id = ?tx_id, "iteration"));
         let name = event.name().to_owned();
 
         let result = match event.kind {
@@ -116,13 +120,13 @@ async fn event_loop(
             error!("invocation not supported");
             panic!("invocation not supported")
           }
-          EventKind::CallComplete(data) => state.handle_call_complete(tx_id, data).instrument(span).await,
-          EventKind::PortData(data) => state.handle_port_data(tx_id, data).instrument(span).await,
-          EventKind::TransactionDone => state.handle_transaction_done(tx_id).instrument(span).await,
+          EventKind::CallComplete(data) => state.handle_call_complete(tx_id, data).instrument(evt_span).await,
+          EventKind::PortData(data) => state.handle_port_data(tx_id, data).instrument(evt_span).await,
+          EventKind::TransactionDone => state.handle_transaction_done(tx_id).instrument(evt_span).await,
           EventKind::TransactionStart(transaction) => {
             state
               .handle_transaction_start(*transaction, &options)
-              .instrument(span)
+              .instrument(evt_span)
               .await
           }
           EventKind::Ping(ping) => {
@@ -131,11 +135,11 @@ async fn event_loop(
           }
           EventKind::Close(error) => match error {
             Some(error) => {
-              error!(%error,"stopped with error");
+              evt_span.in_scope(|| error!(%error,"stopped with error"));
               break Err(error);
             }
             None => {
-              debug!("stopping");
+              evt_span.in_scope(|| debug!("stopping"));
               break Ok(());
             }
           },
@@ -143,13 +147,13 @@ async fn event_loop(
 
         if let Err(e) = result {
           if options.error_on_missing && matches!(e, ExecutionError::MissingTx(_)) {
-            error!(event = %name, tx_id = ?tx_id, response_error = %e, "iteration:end");
+            span.in_scope(|| error!(event = %name, tx_id = ?tx_id, response_error = %e, "iteration:end"));
             channel.dispatcher().dispatch_close(Some(e)).await;
           } else {
-            warn!(event = %name, tx_id = ?tx_id, response_error = %e, "iteration:end");
+            span.in_scope(|| warn!(event = %name, tx_id = ?tx_id, response_error = %e, "iteration:end"));
           }
         } else {
-          trace!(event = %name, tx_id = ?tx_id, "iteration:end");
+          span.in_scope(|| trace!(event = %name, tx_id = ?tx_id, "iteration:end"));
         }
 
         if let Some(observer) = &observer {
@@ -158,18 +162,18 @@ async fn event_loop(
         num += 1;
       }
       Ok(None) => {
-        trace!("done");
+        span.in_scope(|| trace!("done"));
         break Ok(());
       }
       Err(_) => {
         if let Err(error) = state.check_hung(options.error_on_hung).await {
-          error!(%error,"Error checking hung transactions");
+          span.in_scope(|| error!(%error,"Error checking hung transactions"));
           channel.dispatcher().dispatch_close(Some(error)).await;
         };
       }
     }
   };
-  trace!("stopped");
+  span.in_scope(|| trace!("stopped"));
   if let Some(observer) = &observer {
     observer.on_close();
   }
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/event_loop/state.rs b/crates/wick/flow-graph-interpreter/src/interpreter/event_loop/state.rs
index 2b67830dd..70f25157a 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/event_loop/state.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/event_loop/state.rs
@@ -123,9 +123,6 @@ impl State {
     tx.stats
       .mark(format!("input:{}:{}:ready", port.node_index(), port.port_index()));
 
-    let span = trace_span!("input", port = port_name, component = instance.id());
-    let _guard = span.enter();
-
     let is_schematic_output = port.node_index() == graph.output().index();
 
     if is_schematic_output {
@@ -158,9 +155,6 @@ impl State {
       "handling port output"
     );
 
-    let span = trace_span!("output", port = port_name, component = instance.id());
-    let _guard = span.enter();
-
     tx.stats
       .mark(format!("output:{}:{}:ready", port.node_index(), port.port_index()));
 
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/executor.rs b/crates/wick/flow-graph-interpreter/src/interpreter/executor.rs
index 5628576f4..df3a9b763 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/executor.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/executor.rs
@@ -44,7 +44,7 @@ impl SchematicExecutor {
     self_component: Arc<dyn Component + Send + Sync>,
     callback: Arc<RuntimeCallback>,
   ) -> Result<PacketStream> {
-    debug!(schematic = self.name(), ?invocation,);
+    invocation.trace(|| debug!(schematic = self.name(), ?invocation,));
 
     let seed = invocation.seed().map_or(seed, Seed::unsafe_new);
 
@@ -58,7 +58,6 @@ impl SchematicExecutor {
       callback,
       seed,
     );
-    trace!(tx_id = %transaction.id(), "invoking schematic");
     let stream = transaction.take_stream().unwrap();
     self.channel.dispatch_start(Box::new(transaction)).await;
     Ok(stream)
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/executor/transaction.rs b/crates/wick/flow-graph-interpreter/src/interpreter/executor/transaction.rs
index a90338e30..31791d234 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/executor/transaction.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/executor/transaction.rs
@@ -53,7 +53,6 @@ impl std::fmt::Debug for Transaction {
 
 #[allow(clippy::too_many_arguments)]
 impl Transaction {
-  #[instrument(skip_all, name = "tx_new")]
   pub(crate) fn new(
     schematic: Arc<Schematic>,
     mut invocation: Invocation,
@@ -133,11 +132,11 @@ impl Transaction {
     outputs_done
   }
 
-  #[instrument(parent = &self.span, skip_all, name = "tx_start", fields(id = %self.id()))]
   pub(crate) async fn start(&mut self, options: &InterpreterOptions) -> Result<()> {
     self.stats.mark("start");
     self.stats.start("execution");
-    trace!("starting transaction");
+    self.span.in_scope(|| trace!("starting transaction"));
+
     self.start_time = Instant::now();
 
     for instance in self.instances.iter() {
@@ -237,7 +236,6 @@ impl Transaction {
 
   pub(crate) async fn emit_output_message(&self, packets: Vec<Packet>) -> Result<()> {
     for packet in packets {
-      trace!(?packet, "emitting tx output");
       self.output.send(packet).map_err(|_e| ExecutionError::ChannelSend)?;
     }
 
@@ -302,8 +300,6 @@ impl Transaction {
   }
 
   pub(crate) async fn handle_schematic_output(&self) -> Result<()> {
-    debug!("schematic output");
-
     self.emit_output_message(self.take_tx_output()?).await?;
 
     Ok(())
diff --git a/crates/wick/flow-graph-interpreter/src/interpreter/program/validator/error.rs b/crates/wick/flow-graph-interpreter/src/interpreter/program/validator/error.rs
index 39e4019aa..c6b858dff 100644
--- a/crates/wick/flow-graph-interpreter/src/interpreter/program/validator/error.rs
+++ b/crates/wick/flow-graph-interpreter/src/interpreter/program/validator/error.rs
@@ -54,7 +54,7 @@ impl std::fmt::Display for OperationInvalid {
   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     write!(
       f,
-      "Schematic '{}' could not be validated: {}",
+      "Flow '{}' could not be validated: {}",
       self.schematic,
       self.errors.iter().map(|e| e.to_string()).collect::<Vec<_>>().join(", ")
     )
diff --git a/crates/wick/flow-graph/Cargo.toml b/crates/wick/flow-graph/Cargo.toml
index c7b240964..054696088 100644
--- a/crates/wick/flow-graph/Cargo.toml
+++ b/crates/wick/flow-graph/Cargo.toml
@@ -9,7 +9,6 @@ description = "A representation of a graph data structure for flow-based program
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 [dependencies]
 thiserror = { workspace = true }
-tracing = { workspace = true }
 
 [dev-dependencies]
 serde_json = { workspace = true }
@@ -20,3 +19,4 @@ tokio = { workspace = true, features = ["macros", "rt"] }
 wick-logger = { workspace = true }
 test-logger = { workspace = true }
 anyhow = { workspace = true }
+tracing = { workspace = true }
diff --git a/crates/wick/flow-graph/src/lib.rs b/crates/wick/flow-graph/src/lib.rs
index 6ce179c4f..899fc6d9e 100644
--- a/crates/wick/flow-graph/src/lib.rs
+++ b/crates/wick/flow-graph/src/lib.rs
@@ -111,6 +111,3 @@ pub use schematic::{
 };
 
 pub(crate) mod util;
-
-#[macro_use]
-extern crate tracing;
diff --git a/crates/wick/flow-graph/src/network.rs b/crates/wick/flow-graph/src/network.rs
index 454f56e89..3d993149c 100644
--- a/crates/wick/flow-graph/src/network.rs
+++ b/crates/wick/flow-graph/src/network.rs
@@ -42,7 +42,7 @@ mod test {
   {
   }
 
-  #[test_logger::test]
+  #[test]
   fn test_sync_send() -> Result<()> {
     sync_send::<Network<Option<()>>>();
     Ok(())
diff --git a/crates/wick/flow-graph/src/node.rs b/crates/wick/flow-graph/src/node.rs
index b8a5c6270..c5750f298 100644
--- a/crates/wick/flow-graph/src/node.rs
+++ b/crates/wick/flow-graph/src/node.rs
@@ -164,7 +164,7 @@ where
   }
 
   pub fn add_input<T: AsStr>(&mut self, port: T) -> PortReference {
-    let port_ref = match self.kind {
+    match self.kind {
       NodeKind::Output(_) => {
         self.outputs.add(&port, self.index);
         self.inputs.add(&port, self.index)
@@ -174,15 +174,7 @@ where
         panic!("You can not manually add inputs to {} nodes", self.kind);
       }
       NodeKind::External(_) => self.inputs.add(&port, self.index),
-    };
-    trace!(
-      index = self.index,
-      port = port.as_ref(),
-      node = self.id(),
-      r#ref = %port_ref,
-      "added input port"
-    );
-    port_ref
+    }
   }
 
   pub(crate) fn connect_input(&mut self, port: PortIndex, connection: ConnectionIndex) -> Result<(), Error> {
@@ -217,7 +209,7 @@ where
   }
 
   pub fn add_output<T: AsStr>(&mut self, port: T) -> PortReference {
-    let port_ref = match self.kind {
+    match self.kind {
       NodeKind::Input(_) | NodeKind::Inherent(_) => {
         self.inputs.add(&port, self.index);
         self.outputs.add(&port, self.index)
@@ -227,16 +219,7 @@ where
         panic!("You can not manually add outputs to {} nodes", self.kind);
       }
       NodeKind::External(_) => self.outputs.add(&port, self.index),
-    };
-    trace!(
-      index = self.index,
-      port = port.as_ref(),
-      node = self.id(),
-      r#ref = %port_ref,
-      "added output port"
-    );
-
-    port_ref
+    }
   }
 
   pub(crate) fn connect_output(&mut self, port: PortIndex, connection: ConnectionIndex) -> Result<(), Error> {
diff --git a/crates/wick/flow-graph/src/schematic.rs b/crates/wick/flow-graph/src/schematic.rs
index 295fe1702..012639484 100644
--- a/crates/wick/flow-graph/src/schematic.rs
+++ b/crates/wick/flow-graph/src/schematic.rs
@@ -111,14 +111,12 @@ where
   }
 
   pub fn add_input<T: AsStr>(&mut self, port: T) -> PortReference {
-    trace!(port=%port.as_ref(), "add schematic input");
     let input = self.get_mut(self.input).unwrap();
     input.add_input(&port);
     input.add_output(port)
   }
 
   pub fn add_output<T: AsStr>(&mut self, port: T) -> PortReference {
-    trace!(port=%port.as_ref(), "add schematic output");
     let output = self.get_mut(self.output).unwrap();
     output.add_output(&port);
     output.add_input(port)
@@ -204,13 +202,11 @@ where
 
   pub fn add_external<T: AsStr>(&mut self, name: T, reference: NodeReference, data: Option<DATA>) -> NodeIndex {
     let name = name.as_ref().to_owned();
-    trace!(%name, %reference, "adding external node");
     self.add_node(name, NodeKind::External(reference), data)
   }
 
   pub fn add_inherent<T: AsStr>(&mut self, name: T, reference: NodeReference, data: Option<DATA>) -> NodeIndex {
     let name = name.as_ref().to_owned();
-    trace!(%name, %reference, "adding inherent node");
     self.add_node(name, NodeKind::Inherent(reference), data)
   }
 
@@ -218,13 +214,9 @@ where
     let existing_index = self.node_map.get(&name);
 
     match existing_index {
-      Some(index) => {
-        trace!(%name, index, %kind, "retrieving existing node");
-        *index
-      }
+      Some(index) => *index,
       None => {
         let index = self.nodes.len();
-        trace!(%name, index, %kind, "added node");
         let node = Node::new(&name, index, kind, data);
         self.nodes.push(node);
         self.node_map.insert(name, index);
@@ -234,7 +226,6 @@ where
   }
 
   pub fn connect(&mut self, from: PortReference, to: PortReference, data: Option<DATA>) -> Result<(), Error> {
-    trace!(%from, %to, "connecting");
     let connection_index = self.connections.len();
     let downstream_node = &mut self.nodes[to.node_index];
     downstream_node.connect_input(to.port_index, connection_index)?;
diff --git a/crates/wick/wick-asset-reference/src/asset_reference.rs b/crates/wick/wick-asset-reference/src/asset_reference.rs
index 52d45dc96..95c84b01c 100644
--- a/crates/wick/wick-asset-reference/src/asset_reference.rs
+++ b/crates/wick/wick-asset-reference/src/asset_reference.rs
@@ -291,7 +291,6 @@ impl Asset for AssetReference {
     let path = self.path();
     let location = self.location().to_owned();
 
-    debug!(path = ?path, "fetching asset");
     let cache_location = self.cache_location.clone();
     Box::pin(async move {
       let path = path.map_err(|e| assets::Error::Parse(e.to_string()))?;
@@ -301,6 +300,7 @@ impl Asset for AssetReference {
       }
       let file = tokio::fs::File::open(&pb).await;
       if file.is_ok() {
+        debug!(path = ?path, "fetching local asset");
         let mut file = tokio::fs::File::open(&path)
           .await
           .map_err(|err| assets::Error::FileOpen(path.clone(), err.to_string()))?;
@@ -308,6 +308,7 @@ impl Asset for AssetReference {
         file.read_to_end(&mut bytes).await?;
         Ok(bytes)
       } else {
+        debug!(path = ?path, "fetching remote asset");
         let (cache_loc, bytes) = retrieve_remote(&location, options)
           .await
           .map_err(|err| assets::Error::FileOpen(path.clone(), err.to_string()))?;
diff --git a/crates/wick/wick-component-cli/src/cli.rs b/crates/wick/wick-component-cli/src/cli.rs
index e41a516c7..31d712a0c 100644
--- a/crates/wick/wick-component-cli/src/cli.rs
+++ b/crates/wick/wick-component-cli/src/cli.rs
@@ -18,7 +18,7 @@ pub struct Mesh(); // Dummy struct if "mesh" feature is not enabled
 #[cfg(feature = "reflection")]
 pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!("../../wick-rpc/src/generated/descriptors.bin");
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 #[must_use]
 /// Metadata for the running server.
 pub struct ServerState {
@@ -30,6 +30,7 @@ pub struct ServerState {
 }
 
 /// Struct that holds control methods and metadata for a running service.
+#[derive(Clone)]
 pub struct ServerControl {
   /// The address of the RPC server.
   pub addr: SocketAddr,
diff --git a/crates/wick/wick-component-wasm/src/component.rs b/crates/wick/wick-component-wasm/src/component.rs
index 8ff4fb4a2..4de4d0005 100644
--- a/crates/wick/wick-component-wasm/src/component.rs
+++ b/crates/wick/wick-component-wasm/src/component.rs
@@ -2,6 +2,7 @@ use std::collections::HashMap;
 use std::sync::Arc;
 
 use flow_component::{BoxFuture, Component, ComponentError, RuntimeCallback};
+use tracing::Span;
 use wasmrs_host::WasiParams;
 use wick_config::config::components::Permissions;
 use wick_packet::{Entity, Invocation, OperationConfig, PacketStream};
@@ -23,16 +24,13 @@ pub struct WasmComponent {
 }
 
 fn permissions_to_wasi_params(perms: &Permissions) -> WasiParams {
-  debug!(params=?perms, "Collection permissions");
   let preopened_dirs = perms.dirs().values().cloned().collect();
   let map_dirs = perms.dirs().clone().into_iter().collect();
-  let params = WasiParams {
+  WasiParams {
     map_dirs,
     preopened_dirs,
     ..Default::default()
-  };
-  debug!(?params, "WASI configuration");
-  params
+  }
 }
 
 impl WasmComponent {
@@ -43,22 +41,23 @@ impl WasmComponent {
     additional_config: Option<Permissions>,
     callback: Option<Arc<RuntimeCallback>>,
     provided: HashMap<String, String>,
+    span: Span,
   ) -> Result<Self, Error> {
-    let mut builder = WasmHostBuilder::new();
+    let mut builder = WasmHostBuilder::new(span.clone());
 
     let name = module.name().clone().unwrap_or_else(|| module.id().clone());
 
     // If we're passed a "wasi" field in the config map...
     if let Some(config) = config {
-      debug!(id=%name, config=?config, "wasi enabled");
+      span.in_scope(|| debug!(id=%name, config=?config, "wasi enabled"));
       builder = builder.wasi_params(permissions_to_wasi_params(&config));
     } else if let Some(opts) = additional_config {
       // if we were passed wasi params, use those.
-      debug!(id=%name, config=?opts, "wasi enabled");
+      span.in_scope(|| debug!(id=%name, config=?opts, "wasi enabled"));
 
       builder = builder.wasi_params(permissions_to_wasi_params(&opts));
     } else {
-      debug!(id = %name, "wasi disabled");
+      span.in_scope(|| debug!(id = %name, "wasi disabled"));
     }
     builder = builder.max_threads(max_threads);
 
@@ -81,7 +80,7 @@ impl Component for WasmComponent {
     data: Option<OperationConfig>,
     _callback: Arc<RuntimeCallback>,
   ) -> BoxFuture<Result<PacketStream, ComponentError>> {
-    trace!(target = %invocation.target, config=?data, "wasm invoke");
+    invocation.trace(|| trace!(target = %invocation.target, config=?data, "wasm invoke"));
 
     let outputs = self.host.call(invocation, stream, data);
 
@@ -89,18 +88,7 @@ impl Component for WasmComponent {
   }
 
   fn list(&self) -> &wick_interface_types::ComponentSignature {
-    let signature = self.host.get_operations();
-
-    trace!(
-      "WASM:COMPONENTS:[{}]",
-      signature
-        .operations
-        .iter()
-        .map(|op| op.name.clone())
-        .collect::<Vec<_>>()
-        .join(",")
-    );
-    signature
+    self.host.get_operations()
   }
 }
 
@@ -130,10 +118,11 @@ mod tests {
       2,
       None,
       None,
-      Some(Arc::new(|_, _, _, _, _| {
+      Some(Arc::new(|_, _, _, _, _, _| {
         Box::pin(async { Ok(packet_stream!(("test", "test"))) })
       })),
       Default::default(),
+      Span::current(),
     )
     .await?;
     Ok(c)
diff --git a/crates/wick/wick-component-wasm/src/wasm_host.rs b/crates/wick/wick-component-wasm/src/wasm_host.rs
index e339ed7d9..827f153d6 100644
--- a/crates/wick/wick-component-wasm/src/wasm_host.rs
+++ b/crates/wick/wick-component-wasm/src/wasm_host.rs
@@ -6,6 +6,7 @@ use std::time::Instant;
 use flow_component::RuntimeCallback;
 use parking_lot::Mutex;
 use serde::{Deserialize, Serialize};
+use tracing::Span;
 use wasmrs::{GenericError, OperationHandler, RSocket, RawPayload};
 use wasmrs_codec::messagepack::serialize;
 use wasmrs_host::{CallContext, Host, WasiParams};
@@ -24,6 +25,7 @@ pub struct WasmHostBuilder {
   callback: Option<Arc<RuntimeCallback>>,
   min_threads: usize,
   max_threads: usize,
+  span: Span,
 }
 
 impl std::fmt::Debug for WasmHostBuilder {
@@ -35,10 +37,11 @@ impl std::fmt::Debug for WasmHostBuilder {
 }
 
 impl WasmHostBuilder {
-  pub fn new() -> Self {
+  pub fn new(span: Span) -> Self {
     Self {
       wasi_params: None,
       callback: None,
+      span,
       min_threads: 1,
       max_threads: 1,
     }
@@ -68,6 +71,7 @@ impl WasmHostBuilder {
       &self.callback,
       self.min_threads,
       self.max_threads,
+      self.span,
     )
   }
 
@@ -82,12 +86,6 @@ impl WasmHostBuilder {
   }
 }
 
-impl Default for WasmHostBuilder {
-  fn default() -> Self {
-    Self::new()
-  }
-}
-
 #[derive()]
 pub struct WasmHost {
   #[allow(unused)]
@@ -95,6 +93,7 @@ pub struct WasmHost {
   claims: Claims<CollectionClaims>,
   ctx: Arc<CallContext>,
   _rng: seeded_random::Random,
+  span: Span,
 }
 
 impl std::fmt::Debug for WasmHost {
@@ -110,8 +109,10 @@ impl WasmHost {
     callback: &Option<Arc<RuntimeCallback>>,
     _min_threads: usize,
     _max_threads: usize,
+    span: Span,
   ) -> Result<Self> {
     let jwt = &module.token.jwt;
+    let _span = span.enter();
 
     wick_wascap::validate_token::<CollectionClaims>(jwt).map_err(|e| Error::ClaimsInvalid(e.to_string()))?;
 
@@ -137,11 +138,13 @@ impl WasmHost {
     }
     let ctx = host.new_context(128 * 1024, 128 * 1024).unwrap();
 
+    drop(_span);
     Ok(Self {
       claims: module.claims().clone(),
       host: Arc::new(Mutex::new(host)),
       ctx: Arc::new(ctx),
       _rng: seeded_random::Random::new(),
+      span,
     })
   }
 
@@ -152,6 +155,7 @@ impl WasmHost {
     stream: PacketStream,
     config: Option<wick_packet::OperationConfig>,
   ) -> Result<PacketStream> {
+    let _span = self.span.enter();
     let component_name = invocation.target.operation_id();
     debug!(component = component_name, "wasm invoke");
     let seed = invocation.seed();
@@ -175,27 +179,34 @@ impl WasmHost {
   }
 
   pub async fn setup(&self, provided: SetupPayload) -> Result<()> {
-    debug!("wasm setup");
-
     let ctx = self.ctx.clone();
-    let index = ctx
-      .get_export("wick", "__setup")
-      .map_err(|_| crate::Error::SetupOperation)?;
-    let metadata = wasmrs::Metadata::new(index);
-    let data = serialize(&provided).unwrap();
-    let payload = RawPayload::new(metadata.encode(), data.into());
-    match ctx.request_response(payload).await {
-      Ok(_) => {
-        debug!("setup finished");
-      }
-      Err(e) => {
-        error!("setup failed: {}", e);
-        return Err(Error::Setup(e));
+    let payload = self.span.in_scope(|| {
+      debug!("wasm setup");
+
+      let index = ctx
+        .get_export("wick", "__setup")
+        .map_err(|_| crate::Error::SetupOperation)?;
+      let metadata = wasmrs::Metadata::new(index);
+      let data = serialize(&provided).unwrap();
+      Ok::<_, WasmCollectionError>(RawPayload::new(metadata.encode(), data.into()))
+    })?;
+
+    let result = ctx.request_response(payload).await;
+
+    self.span.in_scope(|| {
+      match result {
+        Ok(_) => {
+          debug!("setup finished");
+        }
+        Err(e) => {
+          error!("setup failed: {}", e);
+          return Err(Error::Setup(e));
+        }
       }
-    }
 
-    trace!("wasm setup finished");
-    Ok(())
+      trace!("wasm setup finished");
+      Ok(())
+    })
   }
 
   pub fn get_operations(&self) -> &ComponentSignature {
@@ -215,35 +226,37 @@ fn make_host_callback(
   rt_cb: &Arc<RuntimeCallback>,
 ) -> OperationHandler<wasmrs::IncomingStream, wasmrs::OutgoingStream> {
   let cb = rt_cb.clone();
+  let span = tracing::info_span!("wasmrs callback");
   let func = move |mut incoming: wasmrs::IncomingStream| -> std::result::Result<wasmrs::OutgoingStream, GenericError> {
     use tokio_stream::StreamExt;
     let (tx, rx) = FluxChannel::new_parts();
     let cb = cb.clone();
+    let span = span.clone();
     tokio::spawn(async move {
       let first = incoming.next().await;
       let meta = if let Some(Ok(first)) = first {
         match wasmrs_codec::messagepack::deserialize::<InvocationPayload>(&first.data) {
           Ok(p) => p,
           Err(e) => {
-            error!("bad component ref invocation: {}", e);
+            span.in_scope(|| error!("bad component ref invocation: {}", e));
             let _ = tx.error(wick_packet::Error::component_error(e.to_string()));
             return;
           }
         }
       } else {
-        error!("bad component ref invocation: no payload");
+        span.in_scope(|| error!("bad component ref invocation: no payload"));
         let _ = tx.error(wick_packet::Error::component_error("no payload"));
         return;
       };
       let stream = from_wasmrs(incoming);
-      match cb(meta.reference, meta.operation, stream, None, None).await {
+      match cb(meta.reference, meta.operation, stream, None, None, &span).await {
         Ok(mut response) => {
           while let Some(p) = response.next().await {
             let _ = tx.send_result(p);
           }
         }
         Err(e) => {
-          error!("bad component ref invocation: {}", e);
+          span.in_scope(|| error!("bad component ref invocation: {}", e));
           let _ = tx.error(wick_packet::Error::component_error(e.to_string()));
         }
       }
diff --git a/crates/wick/wick-config/docs/v1.html b/crates/wick/wick-config/docs/v1.html
index e53ac1acc..4ca26c38f 100644
--- a/crates/wick/wick-config/docs/v1.html
+++ b/crates/wick/wick-config/docs/v1.html
@@ -982,7 +982,7 @@ <h3 class=field-name>
   <a name="fallback">fallback</a>
 </h3>
 
-<span class="description field-description">Fallback path (relative to `path`) for files to serve in case of a 404. Useful for SPA's. if path: /www and fallback: index.html, then a 404 will serve /www/index.html</span>
+<span class="description field-description">Fallback path (relative to volume `resource`) for files to serve in case of a 404. Useful for SPA's. if volume resource is: /www and fallback: index.html, then a 404 will serve /www/index.html</span>
 
 
 <ul class="field-notes">
diff --git a/crates/wick/wick-config/json-schema/manifest.json b/crates/wick/wick-config/json-schema/manifest.json
index eaff6e65f..40e97e733 100644
--- a/crates/wick/wick-config/json-schema/manifest.json
+++ b/crates/wick/wick-config/json-schema/manifest.json
@@ -970,7 +970,7 @@
             "type": "string"
           },
           "fallback": {
-            "description": "Fallback path (relative to &#x60;path&#x60;) for files to serve in case of a 404. Useful for SPA&#x27;s. if path: /www and fallback: index.html, then a 404 will serve /www/index.html",
+            "description": "Fallback path (relative to volume &#x60;resource&#x60;) for files to serve in case of a 404. Useful for SPA&#x27;s. if volume resource is: /www and fallback: index.html, then a 404 will serve /www/index.html",
             "required": false,
             "type": "string"
           }
diff --git a/crates/wick/wick-config/json-schema/v1/manifest.json b/crates/wick/wick-config/json-schema/v1/manifest.json
index 9a4f8d101..2318cf145 100644
--- a/crates/wick/wick-config/json-schema/v1/manifest.json
+++ b/crates/wick/wick-config/json-schema/v1/manifest.json
@@ -613,7 +613,7 @@
           "type": "string"
         },
         "fallback": {
-          "description": "Fallback path (relative to &#x60;path&#x60;) for files to serve in case of a 404. Useful for SPA&#x27;s. if path: /www and fallback: index.html, then a 404 will serve /www/index.html",
+          "description": "Fallback path (relative to volume &#x60;resource&#x60;) for files to serve in case of a 404. Useful for SPA&#x27;s. if volume resource is: /www and fallback: index.html, then a 404 will serve /www/index.html",
           "required": false,
 
           "type": "string"
diff --git a/crates/wick/wick-config/src/config.rs b/crates/wick/wick-config/src/config.rs
index 4481e1488..88beaf8b9 100644
--- a/crates/wick/wick-config/src/config.rs
+++ b/crates/wick/wick-config/src/config.rs
@@ -106,14 +106,12 @@ impl WickConfiguration {
   }
 
   pub fn load_from_bytes(bytes: &[u8], source: &Option<PathBuf>) -> Result<Self, Error> {
-    debug!(source=?source,"Trying to parse manifest bytes as yaml");
     let string = &String::from_utf8(bytes.to_vec()).map_err(|_| Error::Utf8)?;
 
     resolve_configuration(string, source)
   }
 
   pub fn from_yaml(src: &str, source: &Option<PathBuf>) -> Result<Self, Error> {
-    debug!(source=?source,"Trying to parse manifest as yaml");
     resolve_configuration(src, source)
   }
 
@@ -222,7 +220,6 @@ impl WickConfiguration {
 fn resolve_configuration(src: &str, source: &Option<PathBuf>) -> Result<WickConfiguration, Error> {
   let raw: serde_yaml::Value = from_yaml(src, source)?;
 
-  debug!("Yaml parsed successfully");
   let raw_version = raw.get("format");
   let raw_kind = raw.get("kind");
   let version = if raw_kind.is_some() {
@@ -234,7 +231,7 @@ fn resolve_configuration(src: &str, source: &Option<PathBuf>) -> Result<WickConf
       .unwrap_or_else(|| -> i64 { raw_version.as_str().and_then(|s| s.parse::<i64>().ok()).unwrap_or(-1) })
   };
   // re-parse the yaml into the correct version from string again for location info.
-  let manifest = match version {
+  match version {
     0 => {
       let host_config = serde_yaml::from_str::<v0::HostManifest>(src)
         .map_err(|e| Error::YamlError(source.clone(), e.to_string(), e.location()))?;
@@ -251,10 +248,7 @@ fn resolve_configuration(src: &str, source: &Option<PathBuf>) -> Result<WickConf
     }
     -1 => Err(Error::NoFormat),
     _ => Err(Error::VersionError(version.to_string())),
-  };
-
-  debug!("Manifest: {:?}", manifest);
-  manifest
+  }
 }
 
 pub(crate) fn make_resolver(
diff --git a/crates/wick/wick-config/src/import_cache.rs b/crates/wick/wick-config/src/import_cache.rs
index 37d0adff0..f8557c7bd 100644
--- a/crates/wick/wick-config/src/import_cache.rs
+++ b/crates/wick/wick-config/src/import_cache.rs
@@ -4,7 +4,6 @@ use std::sync::Arc;
 use asset_container::Asset;
 use flow_component::BoxFuture;
 use parking_lot::RwLock;
-use tracing::trace;
 use wick_asset_reference::{AssetReference, FetchOptions};
 use wick_interface_types::TypeDefinition;
 
@@ -53,7 +52,6 @@ pub(crate) async fn setup_cache(
   mut init: Vec<TypeDefinition>,
   options: FetchOptions,
 ) -> Result<(), ManifestError> {
-  trace!("manifest:cache:setup");
   if cached_types.read().is_some() {
     return Ok(());
   }
diff --git a/crates/wick/wick-config/src/v1.rs b/crates/wick/wick-config/src/v1.rs
index 7650ecd1a..ad60a3d91 100644
--- a/crates/wick/wick-config/src/v1.rs
+++ b/crates/wick/wick-config/src/v1.rs
@@ -484,7 +484,7 @@ pub(crate) struct StaticRouter {
 
   #[serde(deserialize_with = "crate::helpers::with_expand_envs_string")]
   pub(crate) volume: String,
-  /// Fallback path (relative to &#x60;path&#x60;) for files to serve in case of a 404. Useful for SPA&#x27;s. if path: /www and fallback: index.html, then a 404 will serve /www/index.html
+  /// Fallback path (relative to volume &#x60;resource&#x60;) for files to serve in case of a 404. Useful for SPA&#x27;s. if volume resource is: /www and fallback: index.html, then a 404 will serve /www/index.html
 
   #[serde(default)]
   #[serde(skip_serializing_if = "Option::is_none")]
diff --git a/crates/wick/wick-host/Cargo.toml b/crates/wick/wick-host/Cargo.toml
index aecb12af9..286216927 100644
--- a/crates/wick/wick-host/Cargo.toml
+++ b/crates/wick/wick-host/Cargo.toml
@@ -29,6 +29,7 @@ parking_lot = { workspace = true }
 uuid = { workspace = true }
 futures = { workspace = true }
 option-utils = { workspace = true }
+derive_builder = { workspace = true }
 
 [dev-dependencies]
 wick-logger = { workspace = true }
diff --git a/crates/wick/wick-host/examples/basic-host.rs b/crates/wick/wick-host/examples/basic-host.rs
index 5c8bb8a6e..aa514064a 100644
--- a/crates/wick/wick-host/examples/basic-host.rs
+++ b/crates/wick/wick-host/examples/basic-host.rs
@@ -2,7 +2,7 @@ use wick_host::{ComponentHostBuilder, Error};
 
 #[tokio::main]
 async fn main() -> Result<(), Error> {
-  let mut host = ComponentHostBuilder::new().build();
+  let mut host = ComponentHostBuilder::new().build().unwrap();
   host.start(None).await?;
 
   println!("Host started, waiting for ctrl-c / SIGINT");
diff --git a/crates/wick/wick-host/src/app_host.rs b/crates/wick/wick-host/src/app_host.rs
index 12b933f15..fc9fde784 100644
--- a/crates/wick/wick-host/src/app_host.rs
+++ b/crates/wick/wick-host/src/app_host.rs
@@ -1,20 +1,27 @@
 use std::collections::HashMap;
 use std::sync::Arc;
 
-use futures::future::join_all;
+use futures::future::{join_all, select};
+use futures::pin_mut;
 use tokio::task::{JoinError, JoinHandle};
-use wick_config::config::{AppConfiguration, WickConfiguration};
+use tracing::Span;
+use wick_config::config::AppConfiguration;
 use wick_runtime::error::RuntimeError;
 use wick_runtime::resources::Resource;
 use wick_runtime::Trigger;
 
 use crate::{Error, Result};
 
+#[derive(derive_builder::Builder)]
+#[builder(derive(Debug), setter(into))]
 /// A Wick Host wraps a Wick runtime with server functionality like persistence,.
 #[must_use]
 pub struct AppHost {
   manifest: AppConfiguration,
+  #[builder(setter(skip))]
   triggers: Option<TriggerState>,
+  #[builder(default = "tracing::Span::current()")]
+  span: Span,
 }
 
 impl std::fmt::Debug for AppHost {
@@ -25,7 +32,7 @@ impl std::fmt::Debug for AppHost {
 
 impl AppHost {
   pub fn start(&mut self, _seed: Option<u64>) -> Result<()> {
-    debug!("host starting");
+    self.span.in_scope(|| debug!("host starting"));
 
     let resources = self.init_resources()?;
     self.start_triggers(resources)?;
@@ -36,7 +43,7 @@ impl AppHost {
   /// Stops a running host.
   #[allow(clippy::unused_async)]
   pub async fn stop(self) {
-    debug!("host stopping");
+    self.span.in_scope(|| debug!("host stopping"));
   }
 
   fn init_resources(&mut self) -> Result<HashMap<String, Resource>> {
@@ -50,10 +57,12 @@ impl AppHost {
 
   fn start_triggers(&mut self, resources: HashMap<String, Resource>) -> Result<()> {
     assert!(self.triggers.is_none(), "triggers already started");
+
     let resources = Arc::new(resources);
     let mut triggers = TriggerState::new();
+
     for trigger_config in self.manifest.triggers() {
-      debug!(?trigger_config, "loading trigger");
+      self.span.in_scope(|| debug!(?trigger_config, "loading trigger"));
       let config = trigger_config.clone();
       let name = self.manifest.name().to_owned();
       let app_config = self.manifest.clone();
@@ -63,9 +72,14 @@ impl AppHost {
           let loader = loader()?;
           let inner = loader.clone();
           let resources = resources.clone();
+          let span = debug_span!("trigger", kind=%trigger_config.kind());
+
           let task = tokio::spawn(async move {
-            if let Err(e) = inner.run(name, app_config, config, resources).await {
-              error!("trigger failed to start: {}", e);
+            span.in_scope(|| trace!("initializing trigger"));
+            if let Err(e) = inner.run(name, app_config, config, resources, span.clone()).await {
+              span.in_scope(|| error!("trigger failed to start: {}", e));
+            } else {
+              span.in_scope(|| debug!("trigger initialized"));
             };
             Ok(())
           });
@@ -95,11 +109,23 @@ impl AppHost {
       .map(|(trigger, task)| (trigger, task.unwrap()))
       .unzip();
     join_all(start_tasks).await;
-    debug!("all triggers started");
+    self.span.in_scope(|| debug!("all triggers started"));
     for trigger in triggers.iter() {
-      trigger.wait_for_done().await;
+      let ctrl_c = async {
+        let _ = tokio::signal::ctrl_c().await;
+      };
+      pin_mut!(ctrl_c);
+      match select(ctrl_c, trigger.wait_for_done()).await {
+        futures::future::Either::Left(_) => {
+          self.span.in_scope(|| debug!("ctrl-c received, stopping triggers"));
+          break;
+        }
+        futures::future::Either::Right(_) => {
+          self.span.in_scope(|| debug!("trigger finished"));
+        }
+      }
     }
-    debug!("all triggers finished");
+    self.span.in_scope(|| debug!("all triggers finished"));
 
     Ok(())
   }
@@ -141,48 +167,11 @@ impl TriggerState {
   }
 }
 
-/// The HostBuilder builds the configuration for a Wick Host.
-#[must_use]
-#[derive(Debug, Clone)]
-pub struct AppHostBuilder {
-  manifest: AppConfiguration,
-}
-
-impl Default for AppHostBuilder {
-  fn default() -> Self {
-    Self::new()
-  }
-}
-
 impl AppHostBuilder {
   /// Creates a new host builder.
+  #[must_use]
   pub fn new() -> AppHostBuilder {
-    AppHostBuilder {
-      manifest: AppConfiguration::default(),
-    }
-  }
-
-  pub async fn from_manifest_url(location: &str, allow_latest: bool, insecure_registries: &[String]) -> Result<Self> {
-    let fetch_options = wick_config::config::FetchOptions::new()
-      .allow_latest(allow_latest)
-      .allow_insecure(insecure_registries);
-
-    let manifest = WickConfiguration::fetch(location, fetch_options)
-      .await?
-      .try_app_config()?;
-    Ok(Self::from_definition(manifest))
-  }
-
-  pub fn from_definition(definition: AppConfiguration) -> Self {
-    AppHostBuilder { manifest: definition }
-  }
-
-  /// Constructs an instance of a Wick host.
-  pub fn build(self) -> AppHost {
-    AppHost {
-      manifest: self.manifest,
-      triggers: None,
-    }
+    AppHostBuilder::default()
   }
 }
 
diff --git a/crates/wick/wick-host/src/collection.rs b/crates/wick/wick-host/src/collection.rs
index a66045f17..73b966ee3 100644
--- a/crates/wick/wick-host/src/collection.rs
+++ b/crates/wick/wick-host/src/collection.rs
@@ -72,6 +72,7 @@ mod tests {
   use anyhow::Result as TestResult;
   use flow_component::panic_callback;
   use tokio_stream::StreamExt;
+  use wick_config::WickConfiguration;
   use wick_packet::{packet_stream, Entity, Packet};
 
   use super::*;
@@ -79,15 +80,20 @@ mod tests {
 
   #[test_logger::test(tokio::test)]
   async fn test_component() -> TestResult<()> {
-    let builder = ComponentHostBuilder::from_manifest_url("./manifests/logger.yaml", false, &[]).await?;
-    let mut host = builder.build();
+    let manifest = WickConfiguration::fetch("./manifests/logger.yaml", Default::default())
+      .await?
+      .try_component_config()?;
+    let mut builder = ComponentHostBuilder::default();
+    builder.manifest(manifest);
+
+    let mut host = builder.build()?;
     host.start(Some(0)).await?;
     let collection = HostComponent::new(host);
     let input = "Hello world";
 
     let job_payload = packet_stream![("input", input)];
 
-    let invocation = Invocation::new(Entity::test(file!()), Entity::local("logger"), None);
+    let invocation = Invocation::test(file!(), Entity::local("logger"), None)?;
     let mut outputs = collection
       .handle(invocation, job_payload, None, panic_callback())
       .await?;
diff --git a/crates/wick/wick-host/src/component_host.rs b/crates/wick/wick-host/src/component_host.rs
index fdbb1e29f..0dc772b45 100644
--- a/crates/wick/wick-host/src/component_host.rs
+++ b/crates/wick/wick-host/src/component_host.rs
@@ -6,11 +6,11 @@ use flow_component::SharedComponent;
 use once_cell::sync::Lazy;
 use parking_lot::Mutex;
 use seeded_random::Seed;
+use tracing::Span;
 use uuid::Uuid;
 use wick_component_cli::options::{Options as HostOptions, ServerOptions};
 use wick_component_cli::ServerState;
 use wick_config::config::ComponentConfiguration;
-use wick_config::WickConfiguration;
 use wick_interface_types::ComponentSignature;
 use wick_packet::{Entity, InherentData, Invocation, OperationConfig, PacketStream};
 use wick_runtime::{EngineComponent, Runtime, RuntimeBuilder};
@@ -28,19 +28,26 @@ fn from_registry(id: Uuid) -> SharedComponent {
 
 /// A Wick Host wraps a Wick runtime with server functionality like persistence,.
 #[must_use]
-#[derive(Debug)]
+#[derive(Debug, derive_builder::Builder)]
+#[builder(derive(Debug), setter(into))]
 pub struct ComponentHost {
+  #[builder(default = "Uuid::new_v4().to_string()")]
   id: String,
+  #[builder(default)]
   runtime: Option<Runtime>,
+  #[builder(default)]
   manifest: ComponentConfiguration,
+  #[builder(default, setter(strip_option))]
   server_metadata: Option<ServerState>,
+  #[builder(default = "tracing::Span::current()")]
+  span: Span,
 }
 
 impl ComponentHost {
   /// Starts the host. This call is non-blocking, so it is up to the consumer
   /// to wait with a method like `host.wait_for_sigint()`.
   pub async fn start(&mut self, seed: Option<u64>) -> Result<()> {
-    debug!("host starting");
+    self.span.in_scope(|| debug!("host starting"));
 
     // self.mesh = self.get_mesh().await?;
     self.start_engine(seed.map(Seed::unsafe_new)).await?;
@@ -73,7 +80,7 @@ impl ComponentHost {
 
   /// Stops a running host.
   pub async fn stop(self) {
-    debug!("host stopping");
+    self.span.in_scope(|| debug!("host stopping"));
     if let Some(engine) = self.runtime {
       let _ = engine.shutdown().await;
     }
@@ -93,17 +100,17 @@ impl ComponentHost {
       crate::Error::InvalidHostState("Host already has a engine running".into())
     );
 
-    let mut engine_builder = RuntimeBuilder::from_definition(self.manifest.clone())?;
-    if let Some(seed) = seed {
-      engine_builder = engine_builder.with_seed(seed);
-    }
+    let mut engine_builder = RuntimeBuilder::from_definition(self.manifest.clone());
+    let span = debug_span!("host");
+    span.follows_from(&self.span);
+    engine_builder = engine_builder.span(span);
     engine_builder = engine_builder.namespace(self.get_host_id());
     engine_builder = engine_builder.allow_latest(self.manifest.allow_latest());
     if let Some(insecure) = self.manifest.insecure_registries() {
-      engine_builder = engine_builder.allow_insecure(insecure.to_vec());
+      engine_builder = engine_builder.allowed_insecure(insecure.to_vec());
     }
 
-    let engine = engine_builder.build().await?;
+    let engine = engine_builder.build(seed).await?;
 
     self.runtime = Some(engine);
     Ok(())
@@ -146,7 +153,12 @@ impl ComponentHost {
   ) -> Result<PacketStream> {
     match &self.runtime {
       Some(runtime) => {
-        let invocation = Invocation::new(Entity::server(&self.id), Entity::operation(&self.id, operation), data);
+        let invocation = Invocation::new(
+          Entity::server(&self.id),
+          Entity::operation(&self.id, operation),
+          data,
+          &self.span,
+        );
         Ok(runtime.invoke(invocation, stream, None).await?)
       }
       None => Err(crate::Error::InvalidHostState("No engine available".into())),
@@ -167,7 +179,7 @@ impl ComponentHost {
 
   pub async fn wait_for_sigint(&self) -> Result<()> {
     tokio::signal::ctrl_c().await.unwrap();
-    debug!("SIGINT received");
+    self.span.in_scope(|| debug!("SIGINT received"));
     Ok(())
   }
 
@@ -182,52 +194,11 @@ impl ComponentHost {
   }
 }
 
-/// The HostBuilder builds the configuration for a Wick Host.
-#[must_use]
-#[derive(Debug, Clone)]
-pub struct ComponentHostBuilder {
-  manifest: ComponentConfiguration,
-}
-
-impl Default for ComponentHostBuilder {
-  fn default() -> Self {
-    Self::new()
-  }
-}
-
 impl ComponentHostBuilder {
   /// Creates a new host builder.
+  #[must_use]
   pub fn new() -> ComponentHostBuilder {
-    ComponentHostBuilder {
-      manifest: ComponentConfiguration::default(),
-    }
-  }
-
-  pub async fn from_manifest_url(location: &str, allow_latest: bool, insecure_registries: &[String]) -> Result<Self> {
-    let fetch_options = wick_config::config::FetchOptions::new()
-      .allow_latest(allow_latest)
-      .allow_insecure(insecure_registries);
-
-    let manifest = WickConfiguration::fetch(location, fetch_options)
-      .await?
-      .try_component_config()?;
-    Ok(Self::from_definition(manifest))
-  }
-
-  pub fn from_definition(definition: ComponentConfiguration) -> Self {
-    ComponentHostBuilder { manifest: definition }
-  }
-
-  /// Constructs an instance of a Wick host.
-  pub fn build(self) -> ComponentHost {
-    let host_id = Uuid::new_v4().to_string();
-
-    ComponentHost {
-      id: host_id,
-      runtime: None,
-      manifest: self.manifest,
-      server_metadata: None,
-    }
+    ComponentHostBuilder::default()
   }
 }
 
@@ -241,6 +212,7 @@ mod test {
   use futures::StreamExt;
   use http::Uri;
   use wick_config::config::HttpConfigBuilder;
+  use wick_config::WickConfiguration;
   use wick_invocation_server::connect_rpc_client;
   use wick_packet::{packet_stream, packets, Entity, Packet};
 
@@ -254,7 +226,7 @@ mod test {
 
   #[test_logger::test(tokio::test)]
   async fn should_start_and_stop() -> Result<()> {
-    let mut host = ComponentHostBuilder::new().build();
+    let mut host = ComponentHostBuilder::new().build()?;
 
     host.start(None).await?;
     assert!(host.is_started());
@@ -267,7 +239,7 @@ mod test {
   async fn request_direct() -> Result<()> {
     let file = PathBuf::from("manifests/logger.yaml");
     let manifest = WickConfiguration::load_from_file(&file).await?.try_component_config()?;
-    let mut host = ComponentHostBuilder::from_definition(manifest).build();
+    let mut host = ComponentHostBuilder::default().manifest(manifest).build()?;
     host.start(None).await?;
     let passed_data = "logging output";
     let stream = packet_stream!(("input", passed_data));
@@ -296,7 +268,7 @@ mod test {
         .build()?,
     );
 
-    let mut host = ComponentHostBuilder::from_definition(def).build();
+    let mut host = ComponentHostBuilder::default().manifest(def).build()?;
     host.start(None).await?;
     let address = host.rpc_address().unwrap();
     println!("rpc server bound to : {}", address);
@@ -305,7 +277,7 @@ mod test {
     println!("connected to server");
     let passed_data = "logging output";
     let packets = packets![("input", passed_data)];
-    let invocation: wick_rpc::rpc::Invocation = Invocation::new(Entity::test("test"), Entity::local("logger"), None)
+    let invocation: wick_rpc::rpc::Invocation = Invocation::test("test", Entity::local("logger"), None)?
       .try_into()
       .unwrap();
 
diff --git a/crates/wick/wick-logger/Cargo.toml b/crates/wick/wick-logger/Cargo.toml
index 098a03ea2..7aed589df 100644
--- a/crates/wick/wick-logger/Cargo.toml
+++ b/crates/wick/wick-logger/Cargo.toml
@@ -14,19 +14,15 @@ tracing = { workspace = true }
 tracing-subscriber = { workspace = true, features = [
   "env-filter",
   "fmt",
-  "json",
   "time",
+  "parking_lot"
 ] }
-tracing-bunyan-formatter = "0.3"
-tracing-appender = "0.2"
-time = { version = "0.3", features = ["formatting"] }
-ansi_term = "0.12"
-tokio = { workspace = true, features = ["rt"] }
+tracing-appender = { workspace = true }
+time = { workspace = true, features = ["formatting"] }
+ansi_term = { workspace = true }
+tokio = { workspace = true }
 thiserror = { workspace = true }
 tracing-opentelemetry = { workspace = true }
 opentelemetry = { workspace = true, features = ["rt-tokio"] }
-opentelemetry-jaeger = { workspace = true, features = [
-  "rt-tokio",
-  "hyper_collector_client",
-  "collector_client"
-] }
+opentelemetry-otlp = { workspace = true, features = ["tokio"] }
+tokio-stream = { workspace = true }
diff --git a/crates/wick/wick-logger/src/lib.rs b/crates/wick/wick-logger/src/lib.rs
index b97bf0fe3..a6aa5411d 100644
--- a/crates/wick/wick-logger/src/lib.rs
+++ b/crates/wick/wick-logger/src/lib.rs
@@ -98,7 +98,7 @@ pub use options::{LogLevel, LoggingOptions};
 /// The main Logger module.
 mod logger;
 
-pub use crate::logger::{init, init_defaults, init_test, LoggingGuard};
+pub use crate::logger::{init, init_test, with_default, LoggingGuard};
 
 #[macro_use]
 extern crate tracing;
diff --git a/crates/wick/wick-logger/src/logger.rs b/crates/wick/wick-logger/src/logger.rs
index 56ab9cbef..46c9b91c8 100644
--- a/crates/wick/wick-logger/src/logger.rs
+++ b/crates/wick/wick-logger/src/logger.rs
@@ -1,36 +1,42 @@
-use std::path::PathBuf;
-
-use opentelemetry::trace::TracerProvider;
+use opentelemetry::global;
+use tracing::Subscriber;
 use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
-use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
-use tracing_subscriber::filter::FilterFn;
+use tracing_subscriber::filter::DynFilterFn;
 use tracing_subscriber::fmt::time::UtcTime;
 use tracing_subscriber::prelude::*;
 use tracing_subscriber::{filter, Layer};
+mod otel;
 
 use crate::error::LoggerError;
 use crate::LoggingOptions;
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone, Copy)]
 enum Environment {
   Prod,
   Test,
 }
 
 /// Initialize a logger or panic on failure
-pub fn init_defaults() -> LoggingGuard {
-  match try_init(&LoggingOptions::default(), &Environment::Prod) {
-    Ok(guard) => guard,
+pub fn init(opts: &LoggingOptions) -> LoggingGuard {
+  #![allow(clippy::trivially_copy_pass_by_ref, clippy::needless_borrow)]
+  match try_init::<()>(&opts, Environment::Prod, None) {
+    Ok(Either::Logger(guard)) => guard,
     Err(e) => panic!("Error initializing logger: {}", e),
+    _ => unreachable!(),
   }
 }
 
-/// Initialize a logger or panic on failure
-pub fn init(opts: &LoggingOptions) -> LoggingGuard {
+/// Initialize a logger for this specific scope only.
+#[allow(clippy::must_use_candidate)]
+pub fn with_default<T>(opts: &LoggingOptions, f: Box<dyn FnOnce() -> T>) -> T
+where
+  T: 'static,
+{
   #![allow(clippy::trivially_copy_pass_by_ref, clippy::needless_borrow)]
-  match try_init(&opts, &Environment::Prod) {
-    Ok(guard) => guard,
+  match try_init(&opts, Environment::Prod, Some(Box::new(f))) {
+    Ok(Either::ScopeReturn(v)) => v,
     Err(e) => panic!("Error initializing logger: {}", e),
+    _ => unreachable!(),
   }
 }
 
@@ -38,7 +44,10 @@ pub fn init(opts: &LoggingOptions) -> LoggingGuard {
 #[must_use]
 pub fn init_test(opts: &LoggingOptions) -> Option<LoggingGuard> {
   #![allow(clippy::trivially_copy_pass_by_ref, clippy::needless_borrow)]
-  try_init(&opts, &Environment::Test).map_or_else(|_e| None, Some)
+  match try_init::<()>(&opts, Environment::Test, None) {
+    Ok(Either::Logger(guard)) => Some(guard),
+    _ => None,
+  }
 }
 
 fn hushed_modules(module: &str) -> bool {
@@ -57,28 +66,46 @@ fn silly_modules(module: &str) -> bool {
 }
 
 #[must_use]
-fn wick_filter(opts: &LoggingOptions) -> FilterFn {
+#[allow(clippy::too_many_lines)]
+fn wick_filter<S>(opts: &LoggingOptions) -> DynFilterFn<S>
+where
+  S: Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
+{
   // This is split up into an if/else because FilterFn needs an fn type.
   // If the closure captures opts.silly then it won't be coercable to an fn.
   if opts.silly {
-    FilterFn::new(move |e| {
-      let module = &e
-        .module_path()
-        .unwrap_or_default()
-        .split("::")
-        .next()
-        .unwrap_or_default();
-      !hushed_modules(module)
-    })
+    filter::dynamic_filter_fn(move |_metadata, _cx| true)
   } else {
-    FilterFn::new(move |e| {
-      let module = &e
-        .module_path()
-        .unwrap_or_default()
-        .split("::")
-        .next()
-        .unwrap_or_default();
-      !hushed_modules(module) && !silly_modules(module)
+    filter::dynamic_filter_fn(move |metadata, cx| {
+      #[allow(clippy::option_if_let_else)]
+      if let Some(md) = cx.current_span().metadata() {
+        let module = &metadata
+          .module_path()
+          .unwrap_or_default()
+          .split("::")
+          .next()
+          .unwrap_or_default();
+        if hushed_modules(module) {
+          return false;
+        }
+        if silly_modules(module) {
+          matches!(*md.level(), tracing::Level::ERROR | tracing::Level::WARN)
+        } else {
+          true
+        }
+      } else {
+        let module = &metadata
+          .module_path()
+          .unwrap_or_default()
+          .split("::")
+          .next()
+          .unwrap_or_default();
+        if !hushed_modules(module) {
+          !silly_modules(module)
+        } else {
+          false
+        }
+      }
     })
   }
 }
@@ -87,6 +114,8 @@ fn wick_filter(opts: &LoggingOptions) -> FilterFn {
 #[derive(Debug)]
 /// Guard that - when dropped - flushes all log messages and drop I/O handles.
 pub struct LoggingGuard {
+  #[allow(unused)]
+  env: Environment,
   #[allow(unused)]
   logfile: Option<WorkerGuard>,
   #[allow(unused)]
@@ -97,11 +126,13 @@ pub struct LoggingGuard {
 
 impl LoggingGuard {
   fn new(
+    env: Environment,
     logfile: Option<WorkerGuard>,
     console: WorkerGuard,
     tracer_provider: Option<opentelemetry::sdk::trace::TracerProvider>,
   ) -> Self {
     Self {
+      env,
       logfile,
       console,
       tracer_provider,
@@ -111,17 +142,16 @@ impl LoggingGuard {
   pub fn teardown(&self) {
     // noop right now
   }
+
+  /// Flush any remaining logs.
+  pub fn flush(&self) {
+    opentelemetry::global::shutdown_tracer_provider();
+  }
 }
 
 impl Drop for LoggingGuard {
   fn drop(&mut self) {
-    if let Some(provider) = &self.tracer_provider {
-      for result in provider.force_flush() {
-        if let Err(_err) = result {
-          println!("error flushing");
-        }
-      }
-    }
+    self.flush();
   }
 }
 
@@ -131,21 +161,6 @@ fn get_stderr_writer(_opts: &LoggingOptions) -> (NonBlocking, WorkerGuard) {
   (stderr_writer, console_guard)
 }
 
-fn get_logfile_writer(opts: &LoggingOptions) -> Result<(PathBuf, NonBlocking, WorkerGuard), LoggerError> {
-  let logfile_prefix = format!("{}.{}.log", opts.app_name, std::process::id());
-
-  let mut log_dir = match &opts.log_dir {
-    Some(dir) => dir.clone(),
-    None => wick_xdg::Directories::GlobalState.basedir()?,
-  };
-  log_dir.push("logs");
-
-  let (writer, guard) =
-    tracing_appender::non_blocking(tracing_appender::rolling::daily(log_dir.clone(), logfile_prefix));
-
-  Ok((log_dir, writer, guard))
-}
-
 fn get_levelfilter(opts: &LoggingOptions) -> tracing::level_filters::LevelFilter {
   match opts.level {
     crate::LogLevel::Quiet => filter::LevelFilter::OFF,
@@ -157,8 +172,20 @@ fn get_levelfilter(opts: &LoggingOptions) -> tracing::level_filters::LevelFilter
   }
 }
 
+enum Either<T> {
+  Logger(LoggingGuard),
+  ScopeReturn(T),
+}
+
 #[allow(clippy::too_many_lines)]
-fn try_init(opts: &LoggingOptions, environment: &Environment) -> Result<LoggingGuard, LoggerError> {
+fn try_init<T>(
+  opts: &LoggingOptions,
+  environment: Environment,
+  with_default: Option<Box<dyn FnOnce() -> T>>,
+) -> Result<Either<T>, LoggerError>
+where
+  T: 'static,
+{
   #[cfg(windows)]
   let with_color = ansi_term::enable_ansi_support().is_ok();
   #[cfg(not(windows))]
@@ -167,29 +194,21 @@ fn try_init(opts: &LoggingOptions, environment: &Environment) -> Result<LoggingG
   let timer = UtcTime::new(time::format_description::parse("[year]-[month]-[day]T[hour]:[minute]:[second]").unwrap());
   let (stderr_writer, console_guard) = get_stderr_writer(opts);
 
-  let app_name = opts.app_name.clone();
-
-  let (_log_dir, logfile_writer, _logfile_guard) = get_logfile_writer(opts)?;
-  let file_layer = BunyanFormattingLayer::new(app_name, logfile_writer).with_filter(wick_filter(opts));
-
-  let needs_simple_tracer = tokio::runtime::Handle::try_current().is_err() || environment == &Environment::Test;
+  let needs_simple_tracer = tokio::runtime::Handle::try_current().is_err() || environment == Environment::Test;
 
   // Configure a jaeger tracer if we have a configured endpoint.
-  let (otel_layer, tracer_provider) = opts.jaeger_endpoint.as_ref().map_or_else(
+  let (otel_layer, tracer_provider) = opts.otlp_endpoint.as_ref().map_or_else(
     || (None, None),
-    |jaeger_endpoint| {
-      let tracer_provider = opentelemetry_jaeger::new_agent_pipeline()
-        .with_service_name("wick")
-        .with_endpoint(jaeger_endpoint);
-
-      let tracer_provider = if needs_simple_tracer {
-        tracer_provider.build_simple().unwrap()
+    |otlp_endpoint| {
+      let (tracer, provider) = if needs_simple_tracer {
+        otel::build_simple(otlp_endpoint).unwrap()
       } else {
-        tracer_provider.build_batch(opentelemetry::runtime::Tokio).unwrap()
+        otel::build_batch(otlp_endpoint).unwrap() // unwrap OK for now, this is infallible.
       };
 
-      let tracer = tracer_provider.versioned_tracer("wick", Some(env!("CARGO_PKG_VERSION")), None);
-      let _ = opentelemetry::global::set_tracer_provider(tracer_provider.clone());
+      if opts.global {
+        let _ = global::set_tracer_provider(provider.clone());
+      }
 
       let layer = Some(
         tracing_opentelemetry::layer()
@@ -197,13 +216,13 @@ fn try_init(opts: &LoggingOptions, environment: &Environment) -> Result<LoggingG
           .with_filter(get_levelfilter(opts))
           .with_filter(wick_filter(opts)),
       );
-      (layer, Some(tracer_provider))
+      (layer, Some(provider))
     },
   );
 
   // This is ugly. If you can improve it, go for it, but
   // start here to understand why it's laid out like this: https://github.com/tokio-rs/tracing/issues/575
-  let (verbose_layer, normal_layer, json_layer, file_layer, logfile_guard, otel_layer, test_layer) = match environment {
+  let (verbose_layer, normal_layer, logfile_guard, otel_layer, test_layer) = match environment {
     Environment::Prod => {
       if opts.verbose {
         (
@@ -217,10 +236,8 @@ fn try_init(opts: &LoggingOptions, environment: &Environment) -> Result<LoggingG
               .with_filter(wick_filter(opts)),
           ),
           None,
-          Some(JsonStorageLayer),
-          Some(file_layer),
           None,
-          Some(otel_layer),
+          otel_layer,
           None,
         )
       } else {
@@ -236,10 +253,8 @@ fn try_init(opts: &LoggingOptions, environment: &Environment) -> Result<LoggingG
               .with_filter(get_levelfilter(opts))
               .with_filter(wick_filter(opts)),
           ),
-          Some(JsonStorageLayer),
-          Some(file_layer),
           None,
-          Some(otel_layer),
+          otel_layer,
           None,
         )
       }
@@ -247,10 +262,8 @@ fn try_init(opts: &LoggingOptions, environment: &Environment) -> Result<LoggingG
     Environment::Test => (
       None,
       None,
-      Some(JsonStorageLayer),
-      Some(file_layer),
       None,
-      Some(otel_layer),
+      otel_layer,
       Some(
         tracing_subscriber::fmt::layer()
           .with_writer(stderr_writer)
@@ -265,15 +278,24 @@ fn try_init(opts: &LoggingOptions, environment: &Environment) -> Result<LoggingG
   };
 
   let subscriber = tracing_subscriber::registry()
+    .with(otel_layer)
     .with(test_layer)
     .with(verbose_layer)
-    .with(normal_layer)
-    .with(json_layer)
-    .with(otel_layer)
-    .with(file_layer);
-  tracing::subscriber::set_global_default(subscriber)?;
+    .with(normal_layer);
 
   trace!("Logger initialized");
-  // debug!("Writing logs to {}", log_dir.to_string_lossy());
-  Ok(LoggingGuard::new(logfile_guard, console_guard, tracer_provider))
+
+  if let Some(f) = with_default {
+    Ok(Either::ScopeReturn(tracing::subscriber::with_default(subscriber, f)))
+  } else if opts.global {
+    tracing::subscriber::set_global_default(subscriber)?;
+    Ok(Either::Logger(LoggingGuard::new(
+      environment,
+      logfile_guard,
+      console_guard,
+      tracer_provider,
+    )))
+  } else {
+    panic!("Logger must be global or scoped")
+  }
 }
diff --git a/crates/wick/wick-logger/src/logger/otel.rs b/crates/wick/wick-logger/src/logger/otel.rs
new file mode 100644
index 000000000..011317e9a
--- /dev/null
+++ b/crates/wick/wick-logger/src/logger/otel.rs
@@ -0,0 +1,46 @@
+use opentelemetry::sdk::trace::{self, Builder, Sampler};
+use opentelemetry::sdk::{self, Resource};
+use opentelemetry::trace::{TraceError, TracerProvider};
+use opentelemetry::KeyValue;
+use opentelemetry_otlp::{SpanExporter, SpanExporterBuilder, WithExportConfig};
+
+fn exporter_builder(endpoint: &str) -> SpanExporter {
+  SpanExporterBuilder::from(opentelemetry_otlp::new_exporter().tonic().with_endpoint(endpoint))
+    .build_span_exporter()
+    .unwrap()
+}
+
+fn trace_config() -> Option<trace::Config> {
+  Some(
+    trace::config()
+      .with_sampler(Sampler::AlwaysOn)
+      .with_resource(Resource::new(vec![KeyValue::new("service.name", "wick")])),
+  )
+}
+
+pub(super) fn build_batch(endpoint: &str) -> Result<(sdk::trace::Tracer, sdk::trace::TracerProvider), TraceError> {
+  Ok(build_with_exporter(
+    sdk::trace::TracerProvider::builder()
+      .with_batch_exporter(exporter_builder(endpoint), opentelemetry::runtime::Tokio),
+    trace_config(),
+  ))
+}
+
+pub(super) fn build_simple(endpoint: &str) -> Result<(sdk::trace::Tracer, sdk::trace::TracerProvider), TraceError> {
+  Ok(build_with_exporter(
+    sdk::trace::TracerProvider::builder().with_simple_exporter(exporter_builder(endpoint)),
+    trace_config(),
+  ))
+}
+
+pub(super) fn build_with_exporter(
+  mut provider_builder: Builder,
+  trace_config: Option<sdk::trace::Config>,
+) -> (sdk::trace::Tracer, sdk::trace::TracerProvider) {
+  if let Some(config) = trace_config {
+    provider_builder = provider_builder.with_config(config);
+  }
+  let provider = provider_builder.build();
+  let tracer = provider.versioned_tracer("opentelemetry-otlp", Some(env!("CARGO_PKG_VERSION")), None);
+  (tracer, provider)
+}
diff --git a/crates/wick/wick-logger/src/options.rs b/crates/wick/wick-logger/src/options.rs
index 6a3b1d602..a067277da 100644
--- a/crates/wick/wick-logger/src/options.rs
+++ b/crates/wick/wick-logger/src/options.rs
@@ -19,10 +19,13 @@ pub struct LoggingOptions {
   pub log_dir: Option<PathBuf>,
 
   /// The endpoint to send jaeger-format traces.
-  pub jaeger_endpoint: Option<String>,
+  pub otlp_endpoint: Option<String>,
 
   /// The application doing the logging.
   pub app_name: String,
+
+  /// Whether to install the global logger.
+  pub global: bool,
 }
 
 impl LoggingOptions {
diff --git a/crates/wick/wick-packet/src/collection_link.rs b/crates/wick/wick-packet/src/collection_link.rs
index f54e70cd7..4b616a204 100644
--- a/crates/wick/wick-packet/src/collection_link.rs
+++ b/crates/wick/wick-packet/src/collection_link.rs
@@ -18,10 +18,15 @@ impl ComponentReference {
 
   #[cfg(feature = "invocation")]
   /// Create an [crate::Invocation] for this component reference.
-  pub fn to_invocation(&self, operation: &str, inherent: Option<crate::InherentData>) -> crate::Invocation {
+  pub fn to_invocation(
+    &self,
+    operation: &str,
+    inherent: Option<crate::InherentData>,
+    follows_from: &tracing::Span,
+  ) -> crate::Invocation {
     let target = crate::Entity::operation(self.target.component_id(), operation);
 
-    crate::Invocation::new(self.origin.clone(), target, inherent)
+    crate::Invocation::new(self.origin.clone(), target, inherent, follows_from)
   }
 
   #[must_use]
diff --git a/crates/wick/wick-packet/src/invocation.rs b/crates/wick/wick-packet/src/invocation.rs
index 7566570db..00e5c125a 100644
--- a/crates/wick/wick-packet/src/invocation.rs
+++ b/crates/wick/wick-packet/src/invocation.rs
@@ -1,5 +1,6 @@
 use std::time::SystemTime;
 
+use tracing::{debug_span, Span};
 use uuid::Uuid;
 
 use crate::{Entity, InherentData, ParseError};
@@ -18,13 +19,22 @@ pub struct Invocation {
   pub tx_id: Uuid,
   /// Inherent data associated with the transaction.
   pub inherent: Option<InherentData>,
+  /// The trace span associated with the invocation.
+  pub span: Span,
 }
 
 impl Invocation {
   /// Creates an invocation with a new transaction id.
-  pub fn new(origin: impl Into<Entity>, target: impl Into<Entity>, inherent: Option<InherentData>) -> Invocation {
+  pub fn new(
+    origin: impl Into<Entity>,
+    target: impl Into<Entity>,
+    inherent: Option<InherentData>,
+    following_span: &Span,
+  ) -> Invocation {
     let tx_id = get_uuid();
     let invocation_id = get_uuid();
+    let span = debug_span!("invocation",id=%invocation_id,tx_id=%tx_id);
+    span.follows_from(following_span.id());
 
     Invocation {
       origin: origin.into(),
@@ -32,27 +42,29 @@ impl Invocation {
       id: invocation_id,
       tx_id,
       inherent,
+      span,
     }
   }
 
   /// Creates an invocation with a new transaction id.
-  pub fn try_new<O, T, OE, TE>(origin: O, target: T, inherent: Option<InherentData>) -> Result<Invocation, ParseError>
+  pub fn try_new<O, T, OE, TE>(
+    origin: O,
+    target: T,
+    inherent: Option<InherentData>,
+    following_span: &Span,
+  ) -> Result<Invocation, ParseError>
   where
     O: TryInto<Entity, Error = OE>,
     OE: std::error::Error + Send + Sync + 'static,
     T: TryInto<Entity, Error = TE>,
     TE: std::error::Error + Send + Sync + 'static,
   {
-    let tx_id = get_uuid();
-    let invocation_id = get_uuid();
-
-    Ok(Invocation {
-      origin: origin.try_into().map_err(|e| ParseError::Conversion(Box::new(e)))?,
-      target: target.try_into().map_err(|e| ParseError::Conversion(Box::new(e)))?,
-      id: invocation_id,
-      tx_id,
+    Ok(Invocation::new(
+      origin.try_into().map_err(|e| ParseError::Conversion(Box::new(e)))?,
+      target.try_into().map_err(|e| ParseError::Conversion(Box::new(e)))?,
       inherent,
-    })
+      following_span,
+    ))
   }
 
   /// Creates an invocation with a new transaction id.
@@ -61,22 +73,20 @@ impl Invocation {
     T: TryInto<Entity, Error = TE>,
     TE: std::error::Error + Send + Sync + 'static,
   {
-    let tx_id = get_uuid();
-    let invocation_id = get_uuid();
-
-    Ok(Invocation {
-      origin: Entity::test(name),
-      target: target.try_into().map_err(|e| ParseError::Conversion(Box::new(e)))?,
-      id: invocation_id,
-      tx_id,
+    Ok(Invocation::new(
+      Entity::test(name),
+      target.try_into().map_err(|e| ParseError::Conversion(Box::new(e)))?,
       inherent,
-    })
+      &Span::current(),
+    ))
   }
 
   /// Creates an invocation with a specific transaction id, to correlate a chain of
   /// invocations.
   pub fn next_tx(&self, origin: Entity, target: Entity) -> Invocation {
     let invocation_id = get_uuid();
+    let span = debug_span!("invocation",id=%invocation_id);
+    span.follows_from(&self.span);
 
     Invocation {
       origin,
@@ -90,6 +100,7 @@ impl Invocation {
           .unwrap()
           .as_millis() as u64,
       }),
+      span,
     }
   }
 
@@ -116,6 +127,11 @@ impl Invocation {
   pub fn origin_url(&self) -> String {
     self.origin.url()
   }
+
+  /// Do work within this invocation's trace span.
+  pub fn trace<F: FnOnce() -> T, T>(&self, f: F) -> T {
+    self.span.in_scope(f)
+  }
 }
 
 pub(crate) fn get_uuid() -> Uuid {
diff --git a/crates/wick/wick-rpc/src/types/conversions.rs b/crates/wick/wick-rpc/src/types/conversions.rs
index cab7aea92..0fabe2ee5 100644
--- a/crates/wick/wick-rpc/src/types/conversions.rs
+++ b/crates/wick/wick-rpc/src/types/conversions.rs
@@ -243,6 +243,7 @@ impl TryFrom<rpc::Invocation> for wick_packet::Invocation {
         seed: d.seed,
         timestamp: d.timestamp,
       }),
+      span: tracing::Span::current(),
     })
   }
 }
diff --git a/crates/wick/wick-runtime/Cargo.toml b/crates/wick/wick-runtime/Cargo.toml
index 2b4d317ef..fba5454b5 100644
--- a/crates/wick/wick-runtime/Cargo.toml
+++ b/crates/wick/wick-runtime/Cargo.toml
@@ -52,6 +52,7 @@ url = { workspace = true }
 bytes = { workspace = true }
 hyper-staticfile = "0.9"
 hyper-reverse-proxy = "0.5"
+derive_builder = { workspace = true }
 
 [dev-dependencies]
 wick-invocation-server = { workspace = true }
diff --git a/crates/wick/wick-runtime/src/components.rs b/crates/wick/wick-runtime/src/components.rs
index dacf61f51..3e755394e 100644
--- a/crates/wick/wick-runtime/src/components.rs
+++ b/crates/wick/wick-runtime/src/components.rs
@@ -9,6 +9,7 @@ use std::sync::Arc;
 use flow_component::{Component, RuntimeCallback};
 use flow_graph_interpreter::NamespaceHandler;
 use seeded_random::{Random, Seed};
+use tracing::Instrument;
 use uuid::Uuid;
 use wick_component_wasm::error::LinkError;
 use wick_config::config::components::{ManifestComponent, WasmComponent};
@@ -36,16 +37,19 @@ type Result<T> = std::result::Result<T, ComponentError>;
 
 type ComponentInitResult = std::result::Result<NamespaceHandler, EngineError>;
 
-pub(crate) async fn init_wasm_component<'a, 'b>(
-  kind: &'a WasmComponent,
+pub(crate) async fn init_wasm_component(
+  kind: &WasmComponent,
   namespace: String,
-  opts: ComponentInitOptions<'b>,
+  opts: ComponentInitOptions,
   provided: HashMap<String, String>,
 ) -> ComponentInitResult {
-  trace!(namespace = %namespace, ?opts, "registering wasm component");
+  opts
+    .span
+    .in_scope(|| trace!(namespace = %namespace, ?opts, "registering wasm component"));
 
-  let component =
-    wick_component_wasm::helpers::load_wasm(kind.reference(), opts.allow_latest, &opts.allowed_insecure).await?;
+  let component = wick_component_wasm::helpers::load_wasm(kind.reference(), opts.allow_latest, &opts.allowed_insecure)
+    .instrument(opts.span.clone())
+    .await?;
 
   // TODO take max threads from configuration
   let collection = Arc::new(
@@ -56,6 +60,7 @@ pub(crate) async fn init_wasm_component<'a, 'b>(
       None,
       Some(make_link_callback(opts.runtime_id)),
       provided,
+      opts.span,
     )
     .await?,
   );
@@ -65,16 +70,19 @@ pub(crate) async fn init_wasm_component<'a, 'b>(
   Ok(NamespaceHandler::new(namespace, Box::new(service)))
 }
 
-pub(crate) async fn init_wasmrs_component<'a, 'b>(
-  kind: &'a WasmComponentImplementation,
+pub(crate) async fn init_wasmrs_component(
+  kind: &WasmComponentImplementation,
   namespace: String,
-  opts: ComponentInitOptions<'b>,
+  opts: ComponentInitOptions,
   provided: HashMap<String, String>,
 ) -> ComponentInitResult {
-  trace!(namespace = %namespace, ?opts, "registering wasmrs component");
+  opts
+    .span
+    .in_scope(|| trace!( namespace = %namespace, ?opts, "registering wasmrs component"));
 
-  let component =
-    wick_component_wasm::helpers::load_wasm(kind.reference(), opts.allow_latest, &opts.allowed_insecure).await?;
+  let component = wick_component_wasm::helpers::load_wasm(kind.reference(), opts.allow_latest, &opts.allowed_insecure)
+    .instrument(opts.span.clone())
+    .await?;
 
   // TODO take max threads from configuration
   let collection = Arc::new(
@@ -85,6 +93,7 @@ pub(crate) async fn init_wasmrs_component<'a, 'b>(
       None,
       Some(make_link_callback(opts.runtime_id)),
       provided,
+      opts.span,
     )
     .await?,
   );
@@ -93,30 +102,22 @@ pub(crate) async fn init_wasmrs_component<'a, 'b>(
 
   Ok(NamespaceHandler::new(namespace, Box::new(service)))
 }
+
 pub(crate) fn make_link_callback(engine_id: Uuid) -> Arc<RuntimeCallback> {
-  Arc::new(move |compref, op, stream, inherent, config| {
+  Arc::new(move |compref, op, stream, inherent, config, span| {
     let origin_url = compref.get_origin_url();
     let target_id = compref.get_target_id().to_owned();
+    let invocation = compref.to_invocation(&op, inherent, span);
+    invocation.trace(|| {
+      debug!(
+        origin = %origin_url,
+        target = %target_id,
+        engine_id = %engine_id,
+        "link_call"
+      );
+    });
     Box::pin(async move {
       {
-        debug!(
-          origin = %origin_url,
-          target = %target_id,
-          engine_id = %engine_id,
-          "link_call"
-        );
-        let invocation = compref.to_invocation(&op, inherent);
-
-        // let target = Entity::from_str(&target_id).map_err(|e| LinkError::EntityFailure(e.to_string()))?;
-        // let origin = Entity::from_str(&origin_url).map_err(|e| LinkError::EntityFailure(e.to_string()))?;
-        // if let Entity::Operation(origin_ns, _) = &origin {
-        //   if let Entity::Operation(target_ns, _) = &target {
-        //     if target_ns == origin_ns {
-        //       return Err(LinkError::Circular(target_ns.clone()));
-        //     }
-        //   }
-        // }
-
         let result = engine_invoke_async(engine_id, invocation, stream, config)
           .await
           .map_err(|e| flow_component::ComponentError::new(LinkError::CallFailure(e.to_string())))?;
@@ -126,17 +127,20 @@ pub(crate) fn make_link_callback(engine_id: Uuid) -> Arc<RuntimeCallback> {
   })
 }
 
-pub(crate) async fn init_manifest_component<'a, 'b>(
-  kind: &'a ManifestComponent,
+pub(crate) async fn init_manifest_component(
+  kind: &ManifestComponent,
   id: String,
-  mut opts: ComponentInitOptions<'b>,
+  mut opts: ComponentInitOptions,
 ) -> ComponentInitResult {
-  trace!(namespace = %id, ?opts, "registering composite component");
+  opts
+    .span
+    .in_scope(|| trace!(namespace = %id, ?opts, "registering composite component"));
 
   let options = FetchOptions::new()
     .allow_latest(opts.allow_latest)
     .allow_insecure(&opts.allowed_insecure);
   let manifest = WickConfiguration::fetch(kind.reference().path()?.to_string_lossy(), options)
+    .instrument(opts.span.clone())
     .await?
     .try_component_config()?;
 
@@ -162,10 +166,10 @@ pub(crate) async fn init_manifest_component<'a, 'b>(
       Ok(comp)
     }
     config::ComponentImplementation::Composite(_) => {
-      let _engine = RuntimeService::new_from_manifest(uuid, manifest, Some(id.clone()), opts).await?;
+      let _engine = RuntimeService::init_child(uuid, manifest, Some(id.clone()), opts).await?;
 
-      let collection = Arc::new(engine_component::EngineComponent::new(uuid));
-      let service = NativeComponentService::new(collection);
+      let component = Arc::new(engine_component::EngineComponent::new(uuid));
+      let service = NativeComponentService::new(component);
       Ok(NamespaceHandler::new(id, Box::new(service)))
     }
     config::ComponentImplementation::Sql(c) => {
diff --git a/crates/wick/wick-runtime/src/components/engine_component.rs b/crates/wick/wick-runtime/src/components/engine_component.rs
index 776a137b4..1ab610fcd 100644
--- a/crates/wick/wick-runtime/src/components/engine_component.rs
+++ b/crates/wick/wick-runtime/src/components/engine_component.rs
@@ -46,7 +46,7 @@ impl Component for EngineComponent {
       let engine = RuntimeService::for_id(&self.engine_id)
         .ok_or_else(|| flow_component::ComponentError::message(&format!("Engine '{}' not found", target_url)))?;
 
-      trace!(target = %target_url, "invoking");
+      invocation.trace(|| trace!(target = %target_url, "invoking"));
 
       let result: InvocationResponse = engine
         .invoke(invocation, stream, config)
@@ -83,7 +83,7 @@ mod tests {
   async fn request_log(component: &EngineComponent, data: &str) -> Result<String> {
     let stream = packet_stream!(("MAIN_IN", data));
 
-    let invocation = Invocation::new(Entity::test(file!()), Entity::local("simple"), None);
+    let invocation = Invocation::test(file!(), Entity::local("simple"), None)?;
     let outputs = component.handle(invocation, stream, None, panic_callback()).await?;
     let mut packets: Vec<_> = outputs.collect().await;
     println!("packets: {:#?}", packets);
diff --git a/crates/wick/wick-runtime/src/dispatch.rs b/crates/wick/wick-runtime/src/dispatch.rs
index 148234f73..620c9361b 100644
--- a/crates/wick/wick-runtime/src/dispatch.rs
+++ b/crates/wick/wick-runtime/src/dispatch.rs
@@ -75,7 +75,7 @@ mod tests {
 
     let target = Entity::operation("self", "echo");
     let stream = packet_stream![("input", "hello")];
-    let invocation = Invocation::new(Entity::test(file!()), target, None);
+    let invocation = Invocation::test(file!(), target, None)?;
 
     let packets = engine_invoke_async(nuid, invocation, stream, None).await?;
     let mut packets: Vec<_> = packets.collect().await;
@@ -87,16 +87,4 @@ mod tests {
 
     Ok(())
   }
-
-  // fn sync_send<T>()
-  // where
-  //   T: Sync + Send,
-  // {
-  // }
-
-  // #[test_logger::test]
-  // fn test_sync_send() -> Result<()> {
-  //   sync_send::<InvocationResponse>();
-  //   Ok(())
-  // }
 }
diff --git a/crates/wick/wick-runtime/src/runtime.rs b/crates/wick/wick-runtime/src/runtime.rs
index db86d5647..c7da6c2ba 100644
--- a/crates/wick/wick-runtime/src/runtime.rs
+++ b/crates/wick/wick-runtime/src/runtime.rs
@@ -2,14 +2,16 @@ use std::sync::Arc;
 use std::time::Duration;
 
 use seeded_random::Seed;
+use tracing::Span;
 use uuid::Uuid;
+use wick_config::config::{ComponentConfiguration, ComponentConfigurationBuilder};
 use wick_packet::{Entity, OperationConfig};
 
 use crate::dev::prelude::*;
 use crate::runtime_service::{ComponentFactory, ComponentRegistry, Initialize};
 
 type Result<T> = std::result::Result<T, RuntimeError>;
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 #[must_use]
 pub struct Runtime {
   pub uid: Uuid,
@@ -17,34 +19,31 @@ pub struct Runtime {
   timeout: Duration,
 }
 
-#[derive(Debug)]
+#[derive(Debug, derive_builder::Builder)]
+#[builder(pattern = "owned", name = "RuntimeBuilder", setter(into), build_fn(skip))]
 #[must_use]
 pub struct RuntimeInit {
-  definition: config::ComponentConfiguration,
-  allow_latest: bool,
-  allowed_insecure: Vec<String>,
-  timeout: Duration,
-  namespace: Option<String>,
-  rng_seed: Seed,
-  native_components: ComponentRegistry,
-  constraints: Vec<RuntimeConstraint>,
+  #[builder(default)]
+  pub(crate) manifest: ComponentConfiguration,
+  #[builder(default)]
+  pub(crate) allow_latest: bool,
+  #[builder(default)]
+  pub(crate) allowed_insecure: Vec<String>,
+  #[builder(default = "Duration::from_secs(5)")]
+  pub(crate) timeout: Duration,
+  #[builder(setter(strip_option))]
+  pub(crate) namespace: Option<String>,
+  #[builder(default)]
+  pub(crate) constraints: Vec<RuntimeConstraint>,
+  pub(crate) span: Span,
+  #[builder(setter(custom = true))]
+  pub(crate) native_components: ComponentRegistry,
 }
 
 impl Runtime {
-  #[instrument(name = "runtime", skip_all)]
-  pub async fn new(config: RuntimeInit) -> Result<Self> {
-    trace!(?config, "init");
-    let init = Initialize::new(
-      config.rng_seed,
-      config.definition,
-      config.allowed_insecure.clone(),
-      config.allow_latest,
-      config.timeout,
-      config.native_components,
-      config.namespace,
-      config.constraints,
-      debug_span!("runtime:new"),
-    );
+  pub async fn new(seed: Seed, config: RuntimeInit) -> Result<Self> {
+    let timeout = config.timeout;
+    let init = Initialize::new(seed, config);
 
     let service = RuntimeService::new(init)
       .await
@@ -52,7 +51,7 @@ impl Runtime {
     Ok(Self {
       uid: service.id,
       inner: service,
-      timeout: config.timeout,
+      timeout,
     })
   }
 
@@ -92,28 +91,13 @@ impl Runtime {
   }
 }
 
-/// The [RuntimeBuilder] builds the configuration for a Wick [Runtime].
-#[derive(Default)]
-#[must_use]
-pub struct RuntimeBuilder {
-  allow_latest: bool,
-  allowed_insecure: Vec<String>,
-  manifest_builder: config::ComponentConfigurationBuilder,
-  constraints: Vec<RuntimeConstraint>,
-  timeout: Duration,
-  rng_seed: Option<Seed>,
-  namespace: Option<String>,
-  native_components: ComponentRegistry,
-}
-
 impl std::fmt::Debug for RuntimeBuilder {
   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     f.debug_struct("RuntimeBuilder")
       .field("allow_latest", &self.allow_latest)
       .field("allowed_insecure", &self.allowed_insecure)
-      .field("manifest_builder", &self.manifest_builder)
+      .field("manifest", &self.manifest)
       .field("timeout", &self.timeout)
-      .field("rng_seed", &self.rng_seed)
       .field("namespace", &self.namespace)
       .field("native_components", &self.native_components)
       .finish()
@@ -139,91 +123,70 @@ impl std::fmt::Display for RuntimeConstraint {
 }
 
 impl RuntimeBuilder {
+  #[must_use]
   pub fn new() -> Self {
-    Self {
-      timeout: Duration::from_secs(5),
-      ..Default::default()
-    }
+    Self::default()
   }
 
   /// Creates a new [RuntimeBuilder] from a [config::ComponentConfiguration]
-  pub fn from_definition(definition: config::ComponentConfiguration) -> Result<Self> {
-    Ok(Self {
-      allow_latest: definition.allow_latest(),
-      allowed_insecure: definition.insecure_registries().map(|v| v.to_vec()).unwrap_or_default(),
-      manifest_builder: config::ComponentConfigurationBuilder::from_base(definition),
-      timeout: Duration::from_secs(5),
-      native_components: ComponentRegistry::default(),
-      namespace: None,
-      rng_seed: None,
-      constraints: Vec::new(),
-    })
+  #[must_use]
+  pub fn from_definition(definition: ComponentConfiguration) -> Self {
+    let builder = Self::new();
+    builder
+      .allow_latest(definition.allow_latest())
+      .allowed_insecure(definition.insecure_registries().map(|v| v.to_vec()).unwrap_or_default())
+      .manifest(definition)
   }
 
   pub fn add_constraint(&mut self, constraint: RuntimeConstraint) -> &mut Self {
-    self.constraints.push(constraint);
+    let mut val = self.constraints.take().unwrap_or_default();
+    val.push(constraint);
+    self.constraints.replace(val);
     self
   }
 
   pub fn add_import(&mut self, component: config::ImportBinding) -> &mut Self {
-    self.manifest_builder.add_import(component);
+    let val = self.manifest.take().unwrap_or_default();
+    let mut val = ComponentConfigurationBuilder::from_base(val);
+    val.add_import(component);
+    self.manifest.replace(val.build().unwrap());
     self
   }
 
   pub fn add_resource(&mut self, resource: config::ResourceBinding) -> &mut Self {
-    self.manifest_builder.add_resource(resource);
+    let val = self.manifest.take().unwrap_or_default();
+    let mut val = ComponentConfigurationBuilder::from_base(val);
+    val.add_resource(resource);
+    self.manifest.replace(val.build().unwrap());
     self
   }
 
-  pub fn timeout(self, timeout: Duration) -> Self {
-    Self { timeout, ..self }
-  }
-
-  pub fn allow_latest(self, allow_latest: bool) -> Self {
-    Self { allow_latest, ..self }
-  }
-
-  pub fn allow_insecure(self, allowed_insecure: Vec<String>) -> Self {
-    Self {
-      allowed_insecure,
-      ..self
-    }
-  }
-
-  pub fn with_seed(self, seed: Seed) -> Self {
-    Self {
-      rng_seed: Some(seed),
-      ..self
-    }
-  }
-
-  pub fn namespace<T: AsRef<str>>(self, namespace: T) -> Self {
-    Self {
-      namespace: Some(namespace.as_ref().to_owned()),
-      ..self
-    }
-  }
-
-  pub fn native_component(&mut self, factory: Box<ComponentFactory>) {
-    self.native_components.add(factory);
+  pub fn add_native_component(&mut self, factory: Box<ComponentFactory>) -> &mut Self {
+    let mut val = self.native_components.take().unwrap_or_default();
+    val.add(factory);
+    self.native_components.replace(val);
+    self
   }
 
   /// Constructs an instance of a Wick [Runtime].
-  pub async fn build(self) -> Result<Runtime> {
-    let definition = self
-      .manifest_builder
-      .build()
-      .map_err(|e| RuntimeError::InitializationFailed(e.to_string()))?;
-    Runtime::new(RuntimeInit {
-      definition,
-      allow_latest: self.allow_latest,
-      allowed_insecure: self.allowed_insecure,
-      native_components: self.native_components,
-      timeout: self.timeout,
-      namespace: self.namespace,
-      rng_seed: self.rng_seed.unwrap_or_else(new_seed),
-      constraints: self.constraints,
-    })
+  pub async fn build(self, seed: Option<Seed>) -> Result<Runtime> {
+    let from_span = self.span.unwrap_or_else(tracing::Span::current);
+    let span = debug_span!("runtime");
+    span.follows_from(from_span);
+    let definition = self.manifest.unwrap_or_default();
+    Runtime::new(
+      seed.unwrap_or_else(new_seed),
+      RuntimeInit {
+        manifest: definition,
+        allow_latest: self.allow_latest.unwrap_or_default(),
+        allowed_insecure: self.allowed_insecure.unwrap_or_default(),
+        native_components: self.native_components.unwrap_or_default(),
+        timeout: self.timeout.unwrap_or_else(|| Duration::from_secs(5)),
+        namespace: self.namespace.unwrap_or_default(),
+        constraints: self.constraints.unwrap_or_default(),
+        span,
+      },
+    )
     .await
   }
 }
diff --git a/crates/wick/wick-runtime/src/runtime_service.rs b/crates/wick/wick-runtime/src/runtime_service.rs
index ce064dffb..d6bdfb697 100644
--- a/crates/wick/wick-runtime/src/runtime_service.rs
+++ b/crates/wick/wick-runtime/src/runtime_service.rs
@@ -8,7 +8,7 @@ use flow_graph_interpreter::{HandlerMap, InterpreterOptions, NamespaceHandler};
 use once_cell::sync::Lazy;
 use parking_lot::Mutex;
 use seeded_random::{Random, Seed};
-use tracing::{Instrument, Span};
+use tracing::Span;
 use uuid::Uuid;
 use wick_config::config::{ComponentConfiguration, ComponentKind, Metadata};
 use wick_config::Resolver;
@@ -24,89 +24,41 @@ use crate::components::{
   make_link_callback,
 };
 use crate::dev::prelude::*;
-use crate::runtime::RuntimeConstraint;
+use crate::runtime::{RuntimeConstraint, RuntimeInit};
 use crate::{BoxFuture, V0_NAMESPACE};
 
 type Result<T> = std::result::Result<T, EngineError>;
 #[derive(Debug)]
 pub(crate) struct Initialize {
   pub(crate) id: Uuid,
-  pub(crate) manifest: ComponentConfiguration,
-  pub(crate) allowed_insecure: Vec<String>,
-  pub(crate) allow_latest: bool,
-  pub(crate) timeout: Duration,
-  pub(crate) native_components: ComponentRegistry,
-  pub(crate) namespace: Option<String>,
-  pub(crate) span: Span,
-  pub(crate) constraints: Vec<RuntimeConstraint>,
+  pub(crate) config: RuntimeInit,
   rng: Random,
 }
 
 impl Initialize {
-  #[allow(clippy::too_many_arguments)]
-  pub(crate) fn new(
-    rng_seed: Seed,
-    manifest: ComponentConfiguration,
-    allowed_insecure: Vec<String>,
-    allow_latest: bool,
-    timeout: Duration,
-    native_components: ComponentRegistry,
-    namespace: Option<String>,
-    constraints: Vec<RuntimeConstraint>,
-    span: Span,
-  ) -> Self {
-    let rng = Random::from_seed(rng_seed);
+  pub(crate) fn new(seed: Seed, config: RuntimeInit) -> Self {
+    let rng = Random::from_seed(seed);
     Self {
       id: rng.uuid(),
-      manifest,
-      allowed_insecure,
-      allow_latest,
-      timeout,
-      native_components,
-      namespace,
-      span,
-      constraints,
       rng,
+      config,
     }
   }
 
-  #[allow(clippy::too_many_arguments)]
-  pub(crate) fn new_with_id(
-    id: Uuid,
-    rng_seed: Seed,
-    manifest: ComponentConfiguration,
-    allowed_insecure: Vec<String>,
-    allow_latest: bool,
-    timeout: Duration,
-    native_components: ComponentRegistry,
-    namespace: Option<String>,
-    constraints: Vec<RuntimeConstraint>,
-    span: Span,
-  ) -> Self {
-    let rng = Random::from_seed(rng_seed);
-    Self {
-      id,
-      manifest,
-      allowed_insecure,
-      allow_latest,
-      timeout,
-      native_components,
-      namespace,
-      span,
-      constraints,
-      rng,
-    }
+  pub(crate) fn new_with_id(id: Uuid, seed: Seed, config: RuntimeInit) -> Self {
+    let rng = Random::from_seed(seed);
+    Self { id, config, rng }
   }
 
   fn component_init(&self) -> ComponentInitOptions {
     ComponentInitOptions {
       rng_seed: self.rng.seed(),
       runtime_id: self.id,
-      allow_latest: self.allow_latest,
-      allowed_insecure: self.allowed_insecure.clone(),
-      timeout: self.timeout,
-      resolver: self.manifest.resolver(),
-      span: &self.span,
+      allow_latest: self.config.allow_latest,
+      allowed_insecure: self.config.allowed_insecure.clone(),
+      timeout: self.config.timeout,
+      resolver: self.config.manifest.resolver(),
+      span: self.config.span.clone(),
     }
   }
 
@@ -116,7 +68,7 @@ impl Initialize {
 }
 
 #[must_use]
-pub type ComponentFactory = dyn Fn(Seed) -> Result<NamespaceHandler> + Send;
+pub type ComponentFactory = dyn Fn(Seed) -> Result<NamespaceHandler> + Send + Sync;
 
 #[derive()]
 pub(crate) struct ComponentRegistry(Vec<Box<ComponentFactory>>);
@@ -164,60 +116,72 @@ static HOST_REGISTRY: Lazy<Mutex<ServiceMap>> = Lazy::new(|| Mutex::new(HashMap:
 
 impl RuntimeService {
   pub(crate) async fn new(mut init: Initialize) -> Result<Arc<Self>> {
-    debug!("initializing engine service");
-    let graph = flow_graph_interpreter::graph::from_def(&mut init.manifest)?;
+    let span = init.config.span.clone();
+    let graph = init.config.span.in_scope(|| {
+      debug!("initializing engine service");
+      flow_graph_interpreter::graph::from_def(&mut init.config.manifest)
+    })?;
+
+    let config = &init.config;
+
     let mut components = HandlerMap::default();
-    let ns = init.namespace.clone().unwrap_or_else(|| init.id.to_string());
+    let ns = config.namespace.clone().unwrap_or_else(|| init.id.to_string());
 
-    if init.manifest.component().kind() == ComponentKind::Composite {
-      for native_comp in init.native_components.inner() {
+    if config.manifest.component().kind() == ComponentKind::Composite {
+      for native_comp in config.native_components.inner() {
         components
           .add(native_comp(init.seed())?)
-          .map_err(|e| EngineError::InterpreterInit(init.manifest.source().map(Into::into), Box::new(e)))?;
+          .map_err(|e| EngineError::InterpreterInit(config.manifest.source().map(Into::into), Box::new(e)))?;
       }
     } else {
       let component_init = init.component_init();
-      let binding = config::ImportBinding::new(&ns, init.manifest.component().clone().into());
+      let binding = config::ImportBinding::new(&ns, config.manifest.component().clone().into());
 
       if let Some(main_component) = instantiate_import(&binding, component_init).await? {
         let reported_sig = main_component.component().list();
-        let manifest_sig = init.manifest.signature()?;
-        debug!("manifest_sig: {:?}", init.manifest);
+        let manifest_sig = config.manifest.signature()?;
+        span.in_scope(|| debug!("manifest_sig: {:?}", config.manifest));
 
         expect_signature_match(
-          init.manifest.source(),
+          config.manifest.source(),
           reported_sig,
-          init.manifest.source(),
+          config.manifest.source(),
           &manifest_sig,
         )?;
         main_component.expose();
 
-        debug!("Adding main component: {}", main_component.namespace());
+        span.in_scope(|| debug!("Adding main component: {}", main_component.namespace()));
         components
           .add(main_component)
-          .map_err(|e| EngineError::InterpreterInit(init.manifest.source().map(Into::into), Box::new(e)))?;
+          .map_err(|e| EngineError::InterpreterInit(config.manifest.source().map(Into::into), Box::new(e)))?;
       }
     }
 
-    for component in init.manifest.import().values() {
+    for component in config.manifest.import().values() {
       let component_init = init.component_init();
       if let Some(p) = instantiate_import(component, component_init).await? {
         components
           .add(p)
-          .map_err(|e| EngineError::InterpreterInit(init.manifest.source().map(Into::into), Box::new(e)))?;
+          .map_err(|e| EngineError::InterpreterInit(config.manifest.source().map(Into::into), Box::new(e)))?;
       }
     }
 
-    let source = init.manifest.source();
+    let source = config.manifest.source();
     let callback = make_link_callback(init.id);
-    assert_constraints(&init.constraints, &components)?;
-
-    let mut interpreter =
-      flow_graph_interpreter::Interpreter::new(Some(init.seed()), graph, Some(ns.clone()), Some(components), callback)
-        .map_err(|e| EngineError::InterpreterInit(source.map(Into::into), Box::new(e)))?;
+    assert_constraints(&config.constraints, &components)?;
+
+    let mut interpreter = flow_graph_interpreter::Interpreter::new(
+      Some(init.seed()),
+      graph,
+      Some(ns.clone()),
+      Some(components),
+      callback,
+      &span,
+    )
+    .map_err(|e| EngineError::InterpreterInit(source.map(Into::into), Box::new(e)))?;
 
     let options = InterpreterOptions {
-      output_timeout: init.timeout,
+      output_timeout: config.timeout,
       ..Default::default()
     };
     interpreter.start(Some(options), None).await;
@@ -235,33 +199,30 @@ impl RuntimeService {
     Ok(engine)
   }
 
-  pub(crate) fn new_from_manifest<'a, 'b>(
+  pub(crate) fn init_child(
     uid: Uuid,
     manifest: ComponentConfiguration,
     namespace: Option<String>,
-    opts: ComponentInitOptions<'b>,
-  ) -> BoxFuture<'b, Result<Arc<RuntimeService>>>
-  where
-    'a: 'b,
-  {
-    let init = Initialize::new_with_id(
-      uid,
-      opts.rng_seed,
+    opts: ComponentInitOptions,
+  ) -> BoxFuture<'static, Result<Arc<RuntimeService>>> {
+    let child_span = debug_span!("child",id=%uid);
+    child_span.follows_from(opts.span);
+    let config = RuntimeInit {
       manifest,
-      opts.allowed_insecure,
-      opts.allow_latest,
-      opts.timeout,
-      ComponentRegistry::default(),
+      allow_latest: opts.allow_latest,
+      allowed_insecure: opts.allowed_insecure,
+      timeout: opts.timeout,
       namespace,
-      vec![],
-      debug_span!("engine:new"),
-    );
+      constraints: Default::default(),
+      span: child_span,
+      native_components: ComponentRegistry::default(),
+    };
+    let init = Initialize::new_with_id(uid, opts.rng_seed, config);
 
     Box::pin(async move { RuntimeService::new(init).await })
   }
 
   pub(crate) fn for_id(id: &Uuid) -> Option<Arc<Self>> {
-    trace!(%id, "get engine service");
     let registry = HOST_REGISTRY.lock();
     registry.get(id).cloned()
   }
@@ -338,7 +299,7 @@ impl InvocationHandler for RuntimeService {
 }
 
 #[derive()]
-pub(crate) struct ComponentInitOptions<'a> {
+pub(crate) struct ComponentInitOptions {
   pub(crate) rng_seed: Seed,
   pub(crate) runtime_id: Uuid,
   pub(crate) allow_latest: bool,
@@ -346,10 +307,10 @@ pub(crate) struct ComponentInitOptions<'a> {
   pub(crate) timeout: Duration,
   pub(crate) resolver: Box<Resolver>,
   #[allow(unused)]
-  pub(crate) span: &'a Span,
+  pub(crate) span: Span,
 }
 
-impl<'a> std::fmt::Debug for ComponentInitOptions<'a> {
+impl std::fmt::Debug for ComponentInitOptions {
   fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     f.debug_struct("ComponentInitOptions")
       .field("rng_seed", &self.rng_seed)
@@ -361,25 +322,20 @@ impl<'a> std::fmt::Debug for ComponentInitOptions<'a> {
   }
 }
 
-pub(crate) async fn instantiate_import<'a, 'b>(
-  binding: &'a config::ImportBinding,
-  opts: ComponentInitOptions<'b>,
+pub(crate) async fn instantiate_import(
+  binding: &config::ImportBinding,
+  opts: ComponentInitOptions,
 ) -> Result<Option<NamespaceHandler>> {
-  debug!(?binding, ?opts, "initializing component");
+  opts.span.in_scope(|| debug!(?binding, ?opts, "initializing component"));
   let id = binding.id().to_owned();
-  let span = opts.span.clone();
   match binding.kind() {
     config::ImportDefinition::Component(c) => {
       match c {
         #[allow(deprecated)]
-        config::ComponentDefinition::Wasm(def) => Ok(Some(
-          init_wasm_component(def, id, opts, Default::default())
-            .instrument(span)
-            .await?,
-        )),
-        config::ComponentDefinition::Manifest(def) => {
-          Ok(Some(init_manifest_component(def, id, opts).instrument(span).await?))
+        config::ComponentDefinition::Wasm(def) => {
+          Ok(Some(init_wasm_component(def, id, opts, Default::default()).await?))
         }
+        config::ComponentDefinition::Manifest(def) => Ok(Some(init_manifest_component(def, id, opts).await?)),
         config::ComponentDefinition::Reference(_) => unreachable!(),
         config::ComponentDefinition::GrpcUrl(_) => todo!(), // CollectionKind::GrpcUrl(v) => initialize_grpc_collection(v, namespace).await,
         config::ComponentDefinition::HighLevelComponent(hlc) => {
diff --git a/crates/wick/wick-runtime/src/triggers.rs b/crates/wick/wick-runtime/src/triggers.rs
index 6aa88d927..8201abee0 100644
--- a/crates/wick/wick-runtime/src/triggers.rs
+++ b/crates/wick/wick-runtime/src/triggers.rs
@@ -9,13 +9,14 @@ use async_trait::async_trait;
 use once_cell::sync::Lazy;
 use parking_lot::Mutex;
 use structured_output::StructuredOutput;
+use tracing::Span;
 use wick_config::config::{AppConfiguration, ComponentDefinition, TriggerDefinition, TriggerKind};
 
 use crate::dev::prelude::*;
 use crate::resources::Resource;
 use crate::RuntimeBuilder;
 
-fn build_trigger_runtime(config: &AppConfiguration) -> Result<RuntimeBuilder, Infallible> {
+fn build_trigger_runtime(config: &AppConfiguration, span: Span) -> Result<RuntimeBuilder, Infallible> {
   let mut rt = RuntimeBuilder::default();
   for import in config.imports().values() {
     rt.add_import(import.clone());
@@ -23,6 +24,7 @@ fn build_trigger_runtime(config: &AppConfiguration) -> Result<RuntimeBuilder, In
   for resource in config.resources().values() {
     rt.add_resource(resource.clone());
   }
+  rt = rt.span(span);
   Ok(rt)
 }
 
@@ -34,6 +36,7 @@ pub trait Trigger {
     app_config: AppConfiguration,
     config: TriggerDefinition,
     resources: Arc<HashMap<String, Resource>>,
+    span: Span,
   ) -> Result<StructuredOutput, RuntimeError>;
   async fn shutdown_gracefully(self) -> Result<(), RuntimeError>;
   async fn wait_for_done(&self);
diff --git a/crates/wick/wick-runtime/src/triggers/cli.rs b/crates/wick/wick-runtime/src/triggers/cli.rs
index cb06c2c09..7c5df7270 100644
--- a/crates/wick/wick-runtime/src/triggers/cli.rs
+++ b/crates/wick/wick-runtime/src/triggers/cli.rs
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
 use serde_json::json;
 use structured_output::StructuredOutput;
 use tokio_stream::StreamExt;
+use tracing::{Instrument, Span};
 use wick_packet::{packet_stream, Entity, Invocation};
 
 use super::{build_trigger_runtime, resolve_ref, Trigger, TriggerKind};
@@ -55,11 +56,11 @@ impl Cli {
       Entity::server("cli_channel"),
       Entity::operation(cli_binding.id(), config.operation().operation()),
       None,
+      &Span::current(),
     );
-    let mut runtime = build_trigger_runtime(&app_config)?;
-
+    let mut runtime = build_trigger_runtime(&app_config, Span::current())?;
     runtime.add_import(cli_binding);
-    let runtime = runtime.build().await?;
+    let runtime = runtime.build(None).await?;
 
     let is_interactive = IsInteractive {
       stdin: atty::is(atty::Stream::Stdin),
@@ -72,7 +73,7 @@ impl Cli {
     let mut response = runtime.invoke(invocation, packet_stream, None).await?;
     let output = loop {
       if let Some(packet) = response.next().await {
-        trace!(?packet, "rigger:cli:response");
+        trace!(?packet, "trigger:cli:response");
         match packet {
           Ok(p) => {
             if p.port() == "code" && p.has_data() {
@@ -109,6 +110,7 @@ impl Trigger for Cli {
     app_config: AppConfiguration,
     config: TriggerDefinition,
     _resources: Arc<HashMap<String, Resource>>,
+    span: Span,
   ) -> Result<StructuredOutput, RuntimeError> {
     let config = if let TriggerDefinition::Cli(config) = config {
       config
@@ -128,7 +130,7 @@ impl Trigger for Cli {
     // Insert app name as the first argument.
     args.insert(0, name);
 
-    self.handle(app_config, config, args).await?;
+    self.handle(app_config, config, args).instrument(span).await?;
 
     Ok(StructuredOutput::default())
   }
diff --git a/crates/wick/wick-runtime/src/triggers/http.rs b/crates/wick/wick-runtime/src/triggers/http.rs
index 001ff580b..12394f0ba 100644
--- a/crates/wick/wick-runtime/src/triggers/http.rs
+++ b/crates/wick/wick-runtime/src/triggers/http.rs
@@ -20,6 +20,7 @@ use parking_lot::Mutex;
 use serde_json::json;
 use structured_output::StructuredOutput;
 use tokio::task::JoinHandle;
+use tracing::Span;
 use wick_config::config::components::Codec;
 use wick_config::config::{
   AppConfiguration,
@@ -73,24 +74,28 @@ struct HttpInstance {
 }
 
 impl HttpInstance {
-  async fn new(engine: Runtime, routers: Vec<HttpRouter>, socket: &SocketAddr) -> Self {
-    trace!(%socket,"http server starting");
+  async fn new(engine: Runtime, routers: Vec<HttpRouter>, initiating_span: &Span, socket: &SocketAddr) -> Self {
+    let span = debug_span!("http_server", %socket);
+    span.follows_from(initiating_span);
+
+    span.in_scope(|| trace!(%socket,"http server starting"));
     let (tx, rx) = tokio::sync::oneshot::channel::<()>();
     let (running_tx, running_rx) = tokio::sync::oneshot::channel::<()>();
-
-    let server = Server::bind(socket).serve(ServiceFactory::new(engine, routers));
+    let server = Server::bind(socket).serve(ServiceFactory::new(engine, routers, span.clone()));
+    let shutdown_span = span.clone();
     let handle = tokio::spawn(async move {
       let _ = server
         .with_graceful_shutdown(async move {
           match rx.await {
-            Ok(_) => trace!("http server received shutdown signal"),
-            Err(_) => trace!("http server shutdown signal dropped"),
+            Ok(_) => shutdown_span.in_scope(|| trace!("http server received shutdown signal")),
+            Err(_) => shutdown_span.in_scope(|| trace!("http server shutdown signal dropped")),
           }
-          trace!("http server shutting down");
+          shutdown_span.in_scope(|| trace!("http server shutting down"));
         })
         .await;
       let _ = running_tx.send(());
     });
+    span.in_scope(|| trace!(%socket,"http server started"));
 
     Self {
       handle,
@@ -152,15 +157,12 @@ struct RouterOperation {
 #[derive(Default)]
 pub(crate) struct Http {
   instance: Arc<Mutex<Option<HttpInstance>>>,
+  span: Option<Span>,
 }
 
 impl Http {
   pub(crate) fn load() -> Result<Arc<dyn Trigger + Send + Sync>, RuntimeError> {
-    Ok(Arc::new(Http::load_impl()?))
-  }
-
-  pub(crate) fn load_impl() -> Result<Http, RuntimeError> {
-    Ok(Self::default())
+    Ok(Arc::new(Self::default()))
   }
 
   async fn handle(
@@ -168,31 +170,36 @@ impl Http {
     app_config: AppConfiguration,
     config: config::HttpTriggerConfig,
     resources: Arc<HashMap<String, Resource>>,
+    span: Span,
     socket: &SocketAddr,
   ) -> Result<HttpInstance, RuntimeError> {
-    let mut rt = build_trigger_runtime(&app_config)?;
-    let mut routers = Vec::new();
-
-    for (i, router) in config.routers().iter().enumerate() {
-      let (router_bindings, router, constraints) = match router {
-        config::HttpRouterConfig::RawRouter(r) => register_raw_router(i, &app_config, r)?,
-        config::HttpRouterConfig::StaticRouter(r) => register_static_router(i, resources.clone(), &app_config, r)?,
-        config::HttpRouterConfig::ProxyRouter(r) => register_proxy_router(i, resources.clone(), &app_config, r)?,
-        config::HttpRouterConfig::RestRouter(r) => register_rest_router(i, resources.clone(), &app_config, r)?,
-      };
-      for constraint in constraints {
-        rt.add_constraint(constraint);
-      }
-      for binding in router_bindings {
-        rt.add_import(binding);
+    let mut rt = build_trigger_runtime(&app_config, span.clone())?;
+
+    let routers = span.in_scope(|| {
+      let mut routers = Vec::new();
+      for (i, router) in config.routers().iter().enumerate() {
+        let (router_bindings, router, constraints) = match router {
+          config::HttpRouterConfig::RawRouter(r) => register_raw_router(i, &app_config, r)?,
+          config::HttpRouterConfig::StaticRouter(r) => register_static_router(i, resources.clone(), &app_config, r)?,
+          config::HttpRouterConfig::ProxyRouter(r) => register_proxy_router(i, resources.clone(), &app_config, r)?,
+          config::HttpRouterConfig::RestRouter(r) => register_rest_router(i, resources.clone(), &app_config, r)?,
+        };
+        for constraint in constraints {
+          rt.add_constraint(constraint);
+        }
+        for binding in router_bindings {
+          rt.add_import(binding);
+        }
+
+        routers.push(router);
       }
+      debug!(?routers, "http routers");
+      Ok::<_, RuntimeError>(routers)
+    })?;
 
-      routers.push(router);
-    }
-    debug!(?routers, "http routers");
-    let engine = rt.build().await?;
+    let engine = rt.build(None).await?;
 
-    let instance = HttpInstance::new(engine, routers, socket).await;
+    let instance = HttpInstance::new(engine, routers, &span, socket).await;
 
     Ok(instance)
   }
@@ -368,8 +375,9 @@ impl Trigger for Http {
     app_config: AppConfiguration,
     config: config::TriggerDefinition,
     resources: Arc<HashMap<String, Resource>>,
+    span: Span,
   ) -> Result<StructuredOutput, RuntimeError> {
-    debug!(kind = %TriggerKind::Http, "trigger:run");
+    span.in_scope(|| debug!(kind = %TriggerKind::Http, "trigger:run"));
     let config = if let config::TriggerDefinition::Http(config) = config {
       config
     } else {
@@ -390,12 +398,11 @@ impl Trigger for Http {
       }
     };
 
-    let instance = self.handle(app_config, config, resources, &socket).await?;
+    let instance = self.handle(app_config, config, resources, span, &socket).await?;
     let output = StructuredOutput::new(
       format!("HTTP Server started on {}", instance.addr),
       json!({"ip": instance.addr.ip(),"port": instance.addr.port()}),
     );
-    info!("{}", output.lines());
 
     self.instance.lock().replace(instance);
 
@@ -403,7 +410,11 @@ impl Trigger for Http {
   }
 
   async fn shutdown_gracefully(self) -> Result<(), RuntimeError> {
-    info!("HTTP server shutting down gracefully");
+    self
+      .span
+      .clone()
+      .unwrap_or_else(Span::current)
+      .in_scope(|| info!("HTTP server shutting down gracefully"));
     if self.instance.lock().is_none() {
       return Ok(());
     }
@@ -459,12 +470,18 @@ mod test {
       .await?
       .try_app_config()?;
 
-    let trigger = Http::load_impl()?;
+    let trigger = Http::default();
     let resource = Resource::new(app_config.resources().get("http").as_ref().unwrap().kind().clone())?;
     let resources = Arc::new([("http".to_owned(), resource)].iter().cloned().collect());
     let trigger_config = app_config.triggers()[0].clone();
     trigger
-      .run("test".to_owned(), app_config, trigger_config, resources)
+      .run(
+        "test".to_owned(),
+        app_config,
+        trigger_config,
+        resources,
+        Span::current(),
+      )
       .await?;
     let client = reqwest::Client::new();
     let res = client
@@ -491,12 +508,18 @@ mod test {
     let app_config = config::WickConfiguration::fetch(yaml.to_string_lossy(), Default::default())
       .await?
       .try_app_config()?;
-    let trigger = Http::load_impl()?;
+    let trigger = Http::default();
     let resource = Resource::new(app_config.resources().get("http").as_ref().unwrap().kind().clone())?;
     let resources = Arc::new([("http".to_owned(), resource)].iter().cloned().collect());
     let trigger_config = app_config.triggers()[0].clone();
     trigger
-      .run("test".to_owned(), app_config, trigger_config, resources)
+      .run(
+        "test".to_owned(),
+        app_config,
+        trigger_config,
+        resources,
+        Span::current(),
+      )
       .await?;
     let client = reqwest::Client::new();
     let res: serde_json::Value = client
diff --git a/crates/wick/wick-runtime/src/triggers/http/component_utils.rs b/crates/wick/wick-runtime/src/triggers/http/component_utils.rs
index cfc4c5828..ff3c540c4 100644
--- a/crates/wick/wick-runtime/src/triggers/http/component_utils.rs
+++ b/crates/wick/wick-runtime/src/triggers/http/component_utils.rs
@@ -9,6 +9,7 @@ use hyper::http::{HeaderName, HeaderValue};
 use hyper::{Body, Request, Response, StatusCode};
 use serde_json::{Map, Value};
 use tokio_stream::StreamExt;
+use tracing::Span;
 use wick_config::config::components::Codec;
 use wick_packet::{packets, Entity, Invocation, Observer, Packet, PacketStream};
 
@@ -24,7 +25,7 @@ pub(super) async fn handle(
   req: Request<Body>,
 ) -> Result<PacketStream, HttpError> {
   let (tx, rx) = PacketStream::new_channels();
-  let invocation = Invocation::new(Entity::server("http_client"), target, None);
+  let invocation = Invocation::new(Entity::server("http_client"), target, None, &Span::current());
   let stream = engine
     .invoke(invocation, rx, None)
     .await
diff --git a/crates/wick/wick-runtime/src/triggers/http/proxy_router.rs b/crates/wick/wick-runtime/src/triggers/http/proxy_router.rs
index ed62ffe3f..48eb780a4 100644
--- a/crates/wick/wick-runtime/src/triggers/http/proxy_router.rs
+++ b/crates/wick/wick-runtime/src/triggers/http/proxy_router.rs
@@ -3,6 +3,7 @@ use std::sync::Arc;
 
 use futures::future::BoxFuture;
 use hyper::{Body, Request, Response, StatusCode};
+use tracing::Span;
 use wick_config::config::UrlResource;
 
 use super::{HttpError, RawRouter};
@@ -13,14 +14,19 @@ use crate::Runtime;
 pub(super) struct ProxyRouter {
   url: String,
   strip: Option<String>,
+  span: Span,
 }
 
 impl ProxyRouter {
   pub(super) fn new(url: UrlResource, strip: Option<String>) -> Self {
     let url = url.to_string();
     let url = url.trim_end_matches('/').to_owned();
-    debug!(url = %url, "wick:http:proxy: proxying");
-    Self { url, strip }
+
+    Self {
+      strip,
+      span: debug_span!("http:proxy",url = %url),
+      url,
+    }
   }
 }
 
@@ -37,9 +43,13 @@ impl RawRouter for ProxyRouter {
       let orig_path = request.uri().path_and_query().unwrap().as_str().to_owned();
       let path = orig_path.trim_start_matches(to_strip);
       *request.uri_mut() = path.parse().unwrap();
-      trace!(to= url, orig = orig_path, uri = %request.uri(), "http:trigger:proxy proxying");
+      self
+        .span
+        .in_scope(|| trace!(to= url, orig = orig_path, uri = %request.uri(), "http:trigger:proxy proxying"));
     } else {
-      trace!(to= url, uri = %request.uri(), "http:trigger:proxy proxying");
+      self
+        .span
+        .in_scope(|| trace!(to= url, uri = %request.uri(), "http:trigger:proxy proxying"));
     }
     // the proxy library does not set the appropriate host header, but if we delete
     // the header, it will get made correctly for us.
diff --git a/crates/wick/wick-runtime/src/triggers/http/raw_router.rs b/crates/wick/wick-runtime/src/triggers/http/raw_router.rs
index 70f9c1631..49c057cf6 100644
--- a/crates/wick/wick-runtime/src/triggers/http/raw_router.rs
+++ b/crates/wick/wick-runtime/src/triggers/http/raw_router.rs
@@ -5,26 +5,27 @@ use std::task::{Context, Poll};
 use futures::future::BoxFuture;
 use hyper::service::Service;
 use hyper::{Body, Request, Response};
+use tracing::{Instrument, Span};
 use wick_packet::Entity;
 
 use super::component_utils::{handle, respond};
 use super::{HttpError, RawRouter, RouterOperation};
 use crate::Runtime;
 
-static ID: &str = "wick:http:raw";
-
 #[derive()]
 #[must_use]
 pub(super) struct RawComponentRouter {
   config: Arc<RouterOperation>,
+  span: Span,
 }
 
 impl RawComponentRouter {
   pub(super) fn new(config: RouterOperation) -> Self {
-    debug!(component = %config.operation, "{}: serving", ID);
+    let span = debug_span!("http:raw", component = %config.operation);
 
     Self {
       config: Arc::new(config),
+      span,
     }
   }
 }
@@ -37,9 +38,13 @@ impl RawRouter for RawComponentRouter {
     request: Request<Body>,
   ) -> BoxFuture<Result<Response<Body>, HttpError>> {
     let handler = RawHandler::new(self.config.clone(), runtime);
+    let span = debug_span!("handling");
+    span.follows_from(&self.span);
+
     let fut = async move {
       let response = handler
         .serve(request)
+        .instrument(span)
         .await
         .map_err(|e| HttpError::OperationError(e.to_string()))?;
       Ok(response)
diff --git a/crates/wick/wick-runtime/src/triggers/http/rest_router.rs b/crates/wick/wick-runtime/src/triggers/http/rest_router.rs
index fb9ecb1fd..4f2c5a588 100644
--- a/crates/wick/wick-runtime/src/triggers/http/rest_router.rs
+++ b/crates/wick/wick-runtime/src/triggers/http/rest_router.rs
@@ -6,6 +6,7 @@ use futures::future::BoxFuture;
 use hyper::body::to_bytes;
 use hyper::service::Service;
 use hyper::{Body, Request, Response, StatusCode};
+use tracing::{Instrument, Span};
 use wick_config::config::{ComponentOperationExpression, ImportBinding, RestRouterConfig};
 use wick_packet::{Entity, Invocation, Packet};
 mod route;
@@ -14,31 +15,35 @@ use super::{HttpError, RawRouter};
 use crate::triggers::http::component_utils::stream_to_json;
 use crate::Runtime;
 
-static ID: &str = "wick:http:rest";
-
 #[derive()]
 #[must_use]
 pub(super) struct RestRouter {
   routes: Arc<Vec<RestRoute>>,
   root: String,
+  span: Span,
 }
 
 impl RestRouter {
   pub(super) fn new(config: RestRouterConfig, routes: Vec<RestRoute>) -> Self {
+    let span = debug_span!("http:rest", path = %config.path());
+
     let title = config
       .info()
       .and_then(|i| i.title().cloned())
       .unwrap_or_else(|| "Untitled API".to_owned());
 
-    debug!(api = %title, path=%config.path(), "{}: serving", ID);
-    for route in &routes {
-      debug!(route = ?route.route, "{}: route", ID);
-    }
-    let routes = Arc::new(routes);
+    let routes = span.in_scope(|| {
+      debug!(api = %title, path=%config.path(), "serving");
+      for route in &routes {
+        debug!(route = ?route.route, "route");
+      }
+      Arc::new(routes)
+    });
 
     Self {
       routes,
       root: config.path().to_owned(),
+      span,
     }
   }
 }
@@ -50,7 +55,9 @@ impl RawRouter for RestRouter {
     runtime: Arc<Runtime>,
     request: Request<Body>,
   ) -> BoxFuture<Result<Response<Body>, HttpError>> {
-    let handler = RestHandler::new(self.root.clone(), self.routes.clone(), runtime);
+    let span = debug_span!("handling");
+    span.follows_from(&self.span);
+    let handler = RestHandler::new(self.root.clone(), self.routes.clone(), runtime, span);
     let fut = async move {
       let response = handler
         .serve(request)
@@ -67,17 +74,28 @@ struct RestHandler {
   routes: Arc<Vec<RestRoute>>,
   runtime: Arc<Runtime>,
   root: String,
+  span: Span,
 }
 
 impl RestHandler {
-  fn new(root: String, routes: Arc<Vec<RestRoute>>, runtime: Arc<Runtime>) -> Self {
-    RestHandler { runtime, routes, root }
+  fn new(root: String, routes: Arc<Vec<RestRoute>>, runtime: Arc<Runtime>, span: Span) -> Self {
+    RestHandler {
+      runtime,
+      routes,
+      root,
+      span,
+    }
   }
 
   /// Serve a request.
   #[allow(clippy::unused_async)]
   async fn serve(self, request: Request<Body>) -> Result<Response<Body>, HttpError> {
-    let Self { routes, runtime, root } = self;
+    let Self {
+      routes,
+      runtime,
+      root,
+      span,
+    } = self;
 
     for route in routes.iter() {
       if !route.config.methods().is_empty() && !route.config.methods().contains(&request.method().to_string()) {
@@ -87,7 +105,7 @@ impl RestHandler {
       let Some((path_params, query_params)) = route.route.compare(path, request.uri().query())? else {
         continue
       };
-      debug!(route = %request.uri(), "{}: handling", ID);
+      span.in_scope(|| debug!(route = %request.uri(), "handling"));
       let mut packets: Vec<_> = path_params
         .iter()
         .chain(query_params.iter())
@@ -109,14 +127,17 @@ impl RestHandler {
       for port in port_names {
         packets.push(Packet::done(port));
       }
+
       let invocation = Invocation::new(
         Entity::server("http_client"),
         Entity::operation(route.component.id(), route.operation.operation()),
         None,
+        &span,
       );
 
       let stream = runtime
         .invoke(invocation, packets.into(), None)
+        .instrument(span)
         .await
         .map_err(|e| HttpError::OperationError(e.to_string()))?;
       let json = stream_to_json(stream).await?;
diff --git a/crates/wick/wick-runtime/src/triggers/http/rest_router/route.rs b/crates/wick/wick-runtime/src/triggers/http/rest_router/route.rs
index b36fc5cdf..2503cfd27 100644
--- a/crates/wick/wick-runtime/src/triggers/http/rest_router/route.rs
+++ b/crates/wick/wick-runtime/src/triggers/http/rest_router/route.rs
@@ -15,6 +15,8 @@ pub(super) struct Route {
   query_params: Vec<Field>,
 }
 
+pub(super) type MatchedValues = (Vec<FieldValue>, Vec<FieldValue>);
+
 impl Route {
   pub(super) fn parse(path: &str) -> Result<Self, wick_interface_types::ParserError> {
     let (path, query) = path.find('?').map_or((path, ""), |idx| {
@@ -62,11 +64,7 @@ impl Route {
     })
   }
 
-  pub(super) fn compare(
-    &self,
-    path: &str,
-    query_string: Option<&str>,
-  ) -> Result<Option<(Vec<FieldValue>, Vec<FieldValue>)>, HttpError> {
+  pub(super) fn compare(&self, path: &str, query_string: Option<&str>) -> Result<Option<MatchedValues>, HttpError> {
     let mut path_params = Vec::new();
     let mut query_params = Vec::new();
 
diff --git a/crates/wick/wick-runtime/src/triggers/http/service_factory.rs b/crates/wick/wick-runtime/src/triggers/http/service_factory.rs
index b55c4c002..27b264d26 100644
--- a/crates/wick/wick-runtime/src/triggers/http/service_factory.rs
+++ b/crates/wick/wick-runtime/src/triggers/http/service_factory.rs
@@ -8,6 +8,7 @@ use hyper::http::response::Builder;
 use hyper::server::conn::AddrStream;
 use hyper::service::Service;
 use hyper::{Body, Request, Response, StatusCode};
+use tracing::Span;
 
 use super::HttpRouter;
 use crate::Runtime;
@@ -15,13 +16,15 @@ use crate::Runtime;
 pub(super) struct ServiceFactory {
   engine: Arc<Runtime>,
   routers: Arc<Vec<HttpRouter>>,
+  span: Span,
 }
 
 impl ServiceFactory {
-  pub(super) fn new(engine: Runtime, routers: Vec<HttpRouter>) -> Self {
+  pub(super) fn new(engine: Runtime, routers: Vec<HttpRouter>, span: Span) -> Self {
     Self {
       engine: Arc::new(engine),
       routers: Arc::new(routers),
+      span,
     }
   }
 }
@@ -40,8 +43,10 @@ impl Service<&AddrStream> for ServiceFactory {
     let routers = self.routers.clone();
 
     let remote_addr = conn.remote_addr();
+    let span = debug_span!("connection", remote = %remote_addr);
+    span.follows_from(&self.span);
 
-    let fut = async move { Ok(ResponseService::new(remote_addr, engine, routers)) };
+    let fut = async move { Ok(ResponseService::new(remote_addr, engine, routers, span)) };
     Box::pin(fut)
   }
 }
@@ -50,14 +55,16 @@ pub(super) struct ResponseService {
   remote_addr: SocketAddr,
   engine: Arc<Runtime>,
   routers: Arc<Vec<HttpRouter>>,
+  span: Span,
 }
 
 impl ResponseService {
-  fn new(remote_addr: SocketAddr, engine: Arc<Runtime>, routers: Arc<Vec<HttpRouter>>) -> Self {
+  fn new(remote_addr: SocketAddr, engine: Arc<Runtime>, routers: Arc<Vec<HttpRouter>>, span: Span) -> Self {
     Self {
       remote_addr,
       engine,
       routers,
+      span,
     }
   }
 }
@@ -75,15 +82,17 @@ impl Service<Request<Body>> for ResponseService {
     let remote_addr = self.remote_addr;
     let time = chrono::Local::now().format("%d/%b/%Y:%H:%M:%S %z");
     let path = req.uri().path().to_owned();
-
-    info!(
-      "{} - [{}] \"{} {} {:?}\"",
-      remote_addr,
-      time,
-      req.method(),
-      req.uri().path(),
-      req.version()
-    );
+    let span = info_span!("request");
+    span.follows_from(&self.span);
+
+    span.in_scope(|| {
+      info!(
+        time = %time,
+        method = %req.method(),
+        path = req.uri().path(),
+        version = ?req.version(),
+      );
+    });
     let engine = self.engine.clone();
     let router = self
       .routers
@@ -99,23 +108,28 @@ impl Service<Request<Body>> for ResponseService {
             Ok(res) => {
               let status: u16 = res.status().into();
 
-              if status >= 300 {
-                error!(
-                  "[{}] [wick:http] [client {}] [{}] {}",
-                  start,
-                  remote_addr,
-                  path,
-                  res.status()
-                );
+              if status >= 400 {
+                span.in_scope(|| {
+                  error!(
+                    time=%start,
+                    path,
+                    status=%res.status(),
+                    "error",
+                  );
+                });
               };
 
               Ok(res)
             }
             Err(e) => {
-              error!(
-                "[{}] [wick:http] [client {}] [{}] Internal Server Error: {}",
-                start, remote_addr, path, e
-              );
+              span.in_scope(|| {
+                error!(
+                  time=%start,
+                  path,
+                  error=%e,
+                  "internal error",
+                );
+              });
 
               Ok(make_ise(e))
             }
diff --git a/crates/wick/wick-runtime/src/triggers/http/static_router.rs b/crates/wick/wick-runtime/src/triggers/http/static_router.rs
index 5bbb8e3e9..0f5fedbbb 100644
--- a/crates/wick/wick-runtime/src/triggers/http/static_router.rs
+++ b/crates/wick/wick-runtime/src/triggers/http/static_router.rs
@@ -7,23 +7,26 @@ use futures::future::BoxFuture;
 use hyper::service::Service;
 use hyper::{Body, Method, Request, Response};
 use hyper_staticfile::{resolve_path, ResolveResult, ResponseBuilder};
+use tracing::Span;
 
 use super::{HttpError, RawRouter};
 use crate::Runtime;
 
-static ID: &str = "wick:http:static";
-
 #[derive()]
 #[must_use]
 pub(super) struct StaticRouter {
   handler: Static,
+  #[allow(unused)]
+  span: Span,
 }
 
 impl StaticRouter {
   pub(super) fn new(root: PathBuf, strip: Option<String>, fallback: Option<String>) -> Self {
-    debug!(directory = %root.display(), "{}: serving", ID);
+    let span = debug_span!("http:static");
+
+    span.in_scope(|| debug!(directory = %root.display(), "serving"));
     let handler = Static::new(root, strip, fallback);
-    Self { handler }
+    Self { handler, span }
   }
 }
 
@@ -88,7 +91,13 @@ impl Static {
 
     let path = strip.map_or_else(
       || request.uri().path(),
-      |path| request.uri().path().trim_start_matches(&path),
+      |path| {
+        if path.len() > 1 {
+          request.uri().path().trim_start_matches(&path)
+        } else {
+          request.uri().path()
+        }
+      },
     );
 
     let result = resolve_path(root.clone(), path).await;
diff --git a/crates/wick/wick-runtime/src/triggers/time.rs b/crates/wick/wick-runtime/src/triggers/time.rs
index 3cf12ae86..8e1c436d4 100644
--- a/crates/wick/wick-runtime/src/triggers/time.rs
+++ b/crates/wick/wick-runtime/src/triggers/time.rs
@@ -12,6 +12,7 @@ use serde_json::json;
 use structured_output::StructuredOutput;
 use tokio::time::Duration;
 use tokio_stream::StreamExt;
+use tracing::Span;
 use wick_packet::{Entity, Packet};
 
 use super::{build_trigger_runtime, Trigger, TriggerKind};
@@ -35,6 +36,7 @@ async fn invoke_operation(
     Entity::server("schedule_client"),
     Entity::operation("0", operation.as_str()),
     None,
+    &Span::current(),
   );
 
   let mut response = runtime.invoke(invocation, packetstream, None).await?;
@@ -51,16 +53,17 @@ async fn create_schedule(
 ) -> tokio::task::JoinHandle<()> {
   // Create a scheduler loop
   tokio::spawn(async move {
+    let span = debug_span!("trigger:schedule", schedule = ?schedule);
     let schedule_component = match resolve_ref(&app_config, config.operation().component()) {
       Ok(component) => component,
       Err(err) => panic!("Unable to resolve component: {}", err),
     };
 
-    let mut runtime = build_trigger_runtime(&app_config).unwrap();
+    let mut runtime = build_trigger_runtime(&app_config, span.clone()).unwrap();
     let schedule_binding = config::ImportBinding::component("0", schedule_component);
     runtime.add_import(schedule_binding);
     // needed for invoke command
-    let runtime = runtime.build().await.unwrap();
+    let runtime = runtime.build(None).await.unwrap();
 
     let runtime = Arc::new(runtime);
     let operation = Arc::new(config.operation().operation().to_owned());
@@ -80,20 +83,21 @@ async fn create_schedule(
 
       // Calculate the duration until the next scheduled time
       let duration = next.signed_duration_since(Utc::now());
-      debug!("duration until next schedule: {:?}", duration);
+      span.in_scope(|| debug!("duration until next schedule: {:?}", duration));
 
       tokio::time::sleep(Duration::from_millis(duration.num_milliseconds() as u64)).await;
 
-      debug!("done sleeping");
+      span.in_scope(|| debug!("done sleeping"));
 
       let rt_clone = runtime.clone();
       let operation_clone = operation.clone();
       let payload_clone = payload.clone();
 
       let fut = invoke_operation(rt_clone, operation_clone, payload_clone);
+      let span = span.clone();
       tokio::spawn(async move {
         if let Err(e) = fut.await {
-          error!("Error invoking operation: {}", e);
+          span.in_scope(|| error!("Error invoking operation: {}", e));
         }
       });
     }
@@ -124,7 +128,6 @@ impl Time {
     app_config: AppConfiguration,
     config: TimeTriggerConfig,
   ) -> Result<StructuredOutput, RuntimeError> {
-    debug!("Self: {:?}", self);
     let cron = config.schedule().cron().to_owned();
 
     // Create a new cron schedule that runs every minute
@@ -158,6 +161,7 @@ impl Trigger for Time {
     app_config: AppConfiguration,
     config: TriggerDefinition,
     _resources: Arc<HashMap<String, Resource>>,
+    _span: Span,
   ) -> Result<StructuredOutput, RuntimeError> {
     let config = if let TriggerDefinition::Time(config) = config {
       config
@@ -206,7 +210,13 @@ mod test {
     let trigger = Time::load()?;
     let trigger_config = app_config.triggers()[0].clone();
     trigger
-      .run("test".to_owned(), app_config, trigger_config, Default::default())
+      .run(
+        "test".to_owned(),
+        app_config,
+        trigger_config,
+        Default::default(),
+        Span::current(),
+      )
       .await?;
     debug!("scheduler is running");
     trigger.wait_for_done().await;
diff --git a/crates/wick/wick-settings/src/settings.rs b/crates/wick/wick-settings/src/settings.rs
index 29313af73..0b9dc2753 100644
--- a/crates/wick/wick-settings/src/settings.rs
+++ b/crates/wick/wick-settings/src/settings.rs
@@ -13,20 +13,23 @@ use crate::error::Error;
 pub struct Settings {
   #[serde(default)]
   /// Logging configuration.
-  pub logging: Logging,
-  #[serde(default)]
+  pub trace: TraceSettings,
   /// Registry credentials.
+  #[serde(default, skip_serializing_if = "Vec::is_empty")]
   pub credentials: Vec<Credential>,
   #[serde(skip)]
   /// Where this configuration was loaded from.
   pub source: Option<PathBuf>,
 }
 
-#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy)]
+#[derive(Debug, Default, Deserialize, Serialize, Clone)]
 #[serde(rename_all = "snake_case")]
 #[serde(deny_unknown_fields)]
 /// Logging configuration.
-pub struct Logging {
+pub struct TraceSettings {
+  /// OTLP endpoint endpoint.
+  #[serde(default, skip_serializing_if = "Option::is_none")]
+  pub otlp: Option<String>,
   /// Logging level.
   #[serde(default)]
   pub level: LogLevel,
@@ -119,6 +122,7 @@ impl Default for LogModifier {
 
 impl Settings {
   pub fn new() -> Self {
+    let _span = tracing::debug_span!("settings").entered();
     let extensions = vec!["yaml", "yml"];
 
     let mut config_locations = Vec::new();
@@ -129,7 +133,7 @@ impl Settings {
 
     tracing::debug!(
       paths = ?config_locations.iter().map(|v| format!("{}.({})",v,extensions.join(","))).collect::<Vec<_>>(),
-      "wick:settings: searching for config files"
+      "searching for config files"
     );
 
     let mut files = Vec::new();
@@ -142,25 +146,25 @@ impl Settings {
           match std::fs::read_to_string(&path) {
             Ok(src) => match serde_yaml::from_str::<Settings>(&src) {
               Ok(mut settings) => {
-                debug!(file=%path.display(),"wick:settings: found config");
+                debug!(file=%path.display(),"found config");
                 settings.source = Some(path.clone());
                 files.push(settings);
 
                 break; // only load the first one, fix when merging is implemented.
               }
               Err(e) => {
-                warn!(error=%e,file=%path.display(),"wick:settings: failed to parse config");
+                warn!(error=%e,file=%path.display(),"failed to parse config");
               }
             },
             Err(e) => {
-              warn!(error=%e,file=%path.display(),"wick:settings: failed to read config");
+              warn!(error=%e,file=%path.display(),"failed to read config");
             }
           };
         }
       }
     }
 
-    debug!("wick:settings: loaded");
+    debug!("loaded");
 
     // You can deserialize (and thus freeze) the entire configuration as
     if !files.is_empty() {


Test patch:
diff --git a/crates/bins/wick/src/commands/test.rs b/crates/bins/wick/src/commands/test.rs
index 711858358..a2a9af372 100644
--- a/crates/bins/wick/src/commands/test.rs
+++ b/crates/bins/wick/src/commands/test.rs
@@ -36,7 +36,7 @@ pub(crate) struct TestCommand {
   filter: Vec<String>,
 }
 
-pub(crate) async fn handle(opts: TestCommand, _settings: wick_settings::Settings) -> Result<()> {
+pub(crate) async fn handle(opts: TestCommand, _settings: wick_settings::Settings, span: tracing::Span) -> Result<()> {
   let fetch_options = wick_config::config::FetchOptions::new()
     .allow_latest(opts.oci.allow_latest)
     .allow_insecure(&opts.oci.insecure_registries);
@@ -50,7 +50,7 @@ pub(crate) async fn handle(opts: TestCommand, _settings: wick_settings::Settings
 
   let config = merge_config(&config, &opts.oci, Some(server_options));
 
-  let mut host = ComponentHostBuilder::from_definition(config).build();
+  let mut host = ComponentHostBuilder::default().manifest(config).span(span).build()?;
   host.start_engine(opts.seed.map(Seed::unsafe_new)).await?;
 
   let component = Arc::new(wick_host::HostComponent::new(host));
diff --git a/crates/misc/test-logger/src/lib.rs b/crates/misc/test-logger/src/lib.rs
index d05694064..87c3c5950 100644
--- a/crates/misc/test-logger/src/lib.rs
+++ b/crates/misc/test-logger/src/lib.rs
@@ -101,7 +101,8 @@ fn expand_logging_init() -> Tokens {
         level: crate::LogLevel::Trace,
         silly: true,
         app_name: "test".to_owned(),
-        jaeger_endpoint: std::env::var("OTEL_EXPORTER_JAEGER_ENDPOINT").ok(),
+        otlp_endpoint: std::env::var("OTLP_ENDPOINT").ok(),
+        global:true,
         ..Default::default()
       });
     },
@@ -114,7 +115,8 @@ fn expand_logging_init() -> Tokens {
             level: #ident::LogLevel::Trace,
             silly: true,
             app_name: "test".to_owned(),
-            jaeger_endpoint: std::env::var("OTEL_EXPORTER_JAEGER_ENDPOINT").ok(),
+            otlp_endpoint: std::env::var("OTLP_ENDPOINT").ok(),
+            global:true,
             ..Default::default()
           });
 
diff --git a/crates/wick/flow-graph-interpreter/tests/test/mod.rs b/crates/wick/flow-graph-interpreter/tests/test/mod.rs
index 0fca30622..136af0cfd 100644
--- a/crates/wick/flow-graph-interpreter/tests/test/mod.rs
+++ b/crates/wick/flow-graph-interpreter/tests/test/mod.rs
@@ -44,7 +44,7 @@ pub(crate) async fn base_setup(
     Box::new(test::TestComponent::new()),
   )])
   .unwrap();
-  let invocation = Invocation::new(Entity::test("test"), entity, None);
+  let invocation = Invocation::test("test", entity, None)?;
 
   let mut interpreter = Interpreter::new(
     Some(Seed::unsafe_new(1)),
@@ -52,6 +52,7 @@ pub(crate) async fn base_setup(
     None,
     Some(collections),
     panic_callback(),
+    &tracing::Span::current(),
   )?;
   interpreter.start(options, None).await;
   let stream = wick_packet::PacketStream::new(Box::new(futures::stream::iter(packets.into_iter().map(Ok))));
diff --git a/crates/wick/flow-graph-interpreter/tests/test/test_component.rs b/crates/wick/flow-graph-interpreter/tests/test/test_component.rs
index 595fdfc59..b44ee9212 100644
--- a/crates/wick/flow-graph-interpreter/tests/test/test_component.rs
+++ b/crates/wick/flow-graph-interpreter/tests/test/test_component.rs
@@ -8,7 +8,7 @@ use futures::{Future, StreamExt};
 use seeded_random::{Random, Seed};
 use tokio::spawn;
 use tokio::task::JoinHandle;
-use tracing::trace;
+use tracing::{trace, Span};
 use wasmrs_rx::{FluxChannel, Observer};
 use wick_interface_types::{ComponentSignature, OperationSignature, TypeSignature};
 use wick_packet::{fan_out, packet_stream, ComponentReference, Invocation, Packet, PacketStream};
@@ -213,7 +213,9 @@ fn handler(
           let link: ComponentReference = component.deserialize().unwrap();
           let message: String = message.deserialize().unwrap();
           let packets = packet_stream!(("input", message));
-          let mut response = callback(link, "reverse".to_owned(), packets, None, None).await.unwrap();
+          let mut response = callback(link, "reverse".to_owned(), packets, None, None, &Span::current())
+            .await
+            .unwrap();
           while let Some(Ok(res)) = response.next().await {
             defer(vec![send(res.set_port("output"))]);
           }
diff --git a/crates/wick/flow-graph-interpreter/tests/validation.rs b/crates/wick/flow-graph-interpreter/tests/validation.rs
index 7bb8393c6..b0df8893c 100644
--- a/crates/wick/flow-graph-interpreter/tests/validation.rs
+++ b/crates/wick/flow-graph-interpreter/tests/validation.rs
@@ -9,6 +9,7 @@ use flow_graph_interpreter::{HandlerMap, Interpreter, NamespaceHandler};
 use pretty_assertions::assert_eq;
 type BoxFuture<'a, T> = std::pin::Pin<Box<dyn futures::Future<Output = T> + Send + 'a>>;
 use seeded_random::Seed;
+use tracing::Span;
 use wick_interface_types::{ComponentMetadata, ComponentSignature, OperationSignature, TypeSignature};
 use wick_packet::{Invocation, PacketStream};
 fn load<T: AsRef<Path>>(path: T) -> Result<wick_config::config::ComponentConfiguration> {
@@ -49,6 +50,7 @@ fn interp(path: &str, sig: ComponentSignature) -> std::result::Result<Interprete
     None,
     Some(collections(sig)),
     panic_callback(),
+    &Span::current(),
   )
 }
 
@@ -56,8 +58,14 @@ fn interp(path: &str, sig: ComponentSignature) -> std::result::Result<Interprete
 async fn test_missing_collections() -> Result<()> {
   let mut manifest = load("./tests/manifests/v0/external.yaml")?;
   let network = from_def(&mut manifest)?;
-  let result: std::result::Result<Interpreter, _> =
-    Interpreter::new(Some(Seed::unsafe_new(1)), network, None, None, panic_callback());
+  let result: std::result::Result<Interpreter, _> = Interpreter::new(
+    Some(Seed::unsafe_new(1)),
+    network,
+    None,
+    None,
+    panic_callback(),
+    &Span::current(),
+  );
   let validation_errors = ValidationError::ComponentIdNotFound("test".to_owned());
   if let Err(InterpreterError::EarlyError(e)) = result {
     assert_eq!(e, validation_errors);
diff --git a/crates/wick/wick-runtime/src/test.rs b/crates/wick/wick-runtime/src/test.rs
index 769de1b5f..a68b648b8 100644
--- a/crates/wick/wick-runtime/src/test.rs
+++ b/crates/wick/wick-runtime/src/test.rs
@@ -14,7 +14,7 @@ use crate::{Runtime, RuntimeBuilder};
 pub(crate) async fn init_engine_from_yaml(path: &str) -> Result<(Runtime, uuid::Uuid)> {
   let def = WickConfiguration::load_from_file(path).await?.try_component_config()?;
 
-  let engine = RuntimeBuilder::from_definition(def)?.build().await?;
+  let engine = RuntimeBuilder::from_definition(def).build(None).await?;
 
   let engine_id = engine.uid;
   trace!(engine_id = %engine_id, "engine uid");
diff --git a/crates/wick/wick-runtime/tests/triggers.rs b/crates/wick/wick-runtime/tests/triggers.rs
index 930876a73..8ecfb8e46 100644
--- a/crates/wick/wick-runtime/tests/triggers.rs
+++ b/crates/wick/wick-runtime/tests/triggers.rs
@@ -4,7 +4,7 @@ use std::time::Duration;
 
 use anyhow::Result;
 mod utils;
-use tracing::debug;
+use tracing::{debug, Span};
 use wick_config::config::AppConfiguration;
 use wick_config::WickConfiguration;
 use wick_runtime::resources::Resource;
@@ -39,7 +39,9 @@ async fn basic_cli() -> Result<()> {
       let loader = loader()?;
       let inner = loader.clone();
       let resources = resources.clone();
-      inner.run(name, app_config, config, resources.clone()).await?;
+      inner
+        .run(name, app_config, config, resources.clone(), Span::current())
+        .await?;
     }
     _ => {
       panic!("could not find trigger {}", &trigger_config.kind());
@@ -49,6 +51,8 @@ async fn basic_cli() -> Result<()> {
 }
 
 mod integration_test {
+  use tracing::Span;
+
   use super::*;
   #[test_logger::test(tokio::test)]
   async fn cli_with_db() -> Result<()> {
@@ -68,7 +72,9 @@ mod integration_test {
         let inner = loader.clone();
         let resources = resources.clone();
         tokio::spawn(async move {
-          let _ = inner.run(name, app_config, config, resources.clone()).await;
+          let _ = inner
+            .run(name, app_config, config, resources.clone(), Span::current())
+            .await;
           inner.wait_for_done().await;
         })
       }
diff --git a/crates/wick/wick-runtime/tests/utils/mod.rs b/crates/wick/wick-runtime/tests/utils/mod.rs
index da0fd9ee4..b8a93711f 100644
--- a/crates/wick/wick-runtime/tests/utils/mod.rs
+++ b/crates/wick/wick-runtime/tests/utils/mod.rs
@@ -10,11 +10,11 @@ pub async fn init_engine_from_yaml(path: &str, timeout: Duration) -> anyhow::Res
   let host_def = WickConfiguration::load_from_file(path).await?.try_component_config()?;
   debug!("Manifest loaded");
 
-  let builder = RuntimeBuilder::from_definition(host_def)?
+  let builder = RuntimeBuilder::from_definition(host_def)
     .namespace("__TEST__")
     .timeout(timeout);
 
-  let engine = builder.build().await?;
+  let engine = builder.build(None).await?;
 
   let nuid = engine.uid;
   Ok((engine, nuid))
@@ -50,7 +50,7 @@ pub async fn base_test(
 
   let result = engine
     .invoke(
-      Invocation::new(Entity::test("simple schematic"), target, Some(inherent)),
+      Invocation::test("simple schematic", target, Some(inherent))?,
       stream,
       None,
     )
diff --git a/crates/wick/wick-test/src/runner.rs b/crates/wick/wick-test/src/runner.rs
index 793382ac6..940554418 100644
--- a/crates/wick/wick-test/src/runner.rs
+++ b/crates/wick/wick-test/src/runner.rs
@@ -48,12 +48,12 @@ async fn run_unit<'a>(
   let prefix = |msg: &str| format!("{}: {}", test_name, msg);
 
   trace!(i, %entity, "invoke");
-  let invocation = Invocation::new(Entity::test(&test_name), entity, inherent);
+  let invocation = Invocation::new(Entity::test(&test_name), entity, inherent, &tracing::Span::current());
   let fut = component.handle(
     invocation,
     stream,
     def.test.config().cloned(),
-    std::sync::Arc::new(|_, _, _, _, _| panic!()),
+    std::sync::Arc::new(|_, _, _, _, _, _| panic!()),
   );
   let fut = tokio::time::timeout(Duration::from_secs(5), fut);
   let result = fut
diff --git a/tests/integration/src/test.rs b/tests/integration/src/test.rs
index 15a915ed4..160c8f039 100644
--- a/tests/integration/src/test.rs
+++ b/tests/integration/src/test.rs
@@ -102,7 +102,7 @@ mod tests {
     let input_stream = packet_stream!(("input", input));
 
     let entity = Entity::local("test-component");
-    let invocation = Invocation::new(Entity::test(file!()), entity, None);
+    let invocation = Invocation::test(file!(), entity, None)?;
 
     let outputs = collection
       .handle(invocation, input_stream, None, panic_callback())
diff --git a/tests/integration/src/test/wick_host_run.rs b/tests/integration/src/test/wick_host_run.rs
index 042b2cc73..49f693782 100644
--- a/tests/integration/src/test/wick_host_run.rs
+++ b/tests/integration/src/test/wick_host_run.rs
@@ -20,7 +20,7 @@ mod slow_test {
       .await?
       .try_app_config()?;
 
-    let mut host = AppHostBuilder::from_definition(app_config.clone()).build();
+    let mut host = AppHostBuilder::default().manifest(app_config.clone()).build()?;
     host.start(None)?;
     debug!("Waiting on triggers to finish or interrupt...");
     // host.wait_for_done().await?;