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

Attach fields to all associative functions #3124

Open
heaths opened this issue Oct 29, 2024 · 1 comment
Open

Attach fields to all associative functions #3124

heaths opened this issue Oct 29, 2024 · 1 comment

Comments

@heaths
Copy link

heaths commented Oct 29, 2024

Feature Request

Crates

  • tracing

Motivation

We are writing a client library and need to attach several fields to each client method call. To support both generated code and hand-written code - especially for ease of the latter - we'd love to use the #[tracing::instrument] procedural macro. By default, self gets traced using a derived Debug-like behavior (we don't actually derive or impl Debug) but we don't want that for risk of exposing PII.

While generated code could use something like #[instrument(skip_all, fields(client.name = "ExampleClient", client.method = "foo")] pub async foo(&self, name: &str) -> Result<(), Error> that puts the burden on the dev to remember to add client.name each time.

Proposal

Perhaps in concert with #1570 if self implements valueable::Valuable that could be used instead of using Debug or derived-Debug-like generated code with the caveat, however, that struct-likes are flattened...or that we can optional flatten them e.g., perhaps a "#" prefix.

Thus,

pub struct ExampleClient {
    endpoint: url::Url,
}

impl ExampleClient {
    #[instrument(name = "foo", target = "ExampleClient::foo"), skip(name), err]
    pub async fn foo(&self, name: &str) -> Result<(), Error> {
        todo!()
    }
}

impl Valuable for ExampleClient {
    // ...
}

impl Structable for ExampleClient {
    // ...
}

Might generate something like (using stdout subscriber):

2024-10-29T22:12:23.231910Z  INFO foo { client: "ExampleClient" }: ExampleClient::foo: enter

If this would be a breaking change even for v2, perhaps we can override self using #[instrument(..., fields(self = #self)] or similar to flatten the fields defined by Valuable instead of having a self field that contains them as is the case now.

Alternatives

We could include some boilerplate code or even helper functions for each method that effectively takes some data from the associated struct. For generated code this is fine, but we also expect a significant amount of hand-authored code for which this will be tedious and error-prone.

If it'd even work - I have yet to try it - we could also write our own procedural macro that would expand to #[::tracing::instrument] with our own defaults such that we're not duplicating code herein and all the problems that come with that.

However, I opened this feature request because while investigating, I ran across a number of questions asking how to do something similar and the responses were typically along the lines of "just create the spans yourself" which, as I pointed out, gets very tedious when there are a lot of associated functions (methods) one has to generate and/or write. It'd be great to at least consider different ways to support methods and attach data from the associated struct (enum, etc.) to make this scenario easier.

@heaths
Copy link
Author

heaths commented Oct 29, 2024

I just ran across #1123 that is similar and provides another alternative of a #[instrument_group] or something you can attach the associated type. That would also work, so long as any fields were flattened (or could be flattened).

heaths added a commit to heaths/tracing-examples that referenced this issue Oct 29, 2024
This isn't quite what we want because the fields are still buried in a "self" field, despite numerous attempts to flatten them. Opened tokio-rs/tracing#3124 to track a feature request (or similar).

We have options but most are rather error prone. Perhaps the best way is our own attribute macro that would expand to `#[tracing::instrument]` with appropriate defaults to attach client-level fields like `az.namespace`.

We could also write a helper function or two to just wrap the whole thing, which would also give us better control over the error or return value as well, like attaching `error.type` per our guidelines.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant