Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tracing_subscriber: Expose .with_ansi() to custom event/field formatters #1651

Closed
lilyball opened this issue Oct 19, 2021 · 6 comments
Closed
Labels
crate/subscriber Related to the `tracing-subscriber` crate meta/breaking This is a breaking change, and should wait until the next breaking release.

Comments

@lilyball
Copy link
Contributor

Feature Request

Crates

tracing-subscriber

Motivation

The SubscriberBuilder::with_ansi() method only affects the default event formatter (tracing_subscriber::fmt::format::Format). This means tracing_subscriber::fmt().with_ansi(true).event_format(fmt_event).init() silently throws away the with_ansi flag. It also means that this flag isn't available to custom event formatters. And this flag isn't exposed to field formatters (not even the default field formatter, though DefaultFields doesn't currently care about this).

It would be great if instead of being a property of the default event formatter, it was a flag that was exposed to arbitrary FormatEvent and FormatFields implementations. This way if I do have a custom field formatter that cares about ANSI, there's a single source of truth for this, as well as having .event_format() not silently overwrite the .with_ansi() flag. This also makes it easier to integrate logic for only outputting colors to terminals as anything provided by tracing_subscriber directly would then propagate to custom event/field formatters.

Proposal

The simplest solution is probably to change FormatEvent and FormatFields to take a custom wrapper type instead of a fork &mut dyn Write. This wrapper type would still implement Write but would also expose the ansi flag. This way the event and field formatters will get the same flag, controlled by the .with_ansi() method, and custom field formatters can now be provided by crates as drop-in replacements easily.

Alternatives

The proposed change is a breaking one, but is the cleanest solution that comes to mind. A couple of alternatives come to mind:

Change Write to a trait with more semantic meaning

Right now, changing things like terminal colors requires replacing the event and field formatters, potentially discarding any custom logic there (e.g. if I have a field formatter that handles certain fields specially, and another one that adds color to fields, I can't combine the two). We could consider replacing Write with a trait that requires you to specify the nature of the data you're writing, such as write_key(_: &fmt::Arguments<'_>), write_value(_: &fmt::Arguments<'_>), write_separator(_: &fmt::Arguments<'_>), write_delimiter(_: &fmt::Arguments<'_>), etc. Then we can have a separate configurable "format decorator" that provides these implementations and picks appropriate escapes to use. See slog_term::RecordDecorator for an example of this approach. The complexity here is we'd need to figure out all possible semantic meanings that a format decorator would want to distinguish, but it would also allow a format decorator to e.g. emit XML instead (although such a decorator would have to handle e.g. a value with no key, and there's a bit of confusion there over who controls delimiter/separator strings, as an XML decorator would likely want to just use tags there but terminal output wants strings).

This is of course also a breaking change, and a much more significant one. It's unclear to me if it's worth the complexity in order to separate colors from text, though at the moment I would actually love to be able to do this as I'd like to change colors without losing any default behavior around custom handling of fields (such as DefaultFields skipping log.* fields and stripping r# prefixes and including error sources).

Expose the ansi flag via additional defaulted trait methods

We could extend RecordFields and FormatEvent with an additional defaulted fn set_ansi(enabled: Bool) (default impl doing nothing). This is classified as a "possibly breaking" change, in that code that has the trait in scope might throw ambiguity errors when calling the method if another in-scope trait defines the same method, and I suspect this is unlikely to affect downstream code. It's also not a particularly great API as it requires the implementation to maintain state.

Switch traits with a compatibility shim

We could do the original proposal but only expose this in new traits that replace FormatEvents and FormatFields. The new traits could have blanket impls for anything that implements the old traits, thus allowing existing code to keep working, and anything that cares about the ANSI flag would implement the new trait instead.

This should be backwards-compatible but is potentially confusing and requires coming up with new trait names.

@hawkw
Copy link
Member

hawkw commented Oct 19, 2021

I agree that your proposed approach, wrapping the dyn Write trait object in a newtype, seems like the best one --- it would also give us a nice place to potentially put other things that may need to be passed in, in the future.

@lilyball, are you able to work on a PR for this change in the near future? We're currently working on preparing tracing-subscriber 0.3, which is necessary to resolve the recent security advisory for the chrono crate (currently a public API dependency in 0.2). This means that we have the opportunity to make other breaking API changes as well. So, if it's possible to get a branch together for that change soon, we can probably do this as part of 0.3.

@hawkw hawkw added crate/subscriber Related to the `tracing-subscriber` crate meta/breaking This is a breaking change, and should wait until the next breaking release. labels Oct 19, 2021
@lilyball
Copy link
Contributor Author

@hawkw Perhaps. I'm not sure yet. I'll see if it's something I can do.

@hawkw
Copy link
Member

hawkw commented Oct 19, 2021

If you're not able to work on it, that's totally fine --- no pressure! I just figured I'd ask because it would certainly be nice to be able to get this change into 0.3, but if not, we can do it in the next breaking release.

@lilyball
Copy link
Contributor Author

Sounds like based on the intended release time of 0.3 that there's not enough time for me to even find out if I can do a PR.

@hawkw
Copy link
Member

hawkw commented Oct 20, 2021

@lilyball PR #1661 implements the breaking API change necessary for your proposed solution so that it can be merged in time for 0.3. Please feel free to take a look, if you like!

The actual propagation of the ANSI color configuration can be added in a non-breaking change once #1661 has been released as part of 0.3, so there's no rush for that part.

hawkw added a commit that referenced this issue Oct 20, 2021
## Motivation

Currently, the `FormatEvent` and `FormatFields` traits in
`tracing-subscriber` are passed a `&mut dyn fmt::Write` trait object to
write formatted representations of events and fields to. This is fine,
but it doesn't give us the ability to easily provide additional
configuration to the formatter, such as whether ANSI color codes are
supported.

Issue #1651 describes some approaches for adding a way to expose the
ANSI color code configuration to custom formatters. In particular, the
proposed solution involves wrapping the `fmt::Write` trait object in an
opaque struct, which can then implement additional methods for exposing
information like "are ANSI colors enabled" to the formatter. Since this
changes the signature of the `FormatEvent::format_event` and
`FormatFields::format_fields` methods, it's a breaking change.
Therefore, we need to make this change _now_ if we want to get the API
change in for `tracing-subscriber` 0.3.

## Solution

This branch adds a `Writer` struct that wraps the `&mut dyn fmt::Write`
trait object, and changes the various method signatures as appropriate.
It does **not** actually implement the change related to ANSI color
formatting. Once we change these methods' signatures to accept a
`Writer` struct, we can add as many methods to that struct as we like
without making additional breaking API changes. Therefore, this branch
_just_ makes the breaking change that's necessary to get in before v0.3
is released.

Propagating the ANSI color configuration can be implemented in a future
branch.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Oct 21, 2021
## Motivation

Currently, the `FormatEvent` and `FormatFields` traits in
`tracing-subscriber` are passed a `&mut dyn fmt::Write` trait object to
write formatted representations of events and fields to. This is fine,
but it doesn't give us the ability to easily provide additional
configuration to the formatter, such as whether ANSI color codes are
supported.

Issue #1651 describes some approaches for adding a way to expose the
ANSI color code configuration to custom formatters. In particular, the
proposed solution involves wrapping the `fmt::Write` trait object in an
opaque struct, which can then implement additional methods for exposing
information like "are ANSI colors enabled" to the formatter. Since this
changes the signature of the `FormatEvent::format_event` and
`FormatFields::format_fields` methods, it's a breaking change.
Therefore, we need to make this change _now_ if we want to get the API
change in for `tracing-subscriber` 0.3.

## Solution

This branch adds a `Writer` struct that wraps the `&mut dyn fmt::Write`
trait object, and changes the various method signatures as appropriate.
It does **not** actually implement the change related to ANSI color
formatting. Once we change these methods' signatures to accept a
`Writer` struct, we can add as many methods to that struct as we like
without making additional breaking API changes. Therefore, this branch
_just_ makes the breaking change that's necessary to get in before v0.3
is released.

Propagating the ANSI color configuration can be implemented in a future
branch.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Oct 21, 2021
## Motivation

Currently, the `FormatEvent` and `FormatFields` traits in
`tracing-subscriber` are passed a `&mut dyn fmt::Write` trait object to
write formatted representations of events and fields to. This is fine,
but it doesn't give us the ability to easily provide additional
configuration to the formatter, such as whether ANSI color codes are
supported.

Issue #1651 describes some approaches for adding a way to expose the
ANSI color code configuration to custom formatters. In particular, the
proposed solution involves wrapping the `fmt::Write` trait object in an
opaque struct, which can then implement additional methods for exposing
information like "are ANSI colors enabled" to the formatter. Since this
changes the signature of the `FormatEvent::format_event` and
`FormatFields::format_fields` methods, it's a breaking change.
Therefore, we need to make this change _now_ if we want to get the API
change in for `tracing-subscriber` 0.3.

## Solution

This branch adds a `Writer` struct that wraps the `&mut dyn fmt::Write`
trait object, and changes the various method signatures as appropriate.
It does **not** actually implement the change related to ANSI color
formatting. Once we change these methods' signatures to accept a
`Writer` struct, we can add as many methods to that struct as we like
without making additional breaking API changes. Therefore, this branch
_just_ makes the breaking change that's necessary to get in before v0.3
is released.

Propagating the ANSI color configuration can be implemented in a future
branch.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this issue Nov 8, 2021
## Motivation

Currently, whether `tracing-subscriber`'s `fmt` subscriber will ANSI
formatting escape codes use is configured on the `Format` type. This
means that the configuration is honored by the event formatters
implemented in `tracing-subscriber`, but is not easily exposed to those
in other crates. Additionally, it's not currently easy to expose the
configuration to the field formatter, so it's difficult to implement
field formatters that use ANSI escape codes conditionally.

Issue #1651 suggested a new API for this, where the writer that's passed
in to the event and field formatters provides a method for checking if
ANSI escape codes are supported.

## Solution

This branch adds a new method to the `Writer` type added in #1661. The
`FormatEvent` and `FormatFields` implementations can call
`Writer::has_ansi_escapes` to determine whether ANSI escape codes are
supported. This is also propagated to `FormattedFields`, so that it can
be determined when adding new fields to a preexisting set of formatted
fields.

Fixes #1661

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
@hawkw
Copy link
Member

hawkw commented Nov 8, 2021

Implemented in #1696

@hawkw hawkw closed this as completed Nov 8, 2021
hawkw added a commit that referenced this issue Nov 8, 2021
This backports PR #1696 to v0.1.x.

## Motivation

Currently, whether `tracing-subscriber`'s `fmt` subscriber will ANSI
formatting escape codes use is configured on the `Format` type. This
means that the configuration is honored by the event formatters
implemented in `tracing-subscriber`, but is not easily exposed to those
in other crates. Additionally, it's not currently easy to expose the
configuration to the field formatter, so it's difficult to implement
field formatters that use ANSI escape codes conditionally.

Issue #1651 suggested a new API for this, where the writer that's passed
in to the event and field formatters provides a method for checking if
ANSI escape codes are supported.

## Solution

This branch adds a new method to the `Writer` type added in #1661. The
`FormatEvent` and `FormatFields` implementations can call
`Writer::has_ansi_escapes` to determine whether ANSI escape codes are
supported. This is also propagated to `FormattedFields`, so that it can
be determined when adding new fields to a preexisting set of formatted
fields.

Fixes #1651

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crate/subscriber Related to the `tracing-subscriber` crate meta/breaking This is a breaking change, and should wait until the next breaking release.
Projects
None yet
Development

No branches or pull requests

2 participants