-
Notifications
You must be signed in to change notification settings - Fork 605
fix: serialization/deserialization for u64, f64, and "NaN" float fields in Metrics #3284
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
base: main
Are you sure you want to change the base?
Changes from all commits
e7e3b39
eefc117
da5ed50
8d57a93
18b7c71
196d926
45fb828
6635302
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ pub(crate) mod serializers { | |||||||||||||||||||||
| use serde::de::{self, MapAccess, Visitor}; | ||||||||||||||||||||||
| use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct}; | ||||||||||||||||||||||
| use serde::{Deserialize, Deserializer, Serialize, Serializer}; | ||||||||||||||||||||||
| use core::f64; | ||||||||||||||||||||||
| use std::fmt; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // hex string <-> bytes conversion | ||||||||||||||||||||||
|
|
@@ -213,6 +214,89 @@ pub(crate) mod serializers { | |||||||||||||||||||||
| let s: String = Deserialize::deserialize(deserializer)?; | ||||||||||||||||||||||
| s.parse::<i64>().map_err(de::Error::custom) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| pub fn serialize_vec_u64_to_strings<S>(vec: &Vec<u64>, serializer: S) -> Result<S::Ok, S::Error> | ||||||||||||||||||||||
| where | ||||||||||||||||||||||
| S: Serializer, | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| let str_vec: Vec<String> = vec.iter().map(|&num| num.to_string()).collect(); | ||||||||||||||||||||||
|
Comment on lines
+217
to
+221
|
||||||||||||||||||||||
| pub fn serialize_vec_u64_to_strings<S>(vec: &Vec<u64>, serializer: S) -> Result<S::Ok, S::Error> | |
| where | |
| S: Serializer, | |
| { | |
| let str_vec: Vec<String> = vec.iter().map(|&num| num.to_string()).collect(); | |
| pub fn serialize_vec_u64_to_strings<S>(value: &[u64], serializer: S) -> Result<S::Ok, S::Error> | |
| where | |
| S: Serializer, | |
| { | |
| let str_vec: Vec<String> = value.iter().map(|&num| num.to_string()).collect(); |
Copilot
AI
Dec 18, 2025
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.
Trailing whitespace on this line. The line ends with spaces after the closing brace, which should be removed for consistency with coding standards.
Copilot
AI
Dec 18, 2025
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.
Unnecessary blank line. This blank line creates excessive vertical spacing (two consecutive blank lines). Remove this line to maintain consistent spacing throughout the file.
Copilot
AI
Dec 18, 2025
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 deserializer should handle additional numeric types. The visit_u64 method is implemented but visit_i64 is missing. If the JSON contains integer values like 0 or 1 for quantile values, they might be deserialized as i64 depending on the JSON parser, which would cause deserialization to fail. Consider adding a visit_i64 method similar to visit_u64.
| fn visit_i64<E>(self, value: i64) -> Result<f64, E> | |
| where | |
| E: de::Error, | |
| { | |
| Ok(value as f64) | |
| } |
Copilot
AI
Dec 18, 2025
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.
Trailing whitespace on this line. The line ends with a space after the closing brace, which should be removed for consistency with coding standards.
| } | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -101,15 +101,23 @@ fn build_tonic() { | |||||||||||||
| // the proto file uses u64 for timestamp | ||||||||||||||
| // Thus, special serializer and deserializer are needed | ||||||||||||||
| for path in [ | ||||||||||||||
| //trace | ||||||||||||||
| "trace.v1.Span.start_time_unix_nano", | ||||||||||||||
| "trace.v1.Span.end_time_unix_nano", | ||||||||||||||
| "trace.v1.Span.Event.time_unix_nano", | ||||||||||||||
| //logs | ||||||||||||||
| "logs.v1.LogRecord.time_unix_nano", | ||||||||||||||
| "logs.v1.LogRecord.observed_time_unix_nano", | ||||||||||||||
| //metrics | ||||||||||||||
| "metrics.v1.HistogramDataPoint.start_time_unix_nano", | ||||||||||||||
| "metrics.v1.HistogramDataPoint.time_unix_nano", | ||||||||||||||
| "metrics.v1.NumberDataPoint.start_time_unix_nano", | ||||||||||||||
| "metrics.v1.NumberDataPoint.time_unix_nano", | ||||||||||||||
| "metrics.v1.ExponentialHistogramDataPoint.start_time_unix_nano", | ||||||||||||||
| "metrics.v1.ExponentialHistogramDataPoint.time_unix_nano", | ||||||||||||||
| "metrics.v1.SummaryDataPoint.start_time_unix_nano", | ||||||||||||||
| "metrics.v1.SummaryDataPoint.time_unix_nano", | ||||||||||||||
| "metrics.v1.Exemplar.time_unix_nano", | ||||||||||||||
| ] { | ||||||||||||||
| builder = builder | ||||||||||||||
| .field_attribute(path, "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_u64_to_string\", deserialize_with = \"crate::proto::serializers::deserialize_string_to_u64\"))]") | ||||||||||||||
|
|
@@ -123,6 +131,51 @@ fn build_tonic() { | |||||||||||||
| .field_attribute(path, "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_vec_u64_to_string\", deserialize_with = \"crate::proto::serializers::deserialize_vec_string_to_vec_u64\"))]") | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // special serializer and deserializer for metrics count | ||||||||||||||
| // OTLP/JSON format may uses string for count | ||||||||||||||
|
||||||||||||||
| // the proto file uses u64 for count | ||||||||||||||
| // Thus, special serializer and deserializer are needed | ||||||||||||||
| for path in [ | ||||||||||||||
| // metrics count and bucket fields | ||||||||||||||
| "metrics.v1.HistogramDataPoint.count", | ||||||||||||||
| "metrics.v1.ExponentialHistogramDataPoint.count", | ||||||||||||||
| "metrics.v1.ExponentialHistogramDataPoint.zero_count", | ||||||||||||||
| "metrics.v1.SummaryDataPoint.count", | ||||||||||||||
| ] { | ||||||||||||||
| builder = builder.field_attribute( | ||||||||||||||
| path, | ||||||||||||||
| "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_u64_to_string\", deserialize_with = \"crate::proto::serializers::deserialize_string_to_u64\"))]", | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // special serializer and deserializer for metrics bucket counts | ||||||||||||||
| // OTLP/JSON format may uses string for bucket counts | ||||||||||||||
|
||||||||||||||
| // the proto file uses u64 for bucket counts | ||||||||||||||
| // Thus, special serializer and deserializer are needed | ||||||||||||||
| for path in [ | ||||||||||||||
| "metrics.v1.HistogramDataPoint.bucket_counts", | ||||||||||||||
| "metrics.v1.ExponentialHistogramDataPoint.positive.bucket_counts", | ||||||||||||||
| "metrics.v1.ExponentialHistogramDataPoint.negative.bucket_counts", | ||||||||||||||
| ] { | ||||||||||||||
| builder = builder.field_attribute( | ||||||||||||||
| path, | ||||||||||||||
| "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_vec_u64_to_strings\", deserialize_with = \"crate::proto::serializers::deserialize_strings_to_vec_u64\"))]", | ||||||||||||||
| ); | ||||||||||||||
|
Comment on lines
+160
to
+163
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Special handling for floating-point fields that might contain NaN, Infinity, or -Infinity | ||||||||||||||
| // TODO: More needs to be added here as we find more fields that need this special handling | ||||||||||||||
| for path in [ | ||||||||||||||
| // metrics | ||||||||||||||
| "metrics.v1.SummaryDataPoint.ValueAtQuantile.value", | ||||||||||||||
| "metrics.v1.SummaryDataPoint.ValueAtQuantile.quantile", | ||||||||||||||
| ] { | ||||||||||||||
| builder = builder.field_attribute( | ||||||||||||||
| path, | ||||||||||||||
| "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_f64_special\", deserialize_with = \"crate::proto::serializers::deserialize_f64_special\"))]", | ||||||||||||||
| ); | ||||||||||||||
|
Comment on lines
+174
to
+176
|
||||||||||||||
| path, | |
| "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_f64_special\", deserialize_with = \"crate::proto::serializers::deserialize_f64_special\"))]", | |
| ); | |
| path, | |
| "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_f64_special\", deserialize_with = \"crate::proto::serializers::deserialize_f64_special\"))]", | |
| ); |
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.
Unnecessary import statement. The
use core::f64;import is not needed sincef64is a primitive type in Rust and its associated constants (NAN, INFINITY, NEG_INFINITY) can be accessed directly without importing the module. This import can be removed.