## Motivation
Currently we can't run
```bash
cargo test -p tracing-subscriber --no-default-features
```
successfully, because there are a bunch of tests for feature flagged
APIs that are always enabled.
## Solution
This commit adds feature flags to the modules and/or individual test
cases that test feature-flagged APIs.
There are still a few doctests that use feature-flagged APIs, and will
fail with `--no-default-features`. These are primarily the examples for
various `Layer::context` methods that rely on `LookupSpan`, and use the
`Registry` type, as it's the only subscriber that *implements*
`LookupSpan`. We could consider changing these examples, either by
removing the actual use of the layers in them, or by changing them to
use a mocked-out version of the registry. However, I think it's nicer to
show how the API would be used in real life. Perhaps we should just run
```bash
cargo test -p tracing-subscriber --no-default-features--tests --lib
```
to ignore doctests when testing without default features.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Currently, `Layered` only implements `fmt::Debug` if the outer layer,
the inner layer, *and* the subscriber type parameters all implement
`fmt::Debug`. However, in the case of a `Layered` with two `Layer`s, the
subscriber itself is not present --- it's only a `PhantomData`. So the
`fmt::Debug` implementation should be possible when only the actual
`layer` and `inner` values are `Debug`. For the `PhantomData`, we can
just print the type name.
This means that `Layered` consisting of two `Layer`s and no `Subscriber`
will now implement `Debug` if the `Layer`s are `Debug`, regardless of
the subscriber type. When the `Layered` is a `Layer` and a `Subscriber`,
the behavior will be the same, because the separate `PhantomData`
subscriber type param is always the same as the type of the inner
subscriber.
Because printing the whole type name of the subscriber is potentially
verbose (e.g. when the subscriber itself is a big pile of layers), we
only include it in alt-mode.
The `layer::Context` type has additional methods that require the inner
subscriber to implement `LookupSpan`. These currently are only present
when the `registry` feature flag is enabled. However, this isn't
actually necessary --- the `LookupSpan` trait always exists, and doesn't
require the `registry` feature flag; that feature flag only controls
whether the `Registry` _type_, a concrete implementation of
`LookupSpan`, is enabled.
This branch removes the `cfg` attributes from these methods.
This branch fixes some minor RustDoc issues. In particular:
- The `broken_intra_doc_links` lint was renamed to
`rustdoc::broken_intra_doc_links`. This generates a warning, since the
old name was deprecated.
- `ignore` code blocks are specifically for _Rust_ code that should not
be compiled, not for other text blocks. We were using `ignore` on JSON
blocks, which generates a warning.
- A bunch of links in `tracing-subscriber`'s RustDocs were broken. This
fixes that.
I also changed the Netlify configuration to run with `-D warnings`, so that
we can surface RustDoc warnings in CI builds.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
## Motivation
Currently, `tracing-subscriber`'s `Registry` type doesn't document how
span IDs are generated, or the uniqueness guarantees for span IDs. This
can cause confusion for users trying to implement `Subscribe`rs and
other code that interacts with the `Registry`'s generated span IDs.
## Solution
This branch adds a new documentation section to the `Registry` docs
describing how IDs are generated and when they are guaranteed to be
unique. In particular, the new section explicitly states that the
registry's IDs will not uniquely identify a span historically, because
IDs may be reused when a span has closed, and that the registry's
`tracing` span IDs should not be used as span IDs in a distributed
tracing system.
While I was working on these docs, I also fixed a link on adding
subscribers to a registry that went to a specific method on
`FmtSubscriber`, rather than to the more general docs on how
subscribers are composed with collectors.
Thanks to @Folyd for suggesting this docs improvement!
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Co-authored-by: David Barsky <me@davidbarsky.com>
Clippy added a new lint detecting unnecessary borrows in expressions
where the borrowed thing is already a reference...which is nice, and
all, but the downside is that now we have to fix this.
This PR fixes it.
## Motivation
It should be possible to remove `timestamp` and `level` fields when
using json formatting.
As of now this has no effect:
```rs
let subscriber = tracing_subscriber::fmt()
.with_level(false)
.without_time()
.json()
.finish();
```
## Solution
Use the existing `display_timestamp` and `display_level` fields to
conditionally serialize `timestamp` and `level`.
## Motivation
On v0.1.x, `tracing_subscriber::fmt`'s `Pretty` formatter is missing
a space after the event's level, when printing levels is enabled.
## Solution
This branch fixes that.
Fixes#1488
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
## Motivation
Fixes#1347
## Solution
Change the format from `err=message, err: source1` to `err=message
err.sources=[source1, source2, source3, ...]`, as suggested in
https://github.com/tokio-rs/tracing/issues/1347#issuecomment-813674313
(still leaving out the sources if there are none).
## Caveats
Haven't changed the JSON formatter, since I'm not really sure about how
to do that. The current format is `{.., "fields": {.., "err":
"message"}}`, disregarding the sources entirely.
We could change that to `{.., "fields": {.., "err": "message",
"err.sources": ["source1", "source2", "source3", ..]}}`, which would
keep backwards compatibility but looks pretty ugly.
Another option would be `{.., "fields": {.., "err": {"message":
"message", "sources": ["source1", "source2", "source3", ..]}}}` which
leaves room for future expansion.
Then again, that begs the question about why the first error is special,
so maybe it ought to be `{.., "fields": {.., "err": {"message":
"message", "source": {"message": "source1", "source": ..}}}}`.
But that style of linked list is pretty annoying to parse, so maybe it
ought to be flattened to `{.., "fields": {.., "err": [{"message":
"message"}, {"message": "source1"}, ..]}}`?
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
# 0.2.19 (June 25, 2021)
### Deprecated
- **registry**: `SpanRef::parents`, `SpanRef::from_root`, and
`Context::scope` iterators, which are replaced by new `SpanRef::scope`
and `Scope::from_root` iterators (#1413)
### Added
- **registry**: `SpanRef::scope` method, which returns a leaf-to-root
`Iterator` including the leaf span (#1413)
- **registry**: `Scope::from_root` method, which reverses the `scope`
iterator to iterate root-to-leaf (#1413)
- **registry**: `Context::event_span` method, which looks up the parent
span of an event (#1434)
- **registry**: `Context::event_scope` method, returning a `Scope`
iterator over the span scope of an event (#1434)
- **fmt**: `MakeWriter::make_writer_for` method, which allows returning
a different writer based on a span or event's metadata (#1141)
- **fmt**: `MakeWriterExt` trait, with `with_max_level`,
`with_min_level`, `with_filter`, `and`, and `or_else` combinators
(#1274)
- **fmt**: `MakeWriter` implementation for `Arc<W> where &W: io::Write`
(#1274)
Thanks to @teozkr and @Folyd for contributing to this release!
This backports #1274 from `master`.
Depends on #1141.
This branch adds a `MakeWriterExt` trait which adds a number of
combinators for composing multiple types implementing `MakeWriter`.
`MakeWriter`s can now be teed together, filtered with minimum and
maximum levels, filtered with a `Metadata` predicate, and combined with
a fallback for when a writer is _not_ made for a particular `Metadata`.
I've also added a `MakeWriter` impl for `Arc<W>` when `&W` implements
`Write`. This enables `File`s to be used as `MakeWriter`s similarly to
how we will be able to in 0.3, with the additional overhead of having to
do ref-counting because we can't return a reference from `MakeWriter`
until the next breaking change.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This backports #1433 from `master`.
* subscriber: explain why we always call `inner.register_callsite()` before if statement
* Apply suggestions from code review
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
This backports PR #1141 from `master`.
subscriber: add `MakeWriter::make_writer_for`
## Motivation
In some cases, it might be desirable to configure the writer used for
writing out trace data based on the metadata of the span or event being
written. For example, we might want to send different levels to
different outputs, write logs from different targets to separate files,
or wrap formatted output in ANSI color codes based on levels. Currently,
it's not possible for the `MakeWriter` trait to model this kind of
behavior --- it has one method, `make_writer`, which is completely
unaware of *where* the data being written came from.
In particular, this came up in PR #1137, when discussing a proposal that
writing to syslog could be implemented as a `MakeWriter` implementation
rather than as a `Subscribe` implementation, so that all the formatting
logic from `tracing_subscriber::fmt` could be reused. See [here][1] for
details.
## Solution
This branch adds a new `make_writer_for` method to `MakeWriter`, taking
a `Metadata`. Implementations can opt in to metadata-specific behavior
by implementing this method. The method has a default implementation
that just calls `self.make_writer()` and ignores the metadata, so it's
only necessary to implement this when per-metadata behavior is required.
This isn't a breaking change to existing implementations.
There are a couple downsides to this approach: it's possible for callers
to skip the metadata-specific behavior by calling `make_writer` rather
than `make_writer_for`, and the impls for closures can't easily provide
metadata-specific behavior.
Since the upcoming release is going to be a breaking change anyway, we
may want to just make the breaking change of having
`MakeWriter::make_writer` _always_ take a `Metadata`, which solves these
problems. However, that can't be backported to v0.1.x as easily. Additionally,
that would mean that functions like `io::stdout` no longer implement
`MakeWriter`; they would have to be wrapped in a wrapper type or closure
that ignores metadata.
[1]: https://github.com/tokio-rs/tracing/pull/1137#discussion_r542728604
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
## Motivation
Fixes#1428
## Solution
Adds a new `Context::event_span` method as proposed in the issue.
No existing formatters were changed to use it yet, that seems like a
secondary issue (especially if they already work correctly).
## Motivation
Fixes#1429
## Solution
Implemented as described in #1429, and migrated all internal uses of the
deprecated methods. All tests passed both before and after the migration
(9ec8130).
- Add a new method `SpanRef::scope(&self)` that returns a leaf-to-root
`Iterator`, including the specified leaf
- Add a new method `Scope::from_root(self)` (where `Scope` is the type
returned by `SpanRef::scope` defined earlier) that returns a
root-to-leaf `Iterator` that ends at the current leaf (in other
words: it's essentially the same as
`Scope::collect::<Vec<_>>().into_iter().rev()`)
- Deprecate all existing iterators, since they can be replaced by the
new unified mechanism:
- `Span::parents` is equivalent to `Span::scope().skip(1)` (although
the `skip` is typically a bug)
- `Span::from_root` is equivalent to `Span::scope().skip(1).from_root()`
(although the `skip` is typically a bug)
- `Context::scope` is equivalent to
`Context::lookup_current().scope().from_root()` (although the
`lookup_current` is sometimes a bug, see also #1428)
This branch fixes some annoying new clippy lints no one cares about.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
# Conflicts:
# tracing-subscriber/src/layer.rs
Filtering log events using the `target[span{field=value}]=level` syntax
doesn't work when the span name contains a special char.
This PR closes#1367 by adding support for span names with any
characters other than `{` and `]` to `EnvFilter`.
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
## Motivation
Currently, the default `Compact` and `Full` formatters in
`tracing-subscriber` will prefix log lines with a single space when
timestamps are disabled. The space is emitted in order to pad the
timestamp from the rest of the log line, but it shouldn't be emitted
when timestamps are turned off. This should be fixed.
## Solution
This branch fixes the issue by skipping `time::write` entirely when
timestamps are disabled. This is done by tracking an additional boolean
flag for disabling timestamps.
Incidentally, this now means that span lifecycle timing can be enabled
even if event timestamps are disabled, like this:
```rust
use tracing_subscriber::fmt;
let subscriber = fmt::subscriber()
.without_time()
.with_timer(SystemTime::now)
.with_span_events(fmt::FmtSpan::FULL);
```
or similar.
I also added a new test reproducing the issue, and did a little
refactoring to try and clean up the timestamp formatting code a bit.
Closes#1354
* subscriber: fix span data for new, exit, and close events
New, exit and close span events are generated while the current
context is set to either `None` or the parent span of the span the
event relates to. This causes spans data to be absent from the JSON
output in the case of the `None`, or causes the span data to reference
the parent's span data. Changing the way the current span is
determined allows the correct span to be identified for these
events. Trying to access the events `.parent()` allows access of the
correct span for the `on_event` actions, while using `.current_span()`
works for normal events.
Ref: #1032
* Fix style
* subscriber: improve test for #1333
Based on feedback by @hawkw, I've improved the test for #1333 to parse
the json output. This is more specifc for the bug and allows easier
testing of the different span `on_events`.
Ref: https://github.com/tokio-rs/tracing/pull/1333#pullrequestreview-623737828
* subscriber: improve #1334 tests covering all span states
Use the `on_records` test method check all events have the correct
context as described in the PR.
* Apply suggestions from code review
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
Serializing a spans `on_ACTION` events, when no fields are set on the
span, results in invalid JSON. This is because `serializier_map` was
getting a size hint for `self.0.metadata().fields().len()` then
serializing `self.0.fields.field_set()` instead. This resulted in the
fields key being set to an empty object, then Serde serializes the k/v
pairs from `field_set()`. This was causing an erroneous closing brace
`}` to be added after the serialized fields.
This change aligns the size hint with the actual serialized data.
Refs: https://github.com/tokio-rs/tracing/issues/829#issuecomment-661984255
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
This branch changes the `field_filter_events` and `field_filter_spans`
tests in `tracing-subscriber` to be ignored by default, as they often
fail spuriously on CI.
The tests can still be run using
```shell
$ RUSTFLAGS="--cfg flaky_tests" cargo test
```
or similar.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This backports PR #1335 from `master` to `v0.1.x`.
Clippy now warns about implementing `Into` rather than `From`, since
`From` automatically provides `Into` but `Into` does not provide `From`.
This commit fixes the direction of those conversions, placating Clippy.
Additionally, it fixes a redundant use of slice syntax that Clippy also
complained about.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This backports #1240 from `master`.
* subscriber: update pretty formatter for no ansi
## Background
Currently, when the `Pretty` event formatter is being used, it does
not change its output when the `with_ansi` flag is set to false by
the `CollectorBuilder`.
## Overview
While this formatter is generally used in situations such as local
development, where ANSI escape codes are more often acceptable,
there are some situations in which this can lead to mangled output.
This commit makes some minor changes to account for this `ansi` flag
when formatting events using `Pretty`.
Becuase ANSI codes were previously used to imply the event level
using colors, this commit additionally modifies `Pretty` so that
it respects `display_level` when formatting an event.
## Changes
* Changes to `<Format<Pretty, T> as FormatEvent<C, N>>::format_event`
* Add a `PrettyVisitor::ansi` boolean flag, used in its `Visit`
implementation.
* Add a new `PrettyVisitor::with_ansi` builder pattern method to
facilitate this.
## Out of Scope
One detail worth nothing is that this does not solve the problem of
*fields* being formatted without ANSI codes. Configuring a
subscriber using this snippet would still lead to bolded fields in
parent spans.
```rust
tracing_subscriber::fmt()
.pretty()
.with_ansi(false)
.with_level(false)
.with_max_level(tracing::Level::TRACE)
.init();
```
This can be worked around by using a different field formatter, via
`.fmt_fields(tracing_subscriber::fmt::format::DefaultFields::new())`
in the short-term.
In the long-term, #658 is worth investigating further.
Refs: #658
This backports #1277 from `master`.
Fixes#1136.
Allows arbitrarily combining different FmtSpan events to listen to.
Motivation
----------
The idea is to allow any combination of `FmtSpan` options, such as the
currently unachievable combination of `FmtSpan::NEW | FmtSpan::CLOSE`.
Solution
--------
Make `FmtSpan` behave like a bitflag that can be combined using the
bitwise or operator ( `|` ) while maintaining backward compatibility by
keeping the same names for all existing presets and keeping the
implementation details hidden.
## Motivation
This fixes#1212, where extra padding was written when logging with the
pretty formatter.
## Solution
With this change we only write padding when we actually decide to write
a value but not when skipping a metadata value such as `log.file` or
`log.line`
This backports #1289 from `master`.
This PR aims to remove a lot of initializer boilerplate code by adopting
the`struct update syntax. If the [RFC 2528] gets merged and
implemented, we can remove more. 😸
[RFC 2528]: https://github.com/rust-lang/rfcs/pull/2528
PRs #1247, #1248, and #1251 improve `tracing`'s behavior for the `log`
crate's `log_enabled!` macro when a max-level hint is set. However, this
*doesn't* get us correct behavior when a particular target is not
enabled but the level is permitted by the max-level filter. In this
case, `log_enabled!` will still return `true` when using `LogTracer`,
because it doesn't currently call `Subscriber::enabled` on the current
subscriber in its `Log::enabled` method. Instead, `Subscriber::enabled`
is only checked when actually recording an event.
This means that when a target is disabled by a target-specific filter
but it's below the max level, `log::log_enabled!` will erroneously
return `true`. This also means that skipping disabled `log` records in
similar cases will construct the `log::Record` struct even when it isn't
necessary to do so.
This PR improves this behavior by adding a call to `Subscriber::enabled`
in `LogTracer`'s `Log::enabled` method. I've also added to the existing
tests for filtering `log` records to ensure that we also handle the
`log_enabled!` macro correctly.
While I was here, I noticed that the `Log::log` method for `LogTracer`
is somewhat inefficient --- it gets the current dispatcher *three*
times, and checks `enabled` twice. Currently, we check if the event
would be enabled, and then call the`format_trace` function, which *also*
checks if the event is enabled, and then dispatches it if it is. This is
not great. :/ I fixed this by moving the check-and-dispatch inside of a
single `dispatcher::get_default` block, and removing the duplicate
check.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
The `FmtCollector` type is missing a `Collect::max_level_hint
method that forwards the inner stack's max level hint.
This fixes that, and adds tests.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Depends on #1247.
Since `tracing-subscriber`'s `init` and `try_init` functions set the
global default subscriber, we can use the subscriber's max-level hint as
the max level for the log crate, as well. This should significantly
improve performance for skipping `log` records that fall below the
collector's max level, as they will not have to call the
`LogTracer::enabled` method.
This will prevent issues like bytecodealliance/wasmtime#2662
from occurring in the future. See also #1249.
In order to implement this, I also changed the `FmtSubscriber`'s
`try_init` to just use `util::SubscriberInitExt`'s `try_init` function,
so that the same code isn't duplicated in multiple places. I also
added `AsLog` and `AsTrace` conversions for `LevelFilter`s in
the `tracing-log` crate.
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
* Add sample implementation of `FormatEvent`
The docs previously didn't provide much help implementing `FormatEvent`.
Especially getting access to fields on spans could be tricky unless the
user was aware of `FormattedFields`.
This updates the docs with a basic, but working, example.
* Apply suggestions from code review
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
* Add comment explaining what `FormattedFields` is
* Expand docs a bit
* Fix formatting
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
Hi Folks,
This PR is about behavior compatibility with the `env_logger` and `log`
crates. There are references in the `tracing-subscriber` docs noting
some level of partial compatibility with `env_logger`, but it is not
clear to me the extent to which that is a priority.
If the intention is to keep the projects close in behavior where there
is overlap in the representations of logging directive strings, then
this PR is a step toward better compatibility.
On the other hand, if such compatibility is more of a short-term
nice-to-have than a long-term objective, then this PR might be a step in
the wrong direction. If so, please feel free to reject it. I happened to
notice the behavior difference (described below) while working on
something else, and just thought I'd bring it up for discussion.
On the *other* other hand, it is clear that some significant effort
*has* been expended to have compatibly parsed logging directive strings.
Which leads me to read the regex code modified in the second commit of
this PR as possibly introducing a level of compatibility that was
deliberately omitted; the existing regex was clearly structured to
accept *only* all uppercase OR *only* all lowercase log level names. So
I'm getting mixed signals :-)
In the end, regardless of the specific outcome of this PR, understanding
the degree to which `env_logger` compatibility is wanted would be useful
to know in general.
For my own use, `env_logger` compatibility is not something I need.
## Motivation
Logging directive strings parsed to create
`tracing_subscriber::filter::env::Directive`s are currently accepted as
all-lower-case or all-upper-case representations of the log level names
(like "info" and "INFO"), but mixed case representation (like "Info",
"iNfo", and "infO") are rejected.
This behavior is divergent with that of the `env_logger` crate, which
accepts the mixed case names. The `env_logger` crate gets the behavior
of parsing mixed case log level names from the underlying `log` crate,
so there may be an element of user expectations involved in that regard,
too, with `log` users expecting that case-insensitive log level names
will be accepted.
Accepting mixed case names would bring the behavior of the
`tracing_subscriber` crate back into alignment those other crates in
this regard.
## Solution
Accept mixed case names for log levels in directive strings.
This PR includes two commits:
1. The first adds unit tests that demonstrate the mixed case logging
level names being rejected.
2. The second introduces an adjustment to `DIRECTIVE_RE` that accepts
mixed case logging level names. With this change, the tests again all
pass.
* [1 of 2]: subscriber: add more parse_directives* tests
These test parse_directives() against strings that contain only a
legit log level name. The variants that submit the mixed case forms
are currently failing:
$ cargo test --lib 'filter::env::directive::test::parse_directives_'
...
failures:
filter::env::directive::test::parse_directives_global_bare_warn_mixed
filter::env::directive::test::parse_directives_ralith_mixed
test result: FAILED. 12 passed; 2 failed; 0 ignored; 0 measured; 61 filtered out
Fix to come in a follow-up commit.
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
Signed-off-by: Alan D. Salewski <ads@salewski.email>
* [2 of 2]: subscriber: directives: accept log levels in mixed case
Fix issue demonstrated by unit tests in commit f607b7fef2bd. With
this commit, the unit tests all pass.
The 'DIRECTIVE_RE' regex now uses a case-insensitive, non-capturing
subgroup when matching log level names in logging directive
strings. This allows correctly capturing not only, e.g., "info" and
"INFO" (both of which were also accepted previously), but also
"Info" and other combinations of mixed case variations for the legit
log level names.
This change makes the behavior of tracing-subscriber more consistent
with that of the `env_logger` crate, which also accepts mixed case
variations of the log level names.
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
Signed-off-by: Alan D. Salewski <ads@salewski.email>
This backports PR #1081 from v0.2.x. Since this has already been approved
on master, I'll just go ahead and merge it when CI passes.
## Motivation
Currently, the `FmtContext` type implements `FormatFields` using the
subscriber's field formatter. However, this is difficult to use. The
`FmtContext` may _not_ be passed to functions expecting a `for<'writer>
FormatFields<'writer>`, because it only implements `FormatFields` for
its _own_ lifetime. This means the writer must have the same lifetime as
the context, which is not correct.
## Solution
This branch fixes this by changing the impl of `FormatFields` for
`FmtContext` to be generic over both the context's lifetime _and_ the
field formatter's lifetime. Additionally, I've added a method for
borrowing the actual field formatter as its concrete type, in case the
`FormatEvent` impl puts additional constraints on its type or is only
implemented for a specific named type, and wants to actually _use_ that
type.
This backports PR #1067 to v0.1.x. Since this has already been approved
on master, I'm just going to go ahead and merge it when CI passes.
## Motivation
Currently, the `tracing_subscriber::fmt` module contains only
single-line event formatters. Some users have requested a
human-readable, pretty-printing event formatter optimized for
aesthetics.
## Solution
This branch adds a new `Pretty` formatter which uses an _excessively_
pretty output format. It's neither compact, single-line oriented, nor
easily machine-readable, but it looks _quite_ nice, in my opinion. This
is well suited for local development or potentially for user-facing logs
in a CLI application.
Additionally, I tried to improve the docs for the different formatters
currently provided, including example output. Check out [the Netlify
preview][1]!
[1]: https://deploy-preview-1067--tracing-rs.netlify.app/tracing_subscriber/fmt/index.html#formatters
Signed-off-by: Eliza Weisman <eliza@buoyant.io>