Skip to content

Commit

Permalink
Metric stdout exporter to print timestamp in human readable format (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
cijothomas authored Aug 11, 2023
1 parent 1d4db15 commit 2d42766
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 1 deletion.
5 changes: 5 additions & 0 deletions opentelemetry-stdout/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion opentelemetry-stdout/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 }
Expand Down
46 changes: 46 additions & 0 deletions opentelemetry-stdout/src/metrics/transform.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use chrono::{LocalResult, TimeZone, Utc};
use opentelemetry_api::{global, metrics::MetricsError};
use opentelemetry_sdk::metrics::data;
use serde::{Serialize, Serializer};
Expand Down Expand Up @@ -223,6 +224,10 @@ impl<T: Into<DataValue> + Copy> From<&data::Sum<T>> for Sum {
#[serde(rename_all = "camelCase")]
struct DataPoint {
attributes: AttributeSet,
#[serde(serialize_with = "as_opt_human_readable")]
start_time: Option<SystemTime>,
#[serde(serialize_with = "as_opt_human_readable")]
time: Option<SystemTime>,
#[serde(serialize_with = "as_opt_unix_nano")]
start_time_unix_nano: Option<SystemTime>,
#[serde(serialize_with = "as_opt_unix_nano")]
Expand All @@ -244,6 +249,8 @@ impl<T: Into<DataValue> + Copy> From<&data::DataPoint<T>> 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,
Expand Down Expand Up @@ -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<f64>,
bucket_counts: Vec<u64>,
Expand All @@ -291,6 +302,8 @@ impl<T: Into<DataValue> + Copy> From<&data::HistogramDataPoint<T>> 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(),
Expand All @@ -309,11 +322,43 @@ struct Exemplar {
filtered_attributes: Vec<KeyValue>,
#[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<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
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<S>(time: &Option<SystemTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match time {
None => serializer.serialize_none(),
Some(time) => as_human_readable(time, serializer),
}
}

fn as_unix_nano<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Expand Down Expand Up @@ -341,6 +386,7 @@ impl<T: Into<DataValue> + Copy> From<&data::Exemplar<T>> 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)),
Expand Down

0 comments on commit 2d42766

Please sign in to comment.