Repository: iqlusioninc/abscissa (language: rust)

Issue:
Migrate from `log` to `tracing`
Hi, in Zebra https://github.com/ZcashFoundation/zebra we used @hawkw's `tracing` rather than `log` and wrote our own `TracingComponent`.

Would Abscissa be interested in upstreaming (a modified version of) that component and replacing the default log component with a tracing-based one?  In contrast to log, tracing is much more async-friendly, but can remain compatible with log-based crates.

There are some design questions, if this turns out to be of interest to Abscissa:

1. One of the really killer features of `tracing` is dynamic filter reloading -- how should this be exposed to Abscissa applications?

2. In Zebra, we have an HTTP endpoint that allows feeding inputs to the filter reloading.  Should Abscissa provide a default mechanism for doing filter reloading?  My guess is no, that this is application-specific (e.g., we will probably fold ours into the standard RPC interface).


Discussion / hints:
Absolutely. I've been independently very interested in replacing the current logging component with `tracing`.

> One of the really killer features of tracing is dynamic filter reloading -- how should this be exposed to Abscissa applications?

My initial thought would be to expose a method on the `tracing` component allowing modification of the filter. When you want to modify it, look up the tracing component in the component registry, and call the method.

> In Zebra, we have an HTTP endpoint that allows feeding inputs to the filter reloading. Should Abscissa provide a default mechanism for doing filter reloading? My guess is no, that this is application-specific (e.g., we will probably fold ours into the standard RPC interface).

It might potentially be interesting to have a separate reusable component for this, but it probably doesn't belong in `abscissa_core`.
Hi! I just thought I'd swing by and mention that if you have any questions about migrating to `tracing`, I'm happy to help out, just let me know!

Also, a couple things I thought I'd mention:

Since you're currently using `log`, there's [a pair of feature flags in `tracing`](https://docs.rs/tracing/0.1.10/tracing/#crate-feature-flags) that enable outputting `log` records from `tracing` spans and events, potentially in addition to emitting traces. This is intended primarily for libraries where some users are using `log` – it's possible to maintain backwards-compatibility for users expecting `abscissa` to emit `log` records.

> > In Zebra, we have an HTTP endpoint that allows feeding inputs to the filter reloading. Should Abscissa provide a default mechanism for doing filter reloading? My guess is no, that this is application-specific (e.g., we will probably fold ours into the standard RPC interface).
>
> It might potentially be interesting to have a separate reusable component for this, but it probably doesn't belong in `abscissa_core`.

This seems like the right approach to me. Since a couple projects have implemented this kind of endpoint, I'd like there to be a `tracing` crate that provides a reusable implementation of it — I think an `abscissa` component could be implemented on top of that?

> I think an abscissa component could be implemented on top of that?

Yup. Components can just be lightweight wrappers for other crates which handle initialization/lifecycle management (they are in part modeled after Ruby on Rails' [Railties](https://api.rubyonrails.org/classes/Rails/Railtie.html))
Cool, so if I'm reading all of these comments correctly it sounds like:

1. The current `LogComponent` built into Abscissa could be refactored into a `TracingComponent` that uses `tracing`.  This component would have a method that allows reloading the filter.

2. Another optional component could provide an HTTP endpoint that receives filter directives.  This could wrap a `tracing-endpoint` crate rather than having its own implementation, and could be done subsequently to and separately from (1).

Does that sound right?

One remaining question I have is whether the `TracingComponent` should emit `log` events, as @hawkw suggested.  Since Abscissa is intended for binaries, I'm not sure if it makes sense to be compatible with downstream users expecting `log` events, since (at least the way I understand Abscissa) the Abscissa binary is the ultimate downstream user.  Is that correct @tarcieri?
> One remaining question I have is whether the `TracingComponent` should emit `log` events, as @hawkw suggested. Since Abscissa is intended for binaries, I'm not sure if it makes sense to be compatible with downstream users expecting `log` events, since (at least the way I understand Abscissa) the Abscissa binary is the ultimate downstream user. Is that correct @tarcieri?

Ah, okay, this belies my relative lack of knowledge about Abscissa; I wasn't sure whether it was typically used as a library where the user has control over `fn main`, or if Abscissa took control over `fn main`. In that case, we would probably want to have [an adapter in the _other_ direction](https://docs.rs/tracing-log/0.1.1/tracing_log/#convert-log-records-to-tracing-events) instead, so that user code can call into libraries which emit `log` records and have those records exist within the trace tree.


@hdevalence 

> Does that sound right?

Yep, exactly! You can effectively just delete the current `LogComponent` (and for that matter, pretty much everything in the current `logging` module) and replace it with a `TracingComponent` that boots up at the same point in the application lifecycle.

Re 2) 👍 

> One remaining question I have is whether the TracingComponent should emit log events, as @hawkw suggested.

For me, it's very important to continue to receive and emit events logged through the `log` facade. There are a lot of libraries we use which log important information through its facade, and I wouldn't want to lose that.

@hawkw

>  In that case, we would probably want to have an adapter in the other direction instead, so that user code can call into libraries which emit log records and have those records exist within the trace tree.

Yep, exactly!
@tarcieri receive *and* emit? I definitely see the usecase for receiving `log` events – exactly the one you mentioned – but I'm less clear on why an Abscissa binary would need to emit `log` events.
@hdevalence sorry, just "receive", i.e. messages logged by 3rd party libraries through the `log` facade need to show up in `tracing`.

This looks like the relevant functionality:

https://docs.rs/tracing-log/0.1.1/tracing_log/#convert-log-records-to-tracing-events
Yep, we do exactly that in Zebra's `TracingComponent` already, but I just wanted to check in case there was something I was missing re: emitting `log` events.  I think I'll be out of time today but I can try to work on this next week.
Awesome!
Starting work on this today.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5dddf36a..8d59514a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -25,7 +25,6 @@ dependencies = [
  "gumdrop 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "secrecy 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -33,6 +32,9 @@ dependencies = [
  "signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tracing 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tracing-log 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tracing-subscriber 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -68,6 +70,14 @@ dependencies = [
  "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "arc-swap"
 version = "0.4.3"
@@ -349,6 +359,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 name = "lazy_static"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "lazycell"
@@ -373,6 +386,14 @@ name = "maplit"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "matchers"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "maybe-uninit"
 version = "2.0.0"
@@ -419,6 +440,14 @@ name = "opaque-debug"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "owning_ref"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "pest"
 version = "2.1.2"
@@ -516,6 +545,16 @@ dependencies = [
  "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "regex-automata"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "regex-syntax"
 version = "0.6.12"
@@ -654,6 +693,11 @@ dependencies = [
  "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "stable_deref_trait"
 version = "1.1.1"
@@ -729,6 +773,61 @@ dependencies = [
  "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "tracing"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tracing-attributes 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "matchers 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tracing-log 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "typenum"
 version = "1.11.2"
@@ -749,6 +848,11 @@ name = "unicode-xid"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "utf8-ranges"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "wait-timeout"
 version = "0.2.0"
@@ -811,6 +915,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 [metadata]
 "checksum addr2line 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "95b06ae5a8a3bae54910c9029a52f83203ce2001c71b10b1faae3a337fee4ab5"
 "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
+"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
 "checksum arc-swap 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f1a1eca3195b729bbd64e292ef2f5fff6b1c28504fed762ce2b1013dde4d8e92"
 "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
 "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
@@ -850,6 +955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
 "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
 "checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+"checksum matchers 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
 "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
 "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
 "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
@@ -857,6 +963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
 "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
 "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
 "checksum pest 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e4fb201c5c22a55d8b24fef95f78be52738e5e1361129be1b5e862ecdb6894a"
 "checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
 "checksum pest_generator 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9fcf299b5712d06ee128a556c94709aaa04512c4dffb8ead07c5c998447fc0"
@@ -869,6 +976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
 "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
 "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
+"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9"
 "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
 "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
 "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
@@ -886,6 +994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68"
 "checksum signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc"
 "checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
+"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
 "checksum strsim 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "032c03039aae92b350aad2e3779c352e104d919cb192ba2fabbd7b831ce4f0f6"
 "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
@@ -895,10 +1004,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
 "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
 "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724"
+"checksum tracing 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ff4e4f59e752cb3beb5b61c6d5e11191c7946231ba84faec2902c9efdd8691c5"
+"checksum tracing-attributes 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a4263b12c3d3c403274493eb805966093b53214124796552d674ca1dd5d27c2b"
+"checksum tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bc913647c520c959b6d21e35ed8fa6984971deca9f0a2fcb8c51207e0c56af1d"
+"checksum tracing-log 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e0f8c7178e13481ff6765bd169b33e8d554c5d2bbede5e32c356194be02b9b9"
+"checksum tracing-subscriber 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "192ca16595cdd0661ce319e8eede9c975f227cdaabc4faaefdc256f43d852e45"
 "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
 "checksum ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2"
 "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
 "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
 "checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
 "checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
 "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
diff --git a/cli/src/application.rs b/cli/src/application.rs
index fd531d02..7376dd32 100644
--- a/cli/src/application.rs
+++ b/cli/src/application.rs
@@ -2,7 +2,7 @@
 
 use super::{commands::CliCommand, config::CliConfig};
 use abscissa_core::{
-    self, application, logging, Application, EntryPoint, FrameworkError, StandardPaths,
+    self, application, trace, Application, EntryPoint, FrameworkError, StandardPaths,
 };
 use lazy_static::lazy_static;
 
@@ -58,11 +58,11 @@ impl Application for CliApplication {
         Ok(())
     }
 
-    fn logging_config(&self, command: &EntryPoint<CliCommand>) -> logging::Config {
+    fn tracing_config(&self, command: &EntryPoint<CliCommand>) -> trace::Config {
         if command.verbose {
-            logging::Config::verbose()
+            trace::Config::verbose()
         } else {
-            logging::Config::default()
+            trace::Config::default()
         }
     }
 }
diff --git a/cli/src/prelude.rs b/cli/src/prelude.rs
index 17c948af..6a74e246 100644
--- a/cli/src/prelude.rs
+++ b/cli/src/prelude.rs
@@ -7,8 +7,8 @@ pub use abscissa_core::{Application, Command, Runnable};
 /// Error macros
 pub use abscissa_core::{ensure, fail, fatal, format_err};
 
-/// Logging macros
-pub use abscissa_core::log::{debug, error, info, log, log_enabled, trace, warn};
+/// Tracing macros
+pub use abscissa_core::tracing::{debug, error, event, info, span, trace, warn, Level};
 
 /// Status macros
 pub use abscissa_core::{status_err, status_info, status_ok, status_warn};
diff --git a/cli/template/src/application.rs.hbs b/cli/template/src/application.rs.hbs
index 8c55f9ae..68c7f566 100644
--- a/cli/template/src/application.rs.hbs
+++ b/cli/template/src/application.rs.hbs
@@ -2,7 +2,7 @@
 
 use crate::{commands::{{~command_type~}}, config::{{~config_type~}}};
 use abscissa_core::{
-    application, config, logging, Application, EntryPoint, FrameworkError, StandardPaths,
+    application, config, trace, Application, EntryPoint, FrameworkError, StandardPaths,
 };
 use lazy_static::lazy_static;
 
@@ -100,12 +100,12 @@ impl Application for {{application_type}} {
         Ok(())
     }
 
-    /// Get logging configuration from command-line options
-    fn logging_config(&self, command: &EntryPoint<{{~command_type~}}>) -> logging::Config {
+    /// Get tracing configuration from command-line options
+    fn tracing_config(&self, command: &EntryPoint<{{~command_type~}}>) -> trace::Config {
         if command.verbose {
-            logging::Config::verbose()
+            trace::Config::verbose()
         } else {
-            logging::Config::default()
+            trace::Config::default()
         }
     }
 }
diff --git a/cli/template/src/prelude.rs.hbs b/cli/template/src/prelude.rs.hbs
index dc8bd146..13c28326 100644
--- a/cli/template/src/prelude.rs.hbs
+++ b/cli/template/src/prelude.rs.hbs
@@ -10,8 +10,8 @@ pub use abscissa_core::{Application, Command, Runnable};
 /// Error macros
 pub use abscissa_core::{ensure, fail, fatal, format_err};
 
-/// Logging macros
-pub use abscissa_core::log::{debug, error, info, log, log_enabled, trace, warn};
+/// Tracing macros
+pub use abscissa_core::tracing::{debug, error, event, info, span, trace, warn, Level};
 
 /// Status macros
 pub use abscissa_core::{status_err, status_info, status_ok, status_warn};
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 416fe160..de8a822d 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -27,13 +27,15 @@ color-backtrace = { version = "0.3", optional = true, default-features = false }
 generational-arena = { version = "0.2", optional = true }
 gumdrop = { version = "0.7", optional = true }
 lazy_static = "1"
-log = { version = "0.4", optional = true, features = ["std"] }
 regex = { version = "1", optional = true }
 secrecy = { version = "0.5", optional = true, features = ["serde"] }
 semver = { version = "0.9", optional = true }
 serde = { version = "1", optional = true, features = ["serde_derive"] }
 termcolor = { version = "1", optional = true }
 toml = { version = "0.5", optional = true }
+tracing = { version = "0.1", optional = true }
+tracing-log = { version = "0.1", optional = true }
+tracing-subscriber = { version = "0.1", optional = true }
 wait-timeout = { version = "0.2", optional = true }
 
 [target.'cfg(unix)'.dependencies]
@@ -51,7 +53,7 @@ default = [
 application = [
     "config",
     "generational-arena",
-    "logging",
+    "trace",
     "options",
     "semver/serde",
     "terminal"
@@ -66,7 +68,7 @@ gimli-backtrace = [
     "backtrace/gimli-symbolize",
     "color-backtrace/gimli-symbolize"
 ]
-logging = ["log"]
+trace = ["tracing", "tracing-log", "tracing-subscriber"]
 options = ["gumdrop"]
 secrets = ["secrecy"]
 signals = ["libc", "signal-hook"]
diff --git a/core/src/application.rs b/core/src/application.rs
index bd53db2d..49cbda4f 100644
--- a/core/src/application.rs
+++ b/core/src/application.rs
@@ -13,11 +13,11 @@ use crate::{
     command::Command,
     component::Component,
     config::{Config, Configurable},
-    logging::{self, Logging},
     path::{AbsPathBuf, ExePath, RootPath},
     runnable::Runnable,
     shutdown::Shutdown,
     terminal::{component::Terminal, ColorChoice},
+    trace::{self, Tracing},
     FrameworkError,
     FrameworkErrorKind::*,
     Version,
@@ -113,16 +113,16 @@ pub trait Application: Default + Sized + 'static {
     }
 
     /// Initialize the framework's default set of components, potentially
-    /// sourcing terminal and logging options from command line arguments.
+    /// sourcing terminal and tracing options from command line arguments.
     fn framework_components(
         &mut self,
         command: &Self::Cmd,
     ) -> Result<Vec<Box<dyn Component<Self>>>, FrameworkError> {
         let terminal = Terminal::new(self.term_colors(command));
-        let logging = Logging::new(self.logging_config(command))
-            .expect("logging subsystem failed to initialize");
+        let tracing = Tracing::new(self.tracing_config(command), self.term_colors(command))
+            .expect("tracing subsystem failed to initialize");
 
-        Ok(vec![Box::new(terminal), Box::new(logging)])
+        Ok(vec![Box::new(terminal), Box::new(tracing)])
     }
 
     /// Load configuration from the given path.
@@ -165,9 +165,9 @@ pub trait Application: Default + Sized + 'static {
         ColorChoice::Auto
     }
 
-    /// Get the logging configuration for this application.
-    fn logging_config(&self, command: &Self::Cmd) -> logging::Config {
-        logging::Config::default()
+    /// Get the tracing configuration for this application.
+    fn tracing_config(&self, command: &Self::Cmd) -> trace::Config {
+        trace::Config::default()
     }
 
     /// Handle a Unix signal received by this application
diff --git a/core/src/lib.rs b/core/src/lib.rs
index d2cce9d8..00969c59 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -11,7 +11,8 @@
 //! - **configuration**: TOML configuration file parsing on application-defined
 //!   configuration structures which can be dynamically updated at runtime.
 //! - **error handling**: unified error-handling subsystem with generic errors.
-//! - **logging**: uses the `log` crate to provide application-level logging.
+//! - **tracing**: uses the `tracing` crate to provide async-aware application-
+//!   level tracing.
 //! - **secrets management**: the (optional) `secrets` module includes a `Secret`
 //!  type which derives serde's `Deserialize` and can be used to represent secret
 //!  values parsed from configuration files or elsewhere (e.g. credentials loaded
@@ -100,10 +101,10 @@
 /// Abscissa version
 pub const VERSION: &str = env!("CARGO_PKG_VERSION");
 
-#[cfg(feature = "logging")]
+#[cfg(feature = "trace")]
 #[allow(unused_imports)]
 #[macro_use]
-pub extern crate log;
+pub extern crate tracing;
 
 // Modules with macro exports
 
@@ -123,8 +124,6 @@ pub mod command;
 pub mod component;
 #[cfg(feature = "config")]
 pub mod config;
-#[cfg(feature = "logging")]
-pub mod logging;
 pub mod path;
 mod runnable;
 #[cfg(feature = "application")]
@@ -134,6 +133,8 @@ pub mod signal;
 #[cfg(feature = "testing")]
 pub mod testing;
 pub mod thread;
+#[cfg(feature = "trace")]
+pub mod trace;
 
 // Proc macros
 
diff --git a/core/src/logging/component.rs b/core/src/logging/component.rs
deleted file mode 100644
index 1090def8..00000000
--- a/core/src/logging/component.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-//! Abscissa logging component
-
-// TODO(tarcieri): logfile support?
-
-use super::{config::Config, logger};
-use crate::{Component, FrameworkError};
-
-/// Abscissa component for initializing the logging subsystem
-#[derive(Component, Debug, Default)]
-#[component(core)]
-pub struct Logging {
-    config: Config,
-}
-
-impl Logging {
-    /// Create a new logging component
-    pub fn new(config: Config) -> Result<Self, FrameworkError> {
-        logger::init(&config);
-        Ok(Self { config })
-    }
-}
diff --git a/core/src/logging/config.rs b/core/src/logging/config.rs
deleted file mode 100644
index 150a4226..00000000
--- a/core/src/logging/config.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-//! Logging configuration
-
-use log::LevelFilter;
-
-/// Logging configuration
-// TODO: more configurability
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Config {
-    pub(super) level_filter: LevelFilter,
-}
-
-impl Config {
-    /// Create a new LoggingConfig object with verbose logging
-    pub fn verbose() -> Self {
-        LevelFilter::Debug.into()
-    }
-}
-
-impl Default for Config {
-    fn default() -> Self {
-        LevelFilter::Info.into()
-    }
-}
-
-impl From<LevelFilter> for Config {
-    fn from(level_filter: LevelFilter) -> Config {
-        Self { level_filter }
-    }
-}
-
-impl From<Config> for LevelFilter {
-    fn from(logging_config: Config) -> LevelFilter {
-        logging_config.level_filter
-    }
-}
diff --git a/core/src/logging/logger.rs b/core/src/logging/logger.rs
deleted file mode 100644
index 9b484cdb..00000000
--- a/core/src/logging/logger.rs
+++ /dev/null
@@ -1,101 +0,0 @@
-//! Abscissa logger which displays through the terminal subsystem
-// TODO(tarcieri): logfile support?
-
-use super::config::Config;
-use crate::terminal::stream::{STDERR, STDOUT};
-use log::{Level, LevelFilter, Log, Metadata, Record};
-use std::io::{Error, Write};
-use termcolor::{Color, ColorSpec, StandardStreamLock, WriteColor};
-
-/// Initialize the global logger.
-///
-/// Panics if called more than once.
-pub(super) fn init(config: &Config) {
-    log::set_boxed_logger(Box::new(Logger::new(config))).expect("error configuring global logger");
-    log::set_max_level(config.level_filter);
-}
-
-/// Abscissa component for initializing the logging subsystem
-#[derive(Debug)]
-pub struct Logger {
-    level: LevelFilter,
-}
-
-impl Logger {
-    /// Create a new logger at the given log level
-    pub fn new(config: &Config) -> Self {
-        Logger {
-            level: config.level_filter,
-        }
-    }
-
-    /// Attempt to log
-    pub fn try_log(&self, record: &Record<'_>) -> Result<(), Error> {
-        let mut stream = self.level_stream(record);
-        let now = chrono::Utc::now();
-        write!(&mut stream, "{} ", now.format("%H:%M:%S"))?;
-
-        stream.set_color(&self.level_color(record.level()))?;
-        write!(stream, "[{}] ", record.level().to_string().to_lowercase())?;
-        stream.reset()?;
-
-        writeln!(&mut stream, "{}", record.args())?;
-        Ok(())
-    }
-
-    /// Get the stream to which a particular loglevel should be displayed
-    fn level_stream(&self, record: &Record<'_>) -> StandardStreamLock<'_> {
-        match record.level() {
-            Level::Error => &*STDERR,
-            _ => &*STDOUT,
-        }
-        .lock()
-    }
-
-    /// Get the color used to display a particular level
-    fn level_color(&self, level: Level) -> ColorSpec {
-        let color = match level {
-            Level::Error => Color::Red,
-            Level::Warn => Color::Yellow,
-            Level::Info => Color::Blue,
-            Level::Debug => Color::Cyan,
-            Level::Trace => Color::White,
-        };
-
-        let mut cs = ColorSpec::new();
-        cs.set_fg(Some(color));
-        cs.set_bold(true);
-        cs
-    }
-}
-
-impl Log for Logger {
-    fn enabled(&self, metadata: &Metadata<'_>) -> bool {
-        metadata.level() <= self.level
-    }
-
-    fn log(&self, record: &Record<'_>) {
-        let result = self.try_log(record);
-        debug_assert!(result.is_ok(), "logging error: {}", result.err().unwrap());
-    }
-
-    fn flush(&self) {
-        let mut stdout = STDOUT.lock();
-        let stdout_result = stdout.flush();
-
-        debug_assert!(
-            stdout_result.is_ok(),
-            "error flushing stdout: {}",
-            stdout_result.err().unwrap()
-        );
-
-        let mut stderr = STDERR.lock();
-        let stderr_result = stderr.flush();
-
-        debug_assert!(
-            stderr_result.is_ok(),
-            "error flushing stderr: {}",
-            stdout_result.err().unwrap()
-        );
-    }
-}
diff --git a/core/src/logging.rs b/core/src/trace.rs
similarity index 65%
rename from core/src/logging.rs
rename to core/src/trace.rs
index c617c857..1cbb61ea 100644
--- a/core/src/logging.rs
+++ b/core/src/trace.rs
@@ -1,10 +1,9 @@
-//! Logging subsystem
+//! Tracing subsystem
 
 #[cfg(feature = "application")]
 pub mod component;
 mod config;
-mod logger;
 
 #[cfg(feature = "application")]
-pub use self::component::Logging;
+pub use self::component::Tracing;
 pub use self::config::Config;
diff --git a/core/src/trace/component.rs b/core/src/trace/component.rs
new file mode 100644
index 00000000..2f51daef
--- /dev/null
+++ b/core/src/trace/component.rs
@@ -0,0 +1,62 @@
+//! Abscissa tracing component
+
+// TODO(tarcieri): logfile support?
+
+use tracing_log::LogTracer;
+use tracing_subscriber::{fmt::Formatter, reload::Handle, EnvFilter, FmtSubscriber};
+
+use super::config::Config;
+use crate::{terminal::ColorChoice, Component, FrameworkError, FrameworkErrorKind};
+
+/// Abscissa component for initializing the `tracing` subsystem
+#[derive(Component, Debug)]
+#[component(core)]
+pub struct Tracing {
+    filter_handle: Handle<EnvFilter, Formatter>,
+}
+
+impl Tracing {
+    /// Create a new [`Tracing`] component from the given [`Config`].
+    pub fn new(config: Config, color_choice: ColorChoice) -> Result<Self, FrameworkError> {
+        // Configure log/tracing interoperability by setting a `LogTracer` as
+        // the global logger for the log crate, which converts all log events
+        // into tracing events.
+        LogTracer::init().map_err(|e| FrameworkErrorKind::ComponentError.context(e))?;
+
+        // Construct a tracing subscriber with the supplied filter and enable reloading.
+        let builder = FmtSubscriber::builder()
+            .with_ansi(match color_choice {
+                ColorChoice::Always => true,
+                ColorChoice::AlwaysAnsi => true,
+                ColorChoice::Auto => true,
+                ColorChoice::Never => false,
+            })
+            .with_env_filter(config.filter)
+            .with_filter_reloading();
+        let filter_handle = builder.reload_handle();
+        let subscriber = builder.finish();
+
+        // Now set it as the global tracing subscriber and save the handle.
+        tracing::subscriber::set_global_default(subscriber)
+            .map_err(|e| FrameworkErrorKind::ComponentError.context(e))?;
+
+        Ok(Self { filter_handle })
+    }
+
+    /// Return the currently-active tracing filter.
+    pub fn filter(&self) -> String {
+        self.filter_handle
+            .with_current(|filter| filter.to_string())
+            .expect("the subscriber is not dropped before the component is")
+            .to_string()
+    }
+
+    /// Reload the currently-active filter with the supplied value.
+    ///
+    /// This can be used to provide a dynamic tracing filter endpoint.
+    pub fn reload_filter(&mut self, filter: impl Into<EnvFilter>) {
+        self.filter_handle
+            .reload(filter)
+            .expect("the subscriber is not dropped before the component is");
+    }
+}
diff --git a/core/src/trace/config.rs b/core/src/trace/config.rs
new file mode 100644
index 00000000..32ccf924
--- /dev/null
+++ b/core/src/trace/config.rs
@@ -0,0 +1,26 @@
+//! Logging configuration
+
+/// Tracing configuration
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Config {
+    pub(super) filter: String,
+}
+
+impl Config {
+    /// Create a config for verbose output.
+    pub fn verbose() -> Self {
+        "debug".to_owned().into()
+    }
+}
+
+impl Default for Config {
+    fn default() -> Self {
+        "info".to_owned().into()
+    }
+}
+
+impl From<String> for Config {
+    fn from(filter: String) -> Self {
+        Self { filter }
+    }
+}


Test patch: