Skip to content

Commit 71497d5

Browse files
authored
Merge branch 'main' into current-thread-int-test-logs
2 parents 503203e + b017c7b commit 71497d5

File tree

10 files changed

+208
-60
lines changed

10 files changed

+208
-60
lines changed

examples/metrics-advanced/src/main.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,12 @@ fn init_meter_provider() -> opentelemetry_sdk::metrics::SdkMeterProvider {
4949
.with_temporality(Temporality::Delta)
5050
.build();
5151

52-
let reader = PeriodicReader::builder(exporter).build();
53-
5452
let resource = Resource::builder()
5553
.with_service_name("metrics-advanced-example")
5654
.build();
5755

5856
let provider = SdkMeterProvider::builder()
59-
.with_reader(reader)
57+
.with_periodic_exporter(exporter)
6058
.with_resource(resource)
6159
.with_view(my_view_rename_and_unit)
6260
.with_view(my_view_drop_attributes)

examples/metrics-basic/src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ fn init_meter_provider() -> opentelemetry_sdk::metrics::SdkMeterProvider {
1010
// Build exporter using Delta Temporality (Defaults to Temporality::Cumulative)
1111
// .with_temporality(opentelemetry_sdk::metrics::Temporality::Delta)
1212
.build();
13-
let reader = PeriodicReader::builder(exporter).build();
1413
let provider = SdkMeterProvider::builder()
15-
.with_reader(reader)
14+
.with_periodic_exporter(exporter)
1615
.with_resource(
1716
Resource::builder()
1817
.with_service_name("metrics-basic-example")

opentelemetry-otlp/examples/basic-otlp-http/src/main.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@ fn init_metrics() -> Result<opentelemetry_sdk::metrics::SdkMeterProvider, Metric
6161
.with_endpoint("http://localhost:4318/v1/metrics")
6262
.build()?;
6363

64-
let reader = opentelemetry_sdk::metrics::PeriodicReader::builder(exporter).build();
65-
6664
Ok(SdkMeterProvider::builder()
67-
.with_reader(reader)
65+
.with_periodic_exporter(exporter)
6866
.with_resource(RESOURCE.clone())
6967
.build())
7068
}

opentelemetry-otlp/examples/basic-otlp/src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@ fn init_traces() -> Result<sdktrace::TracerProvider, TraceError> {
3333

3434
fn init_metrics() -> Result<opentelemetry_sdk::metrics::SdkMeterProvider, MetricError> {
3535
let exporter = MetricExporter::builder().with_tonic().build()?;
36-
let reader = PeriodicReader::builder(exporter).build();
3736

3837
Ok(SdkMeterProvider::builder()
39-
.with_reader(reader)
38+
.with_periodic_exporter(exporter)
4039
.with_resource(RESOURCE.clone())
4140
.build())
4241
}

opentelemetry-sdk/CHANGELOG.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,14 +405,33 @@ Released 2024-Nov-27
405405
Migration Guidance:
406406
- These methods are intended for log appenders. Keep the clone of the provider handle, instead of depending on above methods.
407407

408-
409408
- **Bug Fix:** Validates the `with_boundaries` bucket boundaries used in
410409
Histograms. The boundaries provided by the user must not contain `f64::NAN`,
411410
`f64::INFINITY` or `f64::NEG_INFINITY` and must be sorted in strictly
412411
increasing order, and contain no duplicates. Instruments will not record
413412
measurements if the boundaries are invalid.
414413
[#2351](https://github.com/open-telemetry/opentelemetry-rust/pull/2351)
415414

415+
- Added `with_periodic_exporter` method to `MeterProviderBuilder`, allowing
416+
users to easily attach an exporter with a PeriodicReader for automatic metric
417+
export. Retained with_reader() for advanced use cases where a custom
418+
MetricReader configuration is needed.
419+
[2597](https://github.com/open-telemetry/opentelemetry-rust/pull/2597)
420+
Example Usage:
421+
422+
```rust
423+
SdkMeterProvider::builder()
424+
.with_periodic_exporter(exporter)
425+
.build();
426+
```
427+
428+
Using a custom PeriodicReader (advanced use case):
429+
430+
let reader = PeriodicReader::builder(exporter).build();
431+
SdkMeterProvider::builder()
432+
.with_reader(reader)
433+
.build();
434+
416435
## 0.27.0
417436

418437
Released 2024-Nov-11

opentelemetry-sdk/src/logs/log_processor.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,19 +228,33 @@ type LogsData = Box<(LogRecord, InstrumentationScope)>;
228228
/// individually. It uses a **dedicated background thread** to manage and export logs
229229
/// asynchronously, ensuring that the application's main execution flow is not blocked.
230230
///
231-
/// - This processor supports the following configurations:
232-
/// - **Queue size**: Maximum number of log records that can be buffered.
233-
/// - **Batch size**: Maximum number of log records to include in a single export.
234-
/// - **Scheduled delay**: Frequency at which the batch is exported.
231+
/// This processor supports the following configurations:
232+
/// - **Queue size**: Maximum number of log records that can be buffered.
233+
/// - **Batch size**: Maximum number of log records to include in a single export.
234+
/// - **Scheduled delay**: Frequency at which the batch is exported.
235235
///
236236
/// When using this processor with the OTLP Exporter, the following exporter
237237
/// features are supported:
238-
/// - `grpc-tonic`: This requires `MeterProvider` to be created within a tokio
239-
/// runtime.
238+
/// - `grpc-tonic`: Requires `LoggerProvider` to be created within a tokio runtime.
240239
/// - `reqwest-blocking-client`: Works with a regular `main` or `tokio::main`.
241240
///
242241
/// In other words, other clients like `reqwest` and `hyper` are not supported.
243242
///
243+
/// `BatchLogProcessor` buffers logs in memory and exports them in batches. An
244+
/// export is triggered when `max_export_batch_size` is reached or every
245+
/// `scheduled_delay` milliseconds. Users can explicitly trigger an export using
246+
/// the `force_flush` method. Shutdown also triggers an export of all buffered
247+
/// logs and is recommended to be called before the application exits to ensure
248+
/// all buffered logs are exported.
249+
///
250+
/// **Warning**: When using tokio's current-thread runtime, `shutdown()`, which
251+
/// is a blocking call ,should not be called from your main thread. This can
252+
/// cause deadlock. Instead, call `shutdown()` from a separate thread or use
253+
/// tokio's `spawn_blocking`.
254+
///
255+
/// [`shutdown()`]: crate::logs::LoggerProvider::shutdown
256+
/// [`force_flush()`]: crate::logs::LoggerProvider::force_flush
257+
///
244258
/// ### Using a BatchLogProcessor:
245259
///
246260
/// ```rust

opentelemetry-sdk/src/metrics/meter_provider.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ use crate::{
1919
};
2020

2121
use super::{
22-
meter::SdkMeter, noop::NoopMeter, pipeline::Pipelines, reader::MetricReader, view::View,
22+
exporter::PushMetricExporter, meter::SdkMeter, noop::NoopMeter, pipeline::Pipelines,
23+
reader::MetricReader, view::View, PeriodicReader,
2324
};
2425

2526
/// Handles the creation and coordination of [Meter]s.
@@ -244,14 +245,37 @@ impl MeterProviderBuilder {
244245
}
245246

246247
/// Associates a [MetricReader] with a [MeterProvider].
248+
/// [`MeterProviderBuilder::with_periodic_exporter()] can be used to add a PeriodicReader which is
249+
/// the most common use case.
247250
///
248-
/// By default, if this option is not used, the [MeterProvider] will perform no
249-
/// operations; no data will be exported without a [MetricReader].
251+
/// A [MeterProvider] will export no metrics without [MetricReader]
252+
/// added.
250253
pub fn with_reader<T: MetricReader>(mut self, reader: T) -> Self {
251254
self.readers.push(Box::new(reader));
252255
self
253256
}
254257

258+
/// Adds a [`PushMetricExporter`] to the [`MeterProvider`] and configures it
259+
/// to export metrics at **fixed** intervals (60 seconds) using a
260+
/// [`PeriodicReader`].
261+
///
262+
/// To customize the export interval, set the
263+
/// **"OTEL_METRIC_EXPORT_INTERVAL"** environment variable (in
264+
/// milliseconds).
265+
///
266+
/// Most users should use this method to attach an exporter. Advanced users
267+
/// who need finer control over the export process can use
268+
/// [`crate::metrics::PeriodicReaderBuilder`] to configure a custom reader and attach it
269+
/// using [`MeterProviderBuilder::with_reader()`].
270+
pub fn with_periodic_exporter<T>(mut self, exporter: T) -> Self
271+
where
272+
T: PushMetricExporter,
273+
{
274+
let reader = PeriodicReader::builder(exporter).build();
275+
self.readers.push(Box::new(reader));
276+
self
277+
}
278+
255279
#[cfg(feature = "spec_unstable_metrics_views")]
256280
/// Associates a [View] with a [MeterProvider].
257281
///

opentelemetry-sdk/src/metrics/periodic_reader.rs

Lines changed: 103 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -91,25 +91,50 @@ where
9191
}
9292
}
9393

94-
/// A [MetricReader] that continuously collects and exports metrics at a set
95-
/// interval.
94+
/// A `MetricReader` that periodically collects and exports metrics at a configurable interval.
9695
///
97-
/// By default, `PeriodicReader` will collect and export metrics every 60
98-
/// seconds. The export time is not counted towards the interval between
99-
/// attempts. `PeriodicReader` itself does not enforce a timeout. Instead, the
100-
/// timeout is passed on to the configured exporter for each export attempt.
96+
/// By default, [`PeriodicReader`] collects and exports metrics every **60 seconds**.
97+
/// The time taken for export is **not** included in the interval. Use [`PeriodicReaderBuilder`]
98+
/// to customize the interval.
10199
///
102-
/// `PeriodicReader` spawns a background thread to handle the periodic
103-
/// collection and export of metrics. The background thread will continue to run
104-
/// until `shutdown()` is called.
100+
/// [`PeriodicReader`] spawns a background thread to handle metric collection and export.
101+
/// This thread remains active until [`shutdown()`] is called.
105102
///
106-
/// When using this reader with the OTLP Exporter, the following exporter
107-
/// features are supported:
108-
/// - `grpc-tonic`: This requires `MeterProvider` to be created within a tokio
109-
/// runtime.
110-
/// - `reqwest-blocking-client`: Works with a regular `main` or `tokio::main`.
103+
/// ## Collection Process
104+
/// "Collection" refers to gathering aggregated metrics from the SDK's internal storage.
105+
/// During this phase, callbacks from observable instruments are also triggered.
111106
///
112-
/// In other words, other clients like `reqwest` and `hyper` are not supported.
107+
/// [`PeriodicReader`] does **not** enforce a timeout for collection. If an
108+
/// observable callback takes too long, it may delay the next collection cycle.
109+
/// If a callback never returns, it **will stall** all metric collection (and exports)
110+
/// indefinitely.
111+
///
112+
/// ## Exporter Compatibility
113+
/// When used with the [`OTLP Exporter`](https://docs.rs/opentelemetry-otlp), the following
114+
/// transport options are supported:
115+
///
116+
/// - **`grpc-tonic`**: Requires [`MeterProvider`] to be initialized within a `tokio` runtime.
117+
/// - **`reqwest-blocking-client`**: Works with both a standard (`main`) function and `tokio::main`.
118+
///
119+
/// [`PeriodicReader`] does **not** enforce a timeout for exports either. Instead,
120+
/// the configured exporter is responsible for enforcing timeouts. If an export operation
121+
/// never returns, [`PeriodicReader`] will **stop exporting new metrics**, stalling
122+
/// metric collection.
123+
///
124+
/// ## Manual Export & Shutdown
125+
/// Users can manually trigger an export via [`force_flush()`]. Calling [`shutdown()`]
126+
/// exports any remaining metrics and should be done before application exit to ensure
127+
/// all data is sent.
128+
///
129+
/// **Warning**: If using **tokio’s current-thread runtime**, calling [`shutdown()`]
130+
/// from the main thread may cause a deadlock. To prevent this, call [`shutdown()`]
131+
/// from a separate thread or use tokio's `spawn_blocking`.
132+
///
133+
/// [`PeriodicReader`]: crate::metrics::PeriodicReader
134+
/// [`PeriodicReaderBuilder`]: crate::metrics::PeriodicReaderBuilder
135+
/// [`MeterProvider`]: crate::metrics::SdkMeterProvider
136+
/// [`shutdown()`]: crate::metrics::SdkMeterProvider::shutdown
137+
/// [`force_flush()`]: crate::metrics::SdkMeterProvider::force_flush
113138
///
114139
/// # Example
115140
///
@@ -175,10 +200,36 @@ impl PeriodicReader {
175200
otel_debug!(
176201
name: "PeriodReaderThreadExportingDueToFlush"
177202
);
178-
if let Err(_e) = cloned_reader.collect_and_export(timeout) {
179-
response_sender.send(false).unwrap();
180-
} else {
181-
response_sender.send(true).unwrap();
203+
204+
let export_result = cloned_reader.collect_and_export(timeout);
205+
otel_debug!(
206+
name: "PeriodReaderInvokedExport",
207+
export_result = format!("{:?}", export_result)
208+
);
209+
210+
// If response_sender is disconnected, we can't send
211+
// the result back. This occurs when the thread that
212+
// initiated flush gave up due to timeout.
213+
// Gracefully handle that with internal logs. The
214+
// internal errors are of Info level, as this is
215+
// useful for user to know whether the flush was
216+
// successful or not, when flush() itself merely
217+
// tells that it timed out.
218+
219+
if export_result.is_err() {
220+
if response_sender.send(false).is_err() {
221+
otel_info!(
222+
name: "PeriodReader.Flush.ResponseSendError",
223+
message = "PeriodicReader's flush has failed, but unable to send this info back to caller.
224+
This occurs when the caller has timed out waiting for the response. If you see this occuring frequently, consider increasing the flush timeout."
225+
);
226+
}
227+
} else if response_sender.send(true).is_err() {
228+
otel_info!(
229+
name: "PeriodReader.Flush.ResponseSendError",
230+
message = "PeriodicReader's flush has completed successfully, but unable to send this info back to caller.
231+
This occurs when the caller has timed out waiting for the response. If you see this occuring frequently, consider increasing the flush timeout."
232+
);
182233
}
183234

184235
// Adjust the remaining interval after the flush
@@ -207,15 +258,39 @@ impl PeriodicReader {
207258
// Perform final export and break out of loop and exit the thread
208259
otel_debug!(name: "PeriodReaderThreadExportingDueToShutdown");
209260
let export_result = cloned_reader.collect_and_export(timeout);
261+
otel_debug!(
262+
name: "PeriodReaderInvokedExport",
263+
export_result = format!("{:?}", export_result)
264+
);
210265
let shutdown_result = exporter_arc.shutdown();
211266
otel_debug!(
212267
name: "PeriodReaderInvokedExporterShutdown",
213268
shutdown_result = format!("{:?}", shutdown_result)
214269
);
270+
271+
// If response_sender is disconnected, we can't send
272+
// the result back. This occurs when the thread that
273+
// initiated shutdown gave up due to timeout.
274+
// Gracefully handle that with internal logs and
275+
// continue with shutdown (i.e exit thread) The
276+
// internal errors are of Info level, as this is
277+
// useful for user to know whether the shutdown was
278+
// successful or not, when shutdown() itself merely
279+
// tells that it timed out.
215280
if export_result.is_err() || shutdown_result.is_err() {
216-
response_sender.send(false).unwrap();
217-
} else {
218-
response_sender.send(true).unwrap();
281+
if response_sender.send(false).is_err() {
282+
otel_info!(
283+
name: "PeriodReaderThreadShutdown.ResponseSendError",
284+
message = "PeriodicReader's shutdown has failed, but unable to send this info back to caller.
285+
This occurs when the caller has timed out waiting for the response. If you see this occuring frequently, consider increasing the shutdown timeout."
286+
);
287+
}
288+
} else if response_sender.send(true).is_err() {
289+
otel_info!(
290+
name: "PeriodReaderThreadShutdown.ResponseSendError",
291+
message = "PeriodicReader completed its shutdown, but unable to send this info back to caller.
292+
This occurs when the caller has timed out waiting for the response. If you see this occuring frequently, consider increasing the shutdown timeout."
293+
);
219294
}
220295

221296
otel_debug!(
@@ -230,11 +305,11 @@ impl PeriodicReader {
230305
name: "PeriodReaderThreadExportingDueToTimer"
231306
);
232307

233-
if let Err(_e) = cloned_reader.collect_and_export(timeout) {
234-
otel_debug!(
235-
name: "PeriodReaderThreadExportingDueToTimerFailed"
236-
);
237-
}
308+
let export_result = cloned_reader.collect_and_export(timeout);
309+
otel_debug!(
310+
name: "PeriodReaderInvokedExport",
311+
export_result = format!("{:?}", export_result)
312+
);
238313

239314
let time_taken_for_export = export_start.elapsed();
240315
if time_taken_for_export > interval {
@@ -365,17 +440,7 @@ impl PeriodicReaderInner {
365440

366441
// Relying on futures executor to execute async call.
367442
// TODO: Pass timeout to exporter
368-
let exporter_result = futures_executor::block_on(self.exporter.export(&mut rm));
369-
#[allow(clippy::question_mark)]
370-
if let Err(e) = exporter_result {
371-
otel_warn!(
372-
name: "PeriodReaderExportError",
373-
error = format!("{:?}", e)
374-
);
375-
return Err(e);
376-
}
377-
378-
Ok(())
443+
futures_executor::block_on(self.exporter.export(&mut rm))
379444
}
380445

381446
fn force_flush(&self) -> MetricResult<()> {

opentelemetry-sdk/src/trace/provider.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ impl Drop for TracerProviderInner {
134134
let _ = self.shutdown(); // errors are handled within shutdown
135135
} else {
136136
otel_debug!(
137-
name: "TracerProvider.Drop.AlreadyShutdown"
137+
name: "TracerProvider.Drop.AlreadyShutdown",
138+
message = "TracerProvider was already shut down; drop will not attempt shutdown again."
138139
);
139140
}
140141
}

0 commit comments

Comments
 (0)