diff --git a/opentelemetry-stdout/CHANGELOG.md b/opentelemetry-stdout/CHANGELOG.md index 2489fcd1f6..d87c8852b0 100644 --- a/opentelemetry-stdout/CHANGELOG.md +++ b/opentelemetry-stdout/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## vNext + +- Timestamp is additionally exported in user-friendly format. + [#1192](https://github.com/open-telemetry/opentelemetry-rust/pull/1192). + ## v0.1.0 ### Added diff --git a/opentelemetry-stdout/Cargo.toml b/opentelemetry-stdout/Cargo.toml index f1024c5ccf..4f6bdc8f8f 100644 --- a/opentelemetry-stdout/Cargo.toml +++ b/opentelemetry-stdout/Cargo.toml @@ -10,7 +10,7 @@ categories = [ "development-tools::profiling", "asynchronous", ] -keywords = ["opentelemetry", "tracing", "metrics"] +keywords = ["opentelemetry", "tracing", "metrics", "logs"] license = "Apache-2.0" edition = "2021" rust-version = "1.60" @@ -22,6 +22,7 @@ logs = ["opentelemetry_api/logs", "opentelemetry_sdk/logs", "async-trait", "this [dependencies] async-trait = { version = "0.1", optional = true } +chrono = { version = "0.4.22", default-features = false, features = ["clock"] } thiserror = { version = "1", optional = true } futures-util = { version = "0.3", optional = true, default-features = false } opentelemetry_api = { version = "0.20", path = "../opentelemetry-api", default_features = false } diff --git a/opentelemetry-stdout/src/metrics/transform.rs b/opentelemetry-stdout/src/metrics/transform.rs index ef4fcee4cf..06ce9e75ca 100644 --- a/opentelemetry-stdout/src/metrics/transform.rs +++ b/opentelemetry-stdout/src/metrics/transform.rs @@ -1,3 +1,4 @@ +use chrono::{LocalResult, TimeZone, Utc}; use opentelemetry_api::{global, metrics::MetricsError}; use opentelemetry_sdk::metrics::data; use serde::{Serialize, Serializer}; @@ -223,6 +224,10 @@ impl + Copy> From<&data::Sum> for Sum { #[serde(rename_all = "camelCase")] struct DataPoint { attributes: AttributeSet, + #[serde(serialize_with = "as_opt_human_readable")] + start_time: Option, + #[serde(serialize_with = "as_opt_human_readable")] + time: Option, #[serde(serialize_with = "as_opt_unix_nano")] start_time_unix_nano: Option, #[serde(serialize_with = "as_opt_unix_nano")] @@ -244,6 +249,8 @@ impl + Copy> From<&data::DataPoint> for DataPoint { attributes: AttributeSet::from(&value.attributes), start_time_unix_nano: value.start_time, time_unix_nano: value.time, + start_time: value.start_time, + time: value.time, value: value.value.into(), exemplars: value.exemplars.iter().map(Into::into).collect(), flags: 0, @@ -275,6 +282,10 @@ struct HistogramDataPoint { start_time_unix_nano: SystemTime, #[serde(serialize_with = "as_unix_nano")] time_unix_nano: SystemTime, + #[serde(serialize_with = "as_human_readable")] + start_time: SystemTime, + #[serde(serialize_with = "as_human_readable")] + time: SystemTime, count: u64, explicit_bounds: Vec, bucket_counts: Vec, @@ -291,6 +302,8 @@ impl + Copy> From<&data::HistogramDataPoint> for Histogram attributes: AttributeSet::from(&value.attributes), start_time_unix_nano: value.start_time, time_unix_nano: value.time, + start_time: value.start_time, + time: value.time, count: value.count, explicit_bounds: value.bounds.clone(), bucket_counts: value.bucket_counts.clone(), @@ -309,11 +322,43 @@ struct Exemplar { filtered_attributes: Vec, #[serde(serialize_with = "as_unix_nano")] time_unix_nano: SystemTime, + #[serde(serialize_with = "as_human_readable")] + time: SystemTime, value: DataValue, span_id: String, trace_id: String, } +fn as_human_readable(time: &SystemTime, serializer: S) -> Result +where + S: Serializer, +{ + let duration_since_epoch = time.duration_since(UNIX_EPOCH).unwrap_or_default(); + + match Utc.timestamp_opt( + duration_since_epoch.as_secs() as i64, + duration_since_epoch.subsec_nanos(), + ) { + LocalResult::Single(datetime) => serializer.serialize_str( + datetime + .format("%Y-%m-%d %H:%M:%S.%3f") + .to_string() + .as_ref(), + ), + _ => Err(serde::ser::Error::custom("Invalid Timestamp.")), + } +} + +fn as_opt_human_readable(time: &Option, serializer: S) -> Result +where + S: Serializer, +{ + match time { + None => serializer.serialize_none(), + Some(time) => as_human_readable(time, serializer), + } +} + fn as_unix_nano(time: &SystemTime, serializer: S) -> Result where S: Serializer, @@ -341,6 +386,7 @@ impl + Copy> From<&data::Exemplar> for Exemplar { Exemplar { filtered_attributes: value.filtered_attributes.iter().map(Into::into).collect(), time_unix_nano: value.time, + time: value.time, value: value.value.into(), span_id: format!("{:016x}", u64::from_be_bytes(value.span_id)), trace_id: format!("{:032x}", u128::from_be_bytes(value.trace_id)),