Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ opentelemetry_sdk = { version = "0.30", default-features = false, features = [
"experimental_metrics_periodicreader_with_async_runtime",
"rt-tokio",
"logs",
"spec_unstable_metrics_views"
] }
opentelemetry-otlp = { version = "0.30", default-features = false, features = [
"trace",
Expand Down
3 changes: 3 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub struct LogfireConfigBuilder {
impl Default for LogfireConfigBuilder {
fn default() -> Self {
Self {
environment: None,
service_name: None,
service_version: None,
local: false,
send_to_logfire: None,
token: None,
Expand Down
109 changes: 105 additions & 4 deletions src/logfire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use opentelemetry::{
use opentelemetry_sdk::{
logs::{SdkLoggerProvider, log_processor_with_async_runtime::BatchLogProcessor},
metrics::{
SdkMeterProvider, exporter::PushMetricExporter,
periodic_reader_with_async_runtime::PeriodicReader,
Aggregation, Instrument, InstrumentKind, SdkMeterProvider, Stream,
exporter::PushMetricExporter, periodic_reader_with_async_runtime::PeriodicReader,
},
runtime,
trace::{
Expand All @@ -43,6 +43,7 @@ use crate::{
log_processor_shutdown_hack::LogProcessorShutdownHack,
logfire_tracer::{GLOBAL_TRACER, LOCAL_TRACER, LogfireTracer},
},
metrics,
ulid_id_generator::UlidIdGenerator,
};

Expand Down Expand Up @@ -396,6 +397,26 @@ impl Logfire {
}
}

let view = |i: &Instrument| {
if i.kind() != InstrumentKind::Histogram {
return None;
}

let scale = {
let histograms = metrics::EXPONENTIAL_HISTOGRAMS.read().ok()?;
*histograms.get(i.name())?
};

Stream::builder()
.with_aggregation(Aggregation::Base2ExponentialHistogram {
max_size: 160,
max_scale: scale, // Upper bound on resolution
record_min_max: true,
})
.build()
.ok()
};
meter_provider_builder = meter_provider_builder.with_view(view);
let meter_provider = meter_provider_builder.build();

for log_processor in advanced_options.log_record_processors {
Expand Down Expand Up @@ -758,9 +779,21 @@ mod tests {
time::Duration,
};

use opentelemetry_sdk::trace::{SpanData, SpanProcessor};
use opentelemetry_sdk::{
metrics::{
InMemoryMetricExporter, PeriodicReader,
data::{Metric, MetricData},
reader::MetricReader,
},
trace::{SpanData, SpanProcessor},
};

use crate::{ConfigureError, Logfire, config::SendToLogfire, configure};
use crate::{
ConfigureError, Logfire,
config::{BoxedMetricReader, MetricsOptions, SendToLogfire},
configure, f64_exponential_histogram, f64_histogram, u64_exponential_histogram,
u64_histogram,
};

#[test]
fn test_send_to_logfire() {
Expand Down Expand Up @@ -1093,4 +1126,72 @@ mod tests {
let logs = log_exporter.get_emitted_logs().unwrap();
assert!(logs.is_empty());
}

#[test]
#[allow(clippy::unwrap_used)]
fn test_exponential_histogram_view() {
let exporter = InMemoryMetricExporter::default();
let reader = PeriodicReader::builder(exporter.clone()).build();
let logfire = configure()
.send_to_logfire(false)
.with_metrics(Some(MetricsOptions {
additional_readers: vec![BoxedMetricReader::new(Box::new(reader.clone()))],
}))
.finish()
.expect("failed to configure logfire");

let _guard = logfire.shutdown_guard();

let f64_hist = f64_histogram("f64_hist").build();
f64_hist.record(1.0, &[]);
let u64_hist = u64_histogram("u64_hist").build();
u64_hist.record(20, &[]);

let f64_exp = f64_exponential_histogram("f64_exp", 2).build();
f64_exp.record(1.0, &[]);
f64_exp.record(1.5, &[]);
f64_exp.record(2.0, &[]);
f64_exp.record(3.0, &[]);
f64_exp.record(10.0, &[]);

let u64_exp = u64_exponential_histogram("u64_exp", 2).build();
u64_exp.record(10, &[]);

reader.force_flush().unwrap();

let metrics = exporter.get_finished_metrics().unwrap();

for scope_metics in metrics[0].scope_metrics() {
for (name, expected) in [
("f64_hist", false),
("u64_hist", false),
("f64_exp", true),
("u64_exp", true),
] {
let metric = scope_metics.metrics().find(|m| m.name() == name).unwrap();
assert_eq!(expected, is_exponential_histogram(metric));
}
}
}

fn is_exponential_histogram(metric: &Metric) -> bool {
match metric.data() {
opentelemetry_sdk::metrics::data::AggregatedMetrics::F64(metric_data) => {
is_exponential_histogram_metric_data(metric_data)
}
opentelemetry_sdk::metrics::data::AggregatedMetrics::U64(metric_data) => {
is_exponential_histogram_metric_data(metric_data)
}
opentelemetry_sdk::metrics::data::AggregatedMetrics::I64(metric_data) => {
is_exponential_histogram_metric_data(metric_data)
}
}
}

fn is_exponential_histogram_metric_data<T>(data: &MetricData<T>) -> bool {
matches!(
data,
opentelemetry_sdk::metrics::data::MetricData::ExponentialHistogram(_)
)
}
}
Loading