-
Notifications
You must be signed in to change notification settings - Fork 34
Allow specifying result labels for enum variants #61
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
Conversation
The target use case is when a service uses a single enumeration with all the possible errors, but some "errors" should not be counted as failures regarding metric collection (for example, all the 4xx errors in a HTTP handler usually mean that the server succeeded in rejecting a bad request).
585f28c
to
6ce2de0
Compare
This allowed to discover a bug in the code generated, which has also been fixed in the commit.
… ok_variants_within_error_enums
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a little surprising to me that this would only work on enums returned as the error part of a Result
. I think I would expect to derive this trait for an enum, then have that enum appear in the Ok
or Error
variant, or as the return type without a Result
, and have it correctly override the default "ok"
or "error"
labels.
I don't think there's a way to make a trait that work on "all enums" in general to be able to derive this behaviour outside of So to summarize:
In |
… ok_variants_within_error_enums
Make sure that having the type behind a reference does _not_ fall back to the generic default case
I think it is possible to make it work using the autoref specialization trick. We'd need to define a dummy trait that returns an empty string (or make both traits return The one subtle gotcha that I realized with @sagacity was that this only works when Rust decides which version to use in macro-generated code, not in the implementation of a trait. |
7038d77
to
2175ccc
Compare
I don't think it's really possible, at least I can't make it work with the current version because to work transparently with Results that have enum or not I need to do: impl<T, E> GetLabelsFromResult for Result<T, E> {
fn __autometrics_get_labels(&self) -> Option<ResultAndReturnTypeLabels> {
match self {
Ok(ok) => Some((
ok.__autometrics_get_result_label().unwrap_or(OK_KEY),
ok.__autometrics_static_str(),
)),
Err(err) => Some((
err.__autometrics_get_result_label().unwrap_or(ERROR_KEY),
err.__autometrics_static_str(),
)),
}
}
}
/// Implementation detail to get enum variants to specify their own
/// "result" label
pub trait GetResultLabel {
/// Return the value to use for the [result](RESULT_KEY) value in the reported metrics
fn __autometrics_get_result_label(&self) -> Option<&'static str> {
None
}
}
impl_trait_for_types!(GetResultLabel); And basically, the "override" of #[derive(ResultLabels, Clone)]
enum MyEnum {
#[label(result = "error")]
Empty,
#[label(result = "ok")]
ClientError { inner: Inner },
AmbiguousValue(u64),
}
fn main() {
let is_ok = MyEnum::ClientError { inner: Inner {} };
assert_eq!(is_ok.__autometrics_get_result_label().unwrap(), "ok");
assert_eq!(is_ok.__autometrics_get_labels().unwrap().0, "ok");
let err = MyEnum::Empty;
assert_eq!(err.__autometrics_get_result_label().unwrap(), "error");
assert_eq!(err.__autometrics_get_labels().unwrap().0, "error");
let no_idea = MyEnum::AmbiguousValue(42);
assert_eq!(no_idea.__autometrics_get_result_label(), None);
assert_eq!(no_idea.__autometrics_get_labels(), None);
} But when calling the method through a let err_in_ok: Result<MyEnum, ()> = Ok(err.clone());
// Fails
assert_eq!(
err_in_ok.__autometrics_get_labels().unwrap().0,
"error",
"When wrapped as the Ok variant of a result, a manually marked 'error' variant translates to 'error'."
);
let ok_in_err: Result<(), MyEnum> = Err(is_ok);
// Fails too
assert_eq!(
ok_in_err.__autometrics_get_labels().unwrap().0,
"ok",
"When wrapped as the Err variant of a result, a manually marked 'ok' variant translates to 'ok'."
); |
@gagbo yeah. So I think instead of implementing the trait for use autometrics::__private::{IsResult, IsNotResult, ResultLabel, EmptyResultLabel};
if result.__is_result() {
match result {
Ok(val) => val.get_label(),
Err(val) => val.get_label(),
} else {
result.get_label()
} |
Might be worth using the spez crate or looking at how it's implemented |
I'll probably end up just using the crate, I don't think it'll add a lot of overhead and it would make separation clearer |
Yeah I've been fiddling a bit and it seems the function redirection inside a generic function (the This seems to have the advantage that it would become the single place to modify to add special cases for label handling |
… ok_variants_within_error_enums
This change is necessary to make spez work, otherwise the compiler gets confused about the specialization to choose in the spez::Match<i> generated traits. The issue is not spez-specific, as the macro is simple sugar over the auto-deref specialization trick in general. This commit is also the "maximalist" solution to #74, as it simply ignores the return type if it has syntax incompatible with the context we want to use. Closes: #74 Co-Authored-By: Anthony Deschamps <anthony.j.deschamps@gmail.com>
c8f2af0
to
6b1869a
Compare
autometrics/src/labels.rs
Outdated
impl_trait_for_types!(GetStaticStr); | ||
|
||
#[macro_export] | ||
macro_rules! result_labels { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all of this should be in the macros crate, no? Why not just move this into the implementation of the autometrics
proc macro?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the macro separate allows to properly test for it (and the ResultLabels
attribute macro) in the trybuild tests. Inlining everything would make the main instrument_function
code harder to read and make it impossible to test the label logic in isolation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason it's not in the macros crate is mostly that it's just expanding code and that I wanted the macro to access values from autometrics
(the constants, the labels, the traits): since the macro is not a proc macro returning an arbitrary TokenStream, we need to have proper access to the crate (which we don't have inside autometrics-macros
)
use $crate::__private::{ | ||
GetLabels, GetStaticStr, ResultAndReturnTypeLabels, ERROR_KEY, OK_KEY, | ||
}; | ||
$crate::__private::spez! { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better if we could call spez
directly in the proc macro code and insert the token stream directly into the code the macro generates, instead of making it a dependency of the main autometrics
crate and having the macro generate code that calls spez (unless there's a reason that cannot work)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling the function spez::spez
to get a proc_macro::TokenStream
would probably work but it doesn't really bring advantages: calling spez::spez
means that there would still be a dependency to spez
(even if the macro is moved to autometrics-macros
, it would be AM needs AM-macros needs spez
), and preparing an input TokenStream
from code is one additional step that would be unnecessary added complexity in the macro.
Generating the match Result…
TokenStream
in the instrument_function
directly has the drawback of having bad testability and makes instrument_function
readability worse
Context
The target use case is when a service uses a single enumeration with all
the possible errors, but some "errors" should not be counted as failures
regarding metric collection (for example, all the 4xx errors in a HTTP
handler usually mean that the server succeeded in rejecting a bad request).
TODOs
This draft PR only has a raw implementation for now, which should allow discussing
implementation details, and the naming of the various attributes/arguments, but
it is still missing a few things.
trybuild, probably)