Vendor upstream's opentelemetry-rust fork at the pinned commit #221

Merged
jcoffey-dev merged 1 commits from chore/vendor-opentelemetry into main 2026-10-28 06:04:04 +00:00
213 changed files with 95597 additions and 13 deletions

No files matched your search

Generated
-6
View File
@@ -5508,7 +5508,6 @@ dependencies = [
[[package]]
name = "opentelemetry"
version = "0.33.0"
source = "git+https://github.com/stalwartlabs/opentelemetry-rust#ae66e97b140f70e477ab710686aafce665cc2f8b"
dependencies = [
"futures-core",
"futures-sink",
@@ -5521,7 +5520,6 @@ dependencies = [
[[package]]
name = "opentelemetry-http"
version = "0.33.0"
source = "git+https://github.com/stalwartlabs/opentelemetry-rust#ae66e97b140f70e477ab710686aafce665cc2f8b"
dependencies = [
"async-trait",
"bytes",
@@ -5533,7 +5531,6 @@ dependencies = [
[[package]]
name = "opentelemetry-otlp"
version = "0.33.0"
source = "git+https://github.com/stalwartlabs/opentelemetry-rust#ae66e97b140f70e477ab710686aafce665cc2f8b"
dependencies = [
"http 1.5.0",
"httpdate",
@@ -5552,7 +5549,6 @@ dependencies = [
[[package]]
name = "opentelemetry-proto"
version = "0.33.0"
source = "git+https://github.com/stalwartlabs/opentelemetry-rust#ae66e97b140f70e477ab710686aafce665cc2f8b"
dependencies = [
"opentelemetry",
"opentelemetry_sdk",
@@ -5564,12 +5560,10 @@ dependencies = [
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.33.0"
source = "git+https://github.com/stalwartlabs/opentelemetry-rust#ae66e97b140f70e477ab710686aafce665cc2f8b"
[[package]]
name = "opentelemetry_sdk"
version = "0.33.0"
source = "git+https://github.com/stalwartlabs/opentelemetry-rust#ae66e97b140f70e477ab710686aafce665cc2f8b"
dependencies = [
"futures-channel",
"futures-executor",
+10 -3
View File
@@ -86,11 +86,18 @@ rpath = false
# vendor/sieve-rs is the published 0.7.3 with those renamed; see its
# VENDORED.md. Re-vendor when the version in Cargo.lock moves.
#
# inbuxa: hickory is upstream's patched branch (DNSSEC fixes), vendored at
# the commit upstream pins so builds never fetch from upstream's
# repositories; see vendor/hickory-dns/VENDORED.md.
# inbuxa: hickory and opentelemetry are upstream's patched branches, vendored
# at the commits upstream pins so builds never fetch from upstream's
# repositories; see vendor/hickory-dns/VENDORED.md and
# vendor/opentelemetry-rust/VENDORED.md.
[patch.crates-io]
sieve-rs = { path = "vendor/sieve-rs" }
hickory-proto = { path = "vendor/hickory-dns/hickory-proto" }
hickory-net = { path = "vendor/hickory-dns/hickory-net" }
hickory-resolver = { path = "vendor/hickory-dns/hickory-resolver" }
opentelemetry = { path = "vendor/opentelemetry-rust/opentelemetry" }
opentelemetry_sdk = { path = "vendor/opentelemetry-rust/opentelemetry_sdk" }
opentelemetry-otlp = { path = "vendor/opentelemetry-rust/opentelemetry-otlp" }
opentelemetry-proto = { path = "vendor/opentelemetry-rust/opentelemetry-proto" }
opentelemetry-http = { path = "vendor/opentelemetry-rust/opentelemetry-http" }
opentelemetry-semantic-conventions = { path = "vendor/opentelemetry-rust/opentelemetry-semantic-conventions" }
+5 -4
View File
@@ -43,10 +43,11 @@ x509-parser = "0.18"
pem = "4.0"
chrono = { version = "0.4", features = ["serde"] }
hyper = { version = "1.11.1", features = ["server", "http1", "http2"] }
opentelemetry = { git = "https://github.com/stalwartlabs/opentelemetry-rust" }
opentelemetry_sdk = { git = "https://github.com/stalwartlabs/opentelemetry-rust" }
opentelemetry-otlp = { git = "https://github.com/stalwartlabs/opentelemetry-rust", default-features = false, features = ["reqwest-client", "http-proto", "trace", "metrics", "logs", "internal-logs", "grpc-tonic", "tls-aws-lc", "tls-roots", "reqwest-rustls"] }
opentelemetry-semantic-conventions = { git = "https://github.com/stalwartlabs/opentelemetry-rust" }
# inbuxa: the opentelemetry crates come from vendor/ (root [patch.crates-io])
opentelemetry = { version = "0.33" }
opentelemetry_sdk = { version = "0.33" }
opentelemetry-otlp = { version = "0.33", default-features = false, features = ["reqwest-client", "http-proto", "trace", "metrics", "logs", "internal-logs", "grpc-tonic", "tls-aws-lc", "tls-roots", "reqwest-rustls"] }
opentelemetry-semantic-conventions = { version = "0.33" }
prometheus = { version = "0.14", default-features = false }
imagesize = "0.15"
sha1 = "0.11"
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+32
View File
@@ -0,0 +1,32 @@
# opentelemetry-rust, vendored
`opentelemetry`, `opentelemetry_sdk`, `opentelemetry-otlp`,
`opentelemetry-proto`, `opentelemetry-http` and
`opentelemetry-semantic-conventions` 0.33.0 as upstream builds them: from its
fork of [opentelemetry-rust](https://github.com/open-telemetry/opentelemetry-rust)
at commit `ae66e97b140f70e477ab710686aafce665cc2f8b` (2026-09-26), under
Apache-2.0 (`LICENSE` here is the project's). The root `Cargo.toml` patches
them in with `[patch.crates-io]`, so builds never fetch from upstream's
repositories.
That commit is opentelemetry-rust's own `main` at `b7852a8c` (2026-09-24,
after the 0.33.0 release candidate) plus one change: public constructors for
the metrics data types (`ResourceMetrics`, `ScopeMetrics`, `Metric`, the
gauge, sum and histogram types and their data points) and a public
`SdkLogRecord::new()` with a `Default`. Nothing else differs.
## Changes
None. Each directory is `cargo package --no-verify` of that commit (the
manifest normalized, so it builds outside the project's workspace), without
`Cargo.lock`, `Cargo.toml.orig` and `.cargo_vcs_info.json`.
## Updating
When upstream moves its pin, or opentelemetry-rust releases the
constructors, re-vendor: clone the repository at the new commit, run `cargo
package --no-verify` for the six crates, unpack each `.crate` here, and drop
`Cargo.lock` and `Cargo.toml.orig`. If the version changes, Cargo stops
using these copies and only warns that the patch went unused, so check
`Cargo.lock` has no `opentelemetry*` entry with a `source` line. Update the
commit above.
@@ -0,0 +1,184 @@
# Changelog
## vNext
## 0.33.0
Released 2026-Sep-18
- Apply `HyperClient`'s configured timeout to the complete response body, not
only request dispatch and response headers.
- **Breaking** Sealed the `ResponseExt` trait so it can no longer be implemented by
downstream crates. The trait provides a blanket implementation for all
`http::Response<T>` types, so calling code is unaffected -- only
`impl ResponseExt for MyType` will stop compiling. If you have a custom
implementation, remove it and rely on the blanket impl instead.
- **Breaking** Removed the deprecated `HttpClient::send` method, which accepted
`Request<Vec<u8>>`. Implement and call `HttpClient::send_bytes` instead,
converting existing requests with `request.map(Bytes::from)` when needed.
- **Breaking:** Remove `opentelemetry_http::hyper::Body`, which is no longer used
by any public constructor. Use `http_body_util::Full<Bytes>` for custom Hyper
client request bodies.
- Limit HTTP response body reads to 4 MiB in built-in HTTP clients (`reqwest` async/blocking and `hyper`). Reads exceeding the limit are aborted to prevent unbounded memory allocation and return the new opaque `ResponseBodyTooLarge` error. Custom HTTP clients can construct this error with `ResponseBodyTooLarge::new()` or `ResponseBodyTooLarge::default()`.
- **Breaking** Built-in reqwest and hyper clients now return HTTP 4xx and 5xx
responses as `Ok(Response<Bytes>)` instead of `Err(HttpError)`. Here, `Ok`
means that the transport completed the request and received an HTTP response;
it does not imply a successful HTTP status. This preserves the response status
and headers for exporter retry classification. Transport failures and timeouts
continue to return `Err`.
If your code relied on `send_bytes` returning `Err` for non-success statuses,
call `ResponseExt::error_for_status()` on the response instead.
- **Breaking** Removed `reqwest-rustls-webpki-roots` feature. The `webpki-roots` cargo feature was
removed from `reqwest` in v0.13.0. Use `reqwest-rustls` instead, which now correctly enables
`reqwest/rustls` (platform native trust roots). To use Mozilla's embedded CA bundle, construct a
custom `reqwest::Client` with a `rustls::ClientConfig` containing
`rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned())`, then supply it
to the exporter with `with_http_client()`.
## 0.32.0
Released 2026-May-08
- `reqwest`'s crypto backend has changed from `ring` to `aws-lc-sys`.
## 0.31.0
Released 2025-Sep-25
- Implementation of `Extractor::get_all` for `HeaderExtractor`
- Support `HttpClient` implementation for `HyperClient<C>` with custom connectors beyond `HttpConnector`, enabling Unix Domain Socket connections and other custom transports
- Add `reqwest` and `reqwest-blocking` features to enable async and blocking
reqwest HTTP clients
- Updated `opentelemetry` dependency to version 0.31.0.
## 0.30.0
Released 2025-May-23
- Updated `opentelemetry` dependency to version 0.30.0.
## 0.29.0
Released 2025-Mar-21
- Update `opentelemetry` dependency version to 0.29.
## 0.28.0
Released 2025-Feb-10
- Update `opentelemetry` dependency version to 0.28.
- Bump msrv to 1.75.0.
- Add "internal-logs" feature flag (enabled by default), and emit internal logs via `tracing` crate.
- Add `HttpClient::send_bytes` with `bytes::Bytes` request payload and deprecate old `HttpClient::send` function.
## 0.27.0
Released 2024-Nov-08
- Update `opentelemetry` dependency version to 0.27
- Bump MSRV to 1.70 [#2179](https://github.com/open-telemetry/opentelemetry-rust/pull/2179)
## v0.26.0
Released 2024-Sep-30
- Update `opentelemetry` dependency version to 0.26
## v0.25.0
- Update `opentelemetry` dependency version to 0.25
- Starting with this version, this crate will align with `opentelemetry` crate
on major,minor versions.
## v0.13.0
- **Breaking** Correct the misspelling of "webkpi" to "webpki" in features [#1842](https://github.com/open-telemetry/opentelemetry-rust/pull/1842)
- **Breaking** Remove support for the `isahc` HTTP client [#1924](https://github.com/open-telemetry/opentelemetry-rust/pull/1924)
- Update to `http` v1 [#1674](https://github.com/open-telemetry/opentelemetry-rust/pull/1674)
- Update `opentelemetry` dependency version to 0.24
## v0.12.0
- Add `reqwest-rustls-webpki-roots` feature flag to configure [`reqwest`](https://docs.rs/reqwest/0.11.27/reqwest/index.html#optional-features) to use embedded `webpki-roots`.
- Update `opentelemetry` dependency version to 0.23
## v0.11.1
- Add feature flag enabling users to configure `reqwest` usage to use rustls via
`reqwest/rustls-tls` feature flag
[1638](https://github.com/open-telemetry/opentelemetry-rust/pull/1638).
## v0.11.0
### Changed
- **Breaking** Remove built-in support for surf HTTP client [#1537](https://github.com/open-telemetry/opentelemetry-rust/pull/1537)
- **Breaking** Surface non-2xx status codes as errors; change `ResponseExt` trait to return `HttpError` instead of `TraceError`[#1484](https://github.com/open-telemetry/opentelemetry-rust/pull/1484)
## v0.10.0
### Changed
- Bump MSRV to 1.65 [#1318](https://github.com/open-telemetry/opentelemetry-rust/pull/1318)
- Bump MSRV to 1.64 [#1203](https://github.com/open-telemetry/opentelemetry-rust/pull/1203)
## v0.9.0
### Changed
- Update to opentelemetry-api v0.20.0
## v0.8.0
### Changed
- Add response headers in response for `HttpClient` implementations [#918](https://github.com/open-telemetry/opentelemetry-rust/pull/918).
- Bump MSRV to 1.57 [#953](https://github.com/open-telemetry/opentelemetry-rust/pull/953).
- Update dependencies and bump MSRV to 1.60 [#969](https://github.com/open-telemetry/opentelemetry-rust/pull/969).
## v0.7.0
### Changed
- Update to opentelemetry v0.18.0
- Export `byte` and `http` types #798
- Implementation of collector http client with pure hyper #853
## v0.6.0
### Changed
- Update to opentelemetry v0.17.0
## v0.5.0
### Changed
- Update to opentelemetry v0.16.0
## v0.4.0
### Changed
- Update to opentelemetry v0.15.0
## v0.3.0
### Changed
- Return response from `HttpClient` #511
- Update to opentelemetry v0.14.0
## v0.2.0
### Changed
- Update to opentelemetry v0.13.0
## v0.1.0
### Added
- Opentelemetry integration with http #415
+132
View File
@@ -0,0 +1,132 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.75.0"
name = "opentelemetry-http"
version = "0.33.0"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Helper implementations for sending HTTP requests. Uses include propagating and extracting context over http, exporting telemetry, requesting sampling strategies."
homepage = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-http"
readme = "README.md"
keywords = [
"opentelemetry",
"tracing",
"context",
"propagation",
]
license = "Apache-2.0"
repository = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-http"
[features]
default = ["internal-logs"]
hyper = [
"dep:http-body-util",
"dep:hyper",
"dep:hyper-util",
"dep:tokio",
]
internal-logs = ["opentelemetry/internal-logs"]
reqwest = ["dep:reqwest"]
reqwest-blocking = [
"dep:reqwest",
"reqwest/blocking",
]
reqwest-rustls = [
"dep:reqwest",
"reqwest/rustls",
]
[lib]
name = "opentelemetry_http"
path = "src/lib.rs"
bench = false
[dependencies.async-trait]
version = "0.1"
[dependencies.bytes]
version = "1"
[dependencies.http]
version = "1.1"
features = ["std"]
default-features = false
[dependencies.http-body-util]
version = "0.1"
optional = true
[dependencies.hyper]
version = "1.3"
optional = true
default-features = false
[dependencies.hyper-util]
version = "0.1"
features = [
"client-legacy",
"http1",
"http2",
]
optional = true
[dependencies.opentelemetry]
version = "0.33"
features = ["trace"]
default-features = false
[dependencies.reqwest]
version = "0.13.1"
optional = true
default-features = false
[dependencies.tokio]
version = "1"
features = ["time"]
optional = true
default-features = false
[dev-dependencies.futures-executor]
version = "0.3"
[dev-dependencies.tokio]
version = "1"
features = [
"rt-multi-thread",
"macros",
"net",
"io-util",
"time",
]
default-features = false
[dev-dependencies.tower-service]
version = "0.3"
[lints.clippy.all]
level = "warn"
priority = 1
[lints.rust]
edition_2024_expr_fragment_specifier = "allow"
if_let_rescope = "allow"
tail_expr_drop_order = "allow"
[lints.rust.rust_2024_compatibility]
level = "warn"
priority = -1
+81
View File
@@ -0,0 +1,81 @@
# OpenTelemetry HTTP
![OpenTelemetry — An observability framework for cloud-native software.][splash]
[splash]: https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo-text.png
This crate contains helper implementations for sending HTTP requests, used
by [OpenTelemetry](https://opentelemetry.io/) components. Common uses include
propagating and extracting context over HTTP, exporting telemetry data over
HTTP, and requesting sampling strategies from remote endpoints.
[![Crates.io: opentelemetry-http](https://img.shields.io/crates/v/opentelemetry-http.svg)](https://crates.io/crates/opentelemetry-http)
[![Documentation](https://docs.rs/opentelemetry-http/badge.svg)](https://docs.rs/opentelemetry-http)
[![LICENSE](https://img.shields.io/crates/l/opentelemetry-http)](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-http/LICENSE)
[![GitHub Actions CI](https://github.com/open-telemetry/opentelemetry-rust/workflows/CI/badge.svg)](https://github.com/open-telemetry/opentelemetry-rust/actions?query=workflow%3ACI+branch%3Amain)
[![Slack](https://img.shields.io/badge/slack-@cncf/otel/rust-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C03GDP0H023)
## Overview
OpenTelemetry is an Observability framework and toolkit designed to create and
manage telemetry data such as traces, metrics, and logs. OpenTelemetry is
vendor- and tool-agnostic, meaning that it can be used with a broad variety of
Observability backends, including open source tools like [Jaeger] and
[Prometheus], as well as commercial offerings.
OpenTelemetry is *not* an observability backend like Jaeger, Prometheus, or other
commercial vendors. OpenTelemetry is focused on the generation, collection,
management, and export of telemetry. A major goal of OpenTelemetry is that you
can easily instrument your applications or systems, no matter their language,
infrastructure, or runtime environment. Crucially, the storage and visualization
of telemetry is intentionally left to other tools.
*[Supported Rust Versions](#supported-rust-versions)*
[Prometheus]: https://prometheus.io
[Jaeger]: https://www.jaegertracing.io
### What does this crate contain?
This crate contains shared HTTP building blocks used across the
OpenTelemetry Rust ecosystem, including:
- An `HttpClient` trait and adapters for popular HTTP clients such as
`reqwest` and `hyper`.
- Helpers for propagating and extracting OpenTelemetry context (for example
W3C TraceContext) over HTTP headers.
- Utilities used by exporters to send telemetry over HTTP.
This crate is primarily used as a dependency by other OpenTelemetry crates
(such as [`opentelemetry-otlp`](https://crates.io/crates/opentelemetry-otlp))
rather than directly by end users.
### Related crates
- **[opentelemetry](https://crates.io/crates/opentelemetry):** The core
OpenTelemetry API.
- **[opentelemetry-sdk](https://crates.io/crates/opentelemetry-sdk):** The
OpenTelemetry SDK implementation.
- **[opentelemetry-otlp](https://crates.io/crates/opentelemetry-otlp):** OTLP
exporter that uses this crate for HTTP transport.
## Getting started
See [docs](https://docs.rs/opentelemetry-http).
## Release Notes
You can find the release notes (changelog) [here](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-http/CHANGELOG.md).
## Supported Rust Versions
OpenTelemetry is built against the latest stable release. The minimum supported
version is 1.75.0. The current OpenTelemetry version is NOT guaranteed to build
on Rust versions earlier than the minimum supported version.
The current stable Rust compiler and the three most recent minor versions
before it will always be supported. For example, if the current stable compiler
version is 1.49, the minimum supported version will not be increased past 1.46,
three minor versions prior. Increasing the minimum supported compiler version
is not considered a semver breaking change as long as doing so complies with
this policy.
@@ -0,0 +1,21 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
# This is used with cargo-check-external-types to reduce the surface area of downstream crates from
# the public API. Ideally this can have as few exceptions as possible.
allowed_external_types = [
# ResponseBodyTooLarge's constructor exposes no external types.
# Public HTTP client contract and propagation adapters.
"bytes::bytes::Bytes",
"http::header::map::HeaderMap",
"http::header::value::HeaderValue",
"http::request::Request",
"http::response::Response",
# HyperClient's public default connector and generic connector bound.
"hyper_util::client::legacy::connect::http::HttpConnector",
"hyper_util::client::legacy::connect::sealed::Connect",
# Traits implemented by the public propagation adapters.
"opentelemetry::propagation::Extractor",
"opentelemetry::propagation::Injector",
]
+842
View File
@@ -0,0 +1,842 @@
//! HTTP types and client adapters shared by OpenTelemetry components.
//!
//! [`HeaderInjector`] and [`HeaderExtractor`] adapt an [`http::HeaderMap`] to
//! OpenTelemetry's text-map propagation interfaces. [`HttpClient`] is the
//! transport abstraction used by exporters and other components that issue
//! HTTP requests.
//!
//! # HTTP clients
//!
//! This crate does not enable a concrete HTTP client by default. Select one of
//! these features when an OpenTelemetry component does not provide one:
//!
//! - `reqwest` implements [`HttpClient`] for the asynchronous
//! `reqwest::Client`.
//! - `reqwest-blocking` additionally implements [`HttpClient`] for
//! `reqwest::blocking::Client`.
//! - `reqwest-rustls` enables reqwest with its Rustls TLS backend.
//! - `hyper` provides `hyper::HyperClient`, including support for custom
//! connectors.
//!
//! The reqwest implementations use the timeout configured on the supplied
//! reqwest client. `hyper::HyperClient` requires a Tokio runtime and uses
//! `hyper_util::rt::TokioExecutor` internally.
//!
//! # Implementing a client
//!
//! A custom client controls connection management, redirects, and timeouts.
//! `send_bytes` returns HTTP responses regardless of their status code;
//! transport failures and timeouts are returned as errors. Use
//! [`ResponseExt::error_for_status`] when non-success status codes should be
//! converted into errors.
//!
//! ```
//! use async_trait::async_trait;
//! use opentelemetry_http::{Bytes, HttpClient, HttpError, Request, Response};
//!
//! #[derive(Debug)]
//! struct ExampleClient;
//!
//! #[async_trait]
//! impl HttpClient for ExampleClient {
//! async fn send_bytes(
//! &self,
//! request: Request<Bytes>,
//! ) -> Result<Response<Bytes>, HttpError> {
//! // A real implementation would send the request and enforce its
//! // configured timeout while collecting the complete response body.
//! Ok(Response::new(request.into_body()))
//! }
//! }
//!
//! let request = Request::post("http://collector.example/v1/traces")
//! .body(Bytes::from_static(b"encoded telemetry"))?;
//! let response = futures_executor::block_on(ExampleClient.send_bytes(request))?;
//! assert_eq!(response.body(), &Bytes::from_static(b"encoded telemetry"));
//! # Ok::<(), HttpError>(())
//! ```
//!
//! # Response size limit
//!
//! The built-in reqwest and Hyper implementations collect response bodies into
//! memory and reject bodies larger than 4 MiB with [`ResponseBodyTooLarge`].
use async_trait::async_trait;
use std::fmt::Debug;
#[doc(no_inline)]
pub use bytes::Bytes;
#[doc(no_inline)]
pub use http::{Request, Response};
use opentelemetry::propagation::{Extractor, Injector};
/// Helper for injecting headers into HTTP Requests. This is used for OpenTelemetry context
/// propagation over HTTP.
/// See [this](https://github.com/open-telemetry/opentelemetry-rust/blob/main/examples/tracing-http-propagator/README.md)
/// for example usage.
pub struct HeaderInjector<'a>(pub &'a mut http::HeaderMap);
impl Injector for HeaderInjector<'_> {
/// Set a key and value in the HeaderMap. Does nothing if the key or value are not valid inputs.
fn set(&mut self, key: &str, value: String) {
if let Ok(name) = http::header::HeaderName::from_bytes(key.as_bytes()) {
if let Ok(val) = http::header::HeaderValue::from_str(&value) {
self.0.insert(name, val);
}
}
}
/// Reserves capacity for at least `additional` more entries to be inserted.
fn reserve(&mut self, additional: usize) {
self.0.reserve(additional);
}
}
/// Helper for extracting headers from HTTP Requests. This is used for OpenTelemetry context
/// propagation over HTTP.
/// See [this](https://github.com/open-telemetry/opentelemetry-rust/blob/main/examples/tracing-http-propagator/README.md)
/// for example usage.
pub struct HeaderExtractor<'a>(pub &'a http::HeaderMap);
impl Extractor for HeaderExtractor<'_> {
/// Get a value for a key from the HeaderMap. If the value is not valid ASCII, returns None.
fn get(&self, key: &str) -> Option<&str> {
self.0.get(key).and_then(|value| value.to_str().ok())
}
/// Collect all the keys from the HeaderMap.
fn keys(&self) -> Vec<&str> {
self.0
.keys()
.map(|value| value.as_str())
.collect::<Vec<_>>()
}
/// Get all the values for a key from the HeaderMap
fn get_all(&self, key: &str) -> Option<Vec<&str>> {
let all_iter = self.0.get_all(key).iter();
if let (0, Some(0)) = all_iter.size_hint() {
return None;
}
Some(all_iter.filter_map(|value| value.to_str().ok()).collect())
}
}
/// Error returned when an HTTP request cannot be completed.
pub type HttpError = Box<dyn std::error::Error + Send + Sync + 'static>;
/// A minimal interface for sending byte-oriented HTTP requests.
///
/// This is primarily used for exporting telemetry and for fetching remote
/// sampling strategies. Implementations are responsible for enforcing any
/// required request timeout, including while reading the response body.
///
/// HTTP clients may depend on a particular async runtime. This trait allows
/// users to supply an implementation suitable for their runtime.
#[async_trait]
pub trait HttpClient: Debug + Send + Sync {
/// Send the specified HTTP request with `Bytes` payload.
///
/// Returns the complete HTTP response, including non-success status codes.
///
/// Returns an error if the request cannot be completed, for example because
/// of a connection failure, timeout, redirect failure, or response body
/// larger than 4 MiB in a built-in client.
async fn send_bytes(&self, request: Request<Bytes>) -> Result<Response<Bytes>, HttpError>;
}
#[cfg(any(feature = "reqwest", feature = "hyper"))]
const MAX_RESPONSE_BODY_BYTES: usize = 4 * 1024 * 1024;
/// Error returned when an HTTP response body exceeds the configured size limit.
///
/// Construct this error with [`Self::new`] or [`Default::default`]. Its fields
/// are private to allow future diagnostic details without changing construction.
#[derive(Debug, Default)]
pub struct ResponseBodyTooLarge {
_private: (),
}
impl ResponseBodyTooLarge {
/// Creates an error indicating that the response body exceeded the size limit.
pub fn new() -> Self {
Self::default()
}
}
impl std::fmt::Display for ResponseBodyTooLarge {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "response body exceeded maximum allowed 4 MiB limit")
}
}
impl std::error::Error for ResponseBodyTooLarge {}
#[cfg(feature = "reqwest")]
mod reqwest {
use opentelemetry::otel_debug;
use crate::ResponseBodyTooLarge;
use super::{
async_trait, Bytes, HttpClient, HttpError, Request, Response, MAX_RESPONSE_BODY_BYTES,
};
#[async_trait]
impl HttpClient for reqwest::Client {
async fn send_bytes(&self, request: Request<Bytes>) -> Result<Response<Bytes>, HttpError> {
otel_debug!(name: "ReqwestClient.Send");
let request = request.try_into()?;
let mut response = self.execute(request).await?;
let capacity = response
.content_length()
.unwrap_or(0)
.min(MAX_RESPONSE_BODY_BYTES as u64) as usize;
let mut body_bytes = bytes::BytesMut::with_capacity(capacity);
let status = response.status();
let headers = std::mem::take(response.headers_mut());
while let Some(chunk) = response.chunk().await? {
if body_bytes.len() + chunk.len() > MAX_RESPONSE_BODY_BYTES {
return Err(Box::new(ResponseBodyTooLarge::new()));
}
body_bytes.extend_from_slice(&chunk);
}
let mut http_response = Response::builder()
.status(status)
.body(body_bytes.freeze())?;
*http_response.headers_mut() = headers;
Ok(http_response)
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "reqwest-blocking")]
#[async_trait]
impl HttpClient for reqwest::blocking::Client {
async fn send_bytes(&self, request: Request<Bytes>) -> Result<Response<Bytes>, HttpError> {
use std::io::Read;
otel_debug!(name: "ReqwestBlockingClient.Send");
let request = request.try_into()?;
let mut response = self.execute(request)?;
let capacity = response
.content_length()
.unwrap_or(0)
.min(MAX_RESPONSE_BODY_BYTES as u64) as usize;
let status = response.status();
let headers = std::mem::take(response.headers_mut());
let mut body_bytes = Vec::with_capacity(capacity);
response
.take(MAX_RESPONSE_BODY_BYTES as u64 + 1)
.read_to_end(&mut body_bytes)?;
if body_bytes.len() > MAX_RESPONSE_BODY_BYTES {
return Err(Box::new(ResponseBodyTooLarge::new()));
}
let mut http_response = Response::builder()
.status(status)
.body(Bytes::from(body_bytes))?;
*http_response.headers_mut() = headers;
Ok(http_response)
}
}
}
#[cfg(feature = "hyper")]
pub mod hyper {
use super::{
async_trait, Bytes, HttpClient, HttpError, Request, Response, MAX_RESPONSE_BODY_BYTES,
};
use crate::ResponseBodyTooLarge;
use http::HeaderValue;
use http_body_util::{BodyExt, Full};
use hyper::body::Body as _;
use hyper_util::client::legacy::{
connect::{Connect, HttpConnector},
Client,
};
use opentelemetry::otel_debug;
use std::fmt::Debug;
use std::time::Duration;
use tokio::time;
/// An [`HttpClient`] backed by Hyper.
///
/// This client requires a Tokio runtime and uses
/// [`hyper_util::rt::TokioExecutor`] to drive connections. Responses larger
/// than 4 MiB are rejected with [`ResponseBodyTooLarge`].
#[derive(Debug, Clone)]
pub struct HyperClient<C = HttpConnector>
where
C: Connect + Clone + Send + Sync + 'static,
{
inner: Client<C, Full<Bytes>>,
timeout: Duration,
authorization: Option<HeaderValue>,
}
impl<C> HyperClient<C>
where
C: Connect + Clone + Send + Sync + 'static,
{
/// Creates a client with a custom Hyper connector.
///
/// The connector must satisfy Hyper's [`Connect`] bounds. `timeout`
/// configures the request deadline. When `authorization` is provided,
/// its value replaces any `Authorization` header already present on
/// each request.
pub fn new(connector: C, timeout: Duration, authorization: Option<HeaderValue>) -> Self {
// TODO - support custom executor
let inner = Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector);
Self {
inner,
timeout,
authorization,
}
}
}
impl HyperClient<HttpConnector> {
/// Creates a client with Hyper's default [`HttpConnector`].
///
/// `timeout` configures the request deadline. When `authorization` is
/// provided, its value replaces any `Authorization` header already
/// present on each request.
pub fn with_default_connector(
timeout: Duration,
authorization: Option<HeaderValue>,
) -> Self {
Self::new(HttpConnector::new(), timeout, authorization)
}
}
#[async_trait]
impl<C> HttpClient for HyperClient<C>
where
C: Connect + Clone + Send + Sync + 'static,
HyperClient<C>: Debug,
{
async fn send_bytes(&self, request: Request<Bytes>) -> Result<Response<Bytes>, HttpError> {
otel_debug!(name: "HyperClient.Send");
let (parts, body) = request.into_parts();
let mut request = Request::from_parts(parts, Full::from(body));
if let Some(ref authorization) = self.authorization {
request
.headers_mut()
.insert(http::header::AUTHORIZATION, authorization.clone());
}
time::timeout(self.timeout, async {
let mut response = self.inner.request(request).await?;
let capacity = response
.body()
.size_hint()
.upper()
.unwrap_or(0)
.min(MAX_RESPONSE_BODY_BYTES as u64) as usize;
let mut body_bytes = bytes::BytesMut::with_capacity(capacity);
let status = response.status();
let headers = std::mem::take(response.headers_mut());
let mut body = response.into_body();
while let Some(frame) = body.frame().await {
let frame = frame?;
if let Ok(chunk) = frame.into_data() {
if body_bytes.len() + chunk.len() > MAX_RESPONSE_BODY_BYTES {
return Err(Box::new(ResponseBodyTooLarge::new()) as HttpError);
}
body_bytes.extend_from_slice(&chunk);
}
}
let mut http_response = Response::builder()
.status(status)
.body(body_bytes.freeze())?;
*http_response.headers_mut() = headers;
Ok(http_response)
})
.await?
}
}
}
mod private {
pub trait Sealed {}
impl<T> Sealed for http::Response<T> {}
}
/// Methods to make working with responses from the [`HttpClient`] trait easier.
///
/// This trait is sealed and cannot be implemented outside of this crate.
pub trait ResponseExt: private::Sealed + Sized {
/// Turn a response into an error if the HTTP status does not indicate success (200 - 299).
fn error_for_status(self) -> Result<Self, HttpError>;
}
impl<T> ResponseExt for Response<T> {
fn error_for_status(self) -> Result<Self, HttpError> {
if self.status().is_success() {
Ok(self)
} else {
Err(format!("request failed with status {}", self.status()).into())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::HeaderValue;
#[test]
fn response_body_too_large_construction() {
for error in [ResponseBodyTooLarge::new(), ResponseBodyTooLarge::default()] {
let error: HttpError = Box::new(error);
assert!(error.downcast_ref::<ResponseBodyTooLarge>().is_some());
assert_eq!(
error.to_string(),
"response body exceeded maximum allowed 4 MiB limit"
);
}
}
#[cfg(all(
any(feature = "hyper", feature = "reqwest", feature = "reqwest-blocking"),
not(target_arch = "wasm32")
))]
use std::io::{Read, Write};
#[cfg(all(
any(feature = "hyper", feature = "reqwest", feature = "reqwest-blocking"),
not(target_arch = "wasm32")
))]
use std::net::{SocketAddr, TcpListener};
#[cfg(all(
any(feature = "hyper", feature = "reqwest", feature = "reqwest-blocking"),
not(target_arch = "wasm32")
))]
use std::thread::JoinHandle;
#[test]
fn http_headers_get() {
let mut carrier = http::HeaderMap::new();
HeaderInjector(&mut carrier).set("headerName", "value".to_string());
assert_eq!(
HeaderExtractor(&carrier).get("HEADERNAME"),
Some("value"),
"case insensitive extraction"
)
}
#[test]
fn http_headers_get_all() {
let mut carrier = http::HeaderMap::new();
carrier.append("headerName", HeaderValue::from_static("value"));
carrier.append("headerName", HeaderValue::from_static("value2"));
carrier.append("headerName", HeaderValue::from_static("value3"));
assert_eq!(
HeaderExtractor(&carrier).get_all("HEADERNAME"),
Some(vec!["value", "value2", "value3"]),
"all values from a key extraction"
)
}
#[test]
fn http_headers_get_all_missing_key() {
let mut carrier = http::HeaderMap::new();
carrier.append("headerName", HeaderValue::from_static("value"));
assert_eq!(
HeaderExtractor(&carrier).get_all("not_existing"),
None,
"all values from a missing key extraction"
)
}
#[test]
fn http_headers_keys() {
let mut carrier = http::HeaderMap::new();
HeaderInjector(&mut carrier).set("headerName1", "value1".to_string());
HeaderInjector(&mut carrier).set("headerName2", "value2".to_string());
let extractor = HeaderExtractor(&carrier);
let got = extractor.keys();
assert_eq!(got.len(), 2);
assert!(got.contains(&"headername1"));
assert!(got.contains(&"headername2"));
}
#[test]
fn http_headers_reserve() {
let mut carrier = http::HeaderMap::new();
// Test that reserve doesn't panic and works correctly
{
let mut injector = HeaderInjector(&mut carrier);
injector.reserve(10);
// Verify the HeaderMap still works after reserve
injector.set("test-header", "test-value".to_string());
}
assert_eq!(
HeaderExtractor(&carrier).get("test-header"),
Some("test-value")
);
// Test reserve with zero capacity
{
let mut injector = HeaderInjector(&mut carrier);
injector.reserve(0);
injector.set("another-header", "another-value".to_string());
}
assert_eq!(
HeaderExtractor(&carrier).get("another-header"),
Some("another-value")
);
// Test that capacity is actually reserved (at least the requested amount)
let mut new_carrier = http::HeaderMap::new();
{
let mut new_injector = HeaderInjector(&mut new_carrier);
new_injector.reserve(5);
}
let initial_capacity = new_carrier.capacity();
// Add some headers and verify capacity doesn't decrease
{
let mut new_injector = HeaderInjector(&mut new_carrier);
for i in 0..3 {
new_injector.set(&format!("header-{}", i), format!("value-{}", i));
}
}
assert!(new_carrier.capacity() >= initial_capacity);
assert!(new_carrier.capacity() >= 5);
}
#[test]
fn error_for_status_matches_http_status_class() {
for status in [http::StatusCode::OK, http::StatusCode::NO_CONTENT] {
let response = Response::builder().status(status).body(()).unwrap();
assert!(response.error_for_status().is_ok());
}
for status in [
http::StatusCode::MOVED_PERMANENTLY,
http::StatusCode::BAD_REQUEST,
http::StatusCode::TOO_MANY_REQUESTS,
http::StatusCode::INTERNAL_SERVER_ERROR,
] {
let response = Response::builder().status(status).body(()).unwrap();
assert!(response.error_for_status().is_err());
}
}
#[cfg(all(
any(feature = "hyper", feature = "reqwest", feature = "reqwest-blocking"),
not(target_arch = "wasm32")
))]
fn spawn_error_response_server() -> (SocketAddr, JoinHandle<()>) {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let server = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().unwrap();
let mut request = [0; 1024];
let _ = stream.read(&mut request).unwrap();
stream
.write_all(
b"HTTP/1.1 429 Too Many Requests\r\n\
Retry-After: 7\r\n\
Content-Length: 0\r\n\
Connection: close\r\n\r\n",
)
.unwrap();
});
(address, server)
}
#[cfg(all(feature = "reqwest-blocking", not(target_arch = "wasm32")))]
#[test]
fn reqwest_blocking_preserves_error_response_status_and_headers() {
let (address, server) = spawn_error_response_server();
let client = ::reqwest::blocking::Client::new();
let request = Request::post(format!("http://{address}/v1/traces"))
.body(Bytes::new())
.unwrap();
let response = futures_executor::block_on(client.send_bytes(request)).unwrap();
server.join().unwrap();
assert_eq!(response.status(), http::StatusCode::TOO_MANY_REQUESTS);
assert_eq!(response.headers().get("retry-after").unwrap(), "7");
}
#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
#[test]
fn reqwest_async_preserves_error_response_status_and_headers() {
let (address, server) = spawn_error_response_server();
let client = ::reqwest::Client::new();
let request = Request::post(format!("http://{address}/v1/traces"))
.body(Bytes::new())
.unwrap();
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let response = runtime.block_on(client.send_bytes(request)).unwrap();
server.join().unwrap();
assert_eq!(response.status(), http::StatusCode::TOO_MANY_REQUESTS);
assert_eq!(response.headers().get("retry-after").unwrap(), "7");
}
#[cfg(all(feature = "hyper", not(target_arch = "wasm32")))]
#[test]
fn hyper_preserves_error_response_status_and_headers() {
let (address, server) = spawn_error_response_server();
let client = crate::hyper::HyperClient::with_default_connector(
std::time::Duration::from_secs(2),
None,
);
let request = Request::post(format!("http://{address}/v1/traces"))
.body(Bytes::new())
.unwrap();
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let response = runtime.block_on(client.send_bytes(request)).unwrap();
server.join().unwrap();
assert_eq!(response.status(), http::StatusCode::TOO_MANY_REQUESTS);
assert_eq!(response.headers().get("retry-after").unwrap(), "7");
}
#[cfg(all(
test,
any(feature = "reqwest", feature = "reqwest-blocking", feature = "hyper")
))]
mod body_limit_tests {
use super::MAX_RESPONSE_BODY_BYTES;
use crate::HttpClient;
use bytes::Bytes;
use http::Request;
#[cfg(feature = "hyper")]
use std::future::Future;
use std::net::SocketAddr;
#[cfg(feature = "hyper")]
use std::pin::Pin;
#[cfg(feature = "hyper")]
use std::task::{Context, Poll};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
#[cfg(feature = "hyper")]
#[derive(Clone, Debug)]
struct LocalConnector(SocketAddr);
#[cfg(feature = "hyper")]
impl tower_service::Service<http::Uri> for LocalConnector {
type Response = hyper_util::rt::TokioIo<tokio::net::TcpStream>;
type Error = std::io::Error;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, _context: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _uri: http::Uri) -> Self::Future {
let address = self.0;
Box::pin(async move {
tokio::net::TcpStream::connect(address)
.await
.map(hyper_util::rt::TokioIo::new)
})
}
}
async fn start_server(body_size: usize) -> SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
if let Ok((mut socket, _)) = listener.accept().await {
let mut buf = [0u8; 1024];
let _ = socket.read(&mut buf).await;
let body = vec![b'a'; body_size];
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
let _ = socket.write_all(response.as_bytes()).await;
let _ = socket.write_all(&body).await;
let _ = socket.shutdown().await;
}
});
addr
}
#[cfg(feature = "hyper")]
async fn start_stalled_body_server() -> SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
if let Ok((mut socket, _)) = listener.accept().await {
let mut buf = [0u8; 1024];
let _ = socket.read(&mut buf).await;
let _ = socket
.write_all(
b"HTTP/1.1 200 OK\r\nContent-Length: 1\r\nConnection: close\r\n\r\n",
)
.await;
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
});
addr
}
async fn assert_body_size(client: &dyn HttpClient, addr: SocketAddr, expected_size: usize) {
let request = Request::builder()
.method("POST")
.uri(format!("http://{}/", addr))
.body(Bytes::new())
.unwrap();
let response = client.send_bytes(request).await.unwrap();
assert_eq!(response.body().len(), expected_size);
}
async fn assert_exceeds_limit(client: &dyn HttpClient, addr: SocketAddr) {
let request = Request::builder()
.method("POST")
.uri(format!("http://{}/", addr))
.body(Bytes::new())
.unwrap();
let error = client.send_bytes(request).await.unwrap_err();
assert!(error
.downcast_ref::<crate::ResponseBodyTooLarge>()
.is_some());
}
#[cfg(feature = "reqwest-blocking")]
fn start_blocking_server(body_size: usize) -> SocketAddr {
use std::io::{Read, Write};
use std::net::TcpListener;
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
std::thread::spawn(move || {
if let Ok((mut socket, _)) = listener.accept() {
let mut buf = [0u8; 1024];
let _ = socket.read(&mut buf);
let body = vec![b'a'; body_size];
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
let _ = socket.write_all(response.as_bytes());
let _ = socket.write_all(&body);
}
});
addr
}
#[cfg(feature = "reqwest")]
#[tokio::test]
async fn reqwest_body_within_limit() {
let addr = start_server(MAX_RESPONSE_BODY_BYTES).await;
assert_body_size(&reqwest::Client::new(), addr, MAX_RESPONSE_BODY_BYTES).await;
}
#[cfg(feature = "reqwest")]
#[tokio::test]
async fn reqwest_body_exceeds_limit() {
let addr = start_server(MAX_RESPONSE_BODY_BYTES + 1).await;
assert_exceeds_limit(&reqwest::Client::new(), addr).await;
}
#[cfg(feature = "reqwest-blocking")]
#[test]
fn reqwest_blocking_body_within_limit() {
let addr = start_blocking_server(MAX_RESPONSE_BODY_BYTES);
futures_executor::block_on(assert_body_size(
&reqwest::blocking::Client::new(),
addr,
MAX_RESPONSE_BODY_BYTES,
));
}
#[cfg(feature = "reqwest-blocking")]
#[test]
fn reqwest_blocking_body_exceeds_limit() {
let addr = start_blocking_server(MAX_RESPONSE_BODY_BYTES + 1);
futures_executor::block_on(assert_exceeds_limit(
&reqwest::blocking::Client::new(),
addr,
));
}
#[cfg(feature = "hyper")]
#[tokio::test]
async fn hyper_body_within_limit() {
let addr = start_server(MAX_RESPONSE_BODY_BYTES).await;
let client = crate::hyper::HyperClient::with_default_connector(
std::time::Duration::from_secs(5),
None,
);
assert_body_size(&client, addr, MAX_RESPONSE_BODY_BYTES).await;
}
#[cfg(feature = "hyper")]
#[tokio::test]
async fn hyper_client_new_accepts_custom_connector() {
let addr = start_server(100).await;
let client = crate::hyper::HyperClient::new(
LocalConnector(addr),
std::time::Duration::from_secs(5),
None,
);
assert_body_size(&client, addr, 100).await;
}
#[cfg(feature = "hyper")]
#[tokio::test]
async fn hyper_body_exceeds_limit() {
let addr = start_server(MAX_RESPONSE_BODY_BYTES + 1).await;
let client = crate::hyper::HyperClient::with_default_connector(
std::time::Duration::from_secs(5),
None,
);
assert_exceeds_limit(&client, addr).await;
}
#[cfg(feature = "hyper")]
#[tokio::test]
async fn hyper_timeout_covers_response_body() {
let addr = start_stalled_body_server().await;
let client = crate::hyper::HyperClient::with_default_connector(
std::time::Duration::from_millis(25),
None,
);
let request = Request::post(format!("http://{addr}/"))
.body(Bytes::new())
.unwrap();
let error = tokio::time::timeout(
std::time::Duration::from_millis(200),
client.send_bytes(request),
)
.await
.expect("HyperClient must enforce its configured timeout")
.expect_err("stalled response body must time out");
assert!(error
.downcast_ref::<tokio::time::error::Elapsed>()
.is_some());
}
}
}
@@ -0,0 +1,687 @@
# Changelog
## vNext
## 0.33.0
Released 2026-Sep-18
- Exporter builder usage and environment configuration are unchanged.
**Breaking for callers parsing compression strings:** `Compression::from_str`
(including `.parse::<Compression>()`) now returns the opaque `ParseConfigError`
instead of `ExporterBuildError`. Update explicit result types and error handling
that expects `ExporterBuildError::UnsupportedCompressionAlgorithm`. The new error
implements `Display` and `std::error::Error`; its message is for diagnostics.
Accepted strings and parsing behavior are unchanged.
```rust
// Before:
let result: Result<Compression, ExporterBuildError> = value.parse();
// After:
let result: Result<Compression, ParseConfigError> = value.parse();
if let Err(error) = result {
eprintln!("invalid compression configuration: {error}");
}
```
- Interpret protocol, compression, and metrics temporality environment values
case-insensitively. Treat empty values as unset, and warn and ignore invalid,
non-Unicode, or feature-unavailable enum values so resolution can continue
to the next environment variable or default. Compression `none` explicitly
disables compression, including when a generic compression value is set.
Programmatic configuration remains strict.
### Retry
- Retries are now enabled by default for OTLP/HTTP and OTLP/gRPC. The default
policy uses exponential backoff and jitter with up to 3 retries (4 attempts
total). Use `.with_retry_policy(RetryPolicy::disabled())` to disable retries,
or provide a custom `RetryPolicy` to change the behavior.
- **Migration for users of the experimental retry features:** If your
`Cargo.toml` enables `experimental-grpc-retry` or
`experimental-http-retry`, remove those feature flags. No migration action is
required for users who did not enable them.
[#3621](https://github.com/open-telemetry/opentelemetry-rust/pull/3621)
- **Breaking** Make the `retry` and `retry_classification` modules crate-private,
removing their retry engine, error type, and protocol classifiers from the
public API. `RetryPolicy` remains available from the crate root with private
fields and fluent configuration methods. Replace imports from
`opentelemetry_otlp::retry` with `opentelemetry_otlp::RetryPolicy`, and replace
struct literals with its `with_*` methods.
[#3672](https://github.com/open-telemetry/opentelemetry-rust/pull/3672)
#### Retry fixes
The following fixes apply to retry behavior that was experimental before this
release:
- Retry only HTTP status codes 429, 502, 503, and 504, as required by the OTLP
specification. The exporter now also honors `Retry-After` on 503 responses.
- Honor positive gRPC `RetryInfo` delays returned with `Unavailable` responses.
- Continue exponential backoff from server-provided `RetryInfo` and
`Retry-After` delays when subsequent export attempts fail.
### Other changes
- Exporter compression configuration and behavior are unchanged; users of
`.with_compression(...)` need no changes. **Breaking only for direct conversion
callers:** removed `TryFrom<Compression>` for
`tonic::codec::CompressionEncoding`. Code explicitly converting between these
enums must map the variants itself.
- Return an exporter build error when construction of a built-in reqwest HTTP
client fails instead of silently falling back to a client without the
exporter-configured timeout. Failure to spawn the blocking client's setup
thread, or a panic in that thread, is also returned instead of panicking.
- **Breaking** Removed `Default` from the `TonicExporterBuilderSet` and
`HttpExporterBuilderSet` typestate markers. This also removes `Default` from
the transport-selected exporter builders (e.g.
`SpanExporterBuilder<TonicExporterBuilderSet>`). Use the intended builder
flow instead:
```rust
// Before (no longer compiles):
let exporter = SpanExporterBuilder::<TonicExporterBuilderSet>::default().build()?;
// After (use the builder entry point):
let exporter = SpanExporter::builder().with_tonic().build()?;
```
Also removed the unused `#[doc(hidden)]` `NoExporterConfig` type.
- **Breaking** Mark `Protocol` and `Compression` as non-exhaustive so new OTLP
protocols, encodings, and compression algorithms can be added without
breaking downstream users. External exhaustive matches must add a wildcard
arm. Constructing existing variants and passing them to exporter builders is
unchanged.
```rust
let protocol_name = match protocol {
Protocol::Grpc => "grpc",
Protocol::HttpBinary => "http/protobuf",
Protocol::HttpJson => "http/json",
_ => "unknown", // Required because Protocol is non-exhaustive.
};
```
- **Breaking** Make `Protocol::from_env()` crate-private. Exporter builders
already resolve `OTEL_EXPORTER_OTLP_PROTOCOL` when built; applications that
need to inspect the raw environment setting should read the variable
directly.
- **Breaking** Remove `OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT`, which always held
the HTTP default (`http://localhost:4318`) despite gRPC using
`http://localhost:4317`. Omit `.with_endpoint(...)` to let the selected
transport use its correct default, or provide the appropriate URL explicitly.
[#3690](https://github.com/open-telemetry/opentelemetry-rust/issues/3690)
- **Breaking** Restrict `MetricExporterBuilder::with_http()` and `with_tonic()`
to builders where no transport has been selected, matching the span and log
exporter builders. Select a transport once; `with_temporality()` remains
available before or after transport selection.
- **Breaking** Remove the public `HttpExporterBuilder` and
`TonicExporterBuilder` transport-first APIs. Configure transports through the
signal builders instead:
- Replace `HttpExporterBuilder::default()` with the corresponding signal
exporter builder followed by `.with_http()`, then replace
`.build_span_exporter()` or `.build_log_exporter()` with `.build()`.
- Replace `.build_metrics_exporter(temporality)` with
`.with_temporality(temporality).build()`.
- Replace `TonicExporterBuilder::default()` with the corresponding signal
exporter builder followed by `.with_tonic()`.
Transport-specific configuration methods remain available after
`.with_http()` or `.with_tonic()`.
- **Breaking** Removed the deprecated `tls` feature alias. Replace `tls` with
`tls-ring`, or select `tls-aws-lc` or `tls-provider-agnostic` explicitly.
- Exporter builder usage is unchanged. **Breaking for code matching or constructing
removed error variants:** Simplified `ExporterBuildError` to the exhaustive
`InvalidConfiguration(String)` and `InternalFailure(String)` variants.
The enum is no longer marked `#[non_exhaustive]`.
Configuration errors such as invalid endpoints, missing HTTP clients,
transport/protocol mismatches, and missing compression features now use
`InvalidConfiguration`. Replace implementation-specific, non-exhaustive
matches such as:
```rust
match error {
ExporterBuildError::InvalidUri(_, _)
| ExporterBuildError::InvalidConfig { .. }
| ExporterBuildError::NoHttpClient => {
eprintln!("fix the exporter configuration");
}
ExporterBuildError::InternalFailure(message) => {
eprintln!("exporter initialization failed: {message}");
}
_ => {}
}
```
with an exhaustive match over the two stable categories:
```rust
match error {
ExporterBuildError::InvalidConfiguration(message) => {
eprintln!("fix the exporter configuration: {message}");
}
ExporterBuildError::InternalFailure(message) => {
eprintln!("exporter initialization failed: {message}");
}
}
```
Code that propagates build errors with `?` without inspecting their variants
needs no changes.
Tonic endpoint errors identify the originating environment variable when
validating the URI or reporting endpoint-related TLS setup failures.
[#3691](https://github.com/open-telemetry/opentelemetry-rust/issues/3691)
- Return an exporter build error for invalid OTLP/HTTP endpoint environment
variables instead of silently falling back to another endpoint or localhost.
Empty endpoint environment variables are now treated as unset.
- Return an exporter build error for invalid OTLP/gRPC endpoint environment
variables instead of silently falling back to another endpoint or localhost.
Empty endpoint environment variables are now treated as unset.
- Add `WithHttpConfig::with_max_request_body_size` to configure the HTTP request
body limit. OTLP/HTTP request bodies are now limited to 64 MiB by default, before and
after compression; oversized requests are discarded without being sent or
retried.
- **Breaking** Seal `WithExportConfig`, `WithHttpConfig`, and
`WithTonicConfig`. These traits remain public for calling configuration
methods on OTLP builders, but can no longer be implemented for external
types.
- Add support for INSECURE environment variables for gRPC (env-var-only, no builder method, per spec):
`OTEL_EXPORTER_OTLP_INSECURE` (generic), `OTEL_EXPORTER_OTLP_TRACES_INSECURE`,
`OTEL_EXPORTER_OTLP_METRICS_INSECURE`, `OTEL_EXPORTER_OTLP_LOGS_INSECURE`.
Per the spec, these only apply to gRPC connections. When an endpoint has no explicit scheme,
`INSECURE=true` uses `http://`, `INSECURE=false` (default) uses `https://` with auto-TLS.
**Breaking:** Schemeless endpoints (e.g., `collector.example.com:4317`) now default to `https://`
instead of being passed as-is. Set `OTEL_EXPORTER_OTLP_INSECURE=true` for plaintext connections.
Endpoints with an explicit scheme (e.g., `http://`, `https://`, `unix://`) are unaffected.
[#774](https://github.com/open-telemetry/opentelemetry-rust/issues/774)
[#984](https://github.com/open-telemetry/opentelemetry-rust/issues/984)
- **Breaking** Removed the `serialize` feature flag and its implicit `serde`
dependency. This feature gated `Serialize`/`Deserialize` derives on
`Protocol` and `Compression`, but the derived representations were incorrect
(Rust variant names instead of spec values) and the feature only covered
these two enums. The equivalent feature was removed from the core
`opentelemetry` crate in 2022.
**Migration**: Remove `serialize` (and `serde`, if listed) from your feature
list. If these values are part of serialisable app config, define a local
config enum or wrapper and convert it to `Protocol` or `Compression` when
building the exporter.
[#3711](https://github.com/open-telemetry/opentelemetry-rust/pull/3711)
- **Breaking** Removed `reqwest-rustls-webpki-roots` feature. The `webpki-roots` cargo feature was
removed from `reqwest` in v0.13.0, making this feature broken for anyone resolving `reqwest >= 0.13.0`.
**Migration**: Use `reqwest-rustls` instead (now correctly uses `reqwest/rustls` with platform native
trust roots). If you specifically need Mozilla's embedded CA bundle, construct a custom client:
```rust
let root_store = rustls::RootCertStore::from_iter(
webpki_roots::TLS_SERVER_ROOTS.iter().cloned(),
);
let tls_config = rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
let client = reqwest::Client::builder()
.tls_backend_preconfigured(tls_config)
.build()?;
exporter_builder.with_http_client(client)
```
- Allow to provide http client wrapped in Arc when configuring HTTP exporter. [3468](https://github.com/open-telemetry/opentelemetry-rust/pull/3468)
## 0.32.0
Released 2026-May-08
- Add `tls-provider-agnostic` feature flag for environments that require a custom crypto backend (e.g., OpenSSL for FIPS compliance). Enables TLS code paths without bundling `ring` or `aws-lc-rs`.
- Add `build()` directly on `SpanExporterBuilder`, `MetricExporterBuilder`, and `LogExporterBuilder`
(before selecting a transport), which auto-selects the transport based on the
`OTEL_EXPORTER_OTLP_PROTOCOL` environment variable or enabled features.
[#3394](https://github.com/open-telemetry/opentelemetry-rust/pull/3394)
- **Breaking** Removed `ExportConfig`, `HasExportConfig`, `with_export_config()`, `HasTonicConfig`, `HasHttpConfig`, `TonicConfig`, and `HttpConfig` from public API.
Use the public `WithExportConfig`, `WithTonicConfig`, and `WithHttpConfig` trait methods instead, which remain unchanged.
- The gRPC/tonic OTLP exporter's build method now returns an error for all signals (traces, metrics, logs) when
an `https://` endpoint is configured but no TLS feature (`tls-ring` or `tls-aws-lc`) is enabled, instead of
silently sending unencrypted traffic. When a TLS feature is enabled and an `https://` endpoint is used without
an explicit `.with_tls_config()`, a default `ClientTlsConfig` is automatically applied.
[#3182](https://github.com/open-telemetry/opentelemetry-rust/issues/3182)
- Prevent auth tokens from leaking in export error messages. gRPC and HTTP
exporter errors no longer include potentially sensitive server responses
(e.g., authentication tokens echoed back). Error messages returned to SDK
processors contain only the gRPC status code or HTTP status code. Full
details are logged at DEBUG level only.
[#3021](https://github.com/open-telemetry/opentelemetry-rust/issues/3021)
- Surface pre-flight transport error details at ERROR level when `grpc-tonic`
OTLP export fails due to a local misconfiguration. When the returned
`tonic::Status` wraps a local transport error (invalid URL, connect failure,
DNS), its source chain (e.g., `"transport error: invalid URI"`) is appended
to the returned error so SDK processors surface it at ERROR without
requiring DEBUG logging. Server-returned gRPC status messages remain
DEBUG-only to preserve the auth-token leak safeguards from
[#3021](https://github.com/open-telemetry/opentelemetry-rust/issues/3021).
[#3331](https://github.com/open-telemetry/opentelemetry-rust/issues/3331)
- Add support for per-signal protocol environment variables:
`OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`, `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL`,
`OTEL_EXPORTER_OTLP_LOGS_PROTOCOL`. These allow configuring different transport protocols
per signal type. Signal-specific vars take precedence over generic `OTEL_EXPORTER_OTLP_PROTOCOL`.
The auto-select `build()` method on each exporter builder now respects the full priority chain:
signal-specific env var > generic env var > feature-based default.
- Transport/protocol mismatch validation: HTTP transport returns `InvalidConfig` when gRPC protocol
is requested; gRPC transport returns `InvalidConfig` when an HTTP protocol is requested.
- **Breaking**: `Protocol::default()` no longer consults the `OTEL_EXPORTER_OTLP_PROTOCOL`
environment variable. It now returns only the feature-based default (http-json > http-proto >
grpc-tonic). Protocol resolution from environment variables is handled internally by the
exporter builders. Users who relied on `Protocol::default()` to read env vars should use
`Protocol::from_env()` instead.
- Add support for `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE` environment variable
to configure metrics temporality. Accepted values: `cumulative` (default), `delta`,
`lowmemory` (case-insensitive). Programmatic `.with_temporality()` overrides the env var.
- Fix `NoHttpClient` error when multiple HTTP client features are enabled by using priority-based selection (`reqwest-client` > `hyper-client` > `reqwest-blocking-client`). [#2994](https://github.com/open-telemetry/opentelemetry-rust/issues/2994)
- Add partial success response handling for OTLP exporters (traces, metrics, logs) per OTLP spec. Exporters now log warnings when the server returns partial success responses with rejected items and error messages. [#865](https://github.com/open-telemetry/opentelemetry-rust/issues/865)
- Refactor `internal-logs` feature in `opentelemetry-otlp` to reduce unnecessary dependencies[3191](https://github.com/open-telemetry/opentelemetry-rust/pull/3192)
- Fixed [#2777](https://github.com/open-telemetry/opentelemetry rust/issues/2777) to properly handle `shutdown_with_timeout()` when using `grpc-tonic`.
- Deprecate `tls` feature in favor of explicit `tls-ring` and `tls-aws-lc` features.
**Migration**: Replace `tls` with `tls-ring` (or `tls-aws-lc`). Users of `tls-roots` or `tls-webpki-roots` must now also enable one of these.
- Prevent logging of header values in OTLP tonic exporter [#3465](https://github.com/open-telemetry/opentelemetry-rust/pull/3465)
## 0.31.0
Released 2025-Sep-25
- Update `opentelemetry-proto` and `opentelemetry-http` dependency version to 0.31.0
- Add HTTP compression support with `gzip-http` and `zstd-http` feature flags
- Add retry with exponential backoff and throttling support for HTTP and gRPC exporters
This behaviour is opt in via the `experimental-grpc-retry` and `experimental-http-retry` flags on this crate. You can customize the retry policy using the `with_retry_policy` on the exporter builders.
## 0.30.0
Released 2025-May-23
- Update `opentelemetry` dependency version to 0.30
- Update `opentelemetry_sdk` dependency version to 0.30
- Update `opentelemetry-http` dependency version to 0.30
- Update `opentelemetry-proto` dependency version to 0.30
- Update `tonic` dependency version to 0.13
- Re-export `tonic` types under `tonic_types`
[2898](https://github.com/open-telemetry/opentelemetry-rust/pull/2898)
- Publicly re-exported `MetricExporterBuilder`, `SpanExporterBuilder`, and
`LogExporterBuilder` types, enabling users to directly reference and use these
builder types for metrics, traces, and logs exporters.
[2966](https://github.com/open-telemetry/opentelemetry-rust/pull/2966)
## 0.29.0
Released 2025-Mar-21
- Update `opentelemetry` dependency version to 0.29
- Update `opentelemetry_sdk` dependency version to 0.29
- Update `opentelemetry-http` dependency version to 0.29
- Update `opentelemetry-proto` dependency version to 0.29
- The `OTEL_EXPORTER_OTLP_TIMEOUT`, `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT`, `OTEL_EXPORTER_OTLP_METRICS_TIMEOUT` and `OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` are changed from seconds to milliseconds.
- Fixed `.with_headers()` in `HttpExporterBuilder` to correctly support multiple key/value pairs. [#2699](https://github.com/open-telemetry/opentelemetry-rust/pull/2699)
- Fixed
[#2770](https://github.com/open-telemetry/opentelemetry-rust/issues/2770)
partially to properly handle `shutdown()` when using `http`. (`tonic` still
does not do proper shutdown)
- *Breaking*
ExporterBuilder's build() method now Result with `ExporterBuildError` being the
Error variant. Previously it returned signal specific errors like `LogError`
from the `opentelemetry_sdk`, which are no longer part of the sdk. No changes
required if you were using unwrap/expect. If you were matching on the returning
Error enum, replace with the enum `ExporterBuildError`. Unlike the previous
`Error` which contained many variants unrelated to building an exporter, the
new one returns specific variants applicable to building an exporter. Some
variants might be applicable only on select features.
Also, now unused `Error` enum is removed.
- **Breaking** `ExportConfig`'s `timeout` field is now optional(`Option<Duration>`)
- **Breaking** Export configuration done via code is final. ENV variables cannot be used to override the code config.
Do not use code based config, if there is desire to control the settings via ENV variables.
List of ENV variables and corresponding setting being affected by this change.
- `OTEL_EXPORTER_OTLP_ENDPOINT` -> `ExportConfig.endpoint`
- `OTEL_EXPORTER_OTLP_TIMEOUT` -> `ExportConfig.timeout`
## 0.28.0
Released 2025-Feb-10
- Update `opentelemetry` dependency version to 0.28.
- Update `opentelemetry_sdk` dependency version to 0.28.
- Update `opentelemetry-http` dependency version to 0.28.
- Update `opentelemetry-proto` dependency version to 0.28.
- Bump msrv to 1.75.0.
- Feature flag "populate-logs-event-name" is removed as no longer relevant.
LogRecord's `event_name()` is now automatically populated on the newly added
"event_name" field in LogRecord proto definition.
- Remove "grpc-tonic" feature from default, and instead add "http-proto" and
"reqwest-blocking-client" features as default, to align with the
specification.
[2516](https://github.com/open-telemetry/opentelemetry-rust/pull/2516)
- Remove unnecessarily public trait `opentelemetry_otlp::metrics::MetricsClient`
and `MetricExporter::new(..)` method. Use
`MetricExporter::builder()...build()` to obtain `MetricExporter`.
- The HTTP clients (reqwest, reqwest-blocking, hyper) now support the
export timeout interval configured in below order
- Signal specific env variable `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT`,
`OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` or `OTEL_EXPORTER_OTLP_TIMEOUT`.
- `OTEL_EXPORTER_OTLP_TIMEOUT` env variable.
- `with_http().with_timeout()` API method of
`LogExporterBuilder` and `SpanExporterBuilder` and `MetricsExporterBuilder`.
- The default interval of 10 seconds is used if none is configured.
## 0.27.0
Released 2024-Nov-11
- Update `opentelemetry` dependency version to 0.27
- Update `opentelemetry_sdk` dependency version to 0.27
- Update `opentelemetry-http` dependency version to 0.27
- Update `opentelemetry-proto` dependency version to 0.27
- **BREAKING**:
- ([#2217](https://github.com/open-telemetry/opentelemetry-rust/pull/2217)) **Replaced**: The `MetricsExporterBuilder` interface is modified from `with_temporality_selector` to `with_temporality` example can be seen below:
Previous Signature:
```rust
MetricsExporterBuilder::default().with_temporality_selector(DeltaTemporalitySelector::new())
```
Updated Signature:
```rust
MetricsExporterBuilder::default().with_temporality(opentelemetry_sdk::metrics::Temporality::Delta)
```
- ([#2221](https://github.com/open-telemetry/opentelemetry-rust/pull/2221)) **Replaced**:
- The `opentelemetry_otlp::new_pipeline().{trace,logging,metrics}()` interface is now replaced with `{TracerProvider,SdkMeterProvider,LoggerProvider}::builder()`.
- The `opentelemetry_otlp::new_exporter()` interface is now replaced with `{SpanExporter,MetricsExporter,LogExporter}::builder()`.
Pull request [#2221](https://github.com/open-telemetry/opentelemetry-rust/pull/2221) has a detailed migration guide in the description. See example below,
and [basic-otlp](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/examples/basic-otlp/src/main.rs) for more details:
Previous Signature:
```rust
let logger_provider: LoggerProvider = opentelemetry_otlp::new_pipeline()
.logging()
.with_resource(RESOURCE.clone())
.with_exporter(
opentelemetry_otlp::new_exporter()
.tonic()
.with_endpoint("http://localhost:4317")
)
.install_batch(runtime::Tokio)?;
```
Updated Signature:
```rust
let exporter = LogExporter::builder()
.with_tonic()
.with_endpoint("http://localhost:4317")
.build()?;
Ok(LoggerProvider::builder()
.with_resource(RESOURCE.clone())
.with_batch_exporter(exporter, runtime::Tokio)
.build())
```
- **Renamed**
- ([#2255](https://github.com/open-telemetry/opentelemetry-rust/pull/2255)): de-pluralize Metric types.
- `MetricsExporter` -> `MetricExporter`
- `MetricsExporterBuilder` -> `MetricExporterBuilder`
- [#2263](https://github.com/open-telemetry/opentelemetry-rust/pull/2263)
Support `hyper` client for opentelemetry-otlp. This can be enabled using flag `hyper-client`.
Refer example: https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-otlp/examples/basic-otlp-http
## v0.26.0
Released 2024-Sep-30
- Update `opentelemetry` dependency version to 0.26
- Update `opentelemetry_sdk` dependency version to 0.26
- Update `opentelemetry-http` dependency version to 0.26
- Update `opentelemetry-proto` dependency version to 0.26
- Bump MSRV to 1.71.1 [2140](https://github.com/open-telemetry/opentelemetry-rust/pull/2140)
## v0.25.0
- Update `opentelemetry` dependency version to 0.25
- Update `opentelemetry_sdk` dependency version to 0.25
- Update `opentelemetry-http` dependency version to 0.25
- Update `opentelemetry-proto` dependency version to 0.25
- Starting with this version, this crate will align with `opentelemetry` crate
on major,minor versions.
- **Breaking**
The logrecord event-name is added as an attribute only if the feature flag
`populate-logs-event-name` is enabled. The name of the attribute is changed from
"name" to "event.name".
[1994](https://github.com/open-telemetry/opentelemetry-rust/pull/1994),
[2050](https://github.com/open-telemetry/opentelemetry-rust/pull/2050)
## v0.17.0
- Add "metrics", "logs" to default features. With this, default feature list is
"trace", "metrics" and "logs".
- **Breaking** `OtlpMetricPipeline.build()` no longer invoke the
`global::set_meter_provider`. User who setup the pipeline must do it
themselves using `global::set_meter_provider(meter_provider.clone());`.
- Add `with_resource` on `OtlpLogPipeline`, replacing the `with_config` method.
Instead of using
`.with_config(Config::default().with_resource(RESOURCE::default()))` users must
now use `.with_resource(RESOURCE::default())` to configure Resource when using
`OtlpLogPipeline`.
- **Breaking** The methods `OtlpTracePipeline::install_simple()` and `OtlpTracePipeline::install_batch()` would now return `TracerProvider` instead of `Tracer`.
These methods would also no longer set the global tracer provider. It would now be the responsibility of users to set it by calling `global::set_tracer_provider(tracer_provider.clone());`. Refer to the [basic-otlp](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/examples/basic-otlp/src/main.rs) and [basic-otlp-http](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs) examples on how to initialize OTLP Trace Exporter.
- **Breaking** Correct the misspelling of "webkpi" to "webpki" in features [#1842](https://github.com/open-telemetry/opentelemetry-rust/pull/1842)
- Bump MSRV to 1.70 [#1840](https://github.com/open-telemetry/opentelemetry-rust/pull/1840)
- Fixing the OTLP HTTP/JSON exporter. [#1882](https://github.com/open-telemetry/opentelemetry-rust/pull/1882) - The exporter was broken in the
previous release.
- **Breaking** [1869](https://github.com/open-telemetry/opentelemetry-rust/pull/1869) The OTLP logs exporter now overrides the [InstrumentationScope::name](https://github.com/open-telemetry/opentelemetry-proto/blob/b3060d2104df364136d75a35779e6bd48bac449a/opentelemetry/proto/common/v1/common.proto#L73) field with the `target` from `LogRecord`, if target is populated.
- Groups batch of `LogRecord` and `Span` by their resource and instrumentation scope before exporting, for better efficiency [#1873](https://github.com/open-telemetry/opentelemetry-rust/pull/1873).
- **Breaking** Update to `http` v1 and `tonic` v0.12 [#1674](https://github.com/open-telemetry/opentelemetry-rust/pull/1674)
- Update `opentelemetry` dependency version to 0.24
- Update `opentelemetry_sdk` dependency version to 0.24
- Update `opentelemetry-http` dependency version to 0.13
- Update `opentelemetry-proto` dependency version to 0.7
## v0.16.0
### Fixed
- URL encoded values in `OTEL_EXPORTER_OTLP_HEADERS` are now correctly decoded. [#1578](https://github.com/open-telemetry/opentelemetry-rust/pull/1578)
- OTLP exporter will not change the URL added through `ExportConfig` [#1706](https://github.com/open-telemetry/opentelemetry-rust/pull/1706)
- Default grpc endpoint will not have path based on signal(e.g `/v1/traces`) [#1706](https://github.com/open-telemetry/opentelemetry-rust/pull/1706)
- Fix feature flags for `OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT` [#1746](https://github.com/open-telemetry/opentelemetry-rust/pull/1746)
### Added
- Added `DeltaTemporalitySelector` ([#1568])
- Add `webpki-roots` features to `reqwest` and `tonic` backends
[#1568]: https://github.com/open-telemetry/opentelemetry-rust/pull/1568
### Changed
- **Breaking** Remove global provider for Logs [#1691](https://github.com/open-telemetry/opentelemetry-rust/pull/1691/)
- The method OtlpLogPipeline::install_simple() and OtlpLogPipeline::install_batch() now return `LoggerProvider` instead of
`Logger`. Refer to the [basic-otlp](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/examples/basic-otlp/src/main.rs) and [basic-otlp-http](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs) examples for how to initialize OTLP Log Exporter to use with OpenTelemetryLogBridge and OpenTelemetryTracingBridge respectively.
- Update `opentelemetry` dependency version to 0.23
- Update `opentelemetry_sdk` dependency version to 0.23
- Update `opentelemetry-http` dependency version to 0.12
- Update `opentelemetry-proto` dependency version to 0.6
## v0.15.0
### Added
- Support custom channels in topic exporters [#1335](https://github.com/open-telemetry/opentelemetry-rust/pull/1335)
- Allow specifying OTLP Tonic metadata from env variable [#1377](https://github.com/open-telemetry/opentelemetry-rust/pull/1377)
### Changed
- Update to tonic 0.11 and prost 0.12 [#1536](https://github.com/open-telemetry/opentelemetry-rust/pull/1536)
### Fixed
- Fix `tonic()` to the use correct port. [#1556](https://github.com/open-telemetry/opentelemetry-rust/pull/1556)
### Removed
- **Breaking** Remove support for surf HTTP client [#1537](https://github.com/open-telemetry/opentelemetry-rust/pull/1537)
- **Breaking** Remove support for grpcio transport [#1534](https://github.com/open-telemetry/opentelemetry-rust/pull/1534)
## v0.14.0
### Added
- Add `build_{signal}_exporter` methods to client builders [#1187](https://github.com/open-telemetry/opentelemetry-rust/pull/1187)
- Add `grpcio` metrics exporter [#1202](https://github.com/open-telemetry/opentelemetry-rust/pull/1202)
- Allow specifying OTLP HTTP headers from env variable [#1290](https://github.com/open-telemetry/opentelemetry-rust/pull/1290)
### Changed
- Bump MSRV to 1.65 [#1318](https://github.com/open-telemetry/opentelemetry-rust/pull/1318)
- Bump MSRV to 1.64 [#1203](https://github.com/open-telemetry/opentelemetry-rust/pull/1203)
- Changed dependency from `opentelemetry_api` to `opentelemetry` as the latter
is now the API crate. [#1226](https://github.com/open-telemetry/opentelemetry-rust/pull/1226)
- Make `NoExporterBuilder` a compiling time error [#1272](https://github.com/open-telemetry/opentelemetry-rust/pull/1272)
## v0.13.0
### Added
- Add OTLP HTTP Metrics Exporter [#1020](https://github.com/open-telemetry/opentelemetry-rust/pull/1020).
- Add tonic compression support [#1165](https://github.com/open-telemetry/opentelemetry-rust/pull/1165).
### Changed
- make the tonic/transport feature optional #985
- update to opentelemetry-api v0.20.0
### Fixed
- Fix a missing import when http-proto is enabled without grpc-sys #1081
## v0.12.0
### Added
- Add batch config for otlp pipeline [#979](https://github.com/open-telemetry/opentelemetry-rust/pull/979).
- Add tonic interceptor [#901](https://github.com/open-telemetry/opentelemetry-rust/pull/901).
### Changed
- Update `opentelemetry` to 0.19.
- Update `opentelemetry-semantic-conventions` to 0.11.
- Update `opentelemetry-http` to 0.8.
- Bump MSRV to 1.57 [#953](https://github.com/open-telemetry/opentelemetry-rust/pull/953).
- Add `User-Agent` header on all exporters [#896](https://github.com/open-telemetry/opentelemetry-rust/pull/896).
- Improve OTLP exporter environment variable handling [#912](https://github.com/open-telemetry/opentelemetry-rust/pull/912).
- Fix the issue where tonic exporter builder ignored provided metadata [#937](https://github.com/open-telemetry/opentelemetry-rust/pull/937).
- Export `MetricsExporterBuilder` [#943](https://github.com/open-telemetry/opentelemetry-rust/pull/943).
- Report OTLP http export errors [#945](https://github.com/open-telemetry/opentelemetry-rust/pull/945).
- Change to export using v0.19.0 protobuf definitions. [#989](https://github.com/open-telemetry/opentelemetry-rust/pull/989).
- Update dependencies and bump MSRV to 1.60 [#969](https://github.com/open-telemetry/opentelemetry-rust/pull/969).
## v0.11.0
### Changed
- reduce `tokio` feature requirements #750
- Update to opentelemetry v0.18.0
- Update to opentelemetry-http v0.7.0
- Update `tonic` to 0.7 #783
- Automatically add traces / metrics paths #806
## v0.10.0
### Changed
- Update to opentelemetry v0.17.0
- Update to opentelemetry-http v0.6.0
- Update `tonic` to 0.6 #660
## v0.9.0
### Changed
- Merge metrics and tracing pipeline #585
- Update to opentelemetry v0.16.0
### Fixed
- `MetricsExporterBuilder` drops `exporter_pipeline` #590
- Improve error messages #603
- Upgrade `tonic` to `0.5.x` #597
## v0.8.0
### Changed
- Update grpcio version and add the coverage badge #556
- Update to opentelemetry v0.15.0
## v0.7.0
### Added
- adding otlp http transport, using proto binary #516
### Fixed
- docs cannot compile #507
- exporter cannot merge IntSum correctly. #518
- update metrics proto and metric transformation #535
### Changed
- Allow users to bring their own tonic channel #515
- Remove default surf features #546
- Update to opentelemetry v0.14.0
### v0.6.0
### Added
- Examples on how to connect to an external otlp using tonic, tls and tokio #449
- Examples on how to connect to an external otlp using grpcio and tls #450
- `with_env` method for `OtlpPipelineBuilder` to use environment variables to config otlp pipeline #451
- Update `tracing-grpc` example to include extractors and injectors #464
- Mentioned `service.name` resource in README #476
### Changed
- Update to opentelemetry v0.13.0
- Update `tonic-build` dependency to 0.4 #463
- Update the opentelemetry pipeline to use API to choose grpc layer instead of feature #467
- Rename trace config with_default_sampler to with_sampler #482
### Removed
- Removed `from_env` and use environment variables to initialize the configurations by default #459
- Removed support for running tonic without tokio runtime #483
## v0.5.0
### Added
- Otlp metric exporter #402
- Otlp exporter integration test #424
### Changed
- Update to opentelemetry v0.12.0
- Update tokio to v1 #421
## v0.4.0
### Added
- Tonic support #352
- Add openssl feature flags for grpcio #367
### Changed
- Update to opentelemetry v0.11.0
- Update default otlp port to `4317` #388
### Fixed
- Propagate `Resource` information #366
- Propagate `Resource` in tonic as well #390
## v0.3.0
### Changed
- Update to opentelemetry v0.10.0
## v0.2.0
### Changed
- Update to opentelemetry v0.9.0
- Add exporter pipeline #210
## v0.1.0
### Added
- Initial Alpha implementation
+271
View File
@@ -0,0 +1,271 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.75.0"
name = "opentelemetry-otlp"
version = "0.33.0"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Exporter for the OpenTelemetry Collector"
homepage = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-otlp"
readme = "README.md"
keywords = [
"opentelemetry",
"otlp",
"logging",
"tracing",
"metrics",
]
categories = [
"development-tools::debugging",
"development-tools::profiling",
"asynchronous",
]
license = "Apache-2.0"
repository = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-otlp"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[features]
default = [
"http-proto",
"reqwest-blocking-client",
"trace",
"metrics",
"logs",
"internal-logs",
]
grpc-tonic = [
"tonic",
"tonic-types",
"prost",
"http",
"tokio",
"opentelemetry-proto/gen-tonic",
]
gzip-http = ["flate2"]
gzip-tonic = ["tonic/gzip"]
http-json = [
"serde_json",
"prost",
"opentelemetry-http",
"opentelemetry-proto/gen-tonic-messages",
"opentelemetry-proto/with-serde",
"http",
"httpdate",
"trace",
"metrics",
]
http-proto = [
"prost",
"opentelemetry-http",
"opentelemetry-proto/gen-tonic-messages",
"http",
"httpdate",
"trace",
"metrics",
]
hyper-client = [
"opentelemetry-http/hyper",
"tokio",
]
integration-testing = [
"tonic",
"prost",
"tokio/full",
"trace",
"logs",
]
internal-logs = [
"opentelemetry_sdk/internal-logs",
"opentelemetry/internal-logs",
]
logs = [
"opentelemetry/logs",
"opentelemetry_sdk/logs",
"opentelemetry-proto/logs",
]
metrics = [
"opentelemetry/metrics",
"opentelemetry_sdk/metrics",
"opentelemetry-proto/metrics",
]
reqwest-blocking-client = [
"reqwest/blocking",
"opentelemetry-http/reqwest-blocking",
]
reqwest-client = [
"reqwest",
"opentelemetry-http/reqwest",
"tokio",
]
reqwest-rustls = [
"reqwest",
"opentelemetry-http/reqwest-rustls",
]
tls-aws-lc = ["tonic/tls-aws-lc"]
tls-provider-agnostic = ["tonic/_tls-any"]
tls-ring = ["tonic/tls-ring"]
tls-roots = ["tonic/tls-native-roots"]
tls-webpki-roots = ["tonic/tls-webpki-roots"]
trace = [
"opentelemetry/trace",
"opentelemetry_sdk/trace",
"opentelemetry-proto/trace",
]
zstd-http = ["zstd"]
zstd-tonic = ["tonic/zstd"]
[lib]
name = "opentelemetry_otlp"
path = "src/lib.rs"
bench = false
[[test]]
name = "smoke"
path = "tests/smoke.rs"
required-features = ["integration-testing"]
[dependencies.flate2]
version = "1.1.2"
optional = true
[dependencies.http]
version = "1.1"
features = ["std"]
optional = true
default-features = false
[dependencies.httpdate]
version = "1.0.3"
optional = true
[dependencies.opentelemetry]
version = "0.33"
default-features = false
[dependencies.opentelemetry-http]
version = "0.33"
optional = true
default-features = false
[dependencies.opentelemetry-proto]
version = "0.33"
default-features = false
[dependencies.opentelemetry_sdk]
version = "0.33"
default-features = false
[dependencies.prost]
version = "0.14"
optional = true
[dependencies.reqwest]
version = "0.13.1"
optional = true
default-features = false
[dependencies.serde_json]
version = "1.0"
optional = true
[dependencies.thiserror]
version = "2"
default-features = false
[dependencies.tokio]
version = "1"
features = [
"sync",
"rt",
"time",
]
optional = true
default-features = false
[dependencies.tonic]
version = "0.14.1"
optional = true
default-features = false
[dependencies.tonic-types]
version = "0.14.1"
optional = true
[dependencies.zstd]
version = "0.13"
optional = true
[dev-dependencies.async-trait]
version = "0.1"
[dev-dependencies.futures-executor]
version = "0.3"
[dev-dependencies.futures-util]
version = "0.3"
default-features = false
[dev-dependencies.opentelemetry_sdk]
version = "0.33"
features = [
"trace",
"testing",
]
default-features = false
[dev-dependencies.temp-env]
version = "0.3.6"
[dev-dependencies.tokio]
version = "1"
features = [
"macros",
"rt-multi-thread",
]
default-features = false
[dev-dependencies.tokio-stream]
version = "0.1"
features = ["net"]
[dev-dependencies.tonic]
version = "0.14.1"
features = [
"router",
"server",
]
default-features = false
[lints.clippy.all]
level = "warn"
priority = 1
[lints.rust]
edition_2024_expr_fragment_specifier = "allow"
if_let_rescope = "allow"
tail_expr_drop_order = "allow"
[lints.rust.rust_2024_compatibility]
level = "warn"
priority = -1
+85
View File
@@ -0,0 +1,85 @@
# OpenTelemetry OTLP Exporter
![OpenTelemetry — An observability framework for cloud-native software.][splash]
[splash]: https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo-text.png
This crate contains the [OpenTelemetry](https://opentelemetry.io/) Exporter
implementation for
[OTLP](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md).
[![Crates.io: opentelemetry-otlp](https://img.shields.io/crates/v/opentelemetry-otlp.svg)](https://crates.io/crates/opentelemetry-otlp)
[![Documentation](https://docs.rs/opentelemetry-otlp/badge.svg)](https://docs.rs/opentelemetry-otlp)
[![LICENSE](https://img.shields.io/crates/l/opentelemetry-otlp)](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/LICENSE)
[![GitHub Actions CI](https://github.com/open-telemetry/opentelemetry-rust/workflows/CI/badge.svg)](https://github.com/open-telemetry/opentelemetry-rust/actions?query=workflow%3ACI+branch%3Amain)
[![codecov](https://codecov.io/gh/open-telemetry/opentelemetry-rust/branch/main/graph/badge.svg)](https://codecov.io/gh/open-telemetry/opentelemetry-rust)
[![Slack](https://img.shields.io/badge/slack-@cncf/otel/rust-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C03GDP0H023)
## Overview
OpenTelemetry is an Observability framework and toolkit designed to create and
manage telemetry data such as traces, metrics, and logs. OpenTelemetry is
vendor- and tool-agnostic, meaning that it can be used with a broad variety of
Observability backends, including open source tools like [Jaeger] and
[Prometheus], as well as commercial offerings.
OpenTelemetry is *not* an observability backend like Jaeger, Prometheus, or other
commercial vendors. OpenTelemetry is focused on the generation, collection,
management, and export of telemetry. A major goal of OpenTelemetry is that you
can easily instrument your applications or systems, no matter their language,
infrastructure, or runtime environment. Crucially, the storage and visualization
of telemetry is intentionally left to other tools.
*[Supported Rust Versions](#supported-rust-versions)*
[Prometheus]: https://prometheus.io
[Jaeger]: https://www.jaegertracing.io
### What does this crate contain?
This crate provides an [OTLP
(OpenTelemetry
Protocol)](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/README.md)
exporter that sends telemetry to any OTLP-compatible backend such as the
[OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector)
or vendor endpoints. Exporters are provided for all three signals - Logs,
Metrics and Traces - and support both `grpc` and `http` (binary protobuf or
JSON) transports.
OTLP is the recommended way to export telemetry from OpenTelemetry, as it is
vendor-neutral and supported by most observability backends.
### Related crates
This crate exports telemetry produced via the OpenTelemetry SDK. It is
commonly used alongside:
- **[opentelemetry](https://crates.io/crates/opentelemetry):** The core
OpenTelemetry API.
- **[opentelemetry-sdk](https://crates.io/crates/opentelemetry-sdk):** The
OpenTelemetry SDK implementation that this exporter plugs into.
- **[opentelemetry-http](https://crates.io/crates/opentelemetry-http):** HTTP
client abstractions used by the HTTP transport in this crate.
- **[opentelemetry-proto](https://crates.io/crates/opentelemetry-proto):**
Generated protobuf types used on the wire.
## Getting started
See [docs](https://docs.rs/opentelemetry-otlp).
## Release Notes
You can find the release notes (changelog) [here](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/CHANGELOG.md).
## Supported Rust Versions
OpenTelemetry is built against the latest stable release. The minimum supported
version is 1.75.0. The current OpenTelemetry version is NOT guaranteed to build
on Rust versions earlier than the minimum supported version.
The current stable Rust compiler and the three most recent minor versions
before it will always be supported. For example, if the current stable compiler
version is 1.49, the minimum supported version will not be increased past 1.46,
three minor versions prior. Increasing the minimum supported compiler version
is not considered a semver breaking change as long as doing so complies with
this policy.
@@ -0,0 +1,22 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
# This is used with cargo-check-external-types to reduce the surface area of downstream crates from
# the public API. Ideally this can have a few exceptions as possible.
allowed_external_types = [
# opentelemetry-http
"opentelemetry_http::HttpClient",
# opentelemetry-sdk
"opentelemetry_sdk::logs::export::LogExporter",
"opentelemetry_sdk::metrics::exporter::PushMetricExporter",
"opentelemetry_sdk::metrics::Temporality",
"opentelemetry_sdk::trace::export::SpanExporter",
# tonic (pre-1.0 crate)
"tonic::metadata::map::MetadataMap",
"tonic::service::interceptor::Interceptor",
"tonic::transport::channel::Channel",
"tonic::transport::channel::tls::ClientTlsConfig",
"tonic::transport::tls::Certificate",
"tonic::transport::tls::Identity",
]
@@ -0,0 +1,111 @@
use super::OtlpHttpClient;
use crate::Protocol;
use opentelemetry::{otel_debug, otel_warn};
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use opentelemetry_sdk::logs::{LogBatch, LogExporter};
#[cfg(feature = "http-proto")]
use prost::Message;
use std::time;
impl LogExporter for OtlpHttpClient {
async fn export(&self, batch: LogBatch<'_>) -> OTelSdkResult {
let response_body = self
.export_http_with_retry(
batch,
OtlpHttpClient::build_logs_export_body,
"HttpLogsClient.Export",
)
.await?;
handle_partial_success(&response_body, self.protocol);
Ok(())
}
fn shutdown_with_timeout(&self, _timeout: time::Duration) -> OTelSdkResult {
let mut client_guard = self.client.lock().map_err(|e| {
OTelSdkError::InternalFailure(format!("Failed to acquire client lock: {e}"))
})?;
if client_guard.take().is_none() {
return Err(OTelSdkError::AlreadyShutdown);
}
Ok(())
}
fn set_resource(&mut self, resource: &opentelemetry_sdk::Resource) {
self.resource = resource.into();
}
}
/// Handles partial success returned by OTLP endpoints. We log the rejected log records,
/// as well as the error message returned.
fn handle_partial_success(response_body: &[u8], protocol: Protocol) {
use opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceResponse;
let response: ExportLogsServiceResponse = match protocol {
#[cfg(feature = "http-json")]
Protocol::HttpJson => match serde_json::from_slice(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpLogsClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => match Message::decode(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpLogsClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};
if let Some(partial_success) = response.partial_success {
if partial_success.rejected_log_records > 0 || !partial_success.error_message.is_empty() {
otel_warn!(
name: "HttpLogsClient.PartialSuccess",
rejected_log_records = partial_success.rejected_log_records,
error_message = partial_success.error_message.as_str(),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "http-proto")]
#[test]
fn test_handle_invalid_protobuf() {
// Corrupted/invalid protobuf data
let invalid = vec![0xFF, 0xFF, 0xFF, 0xFF];
// Should not panic - logs debug and returns early
handle_partial_success(&invalid, Protocol::HttpBinary);
}
#[cfg(feature = "http-proto")]
#[test]
fn test_handle_empty_response() {
let empty = vec![];
// Should not panic
handle_partial_success(&empty, Protocol::HttpBinary);
}
#[cfg(feature = "http-json")]
#[test]
fn test_handle_invalid_json() {
let invalid_json = b"{not valid json}";
// Should not panic - logs debug and returns
handle_partial_success(invalid_json, Protocol::HttpJson);
}
}
@@ -0,0 +1,105 @@
use crate::metric::MetricsClient;
use crate::Protocol;
use opentelemetry::{otel_debug, otel_warn};
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use opentelemetry_sdk::metrics::data::ResourceMetrics;
#[cfg(feature = "http-proto")]
use prost::Message;
use super::OtlpHttpClient;
impl MetricsClient for OtlpHttpClient {
async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult {
let response_body = self
.export_http_with_retry(
metrics,
OtlpHttpClient::build_metrics_export_body,
"HttpMetricsClient.Export",
)
.await?;
handle_partial_success(&response_body, self.protocol);
Ok(())
}
fn shutdown(&self) -> OTelSdkResult {
self.client
.lock()
.map_err(|e| OTelSdkError::InternalFailure(format!("Failed to acquire lock: {e}")))?
.take();
Ok(())
}
}
/// Handles partial success returned by OTLP endpoints. We log the rejected data points,
/// as well as the error message returned.
fn handle_partial_success(response_body: &[u8], protocol: Protocol) {
use opentelemetry_proto::tonic::collector::metrics::v1::ExportMetricsServiceResponse;
let response: ExportMetricsServiceResponse = match protocol {
#[cfg(feature = "http-json")]
Protocol::HttpJson => match serde_json::from_slice(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpMetricsClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => match Message::decode(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpMetricsClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};
if let Some(partial_success) = response.partial_success {
if partial_success.rejected_data_points > 0 || !partial_success.error_message.is_empty() {
otel_warn!(
name: "HttpMetricsClient.PartialSuccess",
rejected_data_points = partial_success.rejected_data_points,
error_message = partial_success.error_message.as_str(),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "http-proto")]
#[test]
fn test_handle_invalid_protobuf() {
// Corrupted/invalid protobuf data
let invalid = vec![0xFF, 0xFF, 0xFF, 0xFF];
// Should not panic - logs debug and returns early
handle_partial_success(&invalid, Protocol::HttpBinary);
}
#[cfg(feature = "http-proto")]
#[test]
fn test_handle_empty_response() {
let empty = vec![];
// Should not panic
handle_partial_success(&empty, Protocol::HttpBinary);
}
#[cfg(feature = "http-json")]
#[test]
fn test_handle_invalid_json() {
let invalid_json = b"{not valid json}";
// Should not panic - logs debug and returns
handle_partial_success(invalid_json, Protocol::HttpJson);
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,112 @@
use super::OtlpHttpClient;
use crate::Protocol;
use opentelemetry::{otel_debug, otel_warn};
use opentelemetry_sdk::{
error::{OTelSdkError, OTelSdkResult},
trace::{SpanData, SpanExporter},
};
#[cfg(feature = "http-proto")]
use prost::Message;
impl SpanExporter for OtlpHttpClient {
async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult {
let response_body = self
.export_http_with_retry(
batch,
OtlpHttpClient::build_trace_export_body,
"HttpTracesClient.Export",
)
.await?;
handle_partial_success(&response_body, self.protocol);
Ok(())
}
fn shutdown(&self) -> OTelSdkResult {
let mut client_guard = self.client.lock().map_err(|e| {
OTelSdkError::InternalFailure(format!("Failed to acquire client lock: {e}"))
})?;
if client_guard.take().is_none() {
return Err(OTelSdkError::AlreadyShutdown);
}
Ok(())
}
fn set_resource(&mut self, resource: &opentelemetry_sdk::Resource) {
self.resource = resource.into();
}
}
/// Handles partial success returned by OTLP endpoints. We log the rejected spans,
/// as well as the error message returned.
fn handle_partial_success(response_body: &[u8], protocol: Protocol) {
use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceResponse;
let response: ExportTraceServiceResponse = match protocol {
#[cfg(feature = "http-json")]
Protocol::HttpJson => match serde_json::from_slice(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpTraceClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "http-proto")]
Protocol::HttpBinary => match Message::decode(response_body) {
Ok(r) => r,
Err(e) => {
otel_debug!(name: "HttpTraceClient.ResponseParseError", error = e.to_string());
return;
}
},
#[cfg(feature = "grpc-tonic")]
Protocol::Grpc => {
unreachable!("HTTP client should not receive Grpc protocol")
}
};
if let Some(partial_success) = response.partial_success {
if partial_success.rejected_spans > 0 || !partial_success.error_message.is_empty() {
otel_warn!(
name: "HttpTraceClient.PartialSuccess",
rejected_spans = partial_success.rejected_spans,
error_message = partial_success.error_message.as_str(),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "http-proto")]
#[test]
fn test_handle_invalid_protobuf() {
// Corrupted/invalid protobuf data
let invalid = vec![0xFF, 0xFF, 0xFF, 0xFF];
// Should not panic - logs debug and returns early
handle_partial_success(&invalid, Protocol::HttpBinary);
}
#[cfg(feature = "http-proto")]
#[test]
fn test_handle_empty_response() {
let empty = vec![];
// Should not panic
handle_partial_success(&empty, Protocol::HttpBinary);
}
#[cfg(feature = "http-json")]
#[test]
fn test_handle_invalid_json() {
let invalid_json = b"{not valid json}";
// Should not panic - logs debug and returns
handle_partial_success(invalid_json, Protocol::HttpJson);
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,150 @@
use core::fmt;
use opentelemetry::{otel_debug, otel_warn};
use opentelemetry_proto::tonic::collector::logs::v1::{
logs_service_client::LogsServiceClient, ExportLogsServiceRequest,
};
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use opentelemetry_sdk::logs::{LogBatch, LogExporter};
use std::sync::{Arc, Mutex};
use std::time;
use tonic::{codegen::CompressionEncoding, service::Interceptor, transport::Channel, Request};
use opentelemetry_proto::transform::logs::tonic::group_logs_by_resource_and_scope;
use super::BoxInterceptor;
use crate::RetryPolicy;
pub(crate) struct TonicLogsClient {
inner: Mutex<Option<ClientInner>>,
retry_policy: RetryPolicy,
timeout: std::time::Duration,
#[allow(dead_code)]
// <allow dead> would be removed once we support set_resource for metrics.
resource: opentelemetry_proto::transform::common::tonic::ResourceAttributesWithSchema,
}
struct ClientInner {
client: LogsServiceClient<Channel>,
interceptor: BoxInterceptor,
}
impl fmt::Debug for TonicLogsClient {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("TonicLogsClient")
}
}
impl TonicLogsClient {
pub(super) fn new(
channel: Channel,
interceptor: BoxInterceptor,
compression: Option<CompressionEncoding>,
retry_policy: Option<RetryPolicy>,
timeout: std::time::Duration,
) -> Self {
let mut client = LogsServiceClient::new(channel);
if let Some(compression) = compression {
client = client
.send_compressed(compression)
.accept_compressed(compression);
}
otel_debug!(name: "TonicsLogsClientBuilt");
TonicLogsClient {
inner: Mutex::new(Some(ClientInner {
client,
interceptor,
})),
retry_policy: retry_policy.unwrap_or_default(),
timeout,
resource: Default::default(),
}
}
}
impl LogExporter for TonicLogsClient {
async fn export(&self, batch: LogBatch<'_>) -> OTelSdkResult {
let batch = Arc::new(batch);
match super::tonic_retry_with_backoff(
&self.retry_policy,
self.timeout,
crate::retry_classification::grpc::classify_tonic_status,
"TonicLogsClient.Export",
|| async {
let batch_clone = Arc::clone(&batch);
// Execute the export operation
let (mut client, metadata, extensions) = self
.inner
.lock()
.map_err(|e| tonic::Status::internal(format!("Failed to acquire lock: {e:?}")))
.and_then(|mut inner| match &mut *inner {
Some(inner) => {
let (m, e, _) = inner
.interceptor
.call(Request::new(()))
.map_err(|e| {
super::handle_interceptor_error!("TonicLogsClient", e)
})?
.into_parts();
Ok((inner.client.clone(), m, e))
}
None => Err(tonic::Status::failed_precondition(
"log exporter is already shut down",
)),
})?;
let resource_logs = group_logs_by_resource_and_scope(&batch_clone, &self.resource);
otel_debug!(name: "TonicLogsClient.ExportStarted");
client
.export(Request::from_parts(
metadata,
extensions,
ExportLogsServiceRequest { resource_logs },
))
.await
.map(|response| {
otel_debug!(name: "TonicLogsClient.ExportSucceeded");
// Handle partial success. As per spec, we log and _do not_ retry.
if let Some(partial_success) = response.into_inner().partial_success {
if partial_success.rejected_log_records > 0
|| !partial_success.error_message.is_empty()
{
otel_warn!(
name: "TonicLogsClient.PartialSuccess",
rejected_log_records = partial_success.rejected_log_records,
error_message = partial_success.error_message.as_str(),
);
}
}
})
},
)
.await
{
Ok(_) => Ok(()),
Err(tonic_status) => {
super::handle_tonic_export_error!("TonicLogsClient", tonic_status)
}
}
}
fn shutdown_with_timeout(&self, _timeout: time::Duration) -> OTelSdkResult {
self.inner
.lock()
.map_err(|e| OTelSdkError::InternalFailure(format!("Failed to acquire lock: {e}")))?
.take();
Ok(())
}
fn set_resource(&mut self, resource: &opentelemetry_sdk::Resource) {
self.resource = resource.into();
}
}
@@ -0,0 +1,135 @@
use core::fmt;
use std::sync::Mutex;
use opentelemetry::{otel_debug, otel_warn};
use opentelemetry_proto::tonic::collector::metrics::v1::{
metrics_service_client::MetricsServiceClient, ExportMetricsServiceRequest,
};
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use opentelemetry_sdk::metrics::data::ResourceMetrics;
use tonic::{codegen::CompressionEncoding, service::Interceptor, transport::Channel, Request};
use super::BoxInterceptor;
use crate::metric::MetricsClient;
use crate::RetryPolicy;
pub(crate) struct TonicMetricsClient {
inner: Mutex<Option<ClientInner>>,
retry_policy: RetryPolicy,
timeout: std::time::Duration,
}
struct ClientInner {
client: MetricsServiceClient<Channel>,
interceptor: BoxInterceptor,
}
impl fmt::Debug for TonicMetricsClient {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("TonicMetricsClient")
}
}
impl TonicMetricsClient {
pub(super) fn new(
channel: Channel,
interceptor: BoxInterceptor,
compression: Option<CompressionEncoding>,
retry_policy: Option<RetryPolicy>,
timeout: std::time::Duration,
) -> Self {
let mut client = MetricsServiceClient::new(channel);
if let Some(compression) = compression {
client = client
.send_compressed(compression)
.accept_compressed(compression);
}
otel_debug!(name: "TonicsMetricsClientBuilt");
TonicMetricsClient {
inner: Mutex::new(Some(ClientInner {
client,
interceptor,
})),
retry_policy: retry_policy.unwrap_or_default(),
timeout,
}
}
}
impl MetricsClient for TonicMetricsClient {
async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult {
match super::tonic_retry_with_backoff(
&self.retry_policy,
self.timeout,
crate::retry_classification::grpc::classify_tonic_status,
"TonicMetricsClient.Export",
|| async {
// Execute the export operation
let (mut client, metadata, extensions) = self
.inner
.lock()
.map_err(|e| tonic::Status::internal(format!("Failed to acquire lock: {e:?}")))
.and_then(|mut inner| match &mut *inner {
Some(inner) => {
let (m, e, _) = inner
.interceptor
.call(Request::new(()))
.map_err(|e| {
super::handle_interceptor_error!("TonicMetricsClient", e)
})?
.into_parts();
Ok((inner.client.clone(), m, e))
}
None => Err(tonic::Status::failed_precondition(
"metrics exporter is already shut down",
)),
})?;
otel_debug!(name: "TonicMetricsClient.ExportStarted");
client
.export(Request::from_parts(
metadata,
extensions,
ExportMetricsServiceRequest::from(metrics),
))
.await
.map(|response| {
otel_debug!(name: "TonicMetricsClient.ExportSucceeded");
// Handle partial success. As per spec, we log and _do not_ retry.
if let Some(partial_success) = response.into_inner().partial_success {
if partial_success.rejected_data_points > 0
|| !partial_success.error_message.is_empty()
{
otel_warn!(
name: "TonicMetricsClient.PartialSuccess",
rejected_data_points = partial_success.rejected_data_points,
error_message = partial_success.error_message.as_str(),
);
}
}
})
},
)
.await
{
Ok(_) => Ok(()),
Err(tonic_status) => {
super::handle_tonic_export_error!("TonicMetricsClient", tonic_status)
}
}
}
fn shutdown(&self) -> OTelSdkResult {
self.inner
.lock()
.map_err(|e| OTelSdkError::InternalFailure(format!("Failed to acquire lock: {e}")))?
.take();
Ok(())
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,155 @@
use core::fmt;
use std::sync::{Arc, Mutex};
use opentelemetry::{otel_debug, otel_warn};
use opentelemetry_proto::tonic::collector::trace::v1::{
trace_service_client::TraceServiceClient, ExportTraceServiceRequest,
};
use opentelemetry_proto::transform::trace::tonic::group_spans_by_resource_and_scope;
use opentelemetry_sdk::error::OTelSdkError;
use opentelemetry_sdk::{
error::OTelSdkResult,
trace::{SpanData, SpanExporter},
};
use tonic::{codegen::CompressionEncoding, service::Interceptor, transport::Channel, Request};
use super::BoxInterceptor;
use crate::RetryPolicy;
pub(crate) struct TonicTracesClient {
inner: Mutex<Option<ClientInner>>,
retry_policy: RetryPolicy,
timeout: std::time::Duration,
#[allow(dead_code)]
// <allow dead> would be removed once we support set_resource for metrics.
resource: opentelemetry_proto::transform::common::tonic::ResourceAttributesWithSchema,
}
struct ClientInner {
client: TraceServiceClient<Channel>,
interceptor: BoxInterceptor,
}
impl fmt::Debug for TonicTracesClient {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("TonicTracesClient")
}
}
impl TonicTracesClient {
pub(super) fn new(
channel: Channel,
interceptor: BoxInterceptor,
compression: Option<CompressionEncoding>,
retry_policy: Option<RetryPolicy>,
timeout: std::time::Duration,
) -> Self {
let mut client = TraceServiceClient::new(channel);
if let Some(compression) = compression {
client = client
.send_compressed(compression)
.accept_compressed(compression);
}
otel_debug!(name: "TonicsTracesClientBuilt");
TonicTracesClient {
inner: Mutex::new(Some(ClientInner {
client,
interceptor,
})),
retry_policy: retry_policy.unwrap_or_default(),
timeout,
resource: Default::default(),
}
}
}
impl SpanExporter for TonicTracesClient {
async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult {
let batch = Arc::new(batch);
match super::tonic_retry_with_backoff(
&self.retry_policy,
self.timeout,
crate::retry_classification::grpc::classify_tonic_status,
"TonicTracesClient.Export",
|| async {
let batch_clone = Arc::clone(&batch);
// Execute the export operation
let (mut client, metadata, extensions) = self
.inner
.lock()
.map_err(|e| tonic::Status::internal(format!("failed to acquire lock: {e}")))
.and_then(|mut inner| match &mut *inner {
Some(inner) => {
let (m, e, _) = inner
.interceptor
.call(Request::new(()))
.map_err(|e| {
super::handle_interceptor_error!("TonicTracesClient", e)
})?
.into_parts();
Ok((inner.client.clone(), m, e))
}
None => Err(tonic::Status::failed_precondition(
"exporter already shutdown",
)),
})?;
let resource_spans =
group_spans_by_resource_and_scope((*batch_clone).clone(), &self.resource);
otel_debug!(name: "TonicTracesClient.ExportStarted");
client
.export(Request::from_parts(
metadata,
extensions,
ExportTraceServiceRequest { resource_spans },
))
.await
.map(|response| {
otel_debug!(name: "TonicTracesClient.ExportSucceeded");
// Handle partial success. As per spec, we log and _do not_ retry.
if let Some(partial_success) = response.into_inner().partial_success {
if partial_success.rejected_spans > 0
|| !partial_success.error_message.is_empty()
{
otel_warn!(
name: "TonicTracesClient.PartialSuccess",
rejected_spans = partial_success.rejected_spans,
error_message = partial_success.error_message.as_str(),
);
}
}
})
},
)
.await
{
Ok(_) => Ok(()),
Err(tonic_status) => {
super::handle_tonic_export_error!("TonicTracesClient", tonic_status)
}
}
}
fn shutdown(&self) -> OTelSdkResult {
let mut inner_guard = self
.inner
.lock()
.map_err(|e| OTelSdkError::InternalFailure(format!("Failed to acquire lock: {e}")))?;
match inner_guard.take() {
Some(_) => Ok(()), // Successfully took `inner`, indicating a successful shutdown.
None => Err(OTelSdkError::AlreadyShutdown), // `inner` was already `None`, meaning it's already shut down.
}
}
fn set_resource(&mut self, resource: &opentelemetry_sdk::Resource) {
self.resource = resource.into();
}
}
+884
View File
@@ -0,0 +1,884 @@
//! # OpenTelemetry OTLP Exporter
//!
//! The OTLP Exporter enables exporting telemetry data (logs, metrics, and traces) in the
//! OpenTelemetry Protocol (OTLP) format to compatible backends. These backends include:
//!
//! - OpenTelemetry Collector
//! - Open-source observability tools (Prometheus, Jaeger, etc.)
//! - Vendor-specific monitoring platforms
//!
//! This crate supports sending OTLP data via:
//! - gRPC
//! - HTTP (binary protobuf or JSON)
//!
//! ## Quickstart with OpenTelemetry Collector
//!
//! The examples below show traces, but the same pattern applies to metrics
//! ([`MetricExporter`]) and logs ([`LogExporter`]) — just swap the exporter
//! builder and the corresponding SDK provider.
//!
//! ### HTTP Transport (Port 4318)
//!
//! Run the OpenTelemetry Collector:
//!
//! ```shell
//! $ docker run -p 4318:4318 otel/opentelemetry-collector:latest
//! ```
//!
//! Configure your application to export traces via HTTP:
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "http-proto"))]
//! # {
//! use opentelemetry::global;
//! use opentelemetry::trace::Tracer;
//! use opentelemetry_otlp::Protocol;
//! use opentelemetry_otlp::WithExportConfig;
//!
//! fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
//! // Initialize OTLP exporter using HTTP binary protocol
//! let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_http()
//! .with_protocol(Protocol::HttpBinary)
//! .build()?;
//!
//! // Create a tracer provider with the exporter
//! let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
//! .with_batch_exporter(otlp_exporter)
//! .build();
//!
//! // Set it as the global provider
//! global::set_tracer_provider(tracer_provider);
//!
//! // Get a tracer and create spans
//! let tracer = global::tracer("my_tracer");
//! tracer.in_span("doing_work", |_cx| {
//! // Your application logic here...
//! });
//!
//! Ok(())
//! # }
//! }
//! ```
//!
//! ### gRPC Transport (Port 4317)
//!
//! Run the OpenTelemetry Collector:
//!
//! ```shell
//! $ docker run -p 4317:4317 otel/opentelemetry-collector:latest
//! ```
//!
//! Configure your application to export traces via gRPC (the tonic client requires a Tokio runtime):
//!
//! - With `[tokio::main]`
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "grpc-tonic"))]
//! # {
//! use opentelemetry::{global, trace::Tracer};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
//! // Initialize OTLP exporter using gRPC (Tonic)
//! let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_tonic()
//! .build()?;
//!
//! // Create a tracer provider with the exporter
//! let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
//! .with_batch_exporter(otlp_exporter)
//! .build();
//!
//! // Set it as the global provider
//! global::set_tracer_provider(tracer_provider);
//!
//! // Get a tracer and create spans
//! let tracer = global::tracer("my_tracer");
//! tracer.in_span("doing_work", |_cx| {
//! // Your application logic here...
//! });
//!
//! Ok(())
//! # }
//! }
//! ```
//!
//! - Without `[tokio::main]`
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "grpc-tonic"))]
//! # {
//! use opentelemetry::{global, trace::Tracer};
//!
//! fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
//! // Initialize OTLP exporter using gRPC (Tonic)
//! let rt = tokio::runtime::Runtime::new()?;
//! let tracer_provider = rt.block_on(async {
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_tonic()
//! .build()
//! .expect("Failed to create span exporter");
//! opentelemetry_sdk::trace::SdkTracerProvider::builder()
//! .with_batch_exporter(exporter)
//! .build()
//! });
//!
//! // Set it as the global provider
//! global::set_tracer_provider(tracer_provider);
//!
//! // Get a tracer and create spans
//! let tracer = global::tracer("my_tracer");
//! tracer.in_span("doing_work", |_cx| {
//! // Your application logic here...
//! });
//!
//! // Ensure the runtime (`rt`) remains active until the program ends
//! Ok(())
//! # }
//! }
//! ```
//!
//! ## Using with Jaeger
//!
//! Jaeger natively supports the OTLP protocol, making it easy to send traces directly:
//!
//! ```shell
//! $ docker run -p 16686:16686 -p 4317:4317 jaegertracing/jaeger:latest
//! ```
//!
//! After running your application configured with the OTLP exporter, view traces at:
//! `http://localhost:16686`
//!
//! ## Using with Prometheus
//!
//! Prometheus natively supports accepting metrics via the OTLP protocol
//! (HTTP/protobuf). You can [run
//! Prometheus](https://prometheus.io/docs/prometheus/latest/installation/) with
//! the following command:
//!
//! ```shell
//! docker run -p 9090:9090 -v ./prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus --config.file=/etc/prometheus/prometheus.yml --web.enable-otlp-receiver
//! ```
//!
//! (An empty prometheus.yml file is sufficient for this example.)
//!
//! Modify your application to export metrics via OTLP:
//!
//! ```no_run
//! # #[cfg(all(feature = "metrics", feature = "http-proto"))]
//! # {
//! use opentelemetry::global;
//! use opentelemetry::metrics::Meter;
//! use opentelemetry::KeyValue;
//! use opentelemetry_otlp::Protocol;
//! use opentelemetry_otlp::WithExportConfig;
//!
//! fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
//! // Initialize OTLP exporter using HTTP binary protocol
//! let exporter = opentelemetry_otlp::MetricExporter::builder()
//! .with_http()
//! .with_protocol(Protocol::HttpBinary)
//! .with_endpoint("http://localhost:9090/api/v1/otlp/v1/metrics")
//! .build()?;
//!
//! // Create a meter provider with the OTLP Metric exporter
//! let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
//! .with_periodic_exporter(exporter)
//! .build();
//! global::set_meter_provider(meter_provider.clone());
//!
//! // Get a meter
//! let meter = global::meter("my_meter");
//!
//! // Create a metric
//! let counter = meter.u64_counter("my_counter").build();
//! counter.add(1, &[KeyValue::new("key", "value")]);
//!
//! // Shutdown the meter provider. This will trigger an export of all metrics.
//! meter_provider.shutdown()?;
//!
//! Ok(())
//! # }
//! }
//! ```
//!
//! After running your application configured with the OTLP exporter, view metrics at:
//! `http://localhost:9090`
//!
//! # Environment Variables
//!
//! The OTLP exporter respects the following environment variables, as defined by the
//! [OpenTelemetry specification]. Programmatic configuration via builder methods
//! takes precedence over environment variables. Signal-specific variables take
//! precedence over the generic `OTEL_EXPORTER_OTLP_*` variables.
//!
//! [OpenTelemetry specification]: https://opentelemetry.io/docs/specs/otel/protocol/exporter/
//!
//! ## General (all signals)
//!
//! | Variable | Description | Default |
//! |---|---|---|
//! | `OTEL_EXPORTER_OTLP_ENDPOINT` | Target URL for the exporter. For HTTP, signal paths (`/v1/traces`, `/v1/metrics`, `/v1/logs`) are appended automatically. | `http://localhost:4318` (HTTP), `http://localhost:4317` (gRPC) |
//! | `OTEL_EXPORTER_OTLP_PROTOCOL` | Transport protocol. Valid values: `grpc`, `http/protobuf`, `http/json`. Requires the corresponding crate feature. | Feature-dependent |
//! | `OTEL_EXPORTER_OTLP_TIMEOUT` | Maximum wait time (in milliseconds) for the backend to process each batch. | `10000` |
//! | `OTEL_EXPORTER_OTLP_HEADERS` | Key-value pairs for request headers. Format: `key1=value1,key2=value2`. Values are URL-decoded. | (none) |
//! | `OTEL_EXPORTER_OTLP_COMPRESSION` | Compression algorithm. Valid values: `gzip`, `zstd`, `none`. | `none` |
//! | `OTEL_EXPORTER_OTLP_INSECURE` | Whether to disable TLS for gRPC connections. Only applies to gRPC; HTTP security is determined by URL scheme. Valid values: `true`, `false` (case-insensitive). | `false` |
//!
//! ## Traces
//!
//! | Variable | Description |
//! |---|---|
//! | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Signal-specific endpoint for trace exports. |
//! | `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` | Signal-specific protocol for trace exports. Valid values: `grpc`, `http/protobuf`, `http/json`. |
//! | `OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` | Signal-specific timeout (in milliseconds) for trace exports. |
//! | `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | Signal-specific headers for trace exports. |
//! | `OTEL_EXPORTER_OTLP_TRACES_COMPRESSION` | Signal-specific compression for trace exports. |
//! | `OTEL_EXPORTER_OTLP_TRACES_INSECURE` | Signal-specific insecure flag for gRPC trace exports. |
//!
//! ## Metrics
//!
//! | Variable | Description |
//! |---|---|
//! | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | Signal-specific endpoint for metrics exports. |
//! | `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` | Signal-specific protocol for metrics exports. Valid values: `grpc`, `http/protobuf`, `http/json`. |
//! | `OTEL_EXPORTER_OTLP_METRICS_TIMEOUT` | Signal-specific timeout (in milliseconds) for metrics exports. |
//! | `OTEL_EXPORTER_OTLP_METRICS_HEADERS` | Signal-specific headers for metrics exports. |
//! | `OTEL_EXPORTER_OTLP_METRICS_COMPRESSION` | Signal-specific compression for metrics exports. |
//! | `OTEL_EXPORTER_OTLP_METRICS_INSECURE` | Signal-specific insecure flag for gRPC metrics exports. |
//! | `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE` | Temporality preference for metrics. Valid values: `cumulative`, `delta`, `lowmemory` (case-insensitive). | `cumulative` |
//!
//! ## Logs
//!
//! | Variable | Description |
//! |---|---|
//! | `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Signal-specific endpoint for log exports. |
//! | `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` | Signal-specific protocol for log exports. Valid values: `grpc`, `http/protobuf`, `http/json`. |
//! | `OTEL_EXPORTER_OTLP_LOGS_TIMEOUT` | Signal-specific timeout (in milliseconds) for log exports. |
//! | `OTEL_EXPORTER_OTLP_LOGS_HEADERS` | Signal-specific headers for log exports. |
//! | `OTEL_EXPORTER_OTLP_LOGS_COMPRESSION` | Signal-specific compression for log exports. |
//! | `OTEL_EXPORTER_OTLP_LOGS_INSECURE` | Signal-specific insecure flag for gRPC log exports. |
//!
//! # Feature Flags
//! The following feature flags can enable exporters for different telemetry signals:
//!
//! * `trace`: Includes the trace exporters.
//! * `metrics`: Includes the metrics exporters.
//! * `logs`: Includes the logs exporters.
//!
//! The following feature flags offer additional configurations on gRPC:
//!
//! For users using `tonic` as grpc layer:
//! * `grpc-tonic`: Use `tonic` as grpc layer.
//! * `gzip-tonic`: Use gzip compression for `tonic` grpc layer.
//! * `zstd-tonic`: Use zstd compression for `tonic` grpc layer.
//! * `tls-ring`: Enable rustls TLS support using ring for `tonic`.
//! * `tls-aws-lc`: Enable rustls TLS support using aws-lc for `tonic`.
//! * `tls-provider-agnostic`: Provider-agnostic TLS — enables TLS code paths without bundling a specific
//! crypto provider. Use this when you install a `CryptoProvider` globally
//! (e.g., via `rustls-openssl` for FIPS/OpenSSL environments).
//! * `tls-roots`: Adds system trust roots to rustls-based gRPC clients using the rustls-native-certs crate (use with `tls-ring` or `tls-aws-lc`).
//! * `tls-webpki-roots`: Embeds Mozilla's trust roots to rustls-based gRPC clients using the webpki-roots crate (use with `tls-ring` or `tls-aws-lc`).
//!
//! The following feature flags offer additional configurations on http:
//!
//! * `http-proto`: Use http as transport layer, protobuf as body format. This feature is enabled by default.
//! * `gzip-http`: Use gzip compression for HTTP transport.
//! * `zstd-http`: Use zstd compression for HTTP transport.
//! * `reqwest-blocking-client`: Use reqwest blocking http client. This feature is enabled by default.
//! * `reqwest-client`: Use reqwest async http client.
//! * `hyper-client`: Use hyper async http client.
//! * `reqwest-rustls`: Use reqwest with TLS. Uses `rustls` with the platform's native trust roots by default.
//! If you need Mozilla's embedded CA bundle (webpki-roots), build a custom `reqwest::Client` with
//! a `rustls::ClientConfig` containing
//! `rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned())`, then pass it
//! via `with_http_client()`.
//!
//! Retry with exponential backoff is always enabled for both gRPC and HTTP exports.
//! Failed exports are automatically retried according to the configured retry policy,
//! with proper classification of retryable vs non-retryable errors and support for
//! server-provided throttling hints (HTTP Retry-After, gRPC RetryInfo).
//!
//! # Full Configuration Reference
//!
//!
//! There are two layers of configuration for the OTLP exporter:
//!
//! 1. **Exporter configuration** – controls how telemetry is sent (endpoint, transport, headers, TLS, compression, timeout).
//! Built via the signal-specific builder: [`SpanExporter::builder()`], [`MetricExporter::builder()`], [`LogExporter::builder()`].
//! 2. **Provider/SDK configuration** – controls how telemetry is collected and batched (sampling, batch size, resource, etc.).
//! Built via [`opentelemetry_sdk::trace::SdkTracerProvider::builder()`],
//! [`opentelemetry_sdk::metrics::SdkMeterProvider::builder()`], or
//! [`opentelemetry_sdk::logs::SdkLoggerProvider::builder()`].
//!
//! **Configuration precedence** (highest wins):
//! 1. Programmatic configuration via builder methods — always wins when set
//! 2. Signal-specific environment variables (e.g. `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) — used when no programmatic value is set
//! 3. Generic environment variables (e.g. `OTEL_EXPORTER_OTLP_ENDPOINT`) — used when neither programmatic nor signal-specific env var is set
//! 4. Built-in defaults — used when nothing else is configured
//!
//! ## gRPC (tonic) — all configuration options
//!
//! Requires the `grpc-tonic` feature. The methods below come from two traits:
//! - [`WithExportConfig`]: `with_endpoint`, `with_timeout` (shared with HTTP)
//! - [`WithTonicConfig`]: `with_metadata`, `with_compression`, `with_tls_config`, `with_channel`, `with_interceptor`, `with_retry_policy`
//!
//! The examples here use [`SpanExporter`], but the same builder methods are
//! available on [`MetricExporter`] and [`LogExporter`].
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "grpc-tonic"))]
//! # {
//! use opentelemetry_otlp::{WithExportConfig, WithTonicConfig, Compression};
//! use std::time::Duration;
//! use tonic::metadata::MetadataMap;
//!
//! // ── gRPC metadata (custom request headers) ───────────────────────────────
//! // MetadataMap carries per-call key/value pairs sent as HTTP/2 headers.
//! // with_metadata() is additive: calling it multiple times merges entries.
//! let mut metadata = MetadataMap::with_capacity(3);
//! metadata.insert("x-host", "example.com".parse().unwrap());
//! metadata.insert("x-api-key", "secret".parse().unwrap());
//! metadata.insert_bin("trace-proto-bin", tonic::metadata::MetadataValue::from_bytes(b"[bin]"));
//!
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_tonic()
//! // Target gRPC endpoint. Defaults to http://localhost:4317.
//! // Env var: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (or OTEL_EXPORTER_OTLP_ENDPOINT).
//! .with_endpoint("http://my-collector:4317")
//! // Per-export timeout. Defaults to 10 s.
//! // Env var: OTEL_EXPORTER_OTLP_TRACES_TIMEOUT (or OTEL_EXPORTER_OTLP_TIMEOUT).
//! .with_timeout(Duration::from_secs(5))
//! // Custom gRPC metadata (auth tokens, routing headers, …).
//! // Env var: OTEL_EXPORTER_OTLP_TRACES_HEADERS (or OTEL_EXPORTER_OTLP_HEADERS).
//! .with_metadata(metadata)
//! // Compression. Requires the `gzip-tonic` or `zstd-tonic` feature.
//! // Env var: OTEL_EXPORTER_OTLP_TRACES_COMPRESSION (or OTEL_EXPORTER_OTLP_COMPRESSION).
//! .with_compression(Compression::Gzip)
//! .build()
//! .expect("Failed to build SpanExporter");
//! # }
//! ```
//!
//! ### TLS (grpc-tonic)
//!
//! Requires the `tls-ring` or `tls-aws-lc` feature (plus optionally `tls-roots` or `tls-webpki-roots`
//! to load CA roots automatically).
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "grpc-tonic", any(feature = "tls-ring", feature = "tls-aws-lc")))]
//! # {
//! use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
//! use opentelemetry_otlp::tonic_types::transport::ClientTlsConfig;
//!
//! let tls = ClientTlsConfig::new()
//! .domain_name("my-collector.example.com")
//! // Optionally verify the server with a CA certificate:
//! // .ca_certificate(tonic::transport::Certificate::from_pem(CA_PEM))
//! // Or present a client identity for mutual TLS (mTLS):
//! // .identity(tonic::transport::Identity::from_pem(CERT_PEM, KEY_PEM))
//! ;
//!
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_tonic()
//! .with_endpoint("https://my-collector.example.com:4317")
//! .with_tls_config(tls)
//! .build()
//! .expect("Failed to build SpanExporter");
//! # }
//! ```
//!
//! ### Pre-built tonic channel
//!
//! Use `with_channel` when you need full control over the transport (e.g. Unix sockets,
//! custom load-balancing). **Note:** `with_channel` overrides any TLS config set via
//! `with_tls_config`, and you are responsible for matching the channel timeout to
//! the exporter timeout.
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "grpc-tonic"))]
//! # {
//! use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
//! use std::time::Duration;
//!
//! let channel = tonic::transport::Channel::from_static("http://localhost:4317")
//! .timeout(Duration::from_secs(5))
//! .connect_lazy();
//!
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_tonic()
//! .with_channel(channel)
//! .with_timeout(Duration::from_secs(5)) // keep in sync with channel timeout above
//! .build()
//! .expect("Failed to build SpanExporter");
//! # }
//! ```
//!
//! ### gRPC interceptors
//!
//! Use `with_interceptor` to modify every outbound gRPC request — useful for injecting
//! auth tokens or dynamic metadata. Only one interceptor can be set; chain multiple together
//! before passing them in.
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "grpc-tonic"))]
//! # {
//! use opentelemetry_otlp::WithTonicConfig;
//! use tonic::{Request, Status};
//!
//! fn auth_interceptor(mut req: Request<()>) -> Result<Request<()>, Status> {
//! req.metadata_mut().insert("authorization", "Bearer my-token".parse().unwrap());
//! Ok(req)
//! }
//!
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_tonic()
//! .with_interceptor(auth_interceptor)
//! .build()
//! .expect("Failed to build SpanExporter");
//! # }
//! ```
//!
//! ### gRPC retry policy
//!
//! Failed exports are retried with exponential backoff and jitter. The retry policy
//! can be customized:
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "grpc-tonic"))]
//! # {
//! use opentelemetry_otlp::{WithTonicConfig, RetryPolicy};
//! use std::time::Duration;
//!
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_tonic()
//! .with_retry_policy(
//! RetryPolicy::default()
//! .with_max_retries(5)
//! .with_initial_delay(Duration::from_millis(500))
//! .with_max_delay(Duration::from_secs(30))
//! .with_max_jitter(Duration::from_millis(100)),
//! )
//! .build()
//! .expect("Failed to build SpanExporter");
//! # }
//! ```
//!
//! ## HTTP — all configuration options
//!
//! Requires the `http-proto` (default) or `http-json` feature. The methods below come from:
//! - [`WithExportConfig`]: `with_endpoint`, `with_timeout`, `with_protocol`
//! - [`WithHttpConfig`]: `with_headers`, `with_compression`, `with_http_client`,
//! `with_retry_policy`, `with_max_request_body_size`
//!
//! The examples here use [`SpanExporter`], but the same builder methods are
//! available on [`MetricExporter`] and [`LogExporter`].
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "http-proto"))]
//! # {
//! use opentelemetry_otlp::{WithExportConfig, WithHttpConfig, Protocol, Compression};
//! use std::time::Duration;
//! use std::collections::HashMap;
//!
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_http()
//! // Target URL, used verbatim: include the signal path, e.g. /v1/traces.
//! // OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is also used verbatim.
//! // OTEL_EXPORTER_OTLP_ENDPOINT is a base URL and appends /v1/traces automatically.
//! // Defaults to http://localhost:4318/v1/traces.
//! .with_endpoint("http://my-collector:4318/v1/traces")
//! // Per-export timeout. Defaults to 10 s.
//! // Env var: OTEL_EXPORTER_OTLP_TRACES_TIMEOUT (or OTEL_EXPORTER_OTLP_TIMEOUT).
//! .with_timeout(Duration::from_secs(5))
//! // Transport encoding. HttpBinary (protobuf) is the default.
//! // HttpJson requires the `http-json` feature.
//! // Env var: OTEL_EXPORTER_OTLP_PROTOCOL.
//! .with_protocol(Protocol::HttpBinary)
//! // Custom HTTP headers (auth tokens, routing headers, …).
//! // Values are URL-decoded when read from environment variables.
//! // Env var: OTEL_EXPORTER_OTLP_TRACES_HEADERS (or OTEL_EXPORTER_OTLP_HEADERS).
//! .with_headers(HashMap::from([
//! ("x-api-key".to_string(), "secret".to_string()),
//! ]))
//! // Compression. Requires the `gzip-http` or `zstd-http` feature.
//! // Env var: OTEL_EXPORTER_OTLP_TRACES_COMPRESSION (or OTEL_EXPORTER_OTLP_COMPRESSION).
//! .with_compression(Compression::Gzip)
//! // Maximum serialized HTTP request size before and after compression.
//! // Defaults to the OTLP-recommended 64 MiB.
//! .with_max_request_body_size(64 * 1024 * 1024)
//! .build()
//! .expect("Failed to build SpanExporter");
//! # }
//! ```
//!
//! ### Custom HTTP client
//!
//! By default the exporter uses a `reqwest` blocking client (`reqwest-blocking-client` feature,
//! enabled by default). Supply your own client to control TLS, proxies, connection pooling, etc.
//! The client must implement the [`opentelemetry_http::HttpClient`] trait.
//!
//! The standard SDK path uses the default `BatchSpanProcessor`,
//! `BatchLogProcessor`, and `PeriodicReader`. These components run exports on
//! SDK-owned background threads, so OTLP/HTTP should use the default
//! `reqwest-blocking-client` with them.
//!
//! HTTP client selection is based on enabled crate features and is not aware of
//! the SDK processor or metric reader that will drive the exporter. If you enable
//! async HTTP clients such as `reqwest-client` or `hyper-client`, do not use them
//! with the default batch processors or periodic reader.
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "http-proto", feature = "reqwest-client"))]
//! # {
//! use opentelemetry_otlp::WithHttpConfig;
//!
//! // reqwest async client (requires the `reqwest-client` feature)
//! let http_client = reqwest::Client::builder()
//! .timeout(std::time::Duration::from_secs(5))
//! // .danger_accept_invalid_certs(true) // for testing only
//! .build()
//! .expect("Failed to build reqwest client");
//!
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_http()
//! .with_http_client(http_client)
//! .build()
//! .expect("Failed to build SpanExporter");
//! # }
//! ```
//!
//! ### HTTP retry policy
//!
//! Failed exports are retried with exponential backoff and jitter. The retry policy
//! can be customized:
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "http-proto"))]
//! # {
//! use opentelemetry_otlp::{WithHttpConfig, RetryPolicy};
//! use std::time::Duration;
//!
//! let exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_http()
//! .with_retry_policy(
//! RetryPolicy::default()
//! .with_max_retries(5)
//! .with_initial_delay(Duration::from_millis(500))
//! .with_max_delay(Duration::from_secs(30))
//! .with_max_jitter(Duration::from_millis(100)),
//! )
//! .build()
//! .expect("Failed to build SpanExporter");
//! # }
//! ```
//!
//! ## All three signals (Traces, Metrics, Logs)
//!
//! The same exporter configuration options apply to all three signals. The only differences are:
//! - The builder entry point: [`SpanExporter::builder()`], [`MetricExporter::builder()`], [`LogExporter::builder()`]
//! - The signal-specific environment variables (e.g. `OTEL_EXPORTER_OTLP_TRACES_*` vs `OTEL_EXPORTER_OTLP_METRICS_*`)
//! - Metrics has an additional `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE` variable
//!
//! ```no_run
//! # #[cfg(all(feature = "trace", feature = "metrics", feature = "logs", feature = "grpc-tonic"))]
//! # {
//! use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
//! use opentelemetry_sdk::{
//! trace::SdkTracerProvider,
//! metrics::SdkMeterProvider,
//! logs::SdkLoggerProvider,
//! Resource,
//! };
//! use std::time::Duration;
//!
//! let resource = Resource::builder()
//! .with_service_name("my-service")
//! .build();
//!
//! // Traces
//! let span_exporter = opentelemetry_otlp::SpanExporter::builder()
//! .with_tonic()
//! .with_endpoint("http://my-collector:4317")
//! .with_timeout(Duration::from_secs(5))
//! .build()
//! .expect("Failed to build SpanExporter");
//! let tracer_provider = SdkTracerProvider::builder()
//! .with_resource(resource.clone())
//! .with_batch_exporter(span_exporter)
//! .build();
//!
//! // Metrics
//! let metric_exporter = opentelemetry_otlp::MetricExporter::builder()
//! .with_tonic()
//! .with_endpoint("http://my-collector:4317")
//! .with_timeout(Duration::from_secs(5))
//! .build()
//! .expect("Failed to build MetricExporter");
//! let meter_provider = SdkMeterProvider::builder()
//! .with_resource(resource.clone())
//! .with_periodic_exporter(metric_exporter)
//! .build();
//!
//! // Logs
//! let log_exporter = opentelemetry_otlp::LogExporter::builder()
//! .with_tonic()
//! .with_endpoint("http://my-collector:4317")
//! .with_timeout(Duration::from_secs(5))
//! .build()
//! .expect("Failed to build LogExporter");
//! let logger_provider = SdkLoggerProvider::builder()
//! .with_resource(resource)
//! .with_batch_exporter(log_exporter)
//! .build();
//! # }
//! ```
#![warn(
future_incompatible,
missing_debug_implementations,
missing_docs,
nonstandard_style,
rust_2018_idioms,
unreachable_pub,
unused
)]
#![allow(elided_lifetimes_in_paths)]
#![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))]
#![cfg_attr(test, deny(warnings))]
mod exporter;
#[cfg(feature = "logs")]
#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
mod logs;
#[cfg(feature = "metrics")]
#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
mod metric;
#[cfg(feature = "trace")]
#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
mod span;
#[cfg(any(
feature = "http-proto",
feature = "http-json",
all(
feature = "grpc-tonic",
any(feature = "trace", feature = "metrics", feature = "logs")
)
))]
mod retry_classification;
#[cfg(any(
feature = "http-proto",
feature = "http-json",
all(
feature = "grpc-tonic",
any(feature = "trace", feature = "metrics", feature = "logs")
)
))]
mod retry;
// RetryPolicy configures transport builders even when no signal is enabled, so
// it is available under a broader feature gate than the execution modules above.
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
mod retry_policy;
pub use crate::exporter::Compression;
pub use crate::exporter::ExporterBuildError;
pub use crate::exporter::ParseConfigError;
#[cfg(feature = "trace")]
#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
pub use crate::span::{
SpanExporter, SpanExporterBuilder, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_HEADERS,
OTEL_EXPORTER_OTLP_TRACES_INSECURE, OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
};
#[cfg(feature = "metrics")]
#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
pub use crate::metric::{
MetricExporter, MetricExporterBuilder, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_HEADERS,
OTEL_EXPORTER_OTLP_METRICS_INSECURE, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL,
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
};
#[cfg(feature = "logs")]
#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
pub use crate::logs::{
LogExporter, LogExporterBuilder, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS,
OTEL_EXPORTER_OTLP_LOGS_INSECURE, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
};
#[cfg(any(feature = "http-proto", feature = "http-json"))]
use crate::exporter::http::HttpExporterBuilder;
#[cfg(any(feature = "http-proto", feature = "http-json"))]
pub use crate::exporter::http::WithHttpConfig;
#[cfg(feature = "grpc-tonic")]
use crate::exporter::tonic::TonicExporterBuilder;
#[cfg(feature = "grpc-tonic")]
pub use crate::exporter::tonic::WithTonicConfig;
pub use crate::exporter::{
WithExportConfig, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT,
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_PROTOCOL,
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC, OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON,
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF, OTEL_EXPORTER_OTLP_TIMEOUT,
OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT,
};
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
pub use retry_policy::RetryPolicy;
/// Type to indicate the builder does not have a client set.
#[derive(Debug, Default, Clone)]
pub struct NoExporterBuilderSet;
/// Type indicating that the tonic transport has been selected.
#[cfg(feature = "grpc-tonic")]
// This is for clippy to work with only the grpc-tonic feature enabled
#[allow(unused)]
#[derive(Debug)]
pub struct TonicExporterBuilderSet(TonicExporterBuilder);
/// Type indicating that the HTTP transport has been selected.
#[cfg(any(feature = "http-proto", feature = "http-json"))]
#[derive(Debug)]
pub struct HttpExporterBuilderSet(HttpExporterBuilder);
/// The communication protocol to use when exporting data.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Protocol {
/// GRPC protocol
#[cfg(feature = "grpc-tonic")]
Grpc,
/// HTTP protocol with binary protobuf
#[cfg(feature = "http-proto")]
HttpBinary,
/// HTTP protocol with JSON payload
#[cfg(feature = "http-json")]
HttpJson,
}
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
impl Protocol {
/// Attempts to parse a protocol from the `OTEL_EXPORTER_OTLP_PROTOCOL` environment variable.
///
/// Returns `None` if:
/// - The environment variable is not set
/// - The value doesn't match a known protocol
/// - The specified protocol's feature is not enabled
pub(crate) fn from_env() -> Option<Self> {
Self::parse_from_env_var(OTEL_EXPORTER_OTLP_PROTOCOL)
}
/// Attempts to parse a protocol from the given environment variable.
pub(crate) fn parse_from_env_var(env_var: &str) -> Option<Self> {
use crate::exporter::{
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC, OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON,
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF,
};
let protocol = crate::exporter::read_enum_env_var(env_var)?;
let normalized = protocol.to_ascii_lowercase();
match normalized.as_str() {
OTEL_EXPORTER_OTLP_PROTOCOL_GRPC => {
#[cfg(feature = "grpc-tonic")]
{
Some(Protocol::Grpc)
}
#[cfg(not(feature = "grpc-tonic"))]
{
crate::exporter::warn_missing_protocol_feature(
env_var,
&protocol,
"grpc-tonic",
);
None
}
}
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF => {
#[cfg(feature = "http-proto")]
{
Some(Protocol::HttpBinary)
}
#[cfg(not(feature = "http-proto"))]
{
crate::exporter::warn_missing_protocol_feature(
env_var,
&protocol,
"http-proto",
);
None
}
}
OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON => {
#[cfg(feature = "http-json")]
{
Some(Protocol::HttpJson)
}
#[cfg(not(feature = "http-json"))]
{
crate::exporter::warn_missing_protocol_feature(env_var, &protocol, "http-json");
None
}
}
_ => {
crate::exporter::warn_ignored_enum_env_var(
env_var,
&protocol,
"expected 'grpc', 'http/protobuf', or 'http/json'",
);
None
}
}
}
/// Returns the default protocol based on enabled features, without
/// consulting environment variables.
///
/// Priority order (first available wins):
/// 1. http-json (if enabled)
/// 2. http-proto (if enabled)
/// 3. grpc-tonic (if enabled)
pub(crate) fn feature_default() -> Self {
#[cfg(feature = "http-json")]
return Protocol::HttpJson;
#[cfg(all(feature = "http-proto", not(feature = "http-json")))]
return Protocol::HttpBinary;
#[cfg(all(
feature = "grpc-tonic",
not(any(feature = "http-proto", feature = "http-json"))
))]
return Protocol::Grpc;
}
}
/// Re-exported types from the `tonic` crate.
#[cfg(feature = "grpc-tonic")]
pub mod tonic_types {
/// Re-exported types from `tonic::metadata`.
pub mod metadata {
#[doc(no_inline)]
pub use tonic::metadata::MetadataMap;
}
/// Re-exported types from `tonic::transport`.
#[cfg(any(
feature = "tls-ring",
feature = "tls-aws-lc",
feature = "tls-provider-agnostic"
))]
pub mod transport {
#[doc(no_inline)]
pub use tonic::transport::{Certificate, ClientTlsConfig, Identity};
}
}
+221
View File
@@ -0,0 +1,221 @@
//! OTLP - Log Exporter
//!
//! Defines a [LogExporter] to send logs via the OpenTelemetry Protocol (OTLP)
#[cfg(feature = "grpc-tonic")]
use opentelemetry::otel_debug;
use opentelemetry_sdk::{error::OTelSdkResult, logs::LogBatch};
use std::fmt::Debug;
use std::time;
use crate::{exporter::HasExportConfig, ExporterBuildError, NoExporterBuilderSet};
#[cfg(feature = "grpc-tonic")]
use crate::{
exporter::tonic::{HasTonicConfig, TonicExporterBuilder},
TonicExporterBuilderSet,
};
#[cfg(any(feature = "http-proto", feature = "http-json"))]
use crate::{
exporter::http::{HasHttpConfig, HttpExporterBuilder},
HttpExporterBuilderSet,
};
/// Compression algorithm to use, defaults to none.
pub const OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION";
/// Target to which the exporter is going to send logs
pub const OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT";
/// Maximum time the OTLP exporter will wait for each batch logs export.
pub const OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT";
/// Key-value pairs to be used as headers associated with gRPC or HTTP requests
/// for sending logs.
/// Example: `k1=v1,k2=v2`
/// Note: this is only supported for HTTP.
pub const OTEL_EXPORTER_OTLP_LOGS_HEADERS: &str = "OTEL_EXPORTER_OTLP_LOGS_HEADERS";
/// Protocol to use for log exports. Valid values: `grpc`, `http/protobuf`, `http/json`.
pub const OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL";
/// Whether to disable TLS for gRPC log exports.
/// Only applies to gRPC; HTTP security is determined by URL scheme.
pub const OTEL_EXPORTER_OTLP_LOGS_INSECURE: &str = "OTEL_EXPORTER_OTLP_LOGS_INSECURE";
/// Builder for creating a new [LogExporter].
#[derive(Debug, Default, Clone)]
pub struct LogExporterBuilder<C> {
client: C,
}
impl LogExporterBuilder<NoExporterBuilderSet> {
/// Create a new [LogExporterBuilder] with default settings.
pub fn new() -> Self {
LogExporterBuilder::default()
}
/// With the gRPC Tonic transport.
#[cfg(feature = "grpc-tonic")]
pub fn with_tonic(self) -> LogExporterBuilder<TonicExporterBuilderSet> {
LogExporterBuilder {
client: TonicExporterBuilderSet(TonicExporterBuilder::default()),
}
}
/// With the HTTP transport.
#[cfg(any(feature = "http-proto", feature = "http-json"))]
pub fn with_http(self) -> LogExporterBuilder<HttpExporterBuilderSet> {
LogExporterBuilder {
client: HttpExporterBuilderSet(HttpExporterBuilder::default()),
}
}
/// Build the [LogExporter] with the default transport selected by environment
/// variable or feature flags.
///
/// The transport is chosen based on:
/// 1. `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` environment variable
/// 2. `OTEL_EXPORTER_OTLP_PROTOCOL` environment variable
/// 3. Enabled features, with priority: `http-json` > `http-proto` > `grpc-tonic`
///
/// Use [`with_tonic`](Self::with_tonic) or [`with_http`](Self::with_http) to
/// explicitly select a transport and access transport-specific configuration.
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
pub fn build(self) -> Result<LogExporter, ExporterBuildError> {
use crate::WithExportConfig;
let protocol = crate::exporter::resolve_protocol(OTEL_EXPORTER_OTLP_LOGS_PROTOCOL, None);
match protocol {
#[cfg(feature = "grpc-tonic")]
crate::Protocol::Grpc => self.with_tonic().with_protocol(protocol).build(),
#[cfg(feature = "http-proto")]
crate::Protocol::HttpBinary => self.with_http().with_protocol(protocol).build(),
#[cfg(feature = "http-json")]
crate::Protocol::HttpJson => self.with_http().with_protocol(protocol).build(),
}
}
}
#[cfg(feature = "grpc-tonic")]
impl LogExporterBuilder<TonicExporterBuilderSet> {
/// Build the [LogExporter] with the gRPC Tonic transport.
pub fn build(self) -> Result<LogExporter, ExporterBuildError> {
let result = self.client.0.build_log_exporter();
otel_debug!(name: "LogExporterBuilt", result = format!("{:?}", &result));
result
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl LogExporterBuilder<HttpExporterBuilderSet> {
/// Build the [LogExporter] with the HTTP transport.
pub fn build(self) -> Result<LogExporter, ExporterBuildError> {
self.client.0.build_log_exporter()
}
}
#[cfg(feature = "grpc-tonic")]
impl HasExportConfig for LogExporterBuilder<TonicExporterBuilderSet> {
fn export_config(&mut self) -> &mut crate::exporter::ExportConfig {
&mut self.client.0.exporter_config
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl HasExportConfig for LogExporterBuilder<HttpExporterBuilderSet> {
fn export_config(&mut self) -> &mut crate::exporter::ExportConfig {
&mut self.client.0.exporter_config
}
}
#[cfg(feature = "grpc-tonic")]
impl HasTonicConfig for LogExporterBuilder<TonicExporterBuilderSet> {
fn tonic_config(&mut self) -> &mut crate::exporter::tonic::TonicConfig {
&mut self.client.0.tonic_config
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl HasHttpConfig for LogExporterBuilder<HttpExporterBuilderSet> {
fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig {
&mut self.client.0.http_config
}
}
/// OTLP exporter that sends log data
#[derive(Debug)]
pub struct LogExporter {
client: SupportedTransportClient,
}
#[derive(Debug)]
enum SupportedTransportClient {
#[cfg(feature = "grpc-tonic")]
Tonic(crate::exporter::tonic::logs::TonicLogsClient),
#[cfg(any(feature = "http-proto", feature = "http-json"))]
Http(crate::exporter::http::OtlpHttpClient),
}
impl LogExporter {
/// Obtain a builder to configure a [LogExporter].
pub fn builder() -> LogExporterBuilder<NoExporterBuilderSet> {
LogExporterBuilder::default()
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
pub(crate) fn from_http(client: crate::exporter::http::OtlpHttpClient) -> Self {
LogExporter {
client: SupportedTransportClient::Http(client),
}
}
#[cfg(feature = "grpc-tonic")]
pub(crate) fn from_tonic(client: crate::exporter::tonic::logs::TonicLogsClient) -> Self {
LogExporter {
client: SupportedTransportClient::Tonic(client),
}
}
}
impl opentelemetry_sdk::logs::LogExporter for LogExporter {
async fn export(&self, batch: LogBatch<'_>) -> OTelSdkResult {
match &self.client {
#[cfg(feature = "grpc-tonic")]
SupportedTransportClient::Tonic(client) => client.export(batch).await,
#[cfg(any(feature = "http-proto", feature = "http-json"))]
SupportedTransportClient::Http(client) => client.export(batch).await,
}
}
fn set_resource(&mut self, resource: &opentelemetry_sdk::Resource) {
match &mut self.client {
#[cfg(feature = "grpc-tonic")]
SupportedTransportClient::Tonic(client) => client.set_resource(resource),
#[cfg(any(feature = "http-proto", feature = "http-json"))]
SupportedTransportClient::Http(client) => client.set_resource(resource),
}
}
fn shutdown_with_timeout(&self, _timeout: time::Duration) -> OTelSdkResult {
match &self.client {
#[cfg(feature = "grpc-tonic")]
SupportedTransportClient::Tonic(client) => client.shutdown(),
#[cfg(any(feature = "http-proto", feature = "http-json"))]
SupportedTransportClient::Http(client) => client.shutdown(),
}
}
}
#[cfg(test)]
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
mod tests {
use crate::LogExporter;
// Uses a tokio runtime because, under a gRPC-only build, the auto-selected
// tonic transport needs an active reactor to construct its channel.
#[tokio::test]
async fn build_with_default_transport() {
let result = LogExporter::builder().build();
assert!(result.is_ok(), "build() should succeed: {:?}", result.err());
}
}
@@ -0,0 +1,409 @@
//! OTEL metric exporter
//!
//! Defines a [MetricExporter] to send metric data to backend via OTLP protocol.
//!
#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
use crate::exporter::HasExportConfig;
#[cfg(any(feature = "http-proto", feature = "http-json"))]
use crate::{
exporter::http::{HasHttpConfig, HttpExporterBuilder},
HttpExporterBuilderSet,
};
#[cfg(feature = "grpc-tonic")]
use crate::{
exporter::tonic::{HasTonicConfig, TonicExporterBuilder},
TonicExporterBuilderSet,
};
use crate::{ExporterBuildError, NoExporterBuilderSet};
use core::fmt;
use opentelemetry_sdk::error::OTelSdkResult;
use opentelemetry_sdk::metrics::{
data::ResourceMetrics, exporter::PushMetricExporter, Temporality,
};
use std::fmt::{Debug, Formatter};
use std::time::Duration;
/// Target to which the exporter is going to send metrics, defaults to https://localhost:4317/v1/metrics.
/// Learn about the relationship between this constant and default/spans/logs at
/// <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#endpoint-urls-for-otlphttp>
pub const OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT";
/// Max waiting time for the backend to process each metrics batch, defaults to 10s.
pub const OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT";
/// Compression algorithm to use, defaults to none.
pub const OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION";
/// Key-value pairs to be used as headers associated with gRPC or HTTP requests
/// for sending metrics.
/// Example: `k1=v1,k2=v2`
/// Note: this is only supported for HTTP.
pub const OTEL_EXPORTER_OTLP_METRICS_HEADERS: &str = "OTEL_EXPORTER_OTLP_METRICS_HEADERS";
/// Protocol to use for metrics exports. Valid values: `grpc`, `http/protobuf`, `http/json`.
pub const OTEL_EXPORTER_OTLP_METRICS_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL";
/// Whether to disable TLS for gRPC metrics exports.
/// Only applies to gRPC; HTTP security is determined by URL scheme.
pub const OTEL_EXPORTER_OTLP_METRICS_INSECURE: &str = "OTEL_EXPORTER_OTLP_METRICS_INSECURE";
/// Temporality preference for metrics, defaults to cumulative.
pub const OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: &str =
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE";
/// A builder for creating a new [MetricExporter].
#[derive(Debug, Default, Clone)]
pub struct MetricExporterBuilder<C> {
client: C,
temporality: Option<Temporality>,
}
impl MetricExporterBuilder<NoExporterBuilderSet> {
/// Create a new [MetricExporterBuilder] with default settings.
pub fn new() -> Self {
MetricExporterBuilder::default()
}
/// Build the [MetricExporter] with the default transport selected by environment
/// variable or feature flags.
///
/// The transport is chosen based on:
/// 1. `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL` environment variable
/// 2. `OTEL_EXPORTER_OTLP_PROTOCOL` environment variable
/// 3. Enabled features, with priority: `http-json` > `http-proto` > `grpc-tonic`
///
/// Use [`with_tonic`](Self::with_tonic) or [`with_http`](Self::with_http) to
/// explicitly select a transport and access transport-specific configuration.
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
pub fn build(self) -> Result<MetricExporter, ExporterBuildError> {
use crate::WithExportConfig;
let protocol = crate::exporter::resolve_protocol(OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, None);
match protocol {
#[cfg(feature = "grpc-tonic")]
crate::Protocol::Grpc => self.with_tonic().with_protocol(protocol).build(),
#[cfg(feature = "http-proto")]
crate::Protocol::HttpBinary => self.with_http().with_protocol(protocol).build(),
#[cfg(feature = "http-json")]
crate::Protocol::HttpJson => self.with_http().with_protocol(protocol).build(),
}
}
/// With the gRPC Tonic transport.
#[cfg(feature = "grpc-tonic")]
pub fn with_tonic(self) -> MetricExporterBuilder<TonicExporterBuilderSet> {
MetricExporterBuilder {
client: TonicExporterBuilderSet(TonicExporterBuilder::default()),
temporality: self.temporality,
}
}
/// With the HTTP transport.
#[cfg(any(feature = "http-proto", feature = "http-json"))]
pub fn with_http(self) -> MetricExporterBuilder<HttpExporterBuilderSet> {
MetricExporterBuilder {
client: HttpExporterBuilderSet(HttpExporterBuilder::default()),
temporality: self.temporality,
}
}
}
impl<C> MetricExporterBuilder<C> {
/// Set the temporality for the metrics.
///
/// Note: Programmatically setting this will override any value set via the environment variable.
pub fn with_temporality(self, temporality: Temporality) -> MetricExporterBuilder<C> {
MetricExporterBuilder {
client: self.client,
temporality: Some(temporality),
}
}
}
/// Resolve temporality with priority:
/// 1. Provided config value
/// 2. OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE environment variable
/// 3. Default (Cumulative)
#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))]
fn resolve_temporality(provided: Option<Temporality>) -> Temporality {
if let Some(temporality) = provided {
return temporality;
}
if let Some(value) =
crate::exporter::read_enum_env_var(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE)
{
return value.parse::<Temporality>().unwrap_or_else(|_| {
crate::exporter::warn_ignored_enum_env_var(
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE,
&value,
"expected 'cumulative', 'delta', or 'lowmemory'",
);
Temporality::default()
});
}
Temporality::default()
}
#[cfg(feature = "grpc-tonic")]
impl MetricExporterBuilder<TonicExporterBuilderSet> {
/// Build the [MetricExporter] with the gRPC Tonic transport.
pub fn build(self) -> Result<MetricExporter, ExporterBuildError> {
let temporality = resolve_temporality(self.temporality);
let exporter = self.client.0.build_metrics_exporter(temporality)?;
opentelemetry::otel_debug!(name: "MetricExporterBuilt");
Ok(exporter)
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl MetricExporterBuilder<HttpExporterBuilderSet> {
/// Build the [MetricExporter] with the HTTP transport.
pub fn build(self) -> Result<MetricExporter, ExporterBuildError> {
let temporality = resolve_temporality(self.temporality);
let exporter = self.client.0.build_metrics_exporter(temporality)?;
Ok(exporter)
}
}
#[cfg(feature = "grpc-tonic")]
impl HasExportConfig for MetricExporterBuilder<TonicExporterBuilderSet> {
fn export_config(&mut self) -> &mut crate::exporter::ExportConfig {
&mut self.client.0.exporter_config
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl HasExportConfig for MetricExporterBuilder<HttpExporterBuilderSet> {
fn export_config(&mut self) -> &mut crate::exporter::ExportConfig {
&mut self.client.0.exporter_config
}
}
#[cfg(feature = "grpc-tonic")]
impl HasTonicConfig for MetricExporterBuilder<TonicExporterBuilderSet> {
fn tonic_config(&mut self) -> &mut crate::exporter::tonic::TonicConfig {
&mut self.client.0.tonic_config
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl HasHttpConfig for MetricExporterBuilder<HttpExporterBuilderSet> {
fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig {
&mut self.client.0.http_config
}
}
/// An interface for OTLP metrics clients
pub(crate) trait MetricsClient: fmt::Debug + Send + Sync + 'static {
fn export(
&self,
metrics: &ResourceMetrics,
) -> impl std::future::Future<Output = OTelSdkResult> + Send;
fn shutdown(&self) -> OTelSdkResult;
}
/// Export metrics in OTEL format.
pub struct MetricExporter {
client: SupportedTransportClient,
temporality: Temporality,
}
#[derive(Debug)]
enum SupportedTransportClient {
#[cfg(feature = "grpc-tonic")]
Tonic(crate::exporter::tonic::metrics::TonicMetricsClient),
#[cfg(any(feature = "http-proto", feature = "http-json"))]
Http(crate::exporter::http::OtlpHttpClient),
}
impl Debug for MetricExporter {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetricExporter").finish()
}
}
impl PushMetricExporter for MetricExporter {
async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult {
match &self.client {
#[cfg(feature = "grpc-tonic")]
SupportedTransportClient::Tonic(client) => client.export(metrics).await,
#[cfg(any(feature = "http-proto", feature = "http-json"))]
SupportedTransportClient::Http(client) => client.export(metrics).await,
}
}
fn force_flush(&self) -> OTelSdkResult {
// this component is stateless
Ok(())
}
fn shutdown(&self) -> OTelSdkResult {
self.shutdown_with_timeout(Duration::from_secs(5))
}
fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> OTelSdkResult {
match &self.client {
#[cfg(feature = "grpc-tonic")]
SupportedTransportClient::Tonic(client) => client.shutdown(),
#[cfg(any(feature = "http-proto", feature = "http-json"))]
SupportedTransportClient::Http(client) => client.shutdown(),
}
}
fn temporality(&self) -> Temporality {
self.temporality
}
}
impl MetricExporter {
/// Obtain a builder to configure a [MetricExporter].
pub fn builder() -> MetricExporterBuilder<NoExporterBuilderSet> {
MetricExporterBuilder::default()
}
#[cfg(feature = "grpc-tonic")]
pub(crate) fn from_tonic(
client: crate::exporter::tonic::metrics::TonicMetricsClient,
temporality: Temporality,
) -> Self {
Self {
client: SupportedTransportClient::Tonic(client),
temporality,
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
pub(crate) fn from_http(
client: crate::exporter::http::OtlpHttpClient,
temporality: Temporality,
) -> Self {
Self {
client: SupportedTransportClient::Http(client),
temporality,
}
}
}
#[cfg(test)]
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
mod build_tests {
use crate::MetricExporter;
// Uses a tokio runtime because, under a gRPC-only build, the auto-selected
// tonic transport needs an active reactor to construct its channel.
#[tokio::test]
async fn build_with_default_transport() {
// Unset the temporality env var so parallel tests (e.g.
// invalid_env_var_returns_error) that set it to invalid values
// don't cause this build to fail.
temp_env::with_var_unset(
super::OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE,
|| {
let result = MetricExporter::builder().build();
assert!(result.is_ok(), "build() should succeed: {:?}", result.err());
},
);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn run_env_test<T, F>(env_vars: T, f: F)
where
F: FnOnce(),
T: Into<Vec<(&'static str, &'static str)>>,
{
temp_env::with_vars(
env_vars
.into()
.iter()
.map(|&(k, v)| (k, Some(v)))
.collect::<Vec<(&'static str, Option<&'static str>)>>(),
f,
)
}
#[test]
fn code_config_overrides_env_var() {
run_env_test(
vec![(
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE,
"cumulative",
)],
|| {
let result = resolve_temporality(Some(Temporality::Delta));
assert_eq!(result, Temporality::Delta);
},
);
}
#[test]
fn env_var_sets_delta() {
run_env_test(
vec![(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, "delta")],
|| {
let result = resolve_temporality(None);
assert_eq!(result, Temporality::Delta);
},
);
}
#[test]
fn env_var_sets_lowmemory() {
run_env_test(
vec![(
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE,
"lowmemory",
)],
|| {
let result = resolve_temporality(None);
assert_eq!(result, Temporality::LowMemory);
},
);
}
#[test]
fn env_var_case_insensitive() {
run_env_test(
vec![(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, "Delta")],
|| {
let result = resolve_temporality(None);
assert_eq!(result, Temporality::Delta);
},
);
}
#[test]
fn invalid_env_var_uses_default() {
run_env_test(
vec![(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, "invalid")],
|| {
let result = resolve_temporality(None);
assert_eq!(result, Temporality::Cumulative);
},
);
}
#[test]
fn empty_env_var_uses_default() {
run_env_test(
vec![(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, "")],
|| {
let result = resolve_temporality(None);
assert_eq!(result, Temporality::Cumulative);
},
);
}
#[test]
fn test_use_default_when_nothing_set() {
temp_env::with_var_unset(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, || {
let result = resolve_temporality(None);
assert_eq!(result, Temporality::Cumulative);
});
}
}
@@ -0,0 +1,767 @@
//! Internal retry execution with exponential backoff, jitter, and throttling hints.
use crate::retry_policy::RetryPolicy;
use opentelemetry::{otel_debug, otel_info, otel_warn};
use std::collections::hash_map::DefaultHasher;
use std::future::Future;
use std::hash::Hasher;
use std::time::{Duration, Instant, SystemTime};
/// Sleeps for the given duration.
///
/// Uses `tokio::time::sleep` when the `tokio` feature is enabled and a Tokio
/// runtime is present (cooperative, won't block the reactor); otherwise falls
/// back to `std::thread::sleep`.
///
/// Note: `tokio` is not a default feature, so default builds
/// (`reqwest-blocking-client`) always use `std::thread::sleep`. It is pulled in
/// by `grpc-tonic`, `reqwest-client`, and `hyper-client`.
async fn sleep_for(duration: Duration) {
#[cfg(feature = "tokio")]
{
if tokio::runtime::Handle::try_current().is_ok() {
tokio::time::sleep(duration).await;
return;
}
}
std::thread::sleep(duration);
}
/// Classification of errors for retry purposes.
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum RetryErrorType {
/// Error is not retryable (e.g., authentication failure, bad request).
NonRetryable,
/// Error is retryable with exponential backoff (e.g., server error, network timeout).
Retryable,
/// Error indicates throttling - wait for at least the specified duration before retrying.
/// The server delay seeds exponential backoff: subsequent retries grow from it.
Throttled(Duration),
}
/// Maximum duration a server-provided throttle delay (Retry-After / RetryInfo) is allowed
/// to block the export thread. Any server-provided delay exceeding this cap is truncated.
/// This prevents a misbehaving server from stalling the export pipeline indefinitely.
const MAX_THROTTLE_DURATION: Duration = Duration::from_secs(30);
// Generates a random jitter value up to max_jitter at millisecond resolution.
fn generate_jitter(max_jitter: Duration) -> Duration {
let nanos = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.subsec_nanos();
let mut hasher = DefaultHasher::default();
hasher.write_u32(nanos);
let max_jitter_ms = max_jitter.as_millis().min(u64::MAX as u128) as u64;
let jitter_ms = if max_jitter_ms == u64::MAX {
hasher.finish()
} else {
hasher.finish() % (max_jitter_ms + 1)
};
Duration::from_millis(jitter_ms)
}
/// Retries the given operation with exponential backoff, jitter, and error classification.
///
/// This function provides retry behavior by classifying errors and honoring server-provided
/// throttling hints (e.g., HTTP Retry-After, gRPC RetryInfo).
///
/// When a throttle delay is received, it seeds the exponential backoff: the delay is set
/// to `max(current_delay, server_delay)` and subsequent retries double from there. If the
/// server delay exceeds the configured `max_delay_ms`, the backoff cap is raised to
/// `MAX_THROTTLE_DURATION` (30 seconds) so that repeated failures produce increasing
/// delays rather than getting stuck at the server value.
///
/// Delays between retries adapt to the calling context: when running inside a Tokio
/// runtime (gRPC/async HTTP exporters), `tokio::time::sleep` is used cooperatively;
/// on a bare OS thread (the SDK's default batch processors), `std::thread::sleep`
/// is used instead.
///
/// A time budget (deadline) bounds the total retry duration. If the budget is exhausted,
/// the function returns the last error without further retries.
///
/// **Note:** The deadline governs whether to start another retry and caps inter-retry
/// sleep durations, but it does not cancel an in-flight export operation. On dedicated
/// threads (the SDK's default batch processors), individual export calls do not have a
/// timeout today, so a single slow export can exceed the deadline. This is an existing
/// limitation.
///
/// # Arguments
///
/// * `policy` - The retry policy configuration.
/// * `deadline` - Maximum total time allowed for all retry attempts combined.
/// * `error_classifier` - Function to classify errors for retry decisions.
/// * `operation_name` - The name of the operation being retried.
/// * `operation` - The operation to be retried.
///
/// # Returns
///
/// A `Result` containing the operation's result or an error if max retries are reached
/// or a non-retryable error occurs.
pub(crate) async fn retry_with_backoff<F, Fut, T, E, C>(
policy: &RetryPolicy,
deadline: Duration,
error_classifier: C,
operation_name: &str,
mut operation: F,
) -> Result<T, E>
where
F: FnMut() -> Fut,
E: std::fmt::Debug,
Fut: Future<Output = Result<T, E>>,
C: Fn(&E) -> RetryErrorType,
{
let start = Instant::now();
let mut attempt = 0;
let mut delay = policy.initial_delay;
// The delay cap may be raised beyond the configured max if a server-provided
// throttle delay exceeds it, since retrying sooner than the server requested
// would defeat the purpose of backpressure signalling. The overall exporter
// timeout remains the final bound.
let mut delay_cap = policy.max_delay;
loop {
match operation().await {
Ok(result) => return Ok(result),
Err(err) => {
// Check time budget before deciding to retry
let elapsed = start.elapsed();
if elapsed >= deadline {
otel_warn!(name: "Export.Failed.DeadlineExceeded",
operation = operation_name,
retries = attempt,
elapsed_ms = elapsed.as_millis(),
message = "OTLP export deadline exceeded - telemetry data will be lost"
);
return Err(err);
}
let error_type = error_classifier(&err);
match error_type {
RetryErrorType::NonRetryable => {
otel_warn!(name: "Export.Failed.NonRetryable",
operation = operation_name,
message = "OTLP export failed with non-retryable error - telemetry data will be lost"
);
return Err(err);
}
RetryErrorType::Retryable if attempt < policy.max_retries => {
attempt += 1;
let jitter = generate_jitter(policy.max_jitter);
let sleep_duration = delay.saturating_add(jitter).min(delay_cap);
// If the backoff delay would consume all remaining budget, fail now
let remaining = deadline.saturating_sub(start.elapsed());
if sleep_duration >= remaining {
otel_warn!(name: "Export.Failed.DeadlineExceeded",
operation = operation_name,
retries = attempt,
message = "OTLP export deadline exceeded during backoff - telemetry data will be lost"
);
return Err(err);
}
otel_debug!(name: "Export.InProgress.Retrying",
operation = operation_name,
attempt = attempt,
delay_ms = sleep_duration.as_millis(),
message = "OTLP export failed with retryable error - retrying"
);
sleep_for(sleep_duration).await;
delay = delay.saturating_mul(2).min(delay_cap);
}
RetryErrorType::Throttled(server_delay) if attempt < policy.max_retries => {
attempt += 1;
let capped_server_delay = server_delay.min(MAX_THROTTLE_DURATION);
// RetryInfo is the base for subsequent exponential backoff. A later
// RetryInfo may extend, but must not shorten, the current backoff.
// When the server delay exceeds the configured max, allow backoff
// to grow up to MAX_THROTTLE_DURATION so repeated failures
// produce increasing delays rather than getting stuck.
if capped_server_delay > delay_cap {
delay_cap = MAX_THROTTLE_DURATION;
}
if !capped_server_delay.is_zero() {
delay = delay.max(capped_server_delay);
}
let jitter = generate_jitter(policy.max_jitter);
let sleep_duration = delay.saturating_add(jitter).min(delay_cap);
// If the throttle delay would consume all remaining budget, fail now
let remaining = deadline.saturating_sub(start.elapsed());
if sleep_duration >= remaining {
otel_warn!(name: "Export.Failed.DeadlineExceeded",
operation = operation_name,
retries = attempt,
message = "OTLP export deadline exceeded during throttle - telemetry data will be lost"
);
return Err(err);
}
otel_info!(name: "Export.InProgress.Throttled",
operation = operation_name,
attempt = attempt,
delay_ms = sleep_duration.as_millis(),
server_requested_ms = capped_server_delay.as_millis(),
message = "OTLP export throttled by OTLP endpoint - delaying and retrying"
);
sleep_for(sleep_duration).await;
delay = delay.saturating_mul(2).min(delay_cap);
}
_ => {
// Max retries reached
otel_warn!(name: "Export.Failed.Exhausted",
operation = operation_name,
retries = attempt,
message = "OTLP export exhausted retries - telemetry data will be lost"
);
return Err(err);
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
fn policy(
max_retries: usize,
initial_delay_ms: u64,
max_delay_ms: u64,
max_jitter_ms: u64,
) -> RetryPolicy {
RetryPolicy::default()
.with_max_retries(max_retries)
.with_initial_delay(Duration::from_millis(initial_delay_ms))
.with_max_delay(Duration::from_millis(max_delay_ms))
.with_max_jitter(Duration::from_millis(max_jitter_ms))
}
#[test]
fn test_generate_jitter() {
let max_jitter = Duration::from_millis(100);
let jitter = generate_jitter(max_jitter);
assert!(jitter <= max_jitter);
}
#[test]
fn test_generate_jitter_handles_max_duration() {
assert!(generate_jitter(Duration::MAX) <= Duration::from_millis(u64::MAX));
}
#[test]
fn test_default_retry_policy() {
let policy = RetryPolicy::default();
assert_eq!(policy.max_retries, 3);
assert_eq!(policy.initial_delay, Duration::from_millis(100));
assert_eq!(policy.max_delay, Duration::from_millis(1600));
assert_eq!(policy.max_jitter, Duration::from_millis(100));
}
#[test]
fn test_recommended_retry_policy() {
let policy = RetryPolicy::recommended();
assert_eq!(policy.max_retries, 3);
assert_eq!(policy.initial_delay, Duration::from_millis(100));
assert_eq!(policy.max_delay, Duration::from_millis(1600));
assert_eq!(policy.max_jitter, Duration::from_millis(100));
}
#[test]
fn test_disabled_retry_policy() {
let policy = RetryPolicy::disabled();
assert_eq!(policy.max_retries, 0);
assert_eq!(policy.initial_delay, Duration::ZERO);
assert_eq!(policy.max_delay, Duration::ZERO);
assert_eq!(policy.max_jitter, Duration::ZERO);
}
#[tokio::test]
async fn test_disabled_retry_policy_performs_single_attempt() {
for classification in [
RetryErrorType::Retryable,
RetryErrorType::Throttled(Duration::from_secs(5)),
] {
let attempts = AtomicUsize::new(0);
let result = retry_with_backoff(
&RetryPolicy::disabled(),
Duration::from_secs(10),
|_| classification.clone(),
"test_operation",
|| {
attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async { Err::<(), _>(()) })
},
)
.await;
assert_eq!(result, Err(()));
assert_eq!(attempts.load(Ordering::SeqCst), 1);
}
}
#[test]
fn test_retry_error_type_equality() {
assert_eq!(RetryErrorType::NonRetryable, RetryErrorType::NonRetryable);
assert_eq!(RetryErrorType::Retryable, RetryErrorType::Retryable);
assert_eq!(
RetryErrorType::Throttled(Duration::from_secs(30)),
RetryErrorType::Throttled(Duration::from_secs(30))
);
assert_ne!(RetryErrorType::Retryable, RetryErrorType::NonRetryable);
}
#[tokio::test]
async fn test_retry_success_on_first_attempt() {
let policy = RetryPolicy::default();
let deadline = Duration::from_secs(10);
let result = retry_with_backoff(
&policy,
deadline,
|_: &()| RetryErrorType::Retryable,
"test_operation",
|| Box::pin(async { Ok::<_, ()>("success") }),
)
.await;
assert_eq!(result, Ok("success"));
}
#[tokio::test]
async fn test_retry_succeeds_after_retries() {
let policy = policy(3, 10, 100, 5);
let deadline = Duration::from_secs(10);
let attempts = AtomicUsize::new(0);
let result = retry_with_backoff(
&policy,
deadline,
|_: &&str| RetryErrorType::Retryable,
"test_operation",
|| {
let attempt = attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async move {
if attempt < 2 {
Err("error")
} else {
Ok("success")
}
})
},
)
.await;
assert_eq!(result, Ok("success"));
assert_eq!(attempts.load(Ordering::SeqCst), 3);
}
#[tokio::test]
async fn test_retry_fails_after_max_retries() {
let policy = policy(3, 10, 100, 5);
let deadline = Duration::from_secs(10);
let attempts = AtomicUsize::new(0);
let result = retry_with_backoff(
&policy,
deadline,
|_: &&str| RetryErrorType::Retryable,
"test_operation",
|| {
attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async { Err::<(), _>("error") })
},
)
.await;
assert_eq!(result, Err("error"));
assert_eq!(attempts.load(Ordering::SeqCst), 4); // initial + 3 retries
}
#[tokio::test]
async fn test_retry_non_retryable_stops_immediately() {
let policy = RetryPolicy::default();
let deadline = Duration::from_secs(10);
let attempts = AtomicUsize::new(0);
let result = retry_with_backoff(
&policy,
deadline,
|_: &()| RetryErrorType::NonRetryable,
"test_operation",
|| {
attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async { Err::<(), _>(()) })
},
)
.await;
assert!(result.is_err());
assert_eq!(attempts.load(Ordering::SeqCst), 1);
}
// -- Throttle/backoff interaction tests --
//
// These tests form a progression: server delay within max_delay_ms (the
// easy case where the configured cap doesn't constrain growth), then
// server delay exceeding max_delay_ms (the case where the cap must be
// lifted to MAX_THROTTLE_DURATION to allow continued growth).
#[tokio::test]
async fn test_throttle_seeds_backoff_within_max() {
// Server delay (50ms) is below max_delay_ms (5000ms), so the
// configured cap never constrains growth. Verifies the basic
// mechanism: server delay raises the backoff base from initial.
let policy = policy(2, 10, 5000, 0);
let deadline = Duration::from_secs(10);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = retry_with_backoff(
&policy,
deadline,
|_: &()| RetryErrorType::Throttled(Duration::from_millis(50)),
"test_operation",
|| {
let attempt = attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async move {
if attempt < 1 {
Err(())
} else {
Ok("success")
}
})
},
)
.await;
let elapsed = start.elapsed();
assert_eq!(result, Ok("success"));
assert_eq!(attempts.load(Ordering::SeqCst), 2);
assert!(elapsed >= Duration::from_millis(50));
assert!(elapsed < Duration::from_millis(500));
}
#[tokio::test]
async fn test_repeated_throttle_doubles_within_max() {
// Repeated throttles (20ms each) with max_delay_ms = 100ms. Server
// delay is within the cap, so doubling works without needing to lift
// the cap: 20ms -> 40ms. Total >= 55ms.
let policy = policy(2, 10, 100, 0);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = retry_with_backoff(
&policy,
Duration::from_secs(2),
|_: &usize| RetryErrorType::Throttled(Duration::from_millis(20)),
"test_operation",
|| {
let attempt = attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async move {
if attempt < 2 {
Err(attempt)
} else {
Ok("success")
}
})
},
)
.await;
assert_eq!(result, Ok("success"));
assert_eq!(attempts.load(Ordering::SeqCst), 3);
assert!(start.elapsed() >= Duration::from_millis(55));
}
#[tokio::test]
async fn test_retryable_after_throttle_continues_backoff_within_max() {
// Throttle (20ms) then a retryable error, with max_delay_ms = 100ms.
// Server delay is within the cap, so backoff doubles normally:
// 20ms (throttled) -> 40ms (retryable, doubled from 20ms base).
let policy = policy(2, 10, 100, 0);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = retry_with_backoff(
&policy,
Duration::from_secs(2),
|attempt: &usize| {
if *attempt == 0 {
RetryErrorType::Throttled(Duration::from_millis(20))
} else {
RetryErrorType::Retryable
}
},
"test_operation",
|| {
let attempt = attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async move {
if attempt < 2 {
Err(attempt)
} else {
Ok("success")
}
})
},
)
.await;
assert_eq!(result, Ok("success"));
assert_eq!(attempts.load(Ordering::SeqCst), 3);
// First: throttled 20ms, second: retryable 40ms (doubled from 20ms base)
assert!(start.elapsed() >= Duration::from_millis(55));
}
#[tokio::test]
async fn test_retryable_after_throttle_continues_backoff_exceeding_max() {
// Same shape as the test above, but the server delay (100ms) exceeds
// max_delay_ms (50ms). This is the critical case: the cap must be
// lifted to MAX_THROTTLE_DURATION so that doubling actually grows
// the delay instead of clamping it back to the server value.
let policy = policy(3, 10, 50, 0); // max delay intentionally below server delay
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = retry_with_backoff(
&policy,
Duration::from_secs(10),
|attempt: &usize| {
if *attempt == 0 {
// Server asks for 100ms, which exceeds max_delay_ms (50ms)
RetryErrorType::Throttled(Duration::from_millis(100))
} else {
RetryErrorType::Retryable
}
},
"test_operation",
|| {
let attempt = attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async move {
if attempt < 3 {
Err(attempt)
} else {
Ok("success")
}
})
},
)
.await;
assert_eq!(result, Ok("success"));
assert_eq!(attempts.load(Ordering::SeqCst), 4);
// Attempt 0 fails Throttled(100ms): delay = max(10ms, 100ms) = 100ms,
// sleep 100ms, delay -> 200ms (doubled, NOT capped back to 100ms)
// Attempt 1 fails Retryable: sleep 200ms, delay -> 400ms
// Attempt 2 fails Retryable: sleep 400ms, delay -> 800ms
// Attempt 3 succeeds
// Total: 100 + 200 + 400 = 700ms
let elapsed = start.elapsed();
assert!(
elapsed >= Duration::from_millis(680),
"Expected >= 680ms (backoff should grow beyond server delay), got {:?}",
elapsed
);
assert!(
elapsed < Duration::from_millis(1500),
"Expected < 1500ms, got {:?}",
elapsed
);
}
#[tokio::test]
async fn test_throttle_after_normal_backoff_does_not_reduce_delay() {
let policy = policy(3, 50, 500, 0);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = retry_with_backoff(
&policy,
Duration::from_secs(5),
|attempt: &usize| {
if *attempt == 1 {
// Server asks for 20ms, but backoff has already grown to 100ms
RetryErrorType::Throttled(Duration::from_millis(20))
} else {
RetryErrorType::Retryable
}
},
"test_operation",
|| {
let attempt = attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async move {
if attempt < 3 {
Err(attempt)
} else {
Ok("success")
}
})
},
)
.await;
assert_eq!(result, Ok("success"));
assert_eq!(attempts.load(Ordering::SeqCst), 4);
// Attempt 0 fails Retryable: sleep 50ms, delay -> 100ms
// Attempt 1 fails Throttled(20ms): delay = max(100ms, 20ms) = 100ms, sleep 100ms, delay -> 200ms
// Attempt 2 fails Retryable: sleep 200ms, delay -> 400ms
// Attempt 3 succeeds
// Total: 50 + 100 + 200 = 350ms
assert!(start.elapsed() >= Duration::from_millis(340));
}
#[tokio::test]
async fn test_retry_deadline_exceeded() {
let policy = policy(100, 50, 200, 0); // many retries allowed
// Very short deadline
let deadline = Duration::from_millis(120);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = retry_with_backoff(
&policy,
deadline,
|_: &()| RetryErrorType::Retryable,
"test_operation",
|| {
attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async { Err::<(), _>(()) })
},
)
.await;
let elapsed = start.elapsed();
assert!(result.is_err());
// Should have stopped before exhausting 100 retries due to deadline
assert!(attempts.load(Ordering::SeqCst) < 10);
// Shouldn't have taken much longer than the deadline
assert!(elapsed < Duration::from_millis(500));
}
#[tokio::test]
async fn test_retry_throttle_capped_at_max() {
let policy = policy(2, 10, 100, 0);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
// Server requests 120s delay (exceeds MAX_THROTTLE_DURATION of 30s)
// But deadline is short so we don't wait 30s in tests
let short_deadline = Duration::from_millis(200);
let result = retry_with_backoff(
&policy,
short_deadline,
|_: &()| RetryErrorType::Throttled(Duration::from_secs(120)),
"test_operation",
|| {
attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async { Err::<(), _>(()) })
},
)
.await;
let elapsed = start.elapsed();
assert!(result.is_err());
// Should be capped by the deadline, not sleep for 120s or even 30s
assert!(elapsed < Duration::from_millis(500));
}
// Tests exercising the dedicated-thread (std::thread::sleep) path.
// These use futures_executor::block_on with no Tokio runtime present.
#[test]
fn test_retry_succeeds_after_retries_no_tokio() {
let policy = policy(3, 10, 100, 5);
let deadline = Duration::from_secs(10);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = futures_executor::block_on(retry_with_backoff(
&policy,
deadline,
|_: &&str| RetryErrorType::Retryable,
"test_operation",
|| {
let attempt = attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async move {
if attempt < 2 {
Err("error")
} else {
Ok("success")
}
})
},
));
let elapsed = start.elapsed();
assert_eq!(result, Ok("success"));
assert_eq!(attempts.load(Ordering::SeqCst), 3);
// Should have slept at least ~10ms for each of 2 retries
assert!(elapsed >= Duration::from_millis(20));
}
#[test]
fn test_retry_deadline_exceeded_no_tokio() {
let policy = policy(100, 50, 200, 0);
let deadline = Duration::from_millis(120);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = futures_executor::block_on(retry_with_backoff(
&policy,
deadline,
|_: &()| RetryErrorType::Retryable,
"test_operation",
|| {
attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async { Err::<(), _>(()) })
},
));
let elapsed = start.elapsed();
assert!(result.is_err());
// Should have stopped before exhausting 100 retries due to deadline
assert!(attempts.load(Ordering::SeqCst) < 10);
// Shouldn't have taken much longer than the deadline
assert!(elapsed < Duration::from_millis(500));
}
#[test]
fn test_retry_throttled_uses_server_delay_no_tokio() {
let policy = policy(2, 10, 5000, 0);
let deadline = Duration::from_secs(10);
let attempts = AtomicUsize::new(0);
let start = Instant::now();
let result = futures_executor::block_on(retry_with_backoff(
&policy,
deadline,
|_: &()| RetryErrorType::Throttled(Duration::from_millis(50)),
"test_operation",
|| {
let attempt = attempts.fetch_add(1, Ordering::SeqCst);
Box::pin(async move {
if attempt < 1 {
Err(())
} else {
Ok("success")
}
})
},
));
let elapsed = start.elapsed();
assert_eq!(result, Ok("success"));
assert_eq!(attempts.load(Ordering::SeqCst), 2);
// Should have waited ~50ms (the server delay raises it from the 10ms default)
assert!(elapsed >= Duration::from_millis(50));
assert!(elapsed < Duration::from_millis(500));
}
}
@@ -0,0 +1,608 @@
//! Error classification for OTLP exporters with protocol-specific throttling support.
//!
//! This module provides error classification functions for HTTP and gRPC protocols,
//! supporting server-provided throttling hints like HTTP Retry-After headers and
//! gRPC RetryInfo metadata.
use crate::retry::RetryErrorType;
#[cfg(feature = "grpc-tonic")]
use tonic_types::StatusExt;
/// HTTP-specific error classification with Retry-After header support.
#[cfg(any(feature = "http-proto", feature = "http-json"))]
pub(crate) mod http {
use super::*;
use std::time::Duration;
/// Classifies HTTP errors based on status code and headers.
///
/// # Arguments
/// * `status_code` - HTTP status code
/// * `retry_after_header` - Value of the Retry-After header, if present
///
/// # Retry-After Header Formats
/// * Seconds: "120"
/// * HTTP Date: "Fri, 31 Dec 1999 23:59:59 GMT"
pub(crate) fn classify_http_error(
status_code: u16,
retry_after_header: Option<&str>,
) -> RetryErrorType {
match status_code {
// OTLP/HTTP throttling responses may provide an explicit retry delay.
429 | 503 => {
if let Some(retry_after) = retry_after_header {
if let Some(duration) = parse_retry_after(retry_after) {
return RetryErrorType::Throttled(duration);
}
}
RetryErrorType::Retryable
}
// Other retryable response codes defined by OTLP/HTTP.
502 | 504 => RetryErrorType::Retryable,
// The HTTP exporter uses status 0 for failures without a response.
0 => RetryErrorType::Retryable,
// All other HTTP response status codes must not be retried.
_ => RetryErrorType::NonRetryable,
}
}
/// Parses the Retry-After header value.
///
/// Supports both formats:
/// - Delay seconds: "120"
/// - HTTP date: "Fri, 31 Dec 1999 23:59:59 GMT"
///
/// Returns None if parsing fails or delay is unreasonable.
fn parse_retry_after(retry_after: &str) -> Option<Duration> {
// Try parsing as seconds first
if let Ok(seconds) = retry_after.trim().parse::<u64>() {
// Cap at 10 minutes. TODO - what's sensible here?
let capped_seconds = seconds.min(600);
return Some(Duration::from_secs(capped_seconds));
}
// Try parsing as HTTP date (requires httpdate crate)
#[cfg(feature = "httpdate")]
if let Ok(delay_seconds) = parse_http_date_to_delay(retry_after) {
// Cap at 10 minutes. TODO - what's sensible here?
let capped_seconds = delay_seconds.min(600);
return Some(Duration::from_secs(capped_seconds));
}
None
}
/// Parses HTTP date format and returns delay in seconds from now.
#[cfg(feature = "httpdate")]
fn parse_http_date_to_delay(date_str: &str) -> Result<u64, ()> {
use std::time::SystemTime;
// Try parse the date; if we fail, propagate an () error up to the caller.
let target_time = httpdate::parse_http_date(date_str).map_err(|_| ())?;
let now = SystemTime::now();
let delay = target_time
.duration_since(now)
.unwrap_or(std::time::Duration::ZERO);
Ok(delay.as_secs())
}
}
/// gRPC-specific error classification with RetryInfo support.
#[cfg(feature = "grpc-tonic")]
pub(crate) mod grpc {
use super::*;
/// Classifies a tonic::Status error
#[cfg(feature = "grpc-tonic")]
pub(crate) fn classify_tonic_status(status: &tonic::Status) -> RetryErrorType {
// Use tonic-types to extract RetryInfo - this is the proper way!
let retry_delay = status
.get_details_retry_info()
.and_then(|retry_info| retry_info.retry_delay);
classify_grpc_error(status.code(), retry_delay)
}
/// Classifies gRPC errors based on status code and metadata.
///
/// Implements the OpenTelemetry OTLP specification for error handling:
/// https://opentelemetry.io/docs/specs/otlp/
/// https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures
///
/// # Arguments
/// * `grpc_code` - gRPC status code as tonic::Code enum
/// * `retry_delay` - Parsed retry delay from RetryInfo metadata, if present
fn classify_grpc_error(
grpc_code: tonic::Code,
retry_delay: Option<std::time::Duration>,
) -> RetryErrorType {
match grpc_code {
// RESOURCE_EXHAUSTED: Special case per OTLP spec
// Retryable only if server provides RetryInfo indicating recovery is possible
tonic::Code::ResourceExhausted => {
if let Some(delay) = retry_delay {
// Server signals recovery is possible - use throttled retry
// Cap at 10 minutes. TODO - what's sensible here?
return RetryErrorType::Throttled(
delay.min(std::time::Duration::from_secs(600)),
);
}
// No RetryInfo - treat as non-retryable per OTLP spec
RetryErrorType::NonRetryable
}
// UNAVAILABLE may include RetryInfo to signal an explicit throttle delay.
tonic::Code::Unavailable => match retry_delay.filter(|delay| !delay.is_zero()) {
Some(delay) => {
RetryErrorType::Throttled(delay.min(std::time::Duration::from_secs(600)))
}
None => RetryErrorType::Retryable,
},
// Retryable errors per OTLP specification
tonic::Code::Cancelled
| tonic::Code::DeadlineExceeded
| tonic::Code::Aborted
| tonic::Code::OutOfRange
| tonic::Code::DataLoss => RetryErrorType::Retryable,
// Non-retryable errors per OTLP specification
tonic::Code::Unknown
| tonic::Code::InvalidArgument
| tonic::Code::NotFound
| tonic::Code::AlreadyExists
| tonic::Code::PermissionDenied
| tonic::Code::FailedPrecondition
| tonic::Code::Unimplemented
| tonic::Code::Internal
| tonic::Code::Unauthenticated => RetryErrorType::NonRetryable,
// OK should never reach here in error scenarios, but handle gracefully
tonic::Code::Ok => RetryErrorType::NonRetryable,
}
}
}
#[cfg(test)]
mod tests {
// Tests for HTTP error classification
#[cfg(any(feature = "http-proto", feature = "http-json"))]
mod http_tests {
use crate::retry::RetryErrorType;
use crate::retry_classification::http::*;
use std::time::Duration;
#[test]
fn test_http_throttling_responses_with_retry_after_seconds() {
for status_code in [429, 503] {
let result = classify_http_error(status_code, Some("30"));
assert_eq!(result, RetryErrorType::Throttled(Duration::from_secs(30)));
}
}
#[test]
fn test_http_throttling_responses_with_large_retry_after_capped() {
for status_code in [429, 503] {
let result = classify_http_error(status_code, Some("900")); // 15 minutes
assert_eq!(
result,
RetryErrorType::Throttled(std::time::Duration::from_secs(600))
); // Capped at 10 minutes
}
}
#[test]
fn test_http_throttling_responses_with_invalid_retry_after() {
for status_code in [429, 503] {
let result = classify_http_error(status_code, Some("invalid"));
assert_eq!(result, RetryErrorType::Retryable); // Fallback
}
}
#[test]
fn test_http_throttling_responses_without_retry_after() {
for status_code in [429, 503] {
let result = classify_http_error(status_code, None);
assert_eq!(result, RetryErrorType::Retryable); // Fallback
}
}
#[test]
fn test_http_retryable_response_codes() {
assert_eq!(classify_http_error(502, None), RetryErrorType::Retryable);
assert_eq!(classify_http_error(503, None), RetryErrorType::Retryable);
assert_eq!(classify_http_error(504, None), RetryErrorType::Retryable);
}
#[test]
fn test_http_non_retryable_response_codes() {
for status_code in [400, 401, 403, 404, 408, 499, 500, 501, 505, 599] {
assert_eq!(
classify_http_error(status_code, None),
RetryErrorType::NonRetryable
);
}
}
#[test]
fn test_http_network_error_is_retryable() {
assert_eq!(classify_http_error(0, None), RetryErrorType::Retryable);
}
#[test]
#[cfg(feature = "httpdate")]
fn test_http_throttling_responses_with_retry_after_valid_date() {
use std::time::SystemTime;
// Create a time 30 seconds in the future
let future_time = SystemTime::now() + Duration::from_secs(30);
let date_str = httpdate::fmt_http_date(future_time);
for status_code in [429, 503] {
let result = classify_http_error(status_code, Some(&date_str));
match result {
RetryErrorType::Throttled(duration) => {
let secs = duration.as_secs();
assert!(
(29..=30).contains(&secs),
"Expected ~30 seconds, got {}",
secs
);
}
_ => panic!("Expected Throttled, got {:?}", result),
}
}
}
#[test]
#[cfg(feature = "httpdate")]
fn test_http_throttling_responses_with_retry_after_invalid_date() {
for status_code in [429, 503] {
let result = classify_http_error(status_code, Some("Not a valid date"));
assert_eq!(result, RetryErrorType::Retryable); // Falls back to retryable
}
}
#[test]
#[cfg(feature = "httpdate")]
fn test_http_throttling_responses_with_retry_after_malformed_date() {
for status_code in [429, 503] {
let result =
classify_http_error(status_code, Some("Sun, 99 Nov 9999 99:99:99 GMT"));
assert_eq!(result, RetryErrorType::Retryable); // Falls back to retryable
}
}
}
// Tests for gRPC error classification using public interface
#[cfg(feature = "grpc-tonic")]
mod grpc_tests {
use crate::retry::RetryErrorType;
use crate::retry_classification::grpc::classify_tonic_status;
use tonic_types::{ErrorDetails, StatusExt};
#[test]
fn test_grpc_resource_exhausted_with_retry_info() {
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_secs(45)));
let status = tonic::Status::with_error_details(
tonic::Code::ResourceExhausted,
"rate limited",
error_details,
);
let result = classify_tonic_status(&status);
assert_eq!(
result,
RetryErrorType::Throttled(std::time::Duration::from_secs(45))
);
}
#[test]
fn test_grpc_resource_exhausted_with_large_retry_info_capped() {
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_secs(900))); // 15 minutes
let status = tonic::Status::with_error_details(
tonic::Code::ResourceExhausted,
"rate limited",
error_details,
);
let result = classify_tonic_status(&status);
assert_eq!(
result,
RetryErrorType::Throttled(std::time::Duration::from_secs(600))
); // Capped at 10 minutes
}
#[test]
fn test_grpc_resource_exhausted_without_retry_info() {
let status = tonic::Status::new(tonic::Code::ResourceExhausted, "rate limited");
let result = classify_tonic_status(&status);
// Per OTLP spec: RESOURCE_EXHAUSTED without RetryInfo is non-retryable
assert_eq!(result, RetryErrorType::NonRetryable);
}
#[test]
fn test_grpc_unavailable_with_retry_info() {
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_secs(45)));
let status = tonic::Status::with_error_details(
tonic::Code::Unavailable,
"service unavailable",
error_details,
);
assert_eq!(
classify_tonic_status(&status),
RetryErrorType::Throttled(std::time::Duration::from_secs(45))
);
}
#[test]
fn test_grpc_unavailable_with_fractional_retry_info() {
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_millis(500)));
let status = tonic::Status::with_error_details(
tonic::Code::Unavailable,
"service unavailable",
error_details,
);
assert_eq!(
classify_tonic_status(&status),
RetryErrorType::Throttled(std::time::Duration::from_millis(500))
);
}
#[test]
fn test_grpc_unavailable_with_large_retry_info_capped() {
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_secs(900)));
let status = tonic::Status::with_error_details(
tonic::Code::Unavailable,
"service unavailable",
error_details,
);
assert_eq!(
classify_tonic_status(&status),
RetryErrorType::Throttled(std::time::Duration::from_secs(600))
);
}
#[test]
fn test_grpc_unavailable_without_positive_retry_info() {
let without_retry_info =
tonic::Status::new(tonic::Code::Unavailable, "service unavailable");
assert_eq!(
classify_tonic_status(&without_retry_info),
RetryErrorType::Retryable
);
let error_details = ErrorDetails::with_retry_info(Some(std::time::Duration::ZERO));
let zero_retry_info = tonic::Status::with_error_details(
tonic::Code::Unavailable,
"service unavailable",
error_details,
);
assert_eq!(
classify_tonic_status(&zero_retry_info),
RetryErrorType::Retryable
);
}
#[test]
fn test_grpc_retryable_errors() {
// Test all retryable errors per OTLP specification
let cancelled = tonic::Status::new(tonic::Code::Cancelled, "cancelled");
assert_eq!(classify_tonic_status(&cancelled), RetryErrorType::Retryable);
let deadline_exceeded =
tonic::Status::new(tonic::Code::DeadlineExceeded, "deadline exceeded");
assert_eq!(
classify_tonic_status(&deadline_exceeded),
RetryErrorType::Retryable
);
let aborted = tonic::Status::new(tonic::Code::Aborted, "aborted");
assert_eq!(classify_tonic_status(&aborted), RetryErrorType::Retryable);
let out_of_range = tonic::Status::new(tonic::Code::OutOfRange, "out of range");
assert_eq!(
classify_tonic_status(&out_of_range),
RetryErrorType::Retryable
);
let unavailable = tonic::Status::new(tonic::Code::Unavailable, "unavailable");
assert_eq!(
classify_tonic_status(&unavailable),
RetryErrorType::Retryable
);
let data_loss = tonic::Status::new(tonic::Code::DataLoss, "data loss");
assert_eq!(classify_tonic_status(&data_loss), RetryErrorType::Retryable);
}
#[test]
fn test_grpc_non_retryable_errors() {
// Test all non-retryable errors per OTLP specification
let unknown = tonic::Status::new(tonic::Code::Unknown, "unknown");
assert_eq!(
classify_tonic_status(&unknown),
RetryErrorType::NonRetryable
);
let invalid_argument =
tonic::Status::new(tonic::Code::InvalidArgument, "invalid argument");
assert_eq!(
classify_tonic_status(&invalid_argument),
RetryErrorType::NonRetryable
);
let not_found = tonic::Status::new(tonic::Code::NotFound, "not found");
assert_eq!(
classify_tonic_status(&not_found),
RetryErrorType::NonRetryable
);
let already_exists = tonic::Status::new(tonic::Code::AlreadyExists, "already exists");
assert_eq!(
classify_tonic_status(&already_exists),
RetryErrorType::NonRetryable
);
let permission_denied =
tonic::Status::new(tonic::Code::PermissionDenied, "permission denied");
assert_eq!(
classify_tonic_status(&permission_denied),
RetryErrorType::NonRetryable
);
let failed_precondition =
tonic::Status::new(tonic::Code::FailedPrecondition, "failed precondition");
assert_eq!(
classify_tonic_status(&failed_precondition),
RetryErrorType::NonRetryable
);
let unimplemented = tonic::Status::new(tonic::Code::Unimplemented, "unimplemented");
assert_eq!(
classify_tonic_status(&unimplemented),
RetryErrorType::NonRetryable
);
let internal = tonic::Status::new(tonic::Code::Internal, "internal error");
assert_eq!(
classify_tonic_status(&internal),
RetryErrorType::NonRetryable
);
let unauthenticated =
tonic::Status::new(tonic::Code::Unauthenticated, "unauthenticated");
assert_eq!(
classify_tonic_status(&unauthenticated),
RetryErrorType::NonRetryable
);
}
#[test]
fn test_grpc_ok_code_handled() {
// OK status should be handled gracefully (though unlikely in error scenarios)
let ok = tonic::Status::new(tonic::Code::Ok, "success");
assert_eq!(classify_tonic_status(&ok), RetryErrorType::NonRetryable);
}
// Tests for tonic-types RetryInfo integration
#[cfg(feature = "grpc-tonic")]
mod retry_info_tests {
use super::*;
use crate::retry_classification::grpc::classify_tonic_status;
use tonic_types::{ErrorDetails, StatusExt};
#[test]
fn test_classify_status_with_retry_info() {
// Create a tonic::Status with RetryInfo using proper StatusExt API
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_secs(30)));
let status = tonic::Status::with_error_details(
tonic::Code::ResourceExhausted,
"rate limited",
error_details,
);
// Test classification
let result = classify_tonic_status(&status);
assert_eq!(
result,
RetryErrorType::Throttled(std::time::Duration::from_secs(30))
);
}
#[test]
fn test_classify_status_with_fractional_retry_info() {
// Create a tonic::Status with fractional seconds RetryInfo
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_millis(500)));
let status = tonic::Status::with_error_details(
tonic::Code::ResourceExhausted,
"rate limited",
error_details,
);
// Fractional seconds are preserved.
let result = classify_tonic_status(&status);
assert_eq!(
result,
RetryErrorType::Throttled(std::time::Duration::from_millis(500))
);
}
#[test]
fn test_classify_status_without_retry_info() {
// Status with resource_exhausted but no RetryInfo
let status = tonic::Status::new(tonic::Code::ResourceExhausted, "rate limited");
// Per OTLP spec: should be non-retryable without RetryInfo
let result = classify_tonic_status(&status);
assert_eq!(result, RetryErrorType::NonRetryable);
}
#[test]
fn test_classify_status_non_retryable_error() {
// Status with non-retryable error code
let status = tonic::Status::new(tonic::Code::InvalidArgument, "bad request");
let result = classify_tonic_status(&status);
assert_eq!(result, RetryErrorType::NonRetryable);
}
#[test]
fn test_classify_status_retryable_error() {
// Status with retryable error code
let status = tonic::Status::new(tonic::Code::Unavailable, "service unavailable");
let result = classify_tonic_status(&status);
assert_eq!(result, RetryErrorType::Retryable);
}
#[test]
fn test_classify_status_large_retry_delay() {
// Test with large retry delay - should be capped at 10 minutes
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_secs(3600))); // 1 hour
let status = tonic::Status::with_error_details(
tonic::Code::ResourceExhausted,
"rate limited",
error_details,
);
let result = classify_tonic_status(&status);
// Should be capped at 10 minutes (600 seconds)
assert_eq!(
result,
RetryErrorType::Throttled(std::time::Duration::from_secs(600))
);
}
#[test]
fn test_status_ext_get_details() {
// Test that StatusExt works correctly
let error_details =
ErrorDetails::with_retry_info(Some(std::time::Duration::from_secs(45)));
let status = tonic::Status::with_error_details(
tonic::Code::ResourceExhausted,
"rate limited",
error_details,
);
// Direct extraction should work
let extracted = status.get_details_retry_info();
assert!(extracted.is_some());
let retry_delay = extracted.unwrap().retry_delay;
assert_eq!(retry_delay, Some(std::time::Duration::from_secs(45)));
}
}
}
}
@@ -0,0 +1,70 @@
use std::time::Duration;
/// Configuration for retry policy.
///
/// The default is [`RetryPolicy::recommended()`]. Use
/// [`RetryPolicy::disabled()`] to perform a single export attempt without
/// retrying, or customize the recommended behavior with the `with_*` methods.
#[derive(Debug, Clone)]
pub struct RetryPolicy {
pub(crate) max_retries: usize,
pub(crate) initial_delay: Duration,
pub(crate) max_delay: Duration,
pub(crate) max_jitter: Duration,
}
impl Default for RetryPolicy {
/// Returns the recommended OTLP retry policy.
fn default() -> Self {
Self::recommended()
}
}
impl RetryPolicy {
/// Returns a retry policy that performs a single export attempt with no retries.
pub fn disabled() -> Self {
Self {
max_retries: 0,
initial_delay: Duration::ZERO,
max_delay: Duration::ZERO,
max_jitter: Duration::ZERO,
}
}
/// Returns the recommended policy: 3 retries with exponential backoff
/// (100ms initial, 1600ms maximum, and up to 100ms jitter).
pub fn recommended() -> Self {
Self {
max_retries: 3,
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_millis(1600),
max_jitter: Duration::from_millis(100),
}
}
/// Sets the maximum number of retry attempts after the initial export attempt.
pub fn with_max_retries(mut self, max_retries: usize) -> Self {
self.max_retries = max_retries;
self
}
/// Sets the delay before the first retry attempt.
pub fn with_initial_delay(mut self, initial_delay: Duration) -> Self {
self.initial_delay = initial_delay;
self
}
/// Sets the maximum delay between retry attempts.
///
/// A server-provided throttling delay may raise this limit.
pub fn with_max_delay(mut self, max_delay: Duration) -> Self {
self.max_delay = max_delay;
self
}
/// Sets the maximum random jitter added to each retry delay.
pub fn with_max_jitter(mut self, max_jitter: Duration) -> Self {
self.max_jitter = max_jitter;
self
}
}
+397
View File
@@ -0,0 +1,397 @@
//! # OTLP - Span Exporter
//!
//! Defines a [SpanExporter] to send trace data via the OpenTelemetry Protocol (OTLP)
use std::fmt::Debug;
use opentelemetry_sdk::error::OTelSdkResult;
use opentelemetry_sdk::trace::SpanData;
use crate::ExporterBuildError;
#[cfg(feature = "grpc-tonic")]
use crate::{
exporter::tonic::{HasTonicConfig, TonicExporterBuilder},
TonicExporterBuilderSet,
};
#[cfg(any(feature = "http-proto", feature = "http-json"))]
use crate::{
exporter::http::{HasHttpConfig, HttpExporterBuilder},
HttpExporterBuilderSet,
};
use crate::{exporter::HasExportConfig, NoExporterBuilderSet};
/// Target to which the exporter is going to send spans, defaults to https://localhost:4317/v1/traces.
/// Learn about the relationship between this constant and default/metrics/logs at
/// <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#endpoint-urls-for-otlphttp>
pub const OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: &str = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT";
/// Max waiting time for the backend to process each spans batch, defaults to 10s.
pub const OTEL_EXPORTER_OTLP_TRACES_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_TRACES_TIMEOUT";
/// Compression algorithm to use, defaults to none.
pub const OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_TRACES_COMPRESSION";
/// Key-value pairs to be used as headers associated with gRPC or HTTP requests
/// for sending spans.
/// Example: `k1=v1,k2=v2`
/// Note: this is only supported for HTTP.
pub const OTEL_EXPORTER_OTLP_TRACES_HEADERS: &str = "OTEL_EXPORTER_OTLP_TRACES_HEADERS";
/// Protocol to use for trace exports. Valid values: `grpc`, `http/protobuf`, `http/json`.
pub const OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: &str = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL";
/// Whether to disable TLS for gRPC trace exports.
/// Only applies to gRPC; HTTP security is determined by URL scheme.
pub const OTEL_EXPORTER_OTLP_TRACES_INSECURE: &str = "OTEL_EXPORTER_OTLP_TRACES_INSECURE";
/// OTLP span exporter builder
#[derive(Debug, Default, Clone)]
pub struct SpanExporterBuilder<C> {
client: C,
}
impl SpanExporterBuilder<NoExporterBuilderSet> {
/// Create a new [SpanExporterBuilder] with default settings.
pub fn new() -> Self {
SpanExporterBuilder::default()
}
/// With the gRPC Tonic transport.
#[cfg(feature = "grpc-tonic")]
pub fn with_tonic(self) -> SpanExporterBuilder<TonicExporterBuilderSet> {
SpanExporterBuilder {
client: TonicExporterBuilderSet(TonicExporterBuilder::default()),
}
}
/// With the HTTP transport.
#[cfg(any(feature = "http-proto", feature = "http-json"))]
pub fn with_http(self) -> SpanExporterBuilder<HttpExporterBuilderSet> {
SpanExporterBuilder {
client: HttpExporterBuilderSet(HttpExporterBuilder::default()),
}
}
/// Build the [SpanExporter] with the default transport selected by environment
/// variable or feature flags.
///
/// The transport is chosen based on:
/// 1. `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` environment variable
/// 2. `OTEL_EXPORTER_OTLP_PROTOCOL` environment variable
/// 3. Enabled features, with priority: `http-json` > `http-proto` > `grpc-tonic`
///
/// Use [`with_tonic`](Self::with_tonic) or [`with_http`](Self::with_http) to
/// explicitly select a transport and access transport-specific configuration.
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
pub fn build(self) -> Result<SpanExporter, ExporterBuildError> {
use crate::WithExportConfig;
let protocol = crate::exporter::resolve_protocol(OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, None);
match protocol {
#[cfg(feature = "grpc-tonic")]
crate::Protocol::Grpc => self.with_tonic().with_protocol(protocol).build(),
#[cfg(feature = "http-proto")]
crate::Protocol::HttpBinary => self.with_http().with_protocol(protocol).build(),
#[cfg(feature = "http-json")]
crate::Protocol::HttpJson => self.with_http().with_protocol(protocol).build(),
}
}
}
#[cfg(feature = "grpc-tonic")]
impl SpanExporterBuilder<TonicExporterBuilderSet> {
/// Build the [SpanExporter] with the gRPC Tonic transport.
pub fn build(self) -> Result<SpanExporter, ExporterBuildError> {
let span_exporter = self.client.0.build_span_exporter()?;
opentelemetry::otel_debug!(name: "SpanExporterBuilt");
Ok(span_exporter)
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl SpanExporterBuilder<HttpExporterBuilderSet> {
/// Build the [SpanExporter] with the HTTP transport.
pub fn build(self) -> Result<SpanExporter, ExporterBuildError> {
let span_exporter = self.client.0.build_span_exporter()?;
Ok(span_exporter)
}
}
#[cfg(feature = "grpc-tonic")]
impl HasExportConfig for SpanExporterBuilder<TonicExporterBuilderSet> {
fn export_config(&mut self) -> &mut crate::exporter::ExportConfig {
&mut self.client.0.exporter_config
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl HasExportConfig for SpanExporterBuilder<HttpExporterBuilderSet> {
fn export_config(&mut self) -> &mut crate::exporter::ExportConfig {
&mut self.client.0.exporter_config
}
}
#[cfg(feature = "grpc-tonic")]
impl HasTonicConfig for SpanExporterBuilder<TonicExporterBuilderSet> {
fn tonic_config(&mut self) -> &mut crate::exporter::tonic::TonicConfig {
&mut self.client.0.tonic_config
}
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
impl HasHttpConfig for SpanExporterBuilder<HttpExporterBuilderSet> {
fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig {
&mut self.client.0.http_config
}
}
/// OTLP exporter that sends tracing data
#[derive(Debug)]
pub struct SpanExporter {
client: SupportedTransportClient,
}
#[derive(Debug)]
enum SupportedTransportClient {
#[cfg(feature = "grpc-tonic")]
Tonic(crate::exporter::tonic::trace::TonicTracesClient),
#[cfg(any(feature = "http-proto", feature = "http-json"))]
Http(crate::exporter::http::OtlpHttpClient),
}
impl SpanExporter {
/// Obtain a builder to configure a [SpanExporter].
pub fn builder() -> SpanExporterBuilder<NoExporterBuilderSet> {
SpanExporterBuilder::default()
}
#[cfg(any(feature = "http-proto", feature = "http-json"))]
pub(crate) fn from_http(client: crate::exporter::http::OtlpHttpClient) -> Self {
SpanExporter {
client: SupportedTransportClient::Http(client),
}
}
#[cfg(feature = "grpc-tonic")]
pub(crate) fn from_tonic(client: crate::exporter::tonic::trace::TonicTracesClient) -> Self {
SpanExporter {
client: SupportedTransportClient::Tonic(client),
}
}
}
impl opentelemetry_sdk::trace::SpanExporter for SpanExporter {
async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult {
match &self.client {
#[cfg(feature = "grpc-tonic")]
SupportedTransportClient::Tonic(client) => client.export(batch).await,
#[cfg(any(feature = "http-proto", feature = "http-json"))]
SupportedTransportClient::Http(client) => client.export(batch).await,
}
}
fn set_resource(&mut self, resource: &opentelemetry_sdk::Resource) {
match &mut self.client {
#[cfg(feature = "grpc-tonic")]
SupportedTransportClient::Tonic(client) => client.set_resource(resource),
#[cfg(any(feature = "http-proto", feature = "http-json"))]
SupportedTransportClient::Http(client) => client.set_resource(resource),
}
}
}
#[cfg(test)]
#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))]
mod tests {
use crate::SpanExporter;
// Uses a tokio runtime because, under a gRPC-only build, the auto-selected
// tonic transport needs an active reactor to construct its channel.
#[tokio::test]
async fn build_with_default_transport() {
let result = SpanExporter::builder().build();
assert!(result.is_ok(), "build() should succeed: {:?}", result.err());
}
/// Verifies that `SpanExporter::builder().build()` respects the
/// signal-specific `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` env var
/// when selecting the transport.
#[cfg(all(feature = "grpc-tonic", feature = "http-proto"))]
#[test]
fn build_auto_select_respects_signal_protocol_env() {
// Tokio runtime is required because tonic's channel setup needs an active reactor.
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
// With signal-specific env var set to grpc, build() should pick tonic
temp_env::with_vars(
vec![
(super::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, Some("grpc")),
(crate::OTEL_EXPORTER_OTLP_PROTOCOL, None::<&str>),
],
|| {
let result = SpanExporter::builder().build();
assert!(
result.is_ok(),
"build() with TRACES_PROTOCOL=grpc should succeed: {:?}",
result.err()
);
},
);
// With signal-specific env var set to http/protobuf, build() should pick HTTP
temp_env::with_vars(
vec![
(
super::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL,
Some("http/protobuf"),
),
(crate::OTEL_EXPORTER_OTLP_PROTOCOL, None::<&str>),
],
|| {
let result = SpanExporter::builder().build();
assert!(
result.is_ok(),
"build() with TRACES_PROTOCOL=http/protobuf should succeed: {:?}",
result.err()
);
},
);
});
}
/// Verifies that signal-specific protocol env var takes precedence
/// over the generic `OTEL_EXPORTER_OTLP_PROTOCOL` for transport selection.
#[cfg(all(feature = "grpc-tonic", feature = "http-proto"))]
#[test]
fn build_auto_select_signal_env_overrides_generic() {
// Tokio runtime is required because tonic's channel setup needs an active reactor.
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
temp_env::with_vars(
vec![
(super::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, Some("grpc")),
(crate::OTEL_EXPORTER_OTLP_PROTOCOL, Some("http/protobuf")),
],
|| {
let result = SpanExporter::builder().build();
assert!(
result.is_ok(),
"signal-specific protocol should override generic: {:?}",
result.err()
);
},
);
});
}
/// Invalid enum values are warned about and ignored, so resolution
/// continues to the next source in the precedence chain.
#[cfg(all(feature = "grpc-tonic", feature = "http-proto"))]
#[test]
fn build_auto_select_ignores_invalid_protocol_env_values() {
let rt = tokio::runtime::Runtime::new().unwrap();
let _guard = rt.enter();
for (signal, generic, expect_grpc) in [
("not-a-protocol", "GRPC", true),
("HTTP/PROTOBUF", "grpc", false),
("not-a-protocol", "also-not-a-protocol", false),
] {
temp_env::with_vars(
[
(super::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, Some(signal)),
(crate::OTEL_EXPORTER_OTLP_PROTOCOL, Some(generic)),
],
|| {
let exporter = SpanExporter::builder().build().unwrap();
assert_eq!(
matches!(exporter.client, super::SupportedTransportClient::Tonic(_)),
expect_grpc,
"signal={signal}, generic={generic}"
);
},
);
}
}
#[cfg(all(feature = "http-proto", not(feature = "grpc-tonic")))]
#[test]
fn build_auto_select_ignores_protocol_with_disabled_feature() {
temp_env::with_vars(
[
(super::OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, None::<&str>),
(crate::OTEL_EXPORTER_OTLP_PROTOCOL, Some("grpc")),
],
|| {
let exporter = SpanExporter::builder().build().unwrap();
assert!(matches!(
exporter.client,
super::SupportedTransportClient::Http(_)
));
},
);
}
#[cfg(all(
any(feature = "http-proto", feature = "http-json"),
feature = "gzip-http"
))]
#[test]
fn build_http_succeeds_with_invalid_compression_env_value() {
temp_env::with_vars(
[
(
super::OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
Some("not-compression"),
),
(crate::OTEL_EXPORTER_OTLP_COMPRESSION, Some("GZIP")),
],
|| {
let result = SpanExporter::builder().with_http().build();
assert!(
result.is_ok(),
"invalid signal compression should fall back to generic: {:?}",
result.err()
);
},
);
}
/// Verifies that explicitly selecting tonic transport with an HTTP protocol
/// via `.with_protocol()` returns an error.
#[cfg(all(feature = "grpc-tonic", feature = "http-proto"))]
#[test]
fn build_tonic_with_http_protocol_returns_error() {
use crate::exporter::WithExportConfig;
// Tokio runtime is required because tonic's channel setup needs an active reactor.
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let result = SpanExporter::builder()
.with_tonic()
.with_protocol(crate::Protocol::HttpBinary)
.build();
assert!(result.is_err(), "tonic with HTTP protocol should fail");
let err = result.unwrap_err().to_string();
assert!(
err.contains("not compatible with gRPC transport"),
"error should mention transport incompatibility, got: {err}"
);
});
}
/// Verifies that explicitly selecting HTTP transport with a gRPC protocol
/// via `.with_protocol()` returns an error.
#[cfg(all(feature = "grpc-tonic", feature = "http-proto"))]
#[test]
fn build_http_with_grpc_protocol_returns_error() {
use crate::exporter::WithExportConfig;
let result = SpanExporter::builder()
.with_http()
.with_protocol(crate::Protocol::Grpc)
.build();
assert!(result.is_err(), "HTTP with gRPC protocol should fail");
let err = result.unwrap_err().to_string();
assert!(
err.contains("not compatible with HTTP transport"),
"error should mention transport incompatibility, got: {err}"
);
}
}
@@ -0,0 +1,243 @@
use futures_util::StreamExt;
use opentelemetry::global;
use opentelemetry::trace::{Span, SpanKind, Tracer};
use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
use opentelemetry_proto::tonic::collector::trace::v1::{
trace_service_server::{TraceService, TraceServiceServer},
ExportTraceServiceRequest, ExportTraceServiceResponse,
};
use std::{net::SocketAddr, sync::Mutex};
use tokio::sync::mpsc;
use tokio_stream::wrappers::TcpListenerStream;
#[cfg(feature = "gzip-tonic")]
use tonic::codec::CompressionEncoding;
struct MockServer {
tx: Mutex<mpsc::Sender<ExportTraceServiceRequest>>,
}
impl MockServer {
pub fn new(tx: mpsc::Sender<ExportTraceServiceRequest>) -> Self {
Self { tx: Mutex::new(tx) }
}
}
#[tonic::async_trait]
impl TraceService for MockServer {
async fn export(
&self,
request: tonic::Request<ExportTraceServiceRequest>,
) -> Result<tonic::Response<ExportTraceServiceResponse>, tonic::Status> {
println!("Sending request into channel...");
// assert we have required metadata key
assert_eq!(
request.metadata().get("x-header-key"),
Some(&("header-value".parse().unwrap()))
);
self.tx
.lock()
.unwrap()
.try_send(request.into_inner())
.expect("Channel full");
Ok(tonic::Response::new(ExportTraceServiceResponse {
partial_success: None,
}))
}
}
async fn setup() -> (SocketAddr, mpsc::Receiver<ExportTraceServiceRequest>) {
let addr: SocketAddr = "[::1]:0".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr)
.await
.expect("failed to bind");
let addr = listener.local_addr().unwrap();
let stream = TcpListenerStream::new(listener).map(|s| {
if let Ok(ref s) = s {
println!("Got new conn at {}", s.peer_addr().unwrap());
}
s
});
let (req_tx, req_rx) = mpsc::channel(10);
#[cfg(feature = "gzip-tonic")]
let service = TraceServiceServer::new(MockServer::new(req_tx))
.accept_compressed(CompressionEncoding::Gzip);
#[cfg(not(feature = "gzip-tonic"))]
let service = TraceServiceServer::new(MockServer::new(req_tx));
tokio::task::spawn(async move {
tonic::transport::Server::builder()
.add_service(service)
.serve_with_incoming(stream)
.await
.expect("Server failed");
});
(addr, req_rx)
}
#[tokio::test(flavor = "multi_thread")]
async fn smoke_tracer() {
println!("Starting server setup...");
let (addr, mut req_rx) = setup().await;
{
println!("Installing tracer provider...");
let mut metadata = tonic::metadata::MetadataMap::new();
metadata.insert("x-header-key", "header-value".parse().unwrap());
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(
#[cfg(feature = "gzip-tonic")]
opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_compression(opentelemetry_otlp::Compression::Gzip)
.with_endpoint(format!("http://{addr}"))
.with_metadata(metadata)
.build()
.expect("gzip-tonic SpanExporter failed to build"),
#[cfg(not(feature = "gzip-tonic"))]
opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(format!("http://{}", addr))
.with_metadata(metadata)
.build()
.expect("NON gzip-tonic SpanExporter failed to build"),
)
.build();
global::set_tracer_provider(tracer_provider.clone());
let tracer = global::tracer("smoke");
println!("Sending span...");
let mut span = tracer
.span_builder("my-test-span")
.with_kind(SpanKind::Server)
.start(&tracer);
span.add_event("my-test-event", vec![]);
span.end();
tracer_provider
.shutdown()
.expect("tracer_provider should shutdown successfully");
}
println!("Waiting for request...");
let req = req_rx.recv().await.expect("missing export request");
let first_span = req
.resource_spans
.first()
.unwrap()
.scope_spans
.first()
.unwrap()
.spans
.first()
.unwrap();
assert_eq!("my-test-span", first_span.name);
let first_event = first_span.events.first().unwrap();
assert_eq!("my-test-event", first_event.name);
}
// Smoke test for partial success handling. We set everything up and push some
// partial success messages through with the expectation that it doesn't all fall apart.
#[tokio::test(flavor = "multi_thread")]
async fn partial_success_handling() {
use opentelemetry_proto::tonic::collector::trace::v1::ExportTracePartialSuccess;
struct PartialSuccessServer {
tx: Mutex<mpsc::Sender<ExportTraceServiceRequest>>,
}
impl PartialSuccessServer {
pub fn new(tx: mpsc::Sender<ExportTraceServiceRequest>) -> Self {
Self { tx: Mutex::new(tx) }
}
}
#[tonic::async_trait]
impl TraceService for PartialSuccessServer {
async fn export(
&self,
request: tonic::Request<ExportTraceServiceRequest>,
) -> Result<tonic::Response<ExportTraceServiceResponse>, tonic::Status> {
self.tx
.lock()
.unwrap()
.try_send(request.into_inner())
.expect("Channel full");
// Return partial success response
Ok(tonic::Response::new(ExportTraceServiceResponse {
partial_success: Some(ExportTracePartialSuccess {
rejected_spans: 5,
error_message: "Some spans were rejected due to invalid data".to_string(),
}),
}))
}
}
println!("Starting partial success test...");
let addr: SocketAddr = "[::1]:0".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr)
.await
.expect("failed to bind");
let addr = listener.local_addr().unwrap();
let stream = TcpListenerStream::new(listener).map(|s| {
if let Ok(ref s) = s {
println!("Got new conn at {}", s.peer_addr().unwrap());
}
s
});
let (req_tx, mut req_rx) = mpsc::channel(10);
let service = TraceServiceServer::new(PartialSuccessServer::new(req_tx));
tokio::task::spawn(async move {
tonic::transport::Server::builder()
.add_service(service)
.serve_with_incoming(stream)
.await
.expect("Server failed");
});
{
println!("Installing tracer provider for partial success test...");
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(
opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(format!("http://{}", addr))
.build()
.expect("SpanExporter failed to build"),
)
.build();
global::set_tracer_provider(tracer_provider.clone());
let tracer = global::tracer("partial-success-test");
println!("Sending span...");
let mut span = tracer
.span_builder("test-span-partial-success")
.with_kind(SpanKind::Server)
.start(&tracer);
span.end();
// Export should succeed even with partial success response
tracer_provider
.shutdown()
.expect("tracer_provider should shutdown successfully despite partial success");
}
println!("Waiting for request...");
let req = req_rx.recv().await.expect("missing export request");
let first_span = req
.resource_spans
.first()
.unwrap()
.scope_spans
.first()
.unwrap()
.spans
.first()
.unwrap();
assert_eq!("test-span-partial-success", first_span.name);
println!("Partial success test completed successfully");
}
@@ -0,0 +1,162 @@
# Changelog
## vNext
## 0.33.0
Released 2026-Sep-18
- **Bug fix**: Keep OTLP logs with the same target but different scope versions,
attributes, or schema URLs in separate groups. Target still overrides the scope
name. Export each scope's schema URL instead of the resource's schema URL.
- **Bug fix**: Accept empty `AnyValue` objects in OTLP/JSON payloads instead of rejecting the entire request.
- **Bug fix**: Accept omitted resource fields in empty OTLP/JSON collector requests.
- **Bug fix**: Accept `null` fields in OTLP/JSON `AnyValue` objects as unset.
- **Bug fix**: Accept OTLP/JSON partial-success responses when protobuf default fields are omitted.
- **Performance**: `group_spans_by_resource_and_scope` no longer clones every `SpanData`. The batch is owned by the function, so spans are now moved into the proto conversion instead of being grouped by reference and cloned. This cuts the transform time of a 512-span batch roughly in half. ([#3652](https://github.com/open-telemetry/opentelemetry-rust/pull/3652))
## 0.32.0
Released 2026-May-08
- Update proto definitions to v1.10.0.
- Updated `schemars` dependency to version 1.0.0.
- **Bug fix**: `InstrumentationScope` version and attributes are now preserved when logs have a target set. Previously, setting a log target would discard the scope's version and attributes. ([#3276](https://github.com/open-telemetry/opentelemetry-rust/issues/3276))
## 0.31.0
Released 2025-Sep-25
- **Feature**: Add span flags support for `isRemote` property in OTLP trace transformation ([#3153](https://github.com/open-telemetry/opentelemetry-rust/pull/3153))
- Updated span and link transformations to properly set flags field (0x100 for local, 0x300 for remote)
- Update proto definitions to v1.8.0.
- Added Rust generated protos for profiles collector. [#3077](https://github.com/open-telemetry/opentelemetry-rust/pull/3077)
- **Breaking change**: package opentelemetry_proto::tonic::profiles::v1 renamed to opentelemetry_proto::tonic::profiles::v1development. [#3077](https://github.com/open-telemetry/opentelemetry-rust/pull/3077)
- Update `tonic` dependency version to 0.14.1
- Update `prost` dependency version to 0.14
- Update `opentelemetry` dependency version to 0.31.0
- Updated `opentelemetry_sdk` dependency to version 0.31.0.
## 0.30.0
Released 2025-May-23
- Update `opentelemetry` dependency version to 0.30
- Updated `opentelemetry_sdk` dependency to version 0.30.0.
- **Feature**: Added Rust code generation for profiles protos. [#2979](https://github.com/open-telemetry/opentelemetry-rust/pull/2979)
- Update `tonic` dependency version to 0.13
- Update proto definitions to v1.6.0.
## 0.29.0
Released 2025-Mar-21
- Update `opentelemetry` dependency version to 0.29
- Update `opentelemetry_sdk` dependency version to 0.29
## 0.28.0
Released 2025-Feb-10
- Update `opentelemetry` dependency version to 0.28.
- Update `opentelemetry_sdk` dependency version to 0.28.
- Bump msrv to 1.75.0.
- Update proto definitions to v1.5.0 [#2439](https://github.com/open-telemetry/opentelemetry-rust/pull/2439)
- Feature flag "populate-logs-event-name" is removed as no longer relevant.
LogRecord's `event_name()` is now automatically populated on the newly added
"event_name" field in LogRecord proto definition.
## 0.27.0
Released 2024-Nov-11
- Update `opentelemetry` dependency version to 0.27
- Update `opentelemetry_sdk` dependency version to 0.27
## v0.26.1
- Require tonic 0.12.3 to match generated gRPC code [#2168](https://github.com/open-telemetry/opentelemetry-rust/pull/2168)
## v0.26.0
Released 2024-Sep-30
- Update `opentelemetry` dependency version to 0.26
- Update `opentelemetry_sdk` dependency version to 0.26
- Fix JSON serialization of `metrics::Exemplar` and `trace::span::Link` [#2069](https://github.com/open-telemetry/opentelemetry-rust/pull/2069)
- Bump MSRV to 1.71.1 [2140](https://github.com/open-telemetry/opentelemetry-rust/pull/2140)
## v0.25.0
- Update `opentelemetry` dependency version to 0.25
- Update `opentelemetry_sdk` dependency version to 0.25
- Starting with this version, this crate will align with `opentelemetry` crate
on major,minor versions.
- Update protobuf definitions to v1.3.2 [#1945](https://github.com/open-telemetry/opentelemetry-rust/pull/1945)
## v0.7.0
- Bump MSRV to 1.70 [1864](https://github.com/open-telemetry/opentelemetry-rust/pull/1874)
- Group log and Span batch by their resource and instrumentation scope before exporting [#1873](https://github.com/open-telemetry/opentelemetry-rust/pull/1873).
- Introduced `group_logs_by_resource_and_scope()` and `group_spans_by_resource_and_scope()` methods to group logs and spans by the resource and scope respectively.
- Update `opentelemetry` dependency version to 0.24
- Update `opentelemetry_sdk` dependency version to 0.24
## v0.6.0
- Update protobuf definitions to v1.3.1 [#1721](https://github.com/open-telemetry/opentelemetry-rust/pull/1721)
- Fix the feature flag condition of `opentelemetry-proto/src/transform/logs.rs` [#1746](https://github.com/open-telemetry/opentelemetry-rust/pull/1746)
- Update `opentelemetry` dependency version to 0.23
- Update `opentelemetry_sdk` dependency version to 0.23
## v0.5.0
### Changed
- Update to tonic 0.11 and prost 0.12 [#1536](https://github.com/open-telemetry/opentelemetry-rust/pull/1536)
- **Breaking** Remove support for grpcio transport [#1534](https://github.com/open-telemetry/opentelemetry-rust/pull/1534)
### Added
- Add `schemars::JsonSchema` trait support with `with-schemars` feature [#1419](https://github.com/open-telemetry/opentelemetry-rust/pull/1419)
- Update protobuf definitions to v1.1.0 [#1482](https://github.com/open-telemetry/opentelemetry-rust/pull/1482)
## v0.4.0
### Added
- Implement tonic metrics proto transformations [#1184](https://github.com/open-telemetry/opentelemetry-rust/pull/1184)
- Move proto for zPage to tonic [#1214](https://github.com/open-telemetry/opentelemetry-rust/pull/1214)
- Support exponential histograms [#1267](https://github.com/open-telemetry/opentelemetry-rust/pull/1267)
### Changed
- Bump MSRV to 1.65 [#1318](https://github.com/open-telemetry/opentelemetry-rust/pull/1318)
- Bump MSRV to 1.64 [#1203](https://github.com/open-telemetry/opentelemetry-rust/pull/1203)
### Fixed
- Rename `traces` feature to the more standard `trace` [#1183](https://github.com/open-telemetry/opentelemetry-rust/pull/1183)
### Changed
- Switch to `prost` for `grpcio` protos. [#1202](https://github.com/open-telemetry/opentelemetry-rust/pull/1202)
The `gen-protoc` feature is accordingly renamed to `gen-grpcio`.
## v0.3.0
### Updated
- Update protobuf definitions to v1.0.0 #1154
- Update to opentelemetry-api v0.20.0
## v0.2.0
### Changed
- Update to opentelemetry v0.19.0.
- Remove build script and generate files using unit tests [#881](https://github.com/open-telemetry/opentelemetry-rust/pull/881).
- Bump MSRV to 1.57 [#953](https://github.com/open-telemetry/opentelemetry-rust/pull/953).
- Update dependencies and bump MSRV to 1.60 [#969](https://github.com/open-telemetry/opentelemetry-rust/pull/969).
- Bump to use the v0.19.0 protobuf definitions. [#989](https://github.com/open-telemetry/opentelemetry-rust/pull/989).
## v0.1.0
Initial crate release.
+164
View File
@@ -0,0 +1,164 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.75.0"
name = "opentelemetry-proto"
version = "0.33.0"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Protobuf generated files and transformations."
homepage = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-proto"
readme = "README.md"
keywords = [
"opentelemetry",
"otlp",
"logging",
"tracing",
"metrics",
]
categories = [
"development-tools::debugging",
"development-tools::profiling",
"asynchronous",
]
license = "Apache-2.0"
repository = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-proto"
[features]
default = ["full"]
full = [
"gen-tonic",
"trace",
"logs",
"metrics",
"zpages",
"with-serde",
"internal-logs",
]
gen-tonic = [
"gen-tonic-messages",
"tonic",
"tonic-prost",
"tonic/channel",
]
gen-tonic-messages = ["prost"]
internal-logs = ["opentelemetry/internal-logs"]
logs = [
"opentelemetry/logs",
"opentelemetry_sdk/logs",
]
metrics = [
"opentelemetry/metrics",
"opentelemetry_sdk/metrics",
]
profiles = []
testing = ["opentelemetry/testing"]
trace = [
"opentelemetry/trace",
"opentelemetry_sdk/trace",
]
with-schemars = ["schemars"]
with-serde = [
"serde",
"const-hex",
"base64",
]
zpages = ["trace"]
[lib]
name = "opentelemetry_proto"
path = "src/lib.rs"
doctest = false
bench = false
[[test]]
name = "grpc_build"
path = "tests/grpc_build.rs"
[[test]]
name = "json_serde"
path = "tests/json_serde.rs"
[dependencies.base64]
version = "0.22.1"
optional = true
[dependencies.const-hex]
version = "1.14.1"
optional = true
[dependencies.opentelemetry]
version = "0.33"
default-features = false
[dependencies.opentelemetry_sdk]
version = "0.33"
default-features = false
[dependencies.prost]
version = "0.14"
optional = true
[dependencies.schemars]
version = "1.0"
optional = true
[dependencies.serde]
version = "1.0"
features = [
"serde_derive",
"std",
]
optional = true
default-features = false
[dependencies.tonic]
version = "0.14.1"
features = ["codegen"]
optional = true
default-features = false
[dependencies.tonic-prost]
version = "0.14.1"
optional = true
[dev-dependencies.opentelemetry]
version = "0.33"
features = ["testing"]
default-features = false
[dev-dependencies.serde_json]
version = "1.0"
[dev-dependencies.tempfile]
version = "3.3.0"
[dev-dependencies.tonic-prost-build]
version = "0.14.1"
[lints.clippy.all]
level = "warn"
priority = 1
[lints.rust]
edition_2024_expr_fragment_specifier = "allow"
if_let_rescope = "allow"
tail_expr_drop_order = "allow"
[lints.rust.rust_2024_compatibility]
level = "warn"
priority = -1
+70
View File
@@ -0,0 +1,70 @@
# OpenTelemetry Proto
![OpenTelemetry — An observability framework for cloud-native software.][splash]
[splash]: https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo-text.png
This crate contains generated files from
[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)
repository and transformations between types from generated files and types
defined in
[opentelemetry](https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry).
[![Crates.io: opentelemetry-proto](https://img.shields.io/crates/v/opentelemetry-proto.svg)](https://crates.io/crates/opentelemetry-proto)
[![Documentation](https://docs.rs/opentelemetry-proto/badge.svg)](https://docs.rs/opentelemetry-proto)
[![LICENSE](https://img.shields.io/crates/l/opentelemetry-proto)](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-proto/LICENSE)
[![GitHub Actions CI](https://github.com/open-telemetry/opentelemetry-rust/workflows/CI/badge.svg)](https://github.com/open-telemetry/opentelemetry-rust/actions?query=workflow%3ACI+branch%3Amain)
[![codecov](https://codecov.io/gh/open-telemetry/opentelemetry-rust/branch/main/graph/badge.svg)](https://codecov.io/gh/open-telemetry/opentelemetry-rust)
[![Slack](https://img.shields.io/badge/slack-@cncf/otel/rust-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C03GDP0H023)
## Overview
OpenTelemetry is an Observability framework and toolkit designed to create and
manage telemetry data such as traces, metrics, and logs. OpenTelemetry is
vendor- and tool-agnostic, meaning that it can be used with a broad variety of
Observability backends, including open source tools like [Jaeger] and
[Prometheus], as well as commercial offerings.
OpenTelemetry is *not* an observability backend like Jaeger, Prometheus, or other
commercial vendors. OpenTelemetry is focused on the generation, collection,
management, and export of telemetry. A major goal of OpenTelemetry is that you
can easily instrument your applications or systems, no matter their language,
infrastructure, or runtime environment. Crucially, the storage and visualization
of telemetry is intentionally left to other tools.
*[Supported Rust Versions](#supported-rust-versions)*
[Prometheus]: https://prometheus.io
[Jaeger]: https://www.jaegertracing.io
### What does this crate contain?
This crate provides auto-generated Protobuf types from the [OpenTelemetry
protocol specification](https://github.com/open-telemetry/opentelemetry-proto),
along with conversion implementations between these generated types and the
types defined in the
[opentelemetry](https://crates.io/crates/opentelemetry) crate. It is used
internally by exporters such as
[opentelemetry-otlp](https://crates.io/crates/opentelemetry-otlp) to serialize
and deserialize telemetry data.
## Getting started
See [docs](https://docs.rs/opentelemetry-proto).
## Release Notes
You can find the release notes (changelog) [here](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-proto/CHANGELOG.md).
## Supported Rust Versions
OpenTelemetry is built against the latest stable release. The minimum supported
version is 1.75.0. The current OpenTelemetry version is NOT guaranteed to build
on Rust versions earlier than the minimum supported version.
The current stable Rust compiler and the three most recent minor versions
before it will always be supported. For example, if the current stable compiler
version is 1.49, the minimum supported version will not be increased past 1.46,
three minor versions prior. Increasing the minimum supported compiler version
is not considered a semver breaking change as long as doing so complies with
this policy.
@@ -0,0 +1,38 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
//! This crate contains generated files from [opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)
//! repository and transformation between types from generated files and types defined in [opentelemetry](https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry)
//!
//! Based on the build tool needed, users can choose to generate files using [tonic](https://github.com/hyperium/tonic)
//! or [grpcio](https://github.com/tikv/grpc-rs).
//!
//!
//! # Feature flags
//! `Opentelemetry-proto` includes a set of feature flags to avoid pull in unnecessary dependencies.
//! The following is the full list of currently supported features:
//!
//! ## Signals
//! - `trace`: generate types that used in traces. Currently supports `gen-tonic`.
//! - `metrics`: generate types that used in metrics. Currently supports `gen-tonic`.
//! - `logs`: generate types that used in logs. Currently supports `gen-tonic`.
//! - `zpages`: generate types that used in zPages. Currently only tracez related types will be generated. Currently supports `gen-tonic`.
//!
//! ## Creates used to generate files
//! - `gen-tonic-messages`: generate OTLP message types using [prost](https://github.com/tokio-rs/prost).
//! - `gen-tonic`: add tonic gRPC client/server transport support on top of `gen-tonic-messages`.
//!
//! ## Misc
//! - `full`: enabled all features above.
//!
//! By default, no feature is enabled.
// proto mod contains file generated by protobuf or other build tools.
// we shouldn't manually change it. Thus skip format and lint check.
#[rustfmt::skip]
#[allow(warnings)]
#[doc(hidden)]
mod proto;
#[cfg(feature = "gen-tonic-messages")]
pub use proto::tonic;
pub mod transform;
@@ -0,0 +1,516 @@
/// provide serde support for proto traceIds and spanIds.
/// Those are hex encoded strings in the jsons but they are byte arrays in the proto.
/// See https://opentelemetry.io/docs/specs/otlp/#json-protobuf-encoding for more details
#[cfg(all(feature = "with-serde", feature = "gen-tonic-messages"))]
pub(crate) mod serializers {
use crate::tonic::common::v1::any_value::{self, Value};
use crate::tonic::common::v1::{AnyValue, ArrayValue, KeyValueList};
use serde::de::{self, MapAccess, Visitor};
use serde::ser::{SerializeMap, SerializeSeq, SerializeStruct};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
pub fn is_default<T>(value: &T) -> bool
where
T: Default + PartialEq,
{
value == &T::default()
}
// hex string <-> bytes conversion
pub fn serialize_to_hex_string<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex_string = const_hex::encode(bytes);
serializer.serialize_str(&hex_string)
}
pub fn deserialize_from_hex_string<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
struct BytesVisitor;
impl<'de> Visitor<'de> for BytesVisitor {
type Value = Vec<u8>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string representing hex-encoded bytes")
}
fn visit_str<E>(self, value: &str) -> Result<Vec<u8>, E>
where
E: de::Error,
{
const_hex::decode(value).map_err(E::custom)
}
}
deserializer.deserialize_str(BytesVisitor)
}
// AnyValue <-> KeyValue conversion
pub fn serialize_to_value<S>(value: &Option<Value>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &value {
Some(Value::IntValue(i)) => {
// Attempt to serialize the intValue field
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("intValue", &i.to_string());
map.end()
}
Some(Value::BytesValue(b)) => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("bytesValue", &base64::encode(b));
map.end()
}
Some(value) => value.serialize(serializer),
None => serializer.serialize_none(),
}
}
pub fn deserialize_from_value<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
where
D: Deserializer<'de>,
{
struct ValueVisitor;
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrInt {
Int(i64),
String(String),
}
impl StringOrInt {
fn get_int<'de, V>(&self) -> Result<i64, V::Error>
where
V: de::MapAccess<'de>,
{
match self {
Self::Int(val) => Ok(*val),
Self::String(val) => Ok(val.parse::<i64>().map_err(de::Error::custom)?),
}
}
}
impl<'de> de::Visitor<'de> for ValueVisitor {
type Value = Option<Value>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a JSON object for AnyValue")
}
fn visit_map<V>(self, mut map: V) -> Result<Option<Value>, V::Error>
where
V: de::MapAccess<'de>,
{
let mut value: Option<any_value::Value> = None;
while let Some(key) = map.next_key::<String>()? {
let key_str = key.as_str();
match key_str {
"stringValue" => {
if let Some(s) = map.next_value::<Option<String>>()? {
value = Some(any_value::Value::StringValue(s));
}
}
"boolValue" => {
if let Some(b) = map.next_value::<Option<bool>>()? {
value = Some(any_value::Value::BoolValue(b));
}
}
"intValue" => {
if let Some(int_value) = map.next_value::<Option<StringOrInt>>()? {
value = Some(any_value::Value::IntValue(int_value.get_int::<V>()?));
}
}
"doubleValue" => {
if let Some(d) = map.next_value::<Option<f64>>()? {
value = Some(any_value::Value::DoubleValue(d));
}
}
"arrayValue" => {
if let Some(a) = map.next_value::<Option<ArrayValue>>()? {
value = Some(any_value::Value::ArrayValue(a));
}
}
"kvlistValue" => {
if let Some(kv) = map.next_value::<Option<KeyValueList>>()? {
value = Some(any_value::Value::KvlistValue(kv));
}
}
"bytesValue" => {
if let Some(base64) = map.next_value::<Option<String>>()? {
let decoded = base64::decode(base64.as_bytes())
.map_err(|e| de::Error::custom(e))?;
value = Some(any_value::Value::BytesValue(decoded));
}
}
_ => {
//skip unknown keys, and handle error later.
continue;
}
}
}
// An AnyValue with no selected field is valid and considered empty.
// Unknown fields are ignored according to the OTLP/JSON specification,
// so an empty or unknown-only object has the same representation.
Ok(value)
}
}
let value = deserializer.deserialize_map(ValueVisitor)?;
Ok(value)
}
pub fn serialize_u64_to_string<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = value.to_string();
serializer.serialize_str(&s)
}
pub fn deserialize_string_to_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
struct U64Visitor;
impl<'de> de::Visitor<'de> for U64Visitor {
type Value = u64;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a u64 integer or a string containing a u64 integer")
}
fn visit_u64<E>(self, value: u64) -> Result<u64, E>
where
E: de::Error,
{
Ok(value)
}
fn visit_i64<E>(self, value: i64) -> Result<u64, E>
where
E: de::Error,
{
u64::try_from(value)
.map_err(|_| E::custom(format!("i64 value {} is out of range for u64", value)))
}
fn visit_str<E>(self, value: &str) -> Result<u64, E>
where
E: de::Error,
{
value.parse::<u64>().map_err(de::Error::custom)
}
}
deserializer.deserialize_any(U64Visitor)
}
pub fn serialize_vec_u64_to_string<S>(value: &[u64], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = value.iter().map(|v| v.to_string()).collect::<Vec<_>>();
let mut sq = serializer.serialize_seq(Some(s.len()))?;
for v in value {
sq.serialize_element(&v.to_string())?;
}
sq.end()
}
pub fn deserialize_vec_string_to_vec_u64<'de, D>(deserializer: D) -> Result<Vec<u64>, D::Error>
where
D: Deserializer<'de>,
{
struct U64ElemVisitor;
impl<'de> de::Visitor<'de> for U64ElemVisitor {
type Value = u64;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a u64 integer or a string containing a u64 integer")
}
fn visit_u64<E>(self, value: u64) -> Result<u64, E>
where
E: de::Error,
{
Ok(value)
}
fn visit_i64<E>(self, value: i64) -> Result<u64, E>
where
E: de::Error,
{
u64::try_from(value)
.map_err(|_| E::custom(format!("i64 value {} is out of range for u64", value)))
}
fn visit_str<E>(self, value: &str) -> Result<u64, E>
where
E: de::Error,
{
value.parse::<u64>().map_err(de::Error::custom)
}
}
struct VecU64Visitor;
impl<'de> de::Visitor<'de> for VecU64Visitor {
type Value = Vec<u64>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence of u64 integers or strings containing u64 integers")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Vec<u64>, A::Error>
where
A: de::SeqAccess<'de>,
{
let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0));
while let Some(value) = seq.next_element_seed(U64ElemSeed)? {
values.push(value);
}
Ok(values)
}
}
struct U64ElemSeed;
impl<'de> de::DeserializeSeed<'de> for U64ElemSeed {
type Value = u64;
fn deserialize<D2>(self, deserializer: D2) -> Result<u64, D2::Error>
where
D2: Deserializer<'de>,
{
deserializer.deserialize_any(U64ElemVisitor)
}
}
deserializer.deserialize_seq(VecU64Visitor)
}
pub fn serialize_i64_to_string<S>(value: &i64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = value.to_string();
serializer.serialize_str(&s)
}
pub fn deserialize_string_to_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
where
D: Deserializer<'de>,
{
struct I64Visitor;
impl<'de> de::Visitor<'de> for I64Visitor {
type Value = i64;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an i64 integer or a string containing an i64 integer")
}
fn visit_i64<E>(self, value: i64) -> Result<i64, E>
where
E: de::Error,
{
Ok(value)
}
fn visit_u64<E>(self, value: u64) -> Result<i64, E>
where
E: de::Error,
{
i64::try_from(value)
.map_err(|_| E::custom(format!("u64 value {} is out of range for i64", value)))
}
fn visit_str<E>(self, value: &str) -> Result<i64, E>
where
E: de::Error,
{
value.parse::<i64>().map_err(de::Error::custom)
}
}
deserializer.deserialize_any(I64Visitor)
}
// Special serializer and deserializer for NaN, Infinity, and -Infinity
pub fn serialize_f64_special<S>(value: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if value.is_nan() {
serializer.serialize_str("NaN")
} else if value.is_infinite() {
if value.is_sign_positive() {
serializer.serialize_str("Infinity")
} else {
serializer.serialize_str("-Infinity")
}
} else {
serializer.serialize_f64(*value)
}
}
pub fn deserialize_f64_special<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
struct F64Visitor;
impl<'de> de::Visitor<'de> for F64Visitor {
type Value = f64;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a float or a string representing NaN, Infinity, or -Infinity")
}
fn visit_f64<E>(self, value: f64) -> Result<f64, E>
where
E: de::Error,
{
Ok(value)
}
fn visit_u64<E>(self, value: u64) -> Result<f64, E>
where
E: de::Error,
{
Ok(value as f64)
}
fn visit_i64<E>(self, value: i64) -> Result<f64, E>
where
E: de::Error,
{
Ok(value as f64)
}
fn visit_str<E>(self, value: &str) -> Result<f64, E>
where
E: de::Error,
{
match value {
"NaN" => Ok(f64::NAN),
"Infinity" => Ok(f64::INFINITY),
"-Infinity" => Ok(f64::NEG_INFINITY),
_ => value.parse::<f64>().map_err(|_| {
E::custom(format!(
"invalid string for f64: expected a number, NaN, Infinity, or -Infinity but got '{}'",
value
))
}),
}
}
}
deserializer.deserialize_any(F64Visitor)
}
}
#[cfg(feature = "gen-tonic-messages")]
#[path = "proto/tonic"]
/// Generated files using [`tonic`](https://docs.rs/crate/tonic) and [`prost`](https://docs.rs/crate/prost)
pub mod tonic {
/// Service stub and clients
#[path = ""]
pub mod collector {
#[cfg(feature = "logs")]
#[path = ""]
pub mod logs {
#[path = "opentelemetry.proto.collector.logs.v1.rs"]
pub mod v1;
}
#[cfg(feature = "metrics")]
#[path = ""]
pub mod metrics {
#[path = "opentelemetry.proto.collector.metrics.v1.rs"]
pub mod v1;
}
#[cfg(feature = "trace")]
#[path = ""]
pub mod trace {
#[path = "opentelemetry.proto.collector.trace.v1.rs"]
pub mod v1;
}
#[cfg(feature = "profiles")]
#[path = ""]
pub mod profiles {
#[path = "opentelemetry.proto.collector.profiles.v1development.rs"]
pub mod v1development;
}
}
/// Common types used across all signals
#[path = ""]
pub mod common {
#[path = "opentelemetry.proto.common.v1.rs"]
pub mod v1;
}
/// Generated types used in logging.
#[cfg(feature = "logs")]
#[path = ""]
pub mod logs {
#[path = "opentelemetry.proto.logs.v1.rs"]
pub mod v1;
}
/// Generated types used in metrics.
#[cfg(feature = "metrics")]
#[path = ""]
pub mod metrics {
#[path = "opentelemetry.proto.metrics.v1.rs"]
pub mod v1;
}
/// Generated types used in resources.
#[path = ""]
pub mod resource {
#[path = "opentelemetry.proto.resource.v1.rs"]
pub mod v1;
}
/// Generated types used in traces.
#[cfg(feature = "trace")]
#[path = ""]
pub mod trace {
#[path = "opentelemetry.proto.trace.v1.rs"]
pub mod v1;
}
/// Generated types used in zpages.
#[cfg(feature = "zpages")]
#[path = ""]
pub mod tracez {
#[path = "opentelemetry.proto.tracez.v1.rs"]
pub mod v1;
}
/// Generated types used in zpages.
#[cfg(feature = "profiles")]
#[path = ""]
pub mod profiles {
#[path = "opentelemetry.proto.profiles.v1development.rs"]
pub mod v1development;
}
pub use crate::transform::common::tonic::Attributes;
}
@@ -0,0 +1,374 @@
// This file is @generated by prost-build.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ExportLogsServiceRequest {
/// An array of ResourceLogs.
/// For data coming from a single resource this array will typically contain one
/// element. Intermediary nodes (such as OpenTelemetry Collector) that receive
/// data from multiple origins typically batch the data before forwarding further and
/// in that case this array will contain multiple elements.
#[prost(message, repeated, tag = "1")]
pub resource_logs: ::prost::alloc::vec::Vec<
super::super::super::logs::v1::ResourceLogs,
>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExportLogsServiceResponse {
/// The details of a partially successful export request.
///
/// If the request is only partially accepted
/// (i.e. when the server accepts only parts of the data and rejects the rest)
/// the server MUST initialize the `partial_success` field and MUST
/// set the `rejected_<signal>` with the number of items it rejected.
///
/// Servers MAY also make use of the `partial_success` field to convey
/// warnings/suggestions to senders even when the request was fully accepted.
/// In such cases, the `rejected_<signal>` MUST have a value of `0` and
/// the `error_message` MUST be non-empty.
///
/// A `partial_success` message with an empty value (rejected\_<signal> = 0 and
/// `error_message` = "") is equivalent to it not being set/present. Senders
/// SHOULD interpret it the same way as in the full success case.
#[prost(message, optional, tag = "1")]
pub partial_success: ::core::option::Option<ExportLogsPartialSuccess>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExportLogsPartialSuccess {
/// The number of rejected log records.
///
/// A `rejected_<signal>` field holding a `0` value indicates that the
/// request was fully accepted.
#[prost(int64, tag = "1")]
pub rejected_log_records: i64,
/// A developer-facing human-readable message in English. It should be used
/// either to explain why the server rejected parts of the data during a partial
/// success or to convey warnings/suggestions during a full success. The message
/// should offer guidance on how users can address such issues.
///
/// error_message is an optional field. An error_message with an empty value
/// is equivalent to it not being set.
#[prost(string, tag = "2")]
pub error_message: ::prost::alloc::string::String,
}
/// Generated client implementations.
#[cfg(feature = "gen-tonic")]
pub mod logs_service_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
/// Service that can be used to push logs between one Application instrumented with
/// OpenTelemetry and an collector, or between an collector and a central collector (in this
/// case logs are sent/received to/from multiple Applications).
#[derive(Debug, Clone)]
pub struct LogsServiceClient<T> {
inner: tonic::client::Grpc<T>,
}
impl LogsServiceClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> LogsServiceClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> LogsServiceClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
LogsServiceClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn export(
&mut self,
request: impl tonic::IntoRequest<super::ExportLogsServiceRequest>,
) -> std::result::Result<
tonic::Response<super::ExportLogsServiceResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/opentelemetry.proto.collector.logs.v1.LogsService/Export",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"opentelemetry.proto.collector.logs.v1.LogsService",
"Export",
),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
#[cfg(feature = "gen-tonic")]
pub mod logs_service_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with LogsServiceServer.
#[async_trait]
pub trait LogsService: std::marker::Send + std::marker::Sync + 'static {
async fn export(
&self,
request: tonic::Request<super::ExportLogsServiceRequest>,
) -> std::result::Result<
tonic::Response<super::ExportLogsServiceResponse>,
tonic::Status,
>;
}
/// Service that can be used to push logs between one Application instrumented with
/// OpenTelemetry and an collector, or between an collector and a central collector (in this
/// case logs are sent/received to/from multiple Applications).
#[derive(Debug)]
pub struct LogsServiceServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> LogsServiceServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for LogsServiceServer<T>
where
T: LogsService,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/opentelemetry.proto.collector.logs.v1.LogsService/Export" => {
#[allow(non_camel_case_types)]
struct ExportSvc<T: LogsService>(pub Arc<T>);
impl<
T: LogsService,
> tonic::server::UnaryService<super::ExportLogsServiceRequest>
for ExportSvc<T> {
type Response = super::ExportLogsServiceResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::ExportLogsServiceRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as LogsService>::export(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = ExportSvc(inner);
let codec = tonic_prost::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for LogsServiceServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "opentelemetry.proto.collector.logs.v1.LogsService";
impl<T> tonic::server::NamedService for LogsServiceServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}
@@ -0,0 +1,374 @@
// This file is @generated by prost-build.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ExportMetricsServiceRequest {
/// An array of ResourceMetrics.
/// For data coming from a single resource this array will typically contain one
/// element. Intermediary nodes (such as OpenTelemetry Collector) that receive
/// data from multiple origins typically batch the data before forwarding further and
/// in that case this array will contain multiple elements.
#[prost(message, repeated, tag = "1")]
pub resource_metrics: ::prost::alloc::vec::Vec<
super::super::super::metrics::v1::ResourceMetrics,
>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExportMetricsServiceResponse {
/// The details of a partially successful export request.
///
/// If the request is only partially accepted
/// (i.e. when the server accepts only parts of the data and rejects the rest)
/// the server MUST initialize the `partial_success` field and MUST
/// set the `rejected_<signal>` with the number of items it rejected.
///
/// Servers MAY also make use of the `partial_success` field to convey
/// warnings/suggestions to senders even when the request was fully accepted.
/// In such cases, the `rejected_<signal>` MUST have a value of `0` and
/// the `error_message` MUST be non-empty.
///
/// A `partial_success` message with an empty value (rejected\_<signal> = 0 and
/// `error_message` = "") is equivalent to it not being set/present. Senders
/// SHOULD interpret it the same way as in the full success case.
#[prost(message, optional, tag = "1")]
pub partial_success: ::core::option::Option<ExportMetricsPartialSuccess>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExportMetricsPartialSuccess {
/// The number of rejected data points.
///
/// A `rejected_<signal>` field holding a `0` value indicates that the
/// request was fully accepted.
#[prost(int64, tag = "1")]
pub rejected_data_points: i64,
/// A developer-facing human-readable message in English. It should be used
/// either to explain why the server rejected parts of the data during a partial
/// success or to convey warnings/suggestions during a full success. The message
/// should offer guidance on how users can address such issues.
///
/// error_message is an optional field. An error_message with an empty value
/// is equivalent to it not being set.
#[prost(string, tag = "2")]
pub error_message: ::prost::alloc::string::String,
}
/// Generated client implementations.
#[cfg(feature = "gen-tonic")]
pub mod metrics_service_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
/// Service that can be used to push metrics between one Application
/// instrumented with OpenTelemetry and a collector, or between a collector and a
/// central collector.
#[derive(Debug, Clone)]
pub struct MetricsServiceClient<T> {
inner: tonic::client::Grpc<T>,
}
impl MetricsServiceClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> MetricsServiceClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> MetricsServiceClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
MetricsServiceClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn export(
&mut self,
request: impl tonic::IntoRequest<super::ExportMetricsServiceRequest>,
) -> std::result::Result<
tonic::Response<super::ExportMetricsServiceResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/opentelemetry.proto.collector.metrics.v1.MetricsService/Export",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"opentelemetry.proto.collector.metrics.v1.MetricsService",
"Export",
),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
#[cfg(feature = "gen-tonic")]
pub mod metrics_service_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with MetricsServiceServer.
#[async_trait]
pub trait MetricsService: std::marker::Send + std::marker::Sync + 'static {
async fn export(
&self,
request: tonic::Request<super::ExportMetricsServiceRequest>,
) -> std::result::Result<
tonic::Response<super::ExportMetricsServiceResponse>,
tonic::Status,
>;
}
/// Service that can be used to push metrics between one Application
/// instrumented with OpenTelemetry and a collector, or between a collector and a
/// central collector.
#[derive(Debug)]
pub struct MetricsServiceServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> MetricsServiceServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for MetricsServiceServer<T>
where
T: MetricsService,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/opentelemetry.proto.collector.metrics.v1.MetricsService/Export" => {
#[allow(non_camel_case_types)]
struct ExportSvc<T: MetricsService>(pub Arc<T>);
impl<
T: MetricsService,
> tonic::server::UnaryService<super::ExportMetricsServiceRequest>
for ExportSvc<T> {
type Response = super::ExportMetricsServiceResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::ExportMetricsServiceRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as MetricsService>::export(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = ExportSvc(inner);
let codec = tonic_prost::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for MetricsServiceServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "opentelemetry.proto.collector.metrics.v1.MetricsService";
impl<T> tonic::server::NamedService for MetricsServiceServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}
@@ -0,0 +1,377 @@
// This file is @generated by prost-build.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ExportProfilesServiceRequest {
/// An array of ResourceProfiles.
/// For data coming from a single resource this array will typically contain one
/// element. Intermediary nodes (such as OpenTelemetry Collector) that receive
/// data from multiple origins typically batch the data before forwarding further and
/// in that case this array will contain multiple elements.
#[prost(message, repeated, tag = "1")]
pub resource_profiles: ::prost::alloc::vec::Vec<
super::super::super::profiles::v1development::ResourceProfiles,
>,
/// The reference table containing all data shared by profiles across the message being sent.
#[prost(message, optional, tag = "2")]
pub dictionary: ::core::option::Option<
super::super::super::profiles::v1development::ProfilesDictionary,
>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExportProfilesServiceResponse {
/// The details of a partially successful export request.
///
/// If the request is only partially accepted
/// (i.e. when the server accepts only parts of the data and rejects the rest)
/// the server MUST initialize the `partial_success` field and MUST
/// set the `rejected_<signal>` with the number of items it rejected.
///
/// Servers MAY also make use of the `partial_success` field to convey
/// warnings/suggestions to senders even when the request was fully accepted.
/// In such cases, the `rejected_<signal>` MUST have a value of `0` and
/// the `error_message` MUST be non-empty.
///
/// A `partial_success` message with an empty value (rejected\_<signal> = 0 and
/// `error_message` = "") is equivalent to it not being set/present. Senders
/// SHOULD interpret it the same way as in the full success case.
#[prost(message, optional, tag = "1")]
pub partial_success: ::core::option::Option<ExportProfilesPartialSuccess>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExportProfilesPartialSuccess {
/// The number of rejected profiles.
///
/// A `rejected_<signal>` field holding a `0` value indicates that the
/// request was fully accepted.
#[prost(int64, tag = "1")]
pub rejected_profiles: i64,
/// A developer-facing human-readable message in English. It should be used
/// either to explain why the server rejected parts of the data during a partial
/// success or to convey warnings/suggestions during a full success. The message
/// should offer guidance on how users can address such issues.
///
/// error_message is an optional field. An error_message with an empty value
/// is equivalent to it not being set.
#[prost(string, tag = "2")]
pub error_message: ::prost::alloc::string::String,
}
/// Generated client implementations.
#[cfg(feature = "gen-tonic")]
pub mod profiles_service_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
/// Service that can be used to push profiles between one Application instrumented with
/// OpenTelemetry and a collector, or between a collector and a central collector.
#[derive(Debug, Clone)]
pub struct ProfilesServiceClient<T> {
inner: tonic::client::Grpc<T>,
}
impl ProfilesServiceClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> ProfilesServiceClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> ProfilesServiceClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
ProfilesServiceClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn export(
&mut self,
request: impl tonic::IntoRequest<super::ExportProfilesServiceRequest>,
) -> std::result::Result<
tonic::Response<super::ExportProfilesServiceResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/opentelemetry.proto.collector.profiles.v1development.ProfilesService/Export",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"opentelemetry.proto.collector.profiles.v1development.ProfilesService",
"Export",
),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
#[cfg(feature = "gen-tonic")]
pub mod profiles_service_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with ProfilesServiceServer.
#[async_trait]
pub trait ProfilesService: std::marker::Send + std::marker::Sync + 'static {
async fn export(
&self,
request: tonic::Request<super::ExportProfilesServiceRequest>,
) -> std::result::Result<
tonic::Response<super::ExportProfilesServiceResponse>,
tonic::Status,
>;
}
/// Service that can be used to push profiles between one Application instrumented with
/// OpenTelemetry and a collector, or between a collector and a central collector.
#[derive(Debug)]
pub struct ProfilesServiceServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> ProfilesServiceServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for ProfilesServiceServer<T>
where
T: ProfilesService,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/opentelemetry.proto.collector.profiles.v1development.ProfilesService/Export" => {
#[allow(non_camel_case_types)]
struct ExportSvc<T: ProfilesService>(pub Arc<T>);
impl<
T: ProfilesService,
> tonic::server::UnaryService<super::ExportProfilesServiceRequest>
for ExportSvc<T> {
type Response = super::ExportProfilesServiceResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::ExportProfilesServiceRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as ProfilesService>::export(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = ExportSvc(inner);
let codec = tonic_prost::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for ProfilesServiceServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "opentelemetry.proto.collector.profiles.v1development.ProfilesService";
impl<T> tonic::server::NamedService for ProfilesServiceServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}
@@ -0,0 +1,374 @@
// This file is @generated by prost-build.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ExportTraceServiceRequest {
/// An array of ResourceSpans.
/// For data coming from a single resource this array will typically contain one
/// element. Intermediary nodes (such as OpenTelemetry Collector) that receive
/// data from multiple origins typically batch the data before forwarding further and
/// in that case this array will contain multiple elements.
#[prost(message, repeated, tag = "1")]
pub resource_spans: ::prost::alloc::vec::Vec<
super::super::super::trace::v1::ResourceSpans,
>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExportTraceServiceResponse {
/// The details of a partially successful export request.
///
/// If the request is only partially accepted
/// (i.e. when the server accepts only parts of the data and rejects the rest)
/// the server MUST initialize the `partial_success` field and MUST
/// set the `rejected_<signal>` with the number of items it rejected.
///
/// Servers MAY also make use of the `partial_success` field to convey
/// warnings/suggestions to senders even when the request was fully accepted.
/// In such cases, the `rejected_<signal>` MUST have a value of `0` and
/// the `error_message` MUST be non-empty.
///
/// A `partial_success` message with an empty value (rejected\_<signal> = 0 and
/// `error_message` = "") is equivalent to it not being set/present. Senders
/// SHOULD interpret it the same way as in the full success case.
#[prost(message, optional, tag = "1")]
pub partial_success: ::core::option::Option<ExportTracePartialSuccess>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ExportTracePartialSuccess {
/// The number of rejected spans.
///
/// A `rejected_<signal>` field holding a `0` value indicates that the
/// request was fully accepted.
#[prost(int64, tag = "1")]
pub rejected_spans: i64,
/// A developer-facing human-readable message in English. It should be used
/// either to explain why the server rejected parts of the data during a partial
/// success or to convey warnings/suggestions during a full success. The message
/// should offer guidance on how users can address such issues.
///
/// error_message is an optional field. An error_message with an empty value
/// is equivalent to it not being set.
#[prost(string, tag = "2")]
pub error_message: ::prost::alloc::string::String,
}
/// Generated client implementations.
#[cfg(feature = "gen-tonic")]
pub mod trace_service_client {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
use tonic::codegen::http::Uri;
/// Service that can be used to push spans between one Application instrumented with
/// OpenTelemetry and a collector, or between a collector and a central collector (in this
/// case spans are sent/received to/from multiple Applications).
#[derive(Debug, Clone)]
pub struct TraceServiceClient<T> {
inner: tonic::client::Grpc<T>,
}
impl TraceServiceClient<tonic::transport::Channel> {
/// Attempt to create a new client by connecting to a given endpoint.
pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
where
D: TryInto<tonic::transport::Endpoint>,
D::Error: Into<StdError>,
{
let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
Ok(Self::new(conn))
}
}
impl<T> TraceServiceClient<T>
where
T: tonic::client::GrpcService<tonic::body::Body>,
T::Error: Into<StdError>,
T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static,
<T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
{
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub fn with_origin(inner: T, origin: Uri) -> Self {
let inner = tonic::client::Grpc::with_origin(inner, origin);
Self { inner }
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> TraceServiceClient<InterceptedService<T, F>>
where
F: tonic::service::Interceptor,
T::ResponseBody: Default,
T: tonic::codegen::Service<
http::Request<tonic::body::Body>,
Response = http::Response<
<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody,
>,
>,
<T as tonic::codegen::Service<
http::Request<tonic::body::Body>,
>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
{
TraceServiceClient::new(InterceptedService::new(inner, interceptor))
}
/// Compress requests with the given encoding.
///
/// This requires the server to support it otherwise it might respond with an
/// error.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.send_compressed(encoding);
self
}
/// Enable decompressing responses.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_decoding_message_size(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.inner = self.inner.max_encoding_message_size(limit);
self
}
pub async fn export(
&mut self,
request: impl tonic::IntoRequest<super::ExportTraceServiceRequest>,
) -> std::result::Result<
tonic::Response<super::ExportTraceServiceResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic_prost::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/opentelemetry.proto.collector.trace.v1.TraceService/Export",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(
GrpcMethod::new(
"opentelemetry.proto.collector.trace.v1.TraceService",
"Export",
),
);
self.inner.unary(req, path, codec).await
}
}
}
/// Generated server implementations.
#[cfg(feature = "gen-tonic")]
pub mod trace_service_server {
#![allow(
unused_variables,
dead_code,
missing_docs,
clippy::wildcard_imports,
clippy::let_unit_value,
)]
use tonic::codegen::*;
/// Generated trait containing gRPC methods that should be implemented for use with TraceServiceServer.
#[async_trait]
pub trait TraceService: std::marker::Send + std::marker::Sync + 'static {
async fn export(
&self,
request: tonic::Request<super::ExportTraceServiceRequest>,
) -> std::result::Result<
tonic::Response<super::ExportTraceServiceResponse>,
tonic::Status,
>;
}
/// Service that can be used to push spans between one Application instrumented with
/// OpenTelemetry and a collector, or between a collector and a central collector (in this
/// case spans are sent/received to/from multiple Applications).
#[derive(Debug)]
pub struct TraceServiceServer<T> {
inner: Arc<T>,
accept_compression_encodings: EnabledCompressionEncodings,
send_compression_encodings: EnabledCompressionEncodings,
max_decoding_message_size: Option<usize>,
max_encoding_message_size: Option<usize>,
}
impl<T> TraceServiceServer<T> {
pub fn new(inner: T) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<T>) -> Self {
Self {
inner,
accept_compression_encodings: Default::default(),
send_compression_encodings: Default::default(),
max_decoding_message_size: None,
max_encoding_message_size: None,
}
}
pub fn with_interceptor<F>(
inner: T,
interceptor: F,
) -> InterceptedService<Self, F>
where
F: tonic::service::Interceptor,
{
InterceptedService::new(Self::new(inner), interceptor)
}
/// Enable decompressing requests with the given encoding.
#[must_use]
pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.accept_compression_encodings.enable(encoding);
self
}
/// Compress responses with the given encoding, if the client supports it.
#[must_use]
pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
self.send_compression_encodings.enable(encoding);
self
}
/// Limits the maximum size of a decoded message.
///
/// Default: `4MB`
#[must_use]
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
self.max_decoding_message_size = Some(limit);
self
}
/// Limits the maximum size of an encoded message.
///
/// Default: `usize::MAX`
#[must_use]
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
self.max_encoding_message_size = Some(limit);
self
}
}
impl<T, B> tonic::codegen::Service<http::Request<B>> for TraceServiceServer<T>
where
T: TraceService,
B: Body + std::marker::Send + 'static,
B::Error: Into<StdError> + std::marker::Send + 'static,
{
type Response = http::Response<tonic::body::Body>;
type Error = std::convert::Infallible;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<B>) -> Self::Future {
match req.uri().path() {
"/opentelemetry.proto.collector.trace.v1.TraceService/Export" => {
#[allow(non_camel_case_types)]
struct ExportSvc<T: TraceService>(pub Arc<T>);
impl<
T: TraceService,
> tonic::server::UnaryService<super::ExportTraceServiceRequest>
for ExportSvc<T> {
type Response = super::ExportTraceServiceResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<super::ExportTraceServiceRequest>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as TraceService>::export(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = ExportSvc(inner);
let codec = tonic_prost::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
_ => {
Box::pin(async move {
let mut response = http::Response::new(
tonic::body::Body::default(),
);
let headers = response.headers_mut();
headers
.insert(
tonic::Status::GRPC_STATUS,
(tonic::Code::Unimplemented as i32).into(),
);
headers
.insert(
http::header::CONTENT_TYPE,
tonic::metadata::GRPC_CONTENT_TYPE,
);
Ok(response)
})
}
}
}
}
impl<T> Clone for TraceServiceServer<T> {
fn clone(&self) -> Self {
let inner = self.inner.clone();
Self {
inner,
accept_compression_encodings: self.accept_compression_encodings,
send_compression_encodings: self.send_compression_encodings,
max_decoding_message_size: self.max_decoding_message_size,
max_encoding_message_size: self.max_encoding_message_size,
}
}
}
/// Generated gRPC service name
pub const SERVICE_NAME: &str = "opentelemetry.proto.collector.trace.v1.TraceService";
impl<T> tonic::server::NamedService for TraceServiceServer<T> {
const NAME: &'static str = SERVICE_NAME;
}
}
@@ -0,0 +1,187 @@
// This file is @generated by prost-build.
/// Represents any type of attribute value. AnyValue may contain a
/// primitive value such as a string or integer or it may contain an arbitrary nested
/// object containing arrays, key-value lists and primitives.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnyValue {
/// The value is one of the listed fields. It is valid for all values to be unspecified
/// in which case this AnyValue is considered to be "empty".
#[prost(oneof = "any_value::Value", tags = "1, 2, 3, 4, 5, 6, 7, 8")]
#[cfg_attr(
feature = "with-serde",
serde(
flatten,
serialize_with = "crate::proto::serializers::serialize_to_value",
deserialize_with = "crate::proto::serializers::deserialize_from_value"
)
)]
pub value: ::core::option::Option<any_value::Value>,
}
/// Nested message and enum types in `AnyValue`.
pub mod any_value {
/// The value is one of the listed fields. It is valid for all values to be unspecified
/// in which case this AnyValue is considered to be "empty".
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Value {
#[prost(string, tag = "1")]
StringValue(::prost::alloc::string::String),
#[prost(bool, tag = "2")]
BoolValue(bool),
#[prost(int64, tag = "3")]
IntValue(i64),
#[prost(double, tag = "4")]
DoubleValue(f64),
#[prost(message, tag = "5")]
ArrayValue(super::ArrayValue),
#[prost(message, tag = "6")]
KvlistValue(super::KeyValueList),
#[prost(bytes, tag = "7")]
BytesValue(::prost::alloc::vec::Vec<u8>),
/// Reference to the string value in ProfilesDictionary.string_table.
///
/// Note: This is currently used exclusively in the Profiling signal.
/// Implementers of OTLP receivers for signals other than Profiling should
/// treat the presence of this value as a non-fatal issue.
/// Log an error or warning indicating an unexpected field intended for the
/// Profiling signal and process the data as if this value were absent or
/// empty, ignoring its semantic content for the non-Profiling signal.
///
/// Status: \[Development\]
#[prost(int32, tag = "8")]
StringValueStrindex(i32),
}
}
/// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message
/// since oneof in AnyValue does not allow repeated fields.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ArrayValue {
/// Array of values. The array may be empty (contain 0 elements).
#[prost(message, repeated, tag = "1")]
pub values: ::prost::alloc::vec::Vec<AnyValue>,
}
/// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message
/// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need
/// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to
/// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches
/// are semantically equivalent.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct KeyValueList {
/// A collection of key/value pairs of key-value pairs. The list may be empty (may
/// contain 0 elements).
///
/// The keys MUST be unique (it is not allowed to have more than one
/// value with the same key).
/// The behavior of software that receives duplicated keys can be unpredictable.
#[prost(message, repeated, tag = "1")]
pub values: ::prost::alloc::vec::Vec<KeyValue>,
}
/// Represents a key-value pair that is used to store Span attributes, Link
/// attributes, etc.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct KeyValue {
/// The key name of the pair.
/// key_ref MUST NOT be set if key is used.
#[prost(string, tag = "1")]
pub key: ::prost::alloc::string::String,
/// The value of the pair.
#[prost(message, optional, tag = "2")]
pub value: ::core::option::Option<AnyValue>,
/// Reference to the string key in ProfilesDictionary.string_table.
/// key MUST NOT be set if key_strindex is used.
///
/// Note: This is currently used exclusively in the Profiling signal.
/// Implementers of OTLP receivers for signals other than Profiling should
/// treat the presence of this key as a non-fatal issue.
/// Log an error or warning indicating an unexpected field intended for the
/// Profiling signal and process the data as if this value were absent or
/// empty, ignoring its semantic content for the non-Profiling signal.
///
/// Status: \[Development\]
#[prost(int32, tag = "3")]
#[cfg_attr(
feature = "with-serde",
serde(default, skip_serializing_if = "crate::proto::serializers::is_default")
)]
pub key_strindex: i32,
}
/// InstrumentationScope is a message representing the instrumentation scope information
/// such as the fully qualified name and version.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct InstrumentationScope {
/// A name denoting the Instrumentation scope.
/// An empty instrumentation scope name means the name is unknown.
#[prost(string, tag = "1")]
pub name: ::prost::alloc::string::String,
/// Defines the version of the instrumentation scope.
/// An empty instrumentation scope version means the version is unknown.
#[prost(string, tag = "2")]
pub version: ::prost::alloc::string::String,
/// Additional attributes that describe the scope. \[Optional\].
/// Attribute keys MUST be unique (it is not allowed to have more than one
/// attribute with the same key).
/// The behavior of software that receives duplicated keys can be unpredictable.
#[prost(message, repeated, tag = "3")]
pub attributes: ::prost::alloc::vec::Vec<KeyValue>,
/// The number of attributes that were discarded. Attributes
/// can be discarded because their keys are too long or because there are too many
/// attributes. If this value is 0, then no attributes were dropped.
#[prost(uint32, tag = "4")]
pub dropped_attributes_count: u32,
}
/// A reference to an Entity.
/// Entity represents an object of interest associated with produced telemetry: e.g spans, metrics, profiles, or logs.
///
/// Status: \[Development\]
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct EntityRef {
/// The Schema URL, if known. This is the identifier of the Schema that the entity data
/// is recorded in. To learn more about Schema URL see
/// <https://opentelemetry.io/docs/specs/otel/schemas/#schema-url>
///
/// This schema_url applies to the data in this message and to the Resource attributes
/// referenced by id_keys and description_keys.
/// TODO: discuss if we are happy with this somewhat complicated definition of what
/// the schema_url applies to.
///
/// This field obsoletes the schema_url field in ResourceMetrics/ResourceSpans/ResourceLogs.
#[prost(string, tag = "1")]
pub schema_url: ::prost::alloc::string::String,
/// Defines the type of the entity. MUST not change during the lifetime of the entity.
/// For example: "service" or "host". This field is required and MUST not be empty
/// for valid entities.
#[prost(string, tag = "2")]
pub r#type: ::prost::alloc::string::String,
/// Attribute Keys that identify the entity.
/// MUST not change during the lifetime of the entity. The Id must contain at least one attribute.
/// These keys MUST exist in the containing {message}.attributes.
#[prost(string, repeated, tag = "3")]
pub id_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
/// Descriptive (non-identifying) attribute keys of the entity.
/// MAY change over the lifetime of the entity. MAY be empty.
/// These attribute keys are not part of entity's identity.
/// These keys MUST exist in the containing {message}.attributes.
#[prost(string, repeated, tag = "4")]
pub description_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
}
@@ -0,0 +1,338 @@
// This file is @generated by prost-build.
/// LogsData represents the logs data that can be stored in a persistent storage,
/// OR can be embedded by other protocols that transfer OTLP logs data but do not
/// implement the OTLP protocol.
///
/// The main difference between this message and collector protocol is that
/// in this message there will not be any "control" or "metadata" specific to
/// OTLP protocol.
///
/// When new fields are added into this message, the OTLP request MUST be updated
/// as well.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LogsData {
/// An array of ResourceLogs.
/// For data coming from a single resource this array will typically contain
/// one element. Intermediary nodes that receive data from multiple origins
/// typically batch the data before forwarding further and in that case this
/// array will contain multiple elements.
#[prost(message, repeated, tag = "1")]
pub resource_logs: ::prost::alloc::vec::Vec<ResourceLogs>,
}
/// A collection of ScopeLogs from a Resource.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ResourceLogs {
/// The resource for the logs in this message.
/// If this field is not set then resource info is unknown.
#[prost(message, optional, tag = "1")]
pub resource: ::core::option::Option<super::super::resource::v1::Resource>,
/// A list of ScopeLogs that originate from a resource.
#[prost(message, repeated, tag = "2")]
pub scope_logs: ::prost::alloc::vec::Vec<ScopeLogs>,
/// The Schema URL, if known. This is the identifier of the Schema that the resource data
/// is recorded in. Notably, the last part of the URL path is the version number of the
/// schema: http\[s\]://server\[:port\]/path/<version>. To learn more about Schema URL see
/// <https://opentelemetry.io/docs/specs/otel/schemas/#schema-url>
/// This schema_url applies to the data in the "resource" field. It does not apply
/// to the data in the "scope_logs" field which have their own schema_url field.
#[prost(string, tag = "3")]
pub schema_url: ::prost::alloc::string::String,
}
/// A collection of Logs produced by a Scope.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ScopeLogs {
/// The instrumentation scope information for the logs in this message.
/// Semantically when InstrumentationScope isn't set, it is equivalent with
/// an empty instrumentation scope name (unknown).
#[prost(message, optional, tag = "1")]
pub scope: ::core::option::Option<super::super::common::v1::InstrumentationScope>,
/// A list of log records.
#[prost(message, repeated, tag = "2")]
pub log_records: ::prost::alloc::vec::Vec<LogRecord>,
/// The Schema URL, if known. This is the identifier of the Schema that the log data
/// is recorded in. Notably, the last part of the URL path is the version number of the
/// schema: http\[s\]://server\[:port\]/path/<version>. To learn more about Schema URL see
/// <https://opentelemetry.io/docs/specs/otel/schemas/#schema-url>
/// This schema_url applies to the data in the "scope" field and all logs in the
/// "log_records" field.
#[prost(string, tag = "3")]
pub schema_url: ::prost::alloc::string::String,
}
/// A log record according to OpenTelemetry Log Data Model:
/// <https://github.com/open-telemetry/oteps/blob/main/text/logs/0097-log-data-model.md>
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LogRecord {
/// time_unix_nano is the time when the event occurred.
/// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970.
/// Value of 0 indicates unknown or missing timestamp.
#[prost(fixed64, tag = "1")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_u64_to_string",
deserialize_with = "crate::proto::serializers::deserialize_string_to_u64"
)
)]
pub time_unix_nano: u64,
/// Time when the event was observed by the collection system.
/// For events that originate in OpenTelemetry (e.g. using OpenTelemetry Logging SDK)
/// this timestamp is typically set at the generation time and is equal to Timestamp.
/// For events originating externally and collected by OpenTelemetry (e.g. using
/// Collector) this is the time when OpenTelemetry's code observed the event measured
/// by the clock of the OpenTelemetry code. This field MUST be set once the event is
/// observed by OpenTelemetry.
///
/// For converting OpenTelemetry log data to formats that support only one timestamp or
/// when receiving OpenTelemetry log data by recipients that support only one timestamp
/// internally the following logic is recommended:
///
/// * Use time_unix_nano if it is present, otherwise use observed_time_unix_nano.
///
/// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970.
/// Value of 0 indicates unknown or missing timestamp.
#[prost(fixed64, tag = "11")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_u64_to_string",
deserialize_with = "crate::proto::serializers::deserialize_string_to_u64"
)
)]
pub observed_time_unix_nano: u64,
/// Numerical value of the severity, normalized to values described in Log Data Model.
/// \[Optional\].
#[prost(enumeration = "SeverityNumber", tag = "2")]
pub severity_number: i32,
/// The severity text (also known as log level). The original string representation as
/// it is known at the source. \[Optional\].
#[prost(string, tag = "3")]
pub severity_text: ::prost::alloc::string::String,
/// A value containing the body of the log record. Can be for example a human-readable
/// string message (including multi-line) describing the event in a free form or it can
/// be a structured data composed of arrays and maps of other values. \[Optional\].
#[prost(message, optional, tag = "5")]
pub body: ::core::option::Option<super::super::common::v1::AnyValue>,
/// Additional attributes that describe the specific event occurrence. \[Optional\].
/// Attribute keys MUST be unique (it is not allowed to have more than one
/// attribute with the same key).
/// The behavior of software that receives duplicated keys can be unpredictable.
#[prost(message, repeated, tag = "6")]
pub attributes: ::prost::alloc::vec::Vec<super::super::common::v1::KeyValue>,
#[prost(uint32, tag = "7")]
pub dropped_attributes_count: u32,
/// Flags, a bit field. 8 least significant bits are the trace flags as
/// defined in W3C Trace Context specification. 24 most significant bits are reserved
/// and must be set to 0. Readers must not assume that 24 most significant bits
/// will be zero and must correctly mask the bits when reading 8-bit trace flag (use
/// flags & LOG_RECORD_FLAGS_TRACE_FLAGS_MASK). \[Optional\].
#[prost(fixed32, tag = "8")]
pub flags: u32,
/// A unique identifier for a trace. All logs from the same trace share
/// the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR
/// of length other than 16 bytes is considered invalid (empty string in OTLP/JSON
/// is zero-length and thus is also invalid).
///
/// This field is optional.
///
/// The receivers SHOULD assume that the log record is not associated with a
/// trace if any of the following is true:
///
/// * the field is not present,
/// * the field contains an invalid value.
#[prost(bytes = "vec", tag = "9")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_to_hex_string",
deserialize_with = "crate::proto::serializers::deserialize_from_hex_string"
)
)]
pub trace_id: ::prost::alloc::vec::Vec<u8>,
/// A unique identifier for a span within a trace, assigned when the span
/// is created. The ID is an 8-byte array. An ID with all zeroes OR of length
/// other than 8 bytes is considered invalid (empty string in OTLP/JSON
/// is zero-length and thus is also invalid).
///
/// This field is optional. If the sender specifies a valid span_id then it SHOULD also
/// specify a valid trace_id.
///
/// The receivers SHOULD assume that the log record is not associated with a
/// span if any of the following is true:
///
/// * the field is not present,
/// * the field contains an invalid value.
#[prost(bytes = "vec", tag = "10")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_to_hex_string",
deserialize_with = "crate::proto::serializers::deserialize_from_hex_string"
)
)]
pub span_id: ::prost::alloc::vec::Vec<u8>,
/// A unique identifier of event category/type.
/// All events with the same event_name are expected to conform to the same
/// schema for both their attributes and their body.
///
/// Recommended to be fully qualified and short (no longer than 256 characters).
///
/// Presence of event_name on the log record identifies this record
/// as an event.
///
/// \[Optional\].
#[prost(string, tag = "12")]
pub event_name: ::prost::alloc::string::String,
}
/// Possible values for LogRecord.SeverityNumber.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum SeverityNumber {
Unspecified = 0,
Trace = 1,
Trace2 = 2,
Trace3 = 3,
Trace4 = 4,
Debug = 5,
Debug2 = 6,
Debug3 = 7,
Debug4 = 8,
Info = 9,
Info2 = 10,
Info3 = 11,
Info4 = 12,
Warn = 13,
Warn2 = 14,
Warn3 = 15,
Warn4 = 16,
Error = 17,
Error2 = 18,
Error3 = 19,
Error4 = 20,
Fatal = 21,
Fatal2 = 22,
Fatal3 = 23,
Fatal4 = 24,
}
impl SeverityNumber {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Unspecified => "SEVERITY_NUMBER_UNSPECIFIED",
Self::Trace => "SEVERITY_NUMBER_TRACE",
Self::Trace2 => "SEVERITY_NUMBER_TRACE2",
Self::Trace3 => "SEVERITY_NUMBER_TRACE3",
Self::Trace4 => "SEVERITY_NUMBER_TRACE4",
Self::Debug => "SEVERITY_NUMBER_DEBUG",
Self::Debug2 => "SEVERITY_NUMBER_DEBUG2",
Self::Debug3 => "SEVERITY_NUMBER_DEBUG3",
Self::Debug4 => "SEVERITY_NUMBER_DEBUG4",
Self::Info => "SEVERITY_NUMBER_INFO",
Self::Info2 => "SEVERITY_NUMBER_INFO2",
Self::Info3 => "SEVERITY_NUMBER_INFO3",
Self::Info4 => "SEVERITY_NUMBER_INFO4",
Self::Warn => "SEVERITY_NUMBER_WARN",
Self::Warn2 => "SEVERITY_NUMBER_WARN2",
Self::Warn3 => "SEVERITY_NUMBER_WARN3",
Self::Warn4 => "SEVERITY_NUMBER_WARN4",
Self::Error => "SEVERITY_NUMBER_ERROR",
Self::Error2 => "SEVERITY_NUMBER_ERROR2",
Self::Error3 => "SEVERITY_NUMBER_ERROR3",
Self::Error4 => "SEVERITY_NUMBER_ERROR4",
Self::Fatal => "SEVERITY_NUMBER_FATAL",
Self::Fatal2 => "SEVERITY_NUMBER_FATAL2",
Self::Fatal3 => "SEVERITY_NUMBER_FATAL3",
Self::Fatal4 => "SEVERITY_NUMBER_FATAL4",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"SEVERITY_NUMBER_UNSPECIFIED" => Some(Self::Unspecified),
"SEVERITY_NUMBER_TRACE" => Some(Self::Trace),
"SEVERITY_NUMBER_TRACE2" => Some(Self::Trace2),
"SEVERITY_NUMBER_TRACE3" => Some(Self::Trace3),
"SEVERITY_NUMBER_TRACE4" => Some(Self::Trace4),
"SEVERITY_NUMBER_DEBUG" => Some(Self::Debug),
"SEVERITY_NUMBER_DEBUG2" => Some(Self::Debug2),
"SEVERITY_NUMBER_DEBUG3" => Some(Self::Debug3),
"SEVERITY_NUMBER_DEBUG4" => Some(Self::Debug4),
"SEVERITY_NUMBER_INFO" => Some(Self::Info),
"SEVERITY_NUMBER_INFO2" => Some(Self::Info2),
"SEVERITY_NUMBER_INFO3" => Some(Self::Info3),
"SEVERITY_NUMBER_INFO4" => Some(Self::Info4),
"SEVERITY_NUMBER_WARN" => Some(Self::Warn),
"SEVERITY_NUMBER_WARN2" => Some(Self::Warn2),
"SEVERITY_NUMBER_WARN3" => Some(Self::Warn3),
"SEVERITY_NUMBER_WARN4" => Some(Self::Warn4),
"SEVERITY_NUMBER_ERROR" => Some(Self::Error),
"SEVERITY_NUMBER_ERROR2" => Some(Self::Error2),
"SEVERITY_NUMBER_ERROR3" => Some(Self::Error3),
"SEVERITY_NUMBER_ERROR4" => Some(Self::Error4),
"SEVERITY_NUMBER_FATAL" => Some(Self::Fatal),
"SEVERITY_NUMBER_FATAL2" => Some(Self::Fatal2),
"SEVERITY_NUMBER_FATAL3" => Some(Self::Fatal3),
"SEVERITY_NUMBER_FATAL4" => Some(Self::Fatal4),
_ => None,
}
}
}
/// LogRecordFlags represents constants used to interpret the
/// LogRecord.flags field, which is protobuf 'fixed32' type and is to
/// be used as bit-fields. Each non-zero value defined in this enum is
/// a bit-mask. To extract the bit-field, for example, use an
/// expression like:
///
/// (logRecord.flags & LOG_RECORD_FLAGS_TRACE_FLAGS_MASK)
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum LogRecordFlags {
/// The zero value for the enum. Should not be used for comparisons.
/// Instead use bitwise "and" with the appropriate mask as shown above.
DoNotUse = 0,
/// Bits 0-7 are used for trace flags.
TraceFlagsMask = 255,
}
impl LogRecordFlags {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::DoNotUse => "LOG_RECORD_FLAGS_DO_NOT_USE",
Self::TraceFlagsMask => "LOG_RECORD_FLAGS_TRACE_FLAGS_MASK",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"LOG_RECORD_FLAGS_DO_NOT_USE" => Some(Self::DoNotUse),
"LOG_RECORD_FLAGS_TRACE_FLAGS_MASK" => Some(Self::TraceFlagsMask),
_ => None,
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,463 @@
// This file is @generated by prost-build.
/// ProfilesDictionary represents the profiles data shared across the
/// entire message being sent. The following applies to all fields in this
/// message:
///
/// * A dictionary is an array of dictionary items. Users of the dictionary
/// compactly reference the items using the index within the array.
///
/// * A dictionary MUST have a zero value encoded as the first element. This
/// allows for \_index fields pointing into the dictionary to use a 0 pointer
/// value to indicate 'null' / 'not set'. Unless otherwise defined, a 'zero
/// value' message value is one with all default field values, so as to
/// minimize wire encoded size.
///
/// * There SHOULD NOT be dupes in a dictionary. The identity of dictionary
/// items is based on their value, recursively as needed. If a particular
/// implementation does emit duplicated items, it MUST NOT attempt to give them
/// meaning based on the index or order. A profile processor may remove
/// duplicate items and this MUST NOT have any observable effects for
/// consumers.
///
/// * There SHOULD NOT be orphaned (unreferenced) items in a dictionary. A
/// profile processor may remove ("garbage-collect") orphaned items and this
/// MUST NOT have any observable effects for consumers.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProfilesDictionary {
/// Mappings from address ranges to the image/binary/library mapped
/// into that address range referenced by locations via Location.mapping_index.
///
/// mapping_table\[0\] must always be zero value (Mapping{}) and present.
#[prost(message, repeated, tag = "1")]
pub mapping_table: ::prost::alloc::vec::Vec<Mapping>,
/// Locations referenced by samples via Stack.location_indices.
///
/// location_table\[0\] must always be zero value (Location{}) and present.
#[prost(message, repeated, tag = "2")]
pub location_table: ::prost::alloc::vec::Vec<Location>,
/// Functions referenced by locations via Line.function_index.
///
/// function_table\[0\] must always be zero value (Function{}) and present.
#[prost(message, repeated, tag = "3")]
pub function_table: ::prost::alloc::vec::Vec<Function>,
/// Links referenced by samples via Sample.link_index.
///
/// link_table\[0\] must always be zero value (Link{}) and present.
#[prost(message, repeated, tag = "4")]
pub link_table: ::prost::alloc::vec::Vec<Link>,
/// A common table for strings referenced by various messages.
///
/// string_table\[0\] must always be "" and present.
#[prost(string, repeated, tag = "5")]
pub string_table: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
/// A common table for attributes referenced by the Profile, Sample, Mapping
/// and Location messages below through attribute_indices field. Each entry is
/// a key/value pair with an optional unit. Since this is a dictionary table,
/// multiple entries with the same key may be present, unlike direct attribute
/// tables like Resource.attributes. The referencing attribute_indices fields,
/// though, do maintain the key uniqueness requirement.
///
/// It's recommended to use attributes for variables with bounded cardinality,
/// such as categorical variables
/// (<https://en.wikipedia.org/wiki/Categorical_variable>). Using an attribute of
/// a floating point type (e.g., CPU time) in a sample can quickly make every
/// attribute value unique, defeating the purpose of the dictionary and
/// impractically increasing the profile size.
///
/// Examples of attributes:
/// "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
/// "abc.com/myattribute": true
/// "allocation_size": 128 bytes
///
/// attribute_table\[0\] must always be zero value (KeyValueAndUnit{}) and present.
#[prost(message, repeated, tag = "6")]
pub attribute_table: ::prost::alloc::vec::Vec<KeyValueAndUnit>,
/// Stacks referenced by samples via Sample.stack_index.
///
/// stack_table\[0\] must always be zero value (Stack{}) and present.
#[prost(message, repeated, tag = "7")]
pub stack_table: ::prost::alloc::vec::Vec<Stack>,
}
/// ProfilesData represents the profiles data that can be stored in persistent storage,
/// OR can be embedded by other protocols that transfer OTLP profiles data but do not
/// implement the OTLP protocol.
///
/// The main difference between this message and collector protocol is that
/// in this message there will not be any "control" or "metadata" specific to
/// OTLP protocol.
///
/// When new fields are added into this message, the OTLP request MUST be updated
/// as well.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProfilesData {
/// An array of ResourceProfiles.
/// For data coming from an SDK profiler, this array will typically contain one
/// element. Host-level profilers will usually create one ResourceProfile per
/// container, as well as one additional ResourceProfile grouping all samples
/// from non-containerized processes.
/// Other resource groupings are possible as well and clarified via
/// Resource.attributes and semantic conventions.
/// Tools that visualize profiles should prefer displaying
/// resources_profiles\[0\].scope_profiles\[0\].profiles\[0\] by default.
#[prost(message, repeated, tag = "1")]
pub resource_profiles: ::prost::alloc::vec::Vec<ResourceProfiles>,
/// One instance of ProfilesDictionary
#[prost(message, optional, tag = "2")]
pub dictionary: ::core::option::Option<ProfilesDictionary>,
}
/// A collection of ScopeProfiles from a Resource.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ResourceProfiles {
/// The resource for the profiles in this message.
/// If this field is not set then no resource info is known.
#[prost(message, optional, tag = "1")]
pub resource: ::core::option::Option<super::super::resource::v1::Resource>,
/// A list of ScopeProfiles that originate from a resource.
#[prost(message, repeated, tag = "2")]
pub scope_profiles: ::prost::alloc::vec::Vec<ScopeProfiles>,
/// The Schema URL, if known. This is the identifier of the Schema that the resource data
/// is recorded in. Notably, the last part of the URL path is the version number of the
/// schema: http\[s\]://server\[:port\]/path/<version>. To learn more about Schema URL see
/// <https://opentelemetry.io/docs/specs/otel/schemas/#schema-url>
/// This schema_url applies to the data in the "resource" field. It does not apply
/// to the data in the "scope_profiles" field which have their own schema_url field.
#[prost(string, tag = "3")]
pub schema_url: ::prost::alloc::string::String,
}
/// A collection of Profiles produced by an InstrumentationScope.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ScopeProfiles {
/// The instrumentation scope information for the profiles in this message.
/// Semantically when InstrumentationScope isn't set, it is equivalent with
/// an empty instrumentation scope name (unknown).
#[prost(message, optional, tag = "1")]
pub scope: ::core::option::Option<super::super::common::v1::InstrumentationScope>,
/// A list of Profiles that originate from an instrumentation scope.
#[prost(message, repeated, tag = "2")]
pub profiles: ::prost::alloc::vec::Vec<Profile>,
/// The Schema URL, if known. This is the identifier of the Schema that the profile data
/// is recorded in. Notably, the last part of the URL path is the version number of the
/// schema: http\[s\]://server\[:port\]/path/<version>. To learn more about Schema URL see
/// <https://opentelemetry.io/docs/specs/otel/schemas/#schema-url>
/// This schema_url applies to the data in the "scope" field and all profiles in the
/// "profiles" field.
#[prost(string, tag = "3")]
pub schema_url: ::prost::alloc::string::String,
}
/// Represents a complete profile, including sample types, samples, mappings to
/// binaries, stacks, locations, functions, string table, and additional
/// metadata. It modifies and annotates pprof Profile with OpenTelemetry
/// specific fields.
///
/// Note that whilst fields in this message retain the name and field id from pprof in most cases
/// for ease of understanding data migration, it is not intended that pprof:Profile and
/// OpenTelemetry:Profile encoding be wire compatible.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Profile {
/// The type and unit of all Sample.values in this profile.
/// For a cpu or off-cpu profile this might be:
/// \["cpu","nanoseconds"\] or \["off_cpu","nanoseconds"\]
/// For a heap profile, this might be:
/// \["allocated_objects","count"\] or \["allocated_space","bytes"\],
#[prost(message, optional, tag = "1")]
pub sample_type: ::core::option::Option<ValueType>,
/// The set of samples recorded in this profile.
#[prost(message, repeated, tag = "2")]
pub samples: ::prost::alloc::vec::Vec<Sample>,
/// Time of collection. Value is UNIX Epoch time in nanoseconds since 00:00:00
/// UTC on 1 January 1970.
#[prost(fixed64, tag = "3")]
pub time_unix_nano: u64,
/// Duration of the profile. For instant profiles like live heap snapshot, the
/// duration can be zero but it may be preferable to set time_unix_nano to the
/// process start time and duration_nano to the relative time when the profile
/// was gathered. This ensures Sample.timestamps_unix_nano values such as
/// allocation timestamp fall into the profile time range.
#[prost(uint64, tag = "4")]
pub duration_nano: u64,
/// The kind of events between sampled occurrences.
/// e.g \[ "cpu","cycles" \] or \[ "heap","bytes" \]
#[prost(message, optional, tag = "5")]
pub period_type: ::core::option::Option<ValueType>,
/// The number of events between sampled occurrences.
#[prost(int64, tag = "6")]
pub period: i64,
/// A globally unique identifier for a profile. The ID is a 16-byte array. An ID with
/// all zeroes is considered invalid. It may be used for deduplication and signal
/// correlation purposes. It is acceptable to treat two profiles with different values
/// in this field as not equal, even if they represented the same object at an earlier
/// time.
/// This field is optional; an ID may be assigned to an ID-less profile in a later step.
#[prost(bytes = "vec", tag = "7")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_to_hex_string",
deserialize_with = "crate::proto::serializers::deserialize_from_hex_string"
)
)]
pub profile_id: ::prost::alloc::vec::Vec<u8>,
/// The number of attributes that were discarded. Attributes
/// can be discarded because their keys are too long or because there are too many
/// attributes. If this value is 0, then no attributes were dropped.
#[prost(uint32, tag = "8")]
pub dropped_attributes_count: u32,
/// The original payload format. See also original_payload. Optional, but the
/// format and the bytes must be set or unset together.
///
/// The allowed values for the format string are defined by the OpenTelemetry
/// specification. Some examples are "jfr", "pprof", "linux_perf".
///
/// The original payload may be optionally provided when the conversion to the
/// OLTP format was done from a different format with some loss of the fidelity
/// and the receiver may want to store the original payload to allow future
/// lossless export or reinterpretation. Some examples of the original format
/// are JFR (Java Flight Recorder), pprof, Linux perf.
///
/// Even when the original payload is in a format that is semantically close to
/// OTLP, such as pprof, a conversion may still be lossy in some cases (e.g. if
/// the pprof file contains custom extensions or conventions).
///
/// The original payload can be large in size, so including the original
/// payload should be configurable by the profiler or collector options. The
/// default behavior should be to not include the original payload.
#[prost(string, tag = "9")]
pub original_payload_format: ::prost::alloc::string::String,
/// The original payload bytes. See also original_payload_format. Optional, but
/// format and the bytes must be set or unset together.
#[prost(bytes = "vec", tag = "10")]
pub original_payload: ::prost::alloc::vec::Vec<u8>,
/// References to attributes in attribute_table. \[optional\]
#[prost(int32, repeated, tag = "11")]
pub attribute_indices: ::prost::alloc::vec::Vec<i32>,
}
/// A pointer from a profile Sample to a trace Span.
/// Connects a profile sample to a trace span, identified by unique trace and span IDs.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Link {
/// A unique identifier of a trace that this linked span is part of. The ID is a
/// 16-byte array.
#[prost(bytes = "vec", tag = "1")]
pub trace_id: ::prost::alloc::vec::Vec<u8>,
/// A unique identifier for the linked span. The ID is an 8-byte array.
#[prost(bytes = "vec", tag = "2")]
pub span_id: ::prost::alloc::vec::Vec<u8>,
}
/// ValueType describes the type and units of a value.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ValueType {
/// Index into ProfilesDictionary.string_table.
#[prost(int32, tag = "1")]
pub type_strindex: i32,
/// Index into ProfilesDictionary.string_table.
#[prost(int32, tag = "2")]
pub unit_strindex: i32,
}
/// Each Sample records values encountered in some program context. The program
/// context is typically a stack trace, perhaps augmented with auxiliary
/// information like the thread-id, some indicator of a higher level request
/// being handled etc.
///
/// A Sample MUST have have at least one values or timestamps_unix_nano entry. If
/// both fields are populated, they MUST contain the same number of elements, and
/// the elements at the same index MUST refer to the same event.
///
/// For the purposes of efficiently representing aggregated data observations, a Sample is regarded
/// as having a shared identity and an associated collection of per-observation data points.
/// Samples having the same identity SHOULD be combined by inserting timestamps and values to the data arrays.
///
/// Examples of different ways ('shapes') of representing a sample with the total value of 10:
///
/// Report of a stacktrace at 10 timestamps (consumers must assume the value is 1 for each point):
/// values: \[\]
/// timestamps_unix_nano: \[1, 2, 3, 4, 5, 6, 7, 8, 9, 10\]
///
/// Report of a stacktrace with an aggregated value without timestamps:
/// values: \[10\]
/// timestamps_unix_nano: \[\]
///
/// Report of a stacktrace at 4 timestamps where each point records a specific value:
/// values: \[2, 2, 3, 3\]
/// timestamps_unix_nano: \[1, 2, 3, 4\]
///
/// All Samples for a Profile SHOULD have the same shape, i.e. all data observation series should consistently
/// adopt the same data recording style.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Sample {
/// Reference to stack in ProfilesDictionary.stack_table.
#[prost(int32, tag = "1")]
pub stack_index: i32,
/// References to attributes in ProfilesDictionary.attribute_table. \[optional\]
#[prost(int32, repeated, tag = "2")]
pub attribute_indices: ::prost::alloc::vec::Vec<i32>,
/// Reference to link in ProfilesDictionary.link_table. \[optional\]
/// It can be unset / set to 0 if no link exists, as link_table\[0\] is always a 'null' default value.
#[prost(int32, tag = "3")]
pub link_index: i32,
/// The type and unit of each value is defined by Profile.sample_type.
#[prost(int64, repeated, tag = "4")]
pub values: ::prost::alloc::vec::Vec<i64>,
/// Timestamps associated with Sample. Value is UNIX Epoch time in nanoseconds
/// since 00:00:00 UTC on 1 January 1970. The timestamps should fall within the
/// \[Profile.time_unix_nano, Profile.time_unix_nano + Profile.duration_nano)
/// time range.
#[prost(fixed64, repeated, tag = "5")]
#[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"
)
)]
pub timestamps_unix_nano: ::prost::alloc::vec::Vec<u64>,
}
/// Describes the mapping of a binary in memory, including its address range,
/// file offset, and metadata like build ID
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Mapping {
/// Address at which the binary (or DLL) is loaded into memory.
#[prost(uint64, tag = "1")]
pub memory_start: u64,
/// The limit of the address range occupied by this mapping.
#[prost(uint64, tag = "2")]
pub memory_limit: u64,
/// Offset in the binary that corresponds to the first mapped address.
#[prost(uint64, tag = "3")]
pub file_offset: u64,
/// The object this entry is loaded from. This can be a filename on
/// disk for the main binary and shared libraries, or virtual
/// abstractions like "\[vdso\]".
///
/// Index into ProfilesDictionary.string_table.
#[prost(int32, tag = "4")]
pub filename_strindex: i32,
/// References to attributes in ProfilesDictionary.attribute_table. \[optional\]
#[prost(int32, repeated, tag = "5")]
pub attribute_indices: ::prost::alloc::vec::Vec<i32>,
}
/// A Stack represents a stack trace as a list of locations.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Stack {
/// References to locations in ProfilesDictionary.location_table.
/// The first location is the leaf frame.
#[prost(int32, repeated, tag = "1")]
pub location_indices: ::prost::alloc::vec::Vec<i32>,
}
/// Describes function and line table debug information.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Location {
/// Reference to mapping in ProfilesDictionary.mapping_table.
/// It can be unset / set to 0 if the mapping is unknown or not applicable for
/// this profile type, as mapping_table\[0\] is always a 'null' default mapping.
#[prost(int32, tag = "1")]
pub mapping_index: i32,
/// The instruction address for this location, if available. It
/// should be within \[Mapping.memory_start...Mapping.memory_limit\]
/// for the corresponding mapping. A non-leaf address may be in the
/// middle of a call instruction. It is up to display tools to find
/// the beginning of the instruction if necessary.
#[prost(uint64, tag = "2")]
pub address: u64,
/// Multiple line indicates this location has inlined functions,
/// where the last entry represents the caller into which the
/// preceding entries were inlined.
///
/// E.g., if memcpy() is inlined into printf:
/// lines\[0\].function_name == "memcpy"
/// lines\[1\].function_name == "printf"
#[prost(message, repeated, tag = "3")]
pub lines: ::prost::alloc::vec::Vec<Line>,
/// References to attributes in ProfilesDictionary.attribute_table. \[optional\]
#[prost(int32, repeated, tag = "4")]
pub attribute_indices: ::prost::alloc::vec::Vec<i32>,
}
/// Details a specific line in a source code, linked to a function.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Line {
/// Reference to function in ProfilesDictionary.function_table.
#[prost(int32, tag = "1")]
pub function_index: i32,
/// Line number in source code. 0 means unset.
#[prost(int64, tag = "2")]
pub line: i64,
/// Column number in source code. 0 means unset.
#[prost(int64, tag = "3")]
pub column: i64,
}
/// Describes a function, including its human-readable name, system name,
/// source file, and starting line number in the source.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Function {
/// The function name. Empty string if not available.
#[prost(int32, tag = "1")]
pub name_strindex: i32,
/// Function name, as identified by the system. For instance,
/// it can be a C++ mangled name. Empty string if not available.
#[prost(int32, tag = "2")]
pub system_name_strindex: i32,
/// Source file containing the function. Empty string if not available.
#[prost(int32, tag = "3")]
pub filename_strindex: i32,
/// Line number in source file. 0 means unset.
#[prost(int64, tag = "4")]
pub start_line: i64,
}
/// A custom 'dictionary native' style of encoding attributes which is more convenient
/// for profiles than opentelemetry.proto.common.v1.KeyValue
/// Specifically, uses the string table for keys and allows optional unit information.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct KeyValueAndUnit {
/// The index into the string table for the attribute's key.
#[prost(int32, tag = "1")]
pub key_strindex: i32,
/// The value of the attribute.
#[prost(message, optional, tag = "2")]
pub value: ::core::option::Option<super::super::common::v1::AnyValue>,
/// The index into the string table for the attribute's unit.
/// zero indicates implicit (by semconv) or non-defined unit.
#[prost(int32, tag = "3")]
pub unit_strindex: i32,
}
@@ -0,0 +1,26 @@
// This file is @generated by prost-build.
/// Resource information.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Resource {
/// Set of attributes that describe the resource.
/// Attribute keys MUST be unique (it is not allowed to have more than one
/// attribute with the same key).
/// The behavior of software that receives duplicated keys can be unpredictable.
#[prost(message, repeated, tag = "1")]
pub attributes: ::prost::alloc::vec::Vec<super::super::common::v1::KeyValue>,
/// The number of dropped attributes. If the value is 0, then
/// no attributes were dropped.
#[prost(uint32, tag = "2")]
pub dropped_attributes_count: u32,
/// Set of entities that participate in this Resource.
///
/// Note: keys in the references MUST exist in attributes of this message.
///
/// Status: \[Development\]
#[prost(message, repeated, tag = "3")]
pub entity_refs: ::prost::alloc::vec::Vec<super::super::common::v1::EntityRef>,
}
@@ -0,0 +1,529 @@
// This file is @generated by prost-build.
/// TracesData represents the traces data that can be stored in a persistent storage,
/// OR can be embedded by other protocols that transfer OTLP traces data but do
/// not implement the OTLP protocol.
///
/// The main difference between this message and collector protocol is that
/// in this message there will not be any "control" or "metadata" specific to
/// OTLP protocol.
///
/// When new fields are added into this message, the OTLP request MUST be updated
/// as well.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TracesData {
/// An array of ResourceSpans.
/// For data coming from a single resource this array will typically contain
/// one element. Intermediary nodes that receive data from multiple origins
/// typically batch the data before forwarding further and in that case this
/// array will contain multiple elements.
#[prost(message, repeated, tag = "1")]
pub resource_spans: ::prost::alloc::vec::Vec<ResourceSpans>,
}
/// A collection of ScopeSpans from a Resource.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ResourceSpans {
/// The resource for the spans in this message.
/// If this field is not set then no resource info is known.
#[prost(message, optional, tag = "1")]
pub resource: ::core::option::Option<super::super::resource::v1::Resource>,
/// A list of ScopeSpans that originate from a resource.
#[prost(message, repeated, tag = "2")]
pub scope_spans: ::prost::alloc::vec::Vec<ScopeSpans>,
/// The Schema URL, if known. This is the identifier of the Schema that the resource data
/// is recorded in. Notably, the last part of the URL path is the version number of the
/// schema: http\[s\]://server\[:port\]/path/<version>. To learn more about Schema URL see
/// <https://opentelemetry.io/docs/specs/otel/schemas/#schema-url>
/// This schema_url applies to the data in the "resource" field. It does not apply
/// to the data in the "scope_spans" field which have their own schema_url field.
#[prost(string, tag = "3")]
pub schema_url: ::prost::alloc::string::String,
}
/// A collection of Spans produced by an InstrumentationScope.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ScopeSpans {
/// The instrumentation scope information for the spans in this message.
/// Semantically when InstrumentationScope isn't set, it is equivalent with
/// an empty instrumentation scope name (unknown).
#[prost(message, optional, tag = "1")]
pub scope: ::core::option::Option<super::super::common::v1::InstrumentationScope>,
/// A list of Spans that originate from an instrumentation scope.
#[prost(message, repeated, tag = "2")]
pub spans: ::prost::alloc::vec::Vec<Span>,
/// The Schema URL, if known. This is the identifier of the Schema that the span data
/// is recorded in. Notably, the last part of the URL path is the version number of the
/// schema: http\[s\]://server\[:port\]/path/<version>. To learn more about Schema URL see
/// <https://opentelemetry.io/docs/specs/otel/schemas/#schema-url>
/// This schema_url applies to the data in the "scope" field and all spans and span
/// events in the "spans" field.
#[prost(string, tag = "3")]
pub schema_url: ::prost::alloc::string::String,
}
/// A Span represents a single operation performed by a single component of the system.
///
/// The next available field id is 17.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Span {
/// A unique identifier for a trace. All spans from the same trace share
/// the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR
/// of length other than 16 bytes is considered invalid (empty string in OTLP/JSON
/// is zero-length and thus is also invalid).
///
/// This field is required.
#[prost(bytes = "vec", tag = "1")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_to_hex_string",
deserialize_with = "crate::proto::serializers::deserialize_from_hex_string"
)
)]
pub trace_id: ::prost::alloc::vec::Vec<u8>,
/// A unique identifier for a span within a trace, assigned when the span
/// is created. The ID is an 8-byte array. An ID with all zeroes OR of length
/// other than 8 bytes is considered invalid (empty string in OTLP/JSON
/// is zero-length and thus is also invalid).
///
/// This field is required.
#[prost(bytes = "vec", tag = "2")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_to_hex_string",
deserialize_with = "crate::proto::serializers::deserialize_from_hex_string"
)
)]
pub span_id: ::prost::alloc::vec::Vec<u8>,
/// trace_state conveys information about request position in multiple distributed tracing graphs.
/// It is a trace_state in w3c-trace-context format: <https://www.w3.org/TR/trace-context/#tracestate-header>
/// See also <https://github.com/w3c/distributed-tracing> for more details about this field.
#[prost(string, tag = "3")]
pub trace_state: ::prost::alloc::string::String,
/// The `span_id` of this span's parent span. If this is a root span, then this
/// field must be empty. The ID is an 8-byte array.
#[prost(bytes = "vec", tag = "4")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_to_hex_string",
deserialize_with = "crate::proto::serializers::deserialize_from_hex_string"
)
)]
pub parent_span_id: ::prost::alloc::vec::Vec<u8>,
/// Flags, a bit field.
///
/// Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace
/// Context specification. To read the 8-bit W3C trace flag, use
/// `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`.
///
/// See <https://www.w3.org/TR/trace-context-2/#trace-flags> for the flag definitions.
///
/// Bits 8 and 9 represent the 3 states of whether a span's parent
/// is remote. The states are (unknown, is not remote, is remote).
/// To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`.
/// To read whether the span is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`.
///
/// When creating span messages, if the message is logically forwarded from another source
/// with an equivalent flags fields (i.e., usually another OTLP span message), the field SHOULD
/// be copied as-is. If creating from a source that does not have an equivalent flags field
/// (such as a runtime representation of an OpenTelemetry span), the high 22 bits MUST
/// be set to zero.
/// Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero.
///
/// \[Optional\].
#[prost(fixed32, tag = "16")]
pub flags: u32,
/// A description of the span's operation.
///
/// For example, the name can be a qualified method name or a file name
/// and a line number where the operation is called. A best practice is to use
/// the same display name at the same call point in an application.
/// This makes it easier to correlate spans in different traces.
///
/// This field is semantically required to be set to non-empty string.
/// Empty value is equivalent to an unknown span name.
///
/// This field is required.
#[prost(string, tag = "5")]
pub name: ::prost::alloc::string::String,
/// Distinguishes between spans generated in a particular context. For example,
/// two spans with the same name may be distinguished using `CLIENT` (caller)
/// and `SERVER` (callee) to identify queueing latency associated with the span.
#[prost(enumeration = "span::SpanKind", tag = "6")]
pub kind: i32,
/// The start time of the span. On the client side, this is the time
/// kept by the local machine where the span execution starts. On the server side, this
/// is the time when the server's application handler starts running.
/// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970.
///
/// This field is semantically required and it is expected that end_time >= start_time.
#[prost(fixed64, tag = "7")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_u64_to_string",
deserialize_with = "crate::proto::serializers::deserialize_string_to_u64"
)
)]
pub start_time_unix_nano: u64,
/// The end time of the span. On the client side, this is the time
/// kept by the local machine where the span execution ends. On the server side, this
/// is the time when the server application handler stops running.
/// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970.
///
/// This field is semantically required and it is expected that end_time >= start_time.
#[prost(fixed64, tag = "8")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_u64_to_string",
deserialize_with = "crate::proto::serializers::deserialize_string_to_u64"
)
)]
pub end_time_unix_nano: u64,
/// A collection of key/value pairs. Note, global attributes
/// like server name can be set using the resource API. Examples of attributes:
///
/// ```text
/// "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
/// "/http/server_latency": 300
/// "example.com/myattribute": true
/// "example.com/score": 10.239
/// ```
///
/// Attribute keys MUST be unique (it is not allowed to have more than one
/// attribute with the same key).
/// The behavior of software that receives duplicated keys can be unpredictable.
#[prost(message, repeated, tag = "9")]
pub attributes: ::prost::alloc::vec::Vec<super::super::common::v1::KeyValue>,
/// The number of attributes that were discarded. Attributes
/// can be discarded because their keys are too long or because there are too many
/// attributes. If this value is 0, then no attributes were dropped.
#[prost(uint32, tag = "10")]
pub dropped_attributes_count: u32,
/// A collection of Event items.
#[prost(message, repeated, tag = "11")]
pub events: ::prost::alloc::vec::Vec<span::Event>,
/// The number of dropped events. If the value is 0, then no
/// events were dropped.
#[prost(uint32, tag = "12")]
pub dropped_events_count: u32,
/// A collection of Links, which are references from this span to a span
/// in the same or different trace.
#[prost(message, repeated, tag = "13")]
pub links: ::prost::alloc::vec::Vec<span::Link>,
/// The number of dropped links after the maximum size was
/// enforced. If this value is 0, then no links were dropped.
#[prost(uint32, tag = "14")]
pub dropped_links_count: u32,
/// An optional final status for this span. Semantically when Status isn't set, it means
/// span's status code is unset, i.e. assume STATUS_CODE_UNSET (code = 0).
#[prost(message, optional, tag = "15")]
pub status: ::core::option::Option<Status>,
}
/// Nested message and enum types in `Span`.
pub mod span {
/// Event is a time-stamped annotation of the span, consisting of user-supplied
/// text description and key-value pairs.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Event {
/// The time the event occurred.
#[prost(fixed64, tag = "1")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_u64_to_string",
deserialize_with = "crate::proto::serializers::deserialize_string_to_u64"
)
)]
pub time_unix_nano: u64,
/// The name of the event.
/// This field is semantically required to be set to non-empty string.
#[prost(string, tag = "2")]
pub name: ::prost::alloc::string::String,
/// A collection of attribute key/value pairs on the event.
/// Attribute keys MUST be unique (it is not allowed to have more than one
/// attribute with the same key).
/// The behavior of software that receives duplicated keys can be unpredictable.
#[prost(message, repeated, tag = "3")]
pub attributes: ::prost::alloc::vec::Vec<
super::super::super::common::v1::KeyValue,
>,
/// The number of dropped attributes. If the value is 0,
/// then no attributes were dropped.
#[prost(uint32, tag = "4")]
pub dropped_attributes_count: u32,
}
/// A pointer from the current span to another span in the same trace or in a
/// different trace. For example, this can be used in batching operations,
/// where a single batch handler processes multiple requests from different
/// traces or when the handler receives a request from a different project.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Link {
/// A unique identifier of a trace that this linked span is part of. The ID is a
/// 16-byte array.
#[prost(bytes = "vec", tag = "1")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_to_hex_string",
deserialize_with = "crate::proto::serializers::deserialize_from_hex_string"
)
)]
pub trace_id: ::prost::alloc::vec::Vec<u8>,
/// A unique identifier for the linked span. The ID is an 8-byte array.
#[prost(bytes = "vec", tag = "2")]
#[cfg_attr(
feature = "with-serde",
serde(
serialize_with = "crate::proto::serializers::serialize_to_hex_string",
deserialize_with = "crate::proto::serializers::deserialize_from_hex_string"
)
)]
pub span_id: ::prost::alloc::vec::Vec<u8>,
/// The trace_state associated with the link.
#[prost(string, tag = "3")]
pub trace_state: ::prost::alloc::string::String,
/// A collection of attribute key/value pairs on the link.
/// Attribute keys MUST be unique (it is not allowed to have more than one
/// attribute with the same key).
/// The behavior of software that receives duplicated keys can be unpredictable.
#[prost(message, repeated, tag = "4")]
pub attributes: ::prost::alloc::vec::Vec<
super::super::super::common::v1::KeyValue,
>,
/// The number of dropped attributes. If the value is 0,
/// then no attributes were dropped.
#[prost(uint32, tag = "5")]
pub dropped_attributes_count: u32,
/// Flags, a bit field.
///
/// Bits 0-7 (8 least significant bits) are the trace flags as defined in W3C Trace
/// Context specification. To read the 8-bit W3C trace flag, use
/// `flags & SPAN_FLAGS_TRACE_FLAGS_MASK`.
///
/// See <https://www.w3.org/TR/trace-context-2/#trace-flags> for the flag definitions.
///
/// Bits 8 and 9 represent the 3 states of whether the link is remote.
/// The states are (unknown, is not remote, is remote).
/// To read whether the value is known, use `(flags & SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK) != 0`.
/// To read whether the link is remote, use `(flags & SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK) != 0`.
///
/// Readers MUST NOT assume that bits 10-31 (22 most significant bits) will be zero.
/// When creating new spans, bits 10-31 (most-significant 22-bits) MUST be zero.
///
/// \[Optional\].
#[prost(fixed32, tag = "6")]
pub flags: u32,
}
/// SpanKind is the type of span. Can be used to specify additional relationships between spans
/// in addition to a parent/child relationship.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum SpanKind {
/// Unspecified. Do NOT use as default.
/// Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED.
Unspecified = 0,
/// Indicates that the span represents an internal operation within an application,
/// as opposed to an operation happening at the boundaries. Default value.
Internal = 1,
/// Indicates that the span covers server-side handling of an RPC or other
/// remote network request.
Server = 2,
/// Indicates that the span describes a request to some remote service.
Client = 3,
/// Indicates that the span describes a producer sending a message to a broker.
/// Unlike CLIENT and SERVER, there is often no direct critical path latency relationship
/// between producer and consumer spans. A PRODUCER span ends when the message was accepted
/// by the broker while the logical processing of the message might span a much longer time.
Producer = 4,
/// Indicates that the span describes consumer receiving a message from a broker.
/// Like the PRODUCER kind, there is often no direct critical path latency relationship
/// between producer and consumer spans.
Consumer = 5,
}
impl SpanKind {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Unspecified => "SPAN_KIND_UNSPECIFIED",
Self::Internal => "SPAN_KIND_INTERNAL",
Self::Server => "SPAN_KIND_SERVER",
Self::Client => "SPAN_KIND_CLIENT",
Self::Producer => "SPAN_KIND_PRODUCER",
Self::Consumer => "SPAN_KIND_CONSUMER",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"SPAN_KIND_UNSPECIFIED" => Some(Self::Unspecified),
"SPAN_KIND_INTERNAL" => Some(Self::Internal),
"SPAN_KIND_SERVER" => Some(Self::Server),
"SPAN_KIND_CLIENT" => Some(Self::Client),
"SPAN_KIND_PRODUCER" => Some(Self::Producer),
"SPAN_KIND_CONSUMER" => Some(Self::Consumer),
_ => None,
}
}
}
}
/// The Status type defines a logical error model that is suitable for different
/// programming environments, including REST APIs and RPC APIs.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "with-serde", serde(default))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct Status {
/// A developer-facing human readable error message.
#[prost(string, tag = "2")]
pub message: ::prost::alloc::string::String,
/// The status code.
#[prost(enumeration = "status::StatusCode", tag = "3")]
pub code: i32,
}
/// Nested message and enum types in `Status`.
pub mod status {
/// For the semantics of status codes see
/// <https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status>
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum StatusCode {
/// The default status.
Unset = 0,
/// The Span has been validated by an Application developer or Operator to
/// have completed successfully.
Ok = 1,
/// The Span contains an error.
Error = 2,
}
impl StatusCode {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Unset => "STATUS_CODE_UNSET",
Self::Ok => "STATUS_CODE_OK",
Self::Error => "STATUS_CODE_ERROR",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"STATUS_CODE_UNSET" => Some(Self::Unset),
"STATUS_CODE_OK" => Some(Self::Ok),
"STATUS_CODE_ERROR" => Some(Self::Error),
_ => None,
}
}
}
}
/// SpanFlags represents constants used to interpret the
/// Span.flags field, which is protobuf 'fixed32' type and is to
/// be used as bit-fields. Each non-zero value defined in this enum is
/// a bit-mask. To extract the bit-field, for example, use an
/// expression like:
///
/// (span.flags & SPAN_FLAGS_TRACE_FLAGS_MASK)
///
/// See <https://www.w3.org/TR/trace-context-2/#trace-flags> for the flag definitions.
///
/// Note that Span flags were introduced in version 1.1 of the
/// OpenTelemetry protocol. Older Span producers do not set this
/// field, consequently consumers should not rely on the absence of a
/// particular flag bit to indicate the presence of a particular feature.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum SpanFlags {
/// The zero value for the enum. Should not be used for comparisons.
/// Instead use bitwise "and" with the appropriate mask as shown above.
DoNotUse = 0,
/// Bits 0-7 are used for trace flags.
TraceFlagsMask = 255,
/// Bits 8 and 9 are used to indicate that the parent span or link span is remote.
/// Bit 8 (`HAS_IS_REMOTE`) indicates whether the value is known.
/// Bit 9 (`IS_REMOTE`) indicates whether the span or link is remote.
ContextHasIsRemoteMask = 256,
ContextIsRemoteMask = 512,
}
impl SpanFlags {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Self::DoNotUse => "SPAN_FLAGS_DO_NOT_USE",
Self::TraceFlagsMask => "SPAN_FLAGS_TRACE_FLAGS_MASK",
Self::ContextHasIsRemoteMask => "SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK",
Self::ContextIsRemoteMask => "SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"SPAN_FLAGS_DO_NOT_USE" => Some(Self::DoNotUse),
"SPAN_FLAGS_TRACE_FLAGS_MASK" => Some(Self::TraceFlagsMask),
"SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK" => Some(Self::ContextHasIsRemoteMask),
"SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK" => Some(Self::ContextIsRemoteMask),
_ => None,
}
}
}
@@ -0,0 +1,90 @@
// This file is @generated by prost-build.
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct TracezCounts {
#[prost(string, tag = "1")]
pub spanname: ::prost::alloc::string::String,
/// \[A\]
#[prost(uint32, repeated, tag = "2")]
pub latency: ::prost::alloc::vec::Vec<u32>,
#[prost(uint32, tag = "3")]
pub running: u32,
#[prost(uint32, tag = "4")]
pub error: u32,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LatencyData {
#[prost(bytes = "vec", tag = "1")]
pub traceid: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "2")]
pub spanid: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "3")]
pub parentid: ::prost::alloc::vec::Vec<u8>,
#[prost(fixed64, tag = "4")]
pub starttime: u64,
#[prost(fixed64, tag = "5")]
pub endtime: u64,
/// \[1\]
#[prost(message, repeated, tag = "6")]
pub attributes: ::prost::alloc::vec::Vec<super::super::common::v1::KeyValue>,
/// \[2\]
#[prost(message, repeated, tag = "7")]
pub events: ::prost::alloc::vec::Vec<super::super::trace::v1::span::Event>,
/// \[3\]
#[prost(message, repeated, tag = "8")]
pub links: ::prost::alloc::vec::Vec<super::super::trace::v1::span::Link>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RunningData {
#[prost(bytes = "vec", tag = "1")]
pub traceid: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "2")]
pub spanid: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "3")]
pub parentid: ::prost::alloc::vec::Vec<u8>,
#[prost(fixed64, tag = "4")]
pub starttime: u64,
/// \[1\]
#[prost(message, repeated, tag = "5")]
pub attributes: ::prost::alloc::vec::Vec<super::super::common::v1::KeyValue>,
/// \[2\]
#[prost(message, repeated, tag = "6")]
pub events: ::prost::alloc::vec::Vec<super::super::trace::v1::span::Event>,
/// \[3\]
#[prost(message, repeated, tag = "7")]
pub links: ::prost::alloc::vec::Vec<super::super::trace::v1::span::Link>,
}
#[cfg_attr(feature = "with-schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "with-serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with-serde", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ErrorData {
#[prost(bytes = "vec", tag = "1")]
pub traceid: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "2")]
pub spanid: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes = "vec", tag = "3")]
pub parentid: ::prost::alloc::vec::Vec<u8>,
#[prost(fixed64, tag = "4")]
pub starttime: u64,
/// \[1\]
#[prost(message, repeated, tag = "5")]
pub attributes: ::prost::alloc::vec::Vec<super::super::common::v1::KeyValue>,
/// \[2\]
#[prost(message, repeated, tag = "6")]
pub events: ::prost::alloc::vec::Vec<super::super::trace::v1::span::Event>,
/// \[3\]
#[prost(message, repeated, tag = "7")]
pub links: ::prost::alloc::vec::Vec<super::super::trace::v1::span::Link>,
/// \[4\]
#[prost(message, optional, tag = "8")]
pub status: ::core::option::Option<super::super::trace::v1::Status>,
}
@@ -0,0 +1,59 @@
// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
package opentelemetry.proto.tracez.v1;
import "opentelemetry/proto/common/v1/common.proto";
import "opentelemetry/proto/trace/v1/trace.proto";
message TracezCounts {
string spanname = 1;
repeated uint32 latency = 2; // [A]
uint32 running = 3;
uint32 error = 4;
}
message LatencyData {
bytes traceid = 1;
bytes spanid = 2;
bytes parentid = 3;
fixed64 starttime = 4;
fixed64 endtime = 5;
repeated opentelemetry.proto.common.v1.KeyValue attributes = 6; // [1]
repeated opentelemetry.proto.trace.v1.Span.Event events = 7; // [2]
repeated opentelemetry.proto.trace.v1.Span.Link links = 8; // [3]
}
message RunningData {
bytes traceid = 1;
bytes spanid = 2;
bytes parentid = 3;
fixed64 starttime = 4;
repeated opentelemetry.proto.common.v1.KeyValue attributes = 5; // [1]
repeated opentelemetry.proto.trace.v1.Span.Event events = 6; // [2]
repeated opentelemetry.proto.trace.v1.Span.Link links = 7; // [3]
}
message ErrorData {
bytes traceid = 1;
bytes spanid = 2;
bytes parentid = 3;
fixed64 starttime = 4;
repeated opentelemetry.proto.common.v1.KeyValue attributes = 5; // [1]
repeated opentelemetry.proto.trace.v1.Span.Event events = 6; // [2]
repeated opentelemetry.proto.trace.v1.Span.Link links = 7; // [3]
opentelemetry.proto.trace.v1.Status status = 8; // [4]
}
@@ -0,0 +1,212 @@
#[cfg(all(
feature = "gen-tonic-messages",
any(feature = "trace", feature = "metrics", feature = "logs")
))]
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[cfg(all(
feature = "gen-tonic-messages",
any(feature = "trace", feature = "metrics", feature = "logs")
))]
pub(crate) fn to_nanos(time: SystemTime) -> u64 {
time.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0))
.as_nanos() as u64
}
#[cfg(feature = "gen-tonic-messages")]
pub mod tonic {
use crate::proto::tonic::common::v1::{
any_value, AnyValue, ArrayValue, InstrumentationScope, KeyValue,
};
use opentelemetry::{Array, Value};
use std::borrow::Cow;
#[cfg(any(feature = "trace", feature = "logs"))]
#[derive(Debug, Default)]
pub struct ResourceAttributesWithSchema {
pub attributes: Attributes,
pub schema_url: Option<String>,
}
#[cfg(any(feature = "trace", feature = "logs"))]
impl From<&opentelemetry_sdk::Resource> for ResourceAttributesWithSchema {
fn from(resource: &opentelemetry_sdk::Resource) -> Self {
ResourceAttributesWithSchema {
attributes: resource_attributes(resource),
schema_url: resource.schema_url().map(ToString::to_string),
}
}
}
#[cfg(any(feature = "trace", feature = "logs"))]
use opentelemetry_sdk::Resource;
impl
From<(
opentelemetry::InstrumentationScope,
Option<Cow<'static, str>>,
)> for InstrumentationScope
{
fn from(
data: (
opentelemetry::InstrumentationScope,
Option<Cow<'static, str>>,
),
) -> Self {
let (library, target) = data;
InstrumentationScope {
name: target.map_or_else(|| library.name().to_owned(), Cow::into_owned),
version: library.version().unwrap_or_default().to_owned(),
attributes: Attributes::from(library.attributes().cloned()).0,
..Default::default()
}
}
}
impl
From<(
&opentelemetry::InstrumentationScope,
Option<Cow<'static, str>>,
)> for InstrumentationScope
{
fn from(
data: (
&opentelemetry::InstrumentationScope,
Option<Cow<'static, str>>,
),
) -> Self {
let (library, target) = data;
InstrumentationScope {
name: target.map_or_else(|| library.name().to_owned(), Cow::into_owned),
version: library.version().unwrap_or_default().to_owned(),
attributes: Attributes::from(library.attributes().cloned()).0,
..Default::default()
}
}
}
/// Wrapper type for Vec<`KeyValue`>
#[derive(Default, Debug)]
pub struct Attributes(pub ::std::vec::Vec<crate::proto::tonic::common::v1::KeyValue>);
impl<I: IntoIterator<Item = opentelemetry::KeyValue>> From<I> for Attributes {
fn from(kvs: I) -> Self {
Attributes(
kvs.into_iter()
.map(|api_kv| KeyValue {
key: api_kv.key.as_str().to_string(),
value: Some(api_kv.value.into()),
key_strindex: 0,
})
.collect(),
)
}
}
#[cfg(feature = "logs")]
impl<K: Into<String>, V: Into<AnyValue>> FromIterator<(K, V)> for Attributes {
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
Attributes(
iter.into_iter()
.map(|(k, v)| KeyValue {
key: k.into(),
value: Some(v.into()),
key_strindex: 0,
})
.collect(),
)
}
}
impl From<Value> for AnyValue {
fn from(value: Value) -> Self {
AnyValue {
value: match value {
Value::Bool(val) => Some(any_value::Value::BoolValue(val)),
Value::I64(val) => Some(any_value::Value::IntValue(val)),
Value::F64(val) => Some(any_value::Value::DoubleValue(val)),
Value::String(val) => Some(any_value::Value::StringValue(val.to_string())),
Value::Array(array) => Some(any_value::Value::ArrayValue(match array {
Array::Bool(vals) => array_into_proto(vals),
Array::I64(vals) => array_into_proto(vals),
Array::F64(vals) => array_into_proto(vals),
Array::String(vals) => array_into_proto(vals),
_ => unreachable!("Nonexistent array type"), // Needs to be updated when new array types are added
})),
_ => unreachable!("Nonexistent value type"), // Needs to be updated when new value types are added
},
}
}
}
fn array_into_proto<T>(vals: Vec<T>) -> ArrayValue
where
Value: From<T>,
{
let values = vals
.into_iter()
.map(|val| AnyValue::from(Value::from(val)))
.collect();
ArrayValue { values }
}
#[cfg(any(feature = "trace", feature = "logs"))]
pub(crate) fn resource_attributes(resource: &Resource) -> Attributes {
resource
.iter()
.map(|(k, v)| opentelemetry::KeyValue::new(k.clone(), v.clone()))
.collect::<Vec<_>>()
.into()
}
#[cfg(test)]
mod tests {
use super::*;
use opentelemetry::KeyValue;
use std::borrow::Cow;
fn assert_scope_fields(
proto_scope: &InstrumentationScope,
expected_name: &str,
expected_version: &str,
expected_attr_key: &str,
) {
assert_eq!(proto_scope.name, expected_name);
assert_eq!(proto_scope.version, expected_version);
assert_eq!(proto_scope.attributes.len(), 1);
assert_eq!(proto_scope.attributes[0].key, expected_attr_key);
}
#[test]
fn instrumentation_scope_with_target_overrides_name_but_preserves_version_and_attributes() {
let scope = opentelemetry::InstrumentationScope::builder("my-lib")
.with_version("1.0.0")
.with_attributes([KeyValue::new("feature", "metrics")])
.build();
let target: Option<Cow<'static, str>> = Some(Cow::Borrowed("my_app::handlers"));
let from_owned = InstrumentationScope::from((scope.clone(), target.clone()));
let from_ref = InstrumentationScope::from((&scope, target));
assert_scope_fields(&from_owned, "my_app::handlers", "1.0.0", "feature");
assert_scope_fields(&from_ref, "my_app::handlers", "1.0.0", "feature");
}
#[test]
fn instrumentation_scope_without_target_preserves_all_fields() {
let scope = opentelemetry::InstrumentationScope::builder("my-lib")
.with_version("1.0.0")
.with_attributes([KeyValue::new("feature", "metrics")])
.build();
let target: Option<Cow<'static, str>> = None;
let from_owned = InstrumentationScope::from((scope.clone(), target.clone()));
let from_ref = InstrumentationScope::from((&scope, target));
assert_scope_fields(&from_owned, "my-lib", "1.0.0", "feature");
assert_scope_fields(&from_ref, "my-lib", "1.0.0", "feature");
}
}
}
@@ -0,0 +1,525 @@
#[cfg(feature = "gen-tonic-messages")]
pub mod tonic {
use crate::{
tonic::{
common::v1::{
any_value::Value, AnyValue, ArrayValue, InstrumentationScope, KeyValue,
KeyValueList,
},
logs::v1::{LogRecord, ResourceLogs, ScopeLogs, SeverityNumber},
resource::v1::Resource,
},
transform::common::{to_nanos, tonic::ResourceAttributesWithSchema},
};
use opentelemetry::logs::{AnyValue as LogsAnyValue, Severity};
use opentelemetry_sdk::logs::LogBatch;
use std::borrow::Cow;
use std::collections::HashMap;
impl From<LogsAnyValue> for AnyValue {
fn from(value: LogsAnyValue) -> Self {
AnyValue {
value: Some(value.into()),
}
}
}
impl From<LogsAnyValue> for Value {
fn from(value: LogsAnyValue) -> Self {
match value {
LogsAnyValue::Double(f) => Value::DoubleValue(f),
LogsAnyValue::Int(i) => Value::IntValue(i),
LogsAnyValue::String(s) => Value::StringValue(s.into()),
LogsAnyValue::Boolean(b) => Value::BoolValue(b),
LogsAnyValue::ListAny(v) => Value::ArrayValue(ArrayValue {
values: v
.into_iter()
.map(|v| AnyValue {
value: Some(v.into()),
})
.collect(),
}),
LogsAnyValue::Map(m) => Value::KvlistValue(KeyValueList {
values: m
.into_iter()
.map(|(key, value)| KeyValue {
key: key.into(),
value: Some(AnyValue {
value: Some(value.into()),
}),
key_strindex: 0,
})
.collect(),
}),
LogsAnyValue::Bytes(v) => Value::BytesValue(*v),
_ => unreachable!("Nonexistent value type"),
}
}
}
impl From<&opentelemetry_sdk::logs::SdkLogRecord> for LogRecord {
fn from(log_record: &opentelemetry_sdk::logs::SdkLogRecord) -> Self {
let trace_context = log_record.trace_context();
let severity_number = match log_record.severity_number() {
Some(Severity::Trace) => SeverityNumber::Trace,
Some(Severity::Trace2) => SeverityNumber::Trace2,
Some(Severity::Trace3) => SeverityNumber::Trace3,
Some(Severity::Trace4) => SeverityNumber::Trace4,
Some(Severity::Debug) => SeverityNumber::Debug,
Some(Severity::Debug2) => SeverityNumber::Debug2,
Some(Severity::Debug3) => SeverityNumber::Debug3,
Some(Severity::Debug4) => SeverityNumber::Debug4,
Some(Severity::Info) => SeverityNumber::Info,
Some(Severity::Info2) => SeverityNumber::Info2,
Some(Severity::Info3) => SeverityNumber::Info3,
Some(Severity::Info4) => SeverityNumber::Info4,
Some(Severity::Warn) => SeverityNumber::Warn,
Some(Severity::Warn2) => SeverityNumber::Warn2,
Some(Severity::Warn3) => SeverityNumber::Warn3,
Some(Severity::Warn4) => SeverityNumber::Warn4,
Some(Severity::Error) => SeverityNumber::Error,
Some(Severity::Error2) => SeverityNumber::Error2,
Some(Severity::Error3) => SeverityNumber::Error3,
Some(Severity::Error4) => SeverityNumber::Error4,
Some(Severity::Fatal) => SeverityNumber::Fatal,
Some(Severity::Fatal2) => SeverityNumber::Fatal2,
Some(Severity::Fatal3) => SeverityNumber::Fatal3,
Some(Severity::Fatal4) => SeverityNumber::Fatal4,
None => SeverityNumber::Unspecified,
};
LogRecord {
time_unix_nano: log_record.timestamp().map(to_nanos).unwrap_or_default(),
observed_time_unix_nano: to_nanos(log_record.observed_timestamp().unwrap()),
attributes: {
log_record
.attributes_iter()
.map(|kv| KeyValue {
key: kv.0.to_string(),
value: Some(AnyValue {
value: Some(kv.1.clone().into()),
}),
key_strindex: 0,
})
.collect()
},
event_name: log_record.event_name().unwrap_or_default().into(),
severity_number: severity_number.into(),
severity_text: log_record
.severity_text()
.map(Into::into)
.unwrap_or_default(),
body: log_record.body().cloned().map(Into::into),
dropped_attributes_count: 0,
flags: trace_context
.map(|ctx| {
ctx.trace_flags
.map(|flags| flags.to_u8() as u32)
.unwrap_or_default()
})
.unwrap_or_default(),
span_id: trace_context
.map(|ctx| ctx.span_id.to_bytes().to_vec())
.unwrap_or_default(),
trace_id: trace_context
.map(|ctx| ctx.trace_id.to_bytes().to_vec())
.unwrap_or_default(),
}
}
}
impl
From<(
(
&opentelemetry_sdk::logs::SdkLogRecord,
&opentelemetry::InstrumentationScope,
),
&ResourceAttributesWithSchema,
)> for ResourceLogs
{
fn from(
data: (
(
&opentelemetry_sdk::logs::SdkLogRecord,
&opentelemetry::InstrumentationScope,
),
&ResourceAttributesWithSchema,
),
) -> Self {
let ((log_record, instrumentation), resource) = data;
ResourceLogs {
resource: Some(Resource {
attributes: resource.attributes.0.clone(),
dropped_attributes_count: 0,
entity_refs: vec![],
}),
schema_url: resource.schema_url.clone().unwrap_or_default(),
scope_logs: vec![ScopeLogs {
schema_url: instrumentation
.schema_url()
.map(ToOwned::to_owned)
.unwrap_or_default(),
scope: Some((instrumentation, log_record.target().cloned()).into()),
log_records: vec![log_record.into()],
}],
}
}
}
pub fn group_logs_by_resource_and_scope<'a>(
logs: &'a LogBatch<'a>,
resource: &ResourceAttributesWithSchema,
) -> Vec<ResourceLogs> {
// Group by the exported scope: target overrides only the name.
let scope_map = logs.iter().fold(
HashMap::new(),
|mut scope_map: HashMap<
opentelemetry::InstrumentationScope,
Vec<(
&opentelemetry_sdk::logs::SdkLogRecord,
&opentelemetry::InstrumentationScope,
)>,
>,
(log_record, instrumentation)| {
let name = log_record
.target()
.cloned()
.unwrap_or_else(|| Cow::Owned(instrumentation.name().to_owned()));
let key = opentelemetry::InstrumentationScope::builder(name)
.with_version(instrumentation.version().unwrap_or_default().to_owned())
.with_schema_url(instrumentation.schema_url().unwrap_or_default().to_owned())
.with_attributes(instrumentation.attributes().cloned())
.build();
scope_map
.entry(key)
.or_default()
.push((log_record, instrumentation));
scope_map
},
);
let scope_logs = scope_map
.into_iter()
.map(|(key, log_data)| ScopeLogs {
scope: Some(InstrumentationScope::from((&key, None))),
schema_url: key.schema_url().unwrap_or_default().to_owned(),
log_records: log_data
.into_iter()
.map(|(log_record, _)| log_record.into())
.collect(),
})
.collect();
vec![ResourceLogs {
resource: Some(Resource {
attributes: resource.attributes.0.clone(),
dropped_attributes_count: 0,
entity_refs: vec![],
}),
scope_logs,
schema_url: resource.schema_url.clone().unwrap_or_default(),
}]
}
}
#[cfg(test)]
mod tests {
use crate::transform::common::tonic::ResourceAttributesWithSchema;
use opentelemetry::logs::LogRecord as _;
use opentelemetry::logs::Logger;
use opentelemetry::logs::LoggerProvider;
use opentelemetry::time::now;
use opentelemetry::{InstrumentationScope, KeyValue};
use opentelemetry_sdk::error::OTelSdkResult;
use opentelemetry_sdk::logs::LogProcessor;
use opentelemetry_sdk::logs::SdkLoggerProvider;
use opentelemetry_sdk::{logs::LogBatch, logs::SdkLogRecord, Resource};
use std::borrow::Cow;
#[derive(Debug)]
struct MockProcessor;
impl LogProcessor for MockProcessor {
fn emit(&self, _record: &mut SdkLogRecord, _instrumentation: &InstrumentationScope) {}
fn force_flush(&self) -> OTelSdkResult {
Ok(())
}
fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> OTelSdkResult {
Ok(())
}
}
fn create_test_log_data(
instrumentation_name: &str,
_message: &str,
) -> (SdkLogRecord, InstrumentationScope) {
let processor = MockProcessor {};
let logger = SdkLoggerProvider::builder()
.with_log_processor(processor)
.build()
.logger("test");
let mut logrecord = logger.create_log_record();
logrecord.set_timestamp(now());
logrecord.set_observed_timestamp(now());
let instrumentation =
InstrumentationScope::builder(instrumentation_name.to_string()).build();
(logrecord, instrumentation)
}
#[test]
fn test_group_logs_by_resource_and_scope_single_scope() {
let resource = Resource::builder().build();
let (log_record1, instrum_lib1) = create_test_log_data("test-lib", "Log 1");
let (log_record2, instrum_lib2) = create_test_log_data("test-lib", "Log 2");
let logs = [(&log_record1, &instrum_lib1), (&log_record2, &instrum_lib2)];
let log_batch = LogBatch::new(&logs);
let resource: ResourceAttributesWithSchema = (&resource).into(); // Convert Resource to ResourceAttributesWithSchema
let grouped_logs =
crate::transform::logs::tonic::group_logs_by_resource_and_scope(&log_batch, &resource);
assert_eq!(grouped_logs.len(), 1);
let resource_logs = &grouped_logs[0];
assert_eq!(resource_logs.scope_logs.len(), 1);
let scope_logs = &resource_logs.scope_logs[0];
assert_eq!(scope_logs.log_records.len(), 2);
}
#[test]
fn test_group_logs_by_resource_and_scope_multiple_scopes() {
let resource = Resource::builder().build();
let (log_record1, instrum_lib1) = create_test_log_data("lib1", "Log 1");
let (log_record2, instrum_lib2) = create_test_log_data("lib2", "Log 2");
let logs = [(&log_record1, &instrum_lib1), (&log_record2, &instrum_lib2)];
let log_batch = LogBatch::new(&logs);
let resource: ResourceAttributesWithSchema = (&resource).into(); // Convert Resource to ResourceAttributesWithSchema
let grouped_logs =
crate::transform::logs::tonic::group_logs_by_resource_and_scope(&log_batch, &resource);
assert_eq!(grouped_logs.len(), 1);
let resource_logs = &grouped_logs[0];
assert_eq!(resource_logs.scope_logs.len(), 2);
let scope_logs_1 = &resource_logs
.scope_logs
.iter()
.find(|scope| scope.scope.as_ref().unwrap().name == "lib1")
.unwrap();
let scope_logs_2 = &resource_logs
.scope_logs
.iter()
.find(|scope| scope.scope.as_ref().unwrap().name == "lib2")
.unwrap();
assert_eq!(scope_logs_1.log_records.len(), 1);
assert_eq!(scope_logs_2.log_records.len(), 1);
}
#[test]
fn scope_grouping_same_target_preserves_distinct_versions() {
let (mut first, _) = create_test_log_data("bridge", "first");
let (mut second, _) = create_test_log_data("bridge", "second");
first.set_target(Cow::Borrowed("my_app::handlers"));
second.set_target(Cow::Borrowed("my_app::handlers"));
let first_scope = InstrumentationScope::builder("bridge")
.with_version("1.0")
.build();
let second_scope = InstrumentationScope::builder("bridge")
.with_version("2.0")
.build();
let logs = [(&first, &first_scope), (&second, &second_scope)];
let batch = LogBatch::new(&logs);
let grouped = crate::transform::logs::tonic::group_logs_by_resource_and_scope(
&batch,
&ResourceAttributesWithSchema::default(),
);
// Target still supplies the exported scope name. Version distinguishes groups.
let mut actual: Vec<_> = grouped[0]
.scope_logs
.iter()
.map(|group| {
let scope = group.scope.as_ref().unwrap();
(
scope.name.as_str(),
scope.version.as_str(),
group.log_records.len(),
)
})
.collect();
actual.sort_unstable();
assert_eq!(
actual,
vec![
("my_app::handlers", "1.0", 1),
("my_app::handlers", "2.0", 1),
]
);
}
#[test]
fn scope_grouping_respects_effective_metadata() {
let cases = [
(
"different attributes",
InstrumentationScope::builder("bridge")
.with_attributes([KeyValue::new("source", "a")])
.build(),
InstrumentationScope::builder("bridge")
.with_attributes([KeyValue::new("source", "b")])
.build(),
2,
),
(
"different schema URLs",
InstrumentationScope::builder("bridge")
.with_schema_url("https://scope.example/v1")
.build(),
InstrumentationScope::builder("bridge")
.with_schema_url("https://scope.example/v2")
.build(),
2,
),
(
"attribute order is irrelevant",
InstrumentationScope::builder("bridge")
.with_attributes([KeyValue::new("a", "1"), KeyValue::new("b", "2")])
.build(),
InstrumentationScope::builder("bridge")
.with_attributes([KeyValue::new("b", "2"), KeyValue::new("a", "1")])
.build(),
1,
),
(
"target overrides different original names",
InstrumentationScope::builder("bridge-a")
.with_version("1")
.build(),
InstrumentationScope::builder("bridge-b")
.with_version("1")
.build(),
1,
),
];
for (description, first_scope, second_scope, expected_groups) in cases {
let (mut first, _) = create_test_log_data("", "first");
let (mut second, _) = create_test_log_data("", "second");
first.set_target(Cow::Borrowed("shared-target"));
second.set_target(Cow::Borrowed("shared-target"));
first.set_body("first".into());
second.set_body("second".into());
let logs = [(&first, &first_scope), (&second, &second_scope)];
let batch = LogBatch::new(&logs);
let resource = ResourceAttributesWithSchema::default();
let grouped =
crate::transform::logs::tonic::group_logs_by_resource_and_scope(&batch, &resource);
assert_eq!(
grouped[0].scope_logs.len(),
expected_groups,
"{description}"
);
// Every record must retain the same metadata as when exported alone.
for (record, scope) in logs {
let single =
crate::tonic::logs::v1::ResourceLogs::from(((record, scope), &resource));
let expected = &single.scope_logs[0];
let actual = grouped[0]
.scope_logs
.iter()
.find(|group| group.log_records.contains(&expected.log_records[0]))
.unwrap();
let mut actual_scope = actual.scope.clone().unwrap();
let mut expected_scope = expected.scope.clone().unwrap();
actual_scope.attributes.sort_by(|a, b| a.key.cmp(&b.key));
expected_scope.attributes.sort_by(|a, b| a.key.cmp(&b.key));
assert_eq!(actual_scope, expected_scope, "{description}");
assert_eq!(actual.schema_url, expected.schema_url, "{description}");
}
}
}
#[test]
fn scope_grouping_same_target_empty_metadata_stays_together() {
let (mut first, first_scope) = create_test_log_data("", "first");
let (mut second, second_scope) = create_test_log_data("", "second");
first.set_target(Cow::Borrowed("my_app::handlers"));
second.set_target(Cow::Borrowed("my_app::handlers"));
let logs = [(&first, &first_scope), (&second, &second_scope)];
let batch = LogBatch::new(&logs);
let grouped = crate::transform::logs::tonic::group_logs_by_resource_and_scope(
&batch,
&ResourceAttributesWithSchema::default(),
);
// Typical appender logs with the same target should remain one group.
assert_eq!(grouped[0].scope_logs.len(), 1);
let group = &grouped[0].scope_logs[0];
assert_eq!(group.scope.as_ref().unwrap().name, "my_app::handlers");
assert_eq!(group.log_records.len(), 2);
}
#[test]
fn scope_grouping_uses_scope_schema_instead_of_resource_schema() {
let (mut record, _) = create_test_log_data("bridge", "message");
record.set_target(Cow::Borrowed("my_app::handlers"));
let scope = InstrumentationScope::builder("bridge")
.with_schema_url("https://scope.example/schema")
.build();
let resource = ResourceAttributesWithSchema {
schema_url: Some("https://resource.example/schema".to_owned()),
..Default::default()
};
let logs = [(&record, &scope)];
let batch = LogBatch::new(&logs);
let grouped =
crate::transform::logs::tonic::group_logs_by_resource_and_scope(&batch, &resource);
assert_eq!(grouped[0].schema_url, "https://resource.example/schema");
let group = &grouped[0].scope_logs[0];
assert_eq!(group.scope.as_ref().unwrap().name, "my_app::handlers");
assert_eq!(group.schema_url, "https://scope.example/schema");
}
#[test]
fn test_group_logs_preserves_scope_version_and_attributes_when_target_set() {
let resource = Resource::builder().build();
let processor = MockProcessor {};
let logger = SdkLoggerProvider::builder()
.with_log_processor(processor)
.build()
.logger("test");
let mut logrecord = logger.create_log_record();
logrecord.set_timestamp(now());
logrecord.set_observed_timestamp(now());
logrecord.set_target(Cow::Borrowed("my_app::handlers"));
let instrumentation = InstrumentationScope::builder("my-lib")
.with_version("1.0.0")
.with_attributes([KeyValue::new("feature", "metrics")])
.build();
let logs = [(&logrecord, &instrumentation)];
let log_batch = LogBatch::new(&logs);
let resource: ResourceAttributesWithSchema = (&resource).into();
let grouped_logs =
crate::transform::logs::tonic::group_logs_by_resource_and_scope(&log_batch, &resource);
assert_eq!(grouped_logs.len(), 1);
let resource_logs = &grouped_logs[0];
assert_eq!(resource_logs.scope_logs.len(), 1);
let scope = resource_logs.scope_logs[0].scope.as_ref().unwrap();
assert_eq!(scope.name, "my_app::handlers");
assert_eq!(scope.version, "1.0.0");
assert_eq!(scope.attributes.len(), 1);
assert_eq!(scope.attributes[0].key, "feature");
}
}
@@ -0,0 +1,334 @@
// The prost currently will generate a non optional deprecated field for labels.
// We cannot assign value to it otherwise clippy will complain.
// We cannot ignore it as it's not an optional field.
// We can remove this after we removed the labels field from proto.
#[allow(deprecated)]
#[cfg(feature = "gen-tonic-messages")]
pub mod tonic {
use std::fmt::Debug;
use opentelemetry::{otel_debug, Key, Value};
use opentelemetry_sdk::metrics::data::{
AggregatedMetrics, Exemplar as SdkExemplar,
ExponentialHistogram as SdkExponentialHistogram, Gauge as SdkGauge,
Histogram as SdkHistogram, Metric as SdkMetric, MetricData, ResourceMetrics,
ScopeMetrics as SdkScopeMetrics, Sum as SdkSum,
};
use opentelemetry_sdk::metrics::Temporality;
use opentelemetry_sdk::Resource as SdkResource;
use crate::proto::tonic::{
collector::metrics::v1::ExportMetricsServiceRequest,
common::v1::KeyValue,
metrics::v1::{
exemplar, exemplar::Value as TonicExemplarValue,
exponential_histogram_data_point::Buckets as TonicBuckets,
metric::Data as TonicMetricData, number_data_point,
number_data_point::Value as TonicDataPointValue,
AggregationTemporality as TonicTemporality, AggregationTemporality,
DataPointFlags as TonicDataPointFlags, Exemplar as TonicExemplar,
ExponentialHistogram as TonicExponentialHistogram,
ExponentialHistogramDataPoint as TonicExponentialHistogramDataPoint,
Gauge as TonicGauge, Histogram as TonicHistogram,
HistogramDataPoint as TonicHistogramDataPoint, Metric as TonicMetric,
NumberDataPoint as TonicNumberDataPoint, ResourceMetrics as TonicResourceMetrics,
ScopeMetrics as TonicScopeMetrics, Sum as TonicSum,
},
resource::v1::Resource as TonicResource,
};
use crate::transform::common::to_nanos;
impl From<u64> for exemplar::Value {
fn from(value: u64) -> Self {
exemplar::Value::AsInt(i64::try_from(value).unwrap_or_default())
}
}
impl From<i64> for exemplar::Value {
fn from(value: i64) -> Self {
exemplar::Value::AsInt(value)
}
}
impl From<f64> for exemplar::Value {
fn from(value: f64) -> Self {
exemplar::Value::AsDouble(value)
}
}
impl From<u64> for number_data_point::Value {
fn from(value: u64) -> Self {
number_data_point::Value::AsInt(i64::try_from(value).unwrap_or_default())
}
}
impl From<i64> for number_data_point::Value {
fn from(value: i64) -> Self {
number_data_point::Value::AsInt(value)
}
}
impl From<f64> for number_data_point::Value {
fn from(value: f64) -> Self {
number_data_point::Value::AsDouble(value)
}
}
impl From<(&Key, &Value)> for KeyValue {
fn from(kv: (&Key, &Value)) -> Self {
KeyValue {
key: kv.0.to_string(),
value: Some(kv.1.clone().into()),
key_strindex: 0,
}
}
}
impl From<&opentelemetry::KeyValue> for KeyValue {
fn from(kv: &opentelemetry::KeyValue) -> Self {
KeyValue {
key: kv.key.to_string(),
value: Some(kv.value.clone().into()),
key_strindex: 0,
}
}
}
impl From<Temporality> for AggregationTemporality {
fn from(temporality: Temporality) -> Self {
match temporality {
Temporality::Cumulative => AggregationTemporality::Cumulative,
Temporality::Delta => AggregationTemporality::Delta,
other => {
otel_debug!(
name: "AggregationTemporality::Unknown",
message = "Unknown temporality,using default instead.",
unknown_temporality = format!("{:?}", other),
default_temporality = format!("{:?}", Temporality::Cumulative)
);
AggregationTemporality::Cumulative
}
}
}
}
impl From<&ResourceMetrics> for ExportMetricsServiceRequest {
fn from(rm: &ResourceMetrics) -> Self {
ExportMetricsServiceRequest {
resource_metrics: vec![TonicResourceMetrics {
resource: Some((rm.resource()).into()),
scope_metrics: rm.scope_metrics().map(Into::into).collect(),
schema_url: rm
.resource()
.schema_url()
.map(Into::into)
.unwrap_or_default(),
}],
}
}
}
impl From<&SdkResource> for TonicResource {
fn from(resource: &SdkResource) -> Self {
TonicResource {
attributes: resource.iter().map(Into::into).collect(),
dropped_attributes_count: 0,
entity_refs: vec![], // internal and currently unused
}
}
}
impl From<&SdkScopeMetrics> for TonicScopeMetrics {
fn from(sm: &SdkScopeMetrics) -> Self {
TonicScopeMetrics {
scope: Some((sm.scope(), None).into()),
metrics: sm.metrics().map(Into::into).collect(),
schema_url: sm
.scope()
.schema_url()
.map(ToOwned::to_owned)
.unwrap_or_default(),
}
}
}
impl From<&SdkMetric> for TonicMetric {
fn from(metric: &SdkMetric) -> Self {
TonicMetric {
name: metric.name().to_string(),
description: metric.description().to_string(),
unit: metric.unit().to_string(),
metadata: vec![], // internal and currently unused
data: Some(match metric.data() {
AggregatedMetrics::F64(data) => data.into(),
AggregatedMetrics::U64(data) => data.into(),
AggregatedMetrics::I64(data) => data.into(),
}),
}
}
}
impl<T> From<&MetricData<T>> for TonicMetricData
where
T: Numeric + Debug,
{
fn from(data: &MetricData<T>) -> Self {
match data {
MetricData::Gauge(gauge) => TonicMetricData::Gauge(gauge.into()),
MetricData::Sum(sum) => TonicMetricData::Sum(sum.into()),
MetricData::Histogram(hist) => TonicMetricData::Histogram(hist.into()),
MetricData::ExponentialHistogram(hist) => {
TonicMetricData::ExponentialHistogram(hist.into())
}
}
}
}
trait Numeric: Into<TonicExemplarValue> + Into<TonicDataPointValue> + Copy {
// lossy at large values for u64 and i64 but otlp histograms only handle float values
fn into_f64(self) -> f64;
}
impl Numeric for u64 {
fn into_f64(self) -> f64 {
self as f64
}
}
impl Numeric for i64 {
fn into_f64(self) -> f64 {
self as f64
}
}
impl Numeric for f64 {
fn into_f64(self) -> f64 {
self
}
}
impl<T> From<&SdkHistogram<T>> for TonicHistogram
where
T: Numeric,
{
fn from(hist: &SdkHistogram<T>) -> Self {
TonicHistogram {
data_points: hist
.data_points()
.map(|dp| TonicHistogramDataPoint {
attributes: dp.attributes().map(Into::into).collect(),
start_time_unix_nano: to_nanos(hist.start_time()),
time_unix_nano: to_nanos(hist.time()),
count: dp.count(),
sum: Some(dp.sum().into_f64()),
bucket_counts: dp.bucket_counts().collect(),
explicit_bounds: dp.bounds().collect(),
exemplars: dp.exemplars().map(Into::into).collect(),
flags: TonicDataPointFlags::default() as u32,
min: dp.min().map(Numeric::into_f64),
max: dp.max().map(Numeric::into_f64),
})
.collect(),
aggregation_temporality: TonicTemporality::from(hist.temporality()).into(),
}
}
}
impl<T> From<&SdkExponentialHistogram<T>> for TonicExponentialHistogram
where
T: Numeric,
{
fn from(hist: &SdkExponentialHistogram<T>) -> Self {
TonicExponentialHistogram {
data_points: hist
.data_points()
.map(|dp| TonicExponentialHistogramDataPoint {
attributes: dp.attributes().map(Into::into).collect(),
start_time_unix_nano: to_nanos(hist.start_time()),
time_unix_nano: to_nanos(hist.time()),
count: dp.count() as u64,
sum: Some(dp.sum().into_f64()),
scale: dp.scale().into(),
zero_count: dp.zero_count(),
positive: Some(TonicBuckets {
offset: dp.positive_bucket().offset(),
bucket_counts: dp.positive_bucket().counts().collect(),
}),
negative: Some(TonicBuckets {
offset: dp.negative_bucket().offset(),
bucket_counts: dp.negative_bucket().counts().collect(),
}),
flags: TonicDataPointFlags::default() as u32,
exemplars: dp.exemplars().map(Into::into).collect(),
min: dp.min().map(Numeric::into_f64),
max: dp.max().map(Numeric::into_f64),
zero_threshold: dp.zero_threshold(),
})
.collect(),
aggregation_temporality: TonicTemporality::from(hist.temporality()).into(),
}
}
}
impl<T> From<&SdkSum<T>> for TonicSum
where
T: Debug + Into<TonicExemplarValue> + Into<TonicDataPointValue> + Copy,
{
fn from(sum: &SdkSum<T>) -> Self {
TonicSum {
data_points: sum
.data_points()
.map(|dp| TonicNumberDataPoint {
attributes: dp.attributes().map(Into::into).collect(),
start_time_unix_nano: to_nanos(sum.start_time()),
time_unix_nano: to_nanos(sum.time()),
exemplars: dp.exemplars().map(Into::into).collect(),
flags: TonicDataPointFlags::default() as u32,
value: Some(dp.value().into()),
})
.collect(),
aggregation_temporality: TonicTemporality::from(sum.temporality()).into(),
is_monotonic: sum.is_monotonic(),
}
}
}
impl<T> From<&SdkGauge<T>> for TonicGauge
where
T: Debug + Into<TonicExemplarValue> + Into<TonicDataPointValue> + Copy,
{
fn from(gauge: &SdkGauge<T>) -> Self {
TonicGauge {
data_points: gauge
.data_points()
.map(|dp| TonicNumberDataPoint {
attributes: dp.attributes().map(Into::into).collect(),
start_time_unix_nano: gauge.start_time().map(to_nanos).unwrap_or_default(),
time_unix_nano: to_nanos(gauge.time()),
exemplars: dp.exemplars().map(Into::into).collect(),
flags: TonicDataPointFlags::default() as u32,
value: Some(dp.value().into()),
})
.collect(),
}
}
}
impl<T> From<&SdkExemplar<T>> for TonicExemplar
where
T: Into<TonicExemplarValue> + Copy,
{
fn from(ex: &SdkExemplar<T>) -> Self {
TonicExemplar {
filtered_attributes: ex
.filtered_attributes()
.map(|kv| (&kv.key, &kv.value).into())
.collect(),
time_unix_nano: to_nanos(ex.time()),
span_id: ex.span_id().into(),
trace_id: ex.trace_id().into(),
value: Some(ex.value.into()),
}
}
}
}
@@ -0,0 +1,16 @@
pub mod common;
#[cfg(feature = "metrics")]
pub mod metrics;
#[cfg(feature = "trace")]
pub mod trace;
#[cfg(feature = "logs")]
pub mod logs;
#[cfg(feature = "zpages")]
pub mod tracez;
#[cfg(feature = "profiles")]
pub mod profiles;
@@ -0,0 +1 @@
@@ -0,0 +1,524 @@
#[cfg(feature = "gen-tonic-messages")]
/// Builds span flags based on the parent span's remote property.
/// This follows the OTLP specification for span flags.
pub(crate) fn build_span_flags(parent_span_is_remote: bool, base_flags: u32) -> u32 {
use crate::proto::tonic::trace::v1::SpanFlags;
let mut flags = base_flags;
flags |= SpanFlags::ContextHasIsRemoteMask as u32;
if parent_span_is_remote {
flags |= SpanFlags::ContextIsRemoteMask as u32;
}
flags
}
#[cfg(feature = "gen-tonic-messages")]
pub mod tonic {
use crate::proto::tonic::resource::v1::Resource;
use crate::proto::tonic::trace::v1::{span, status, ResourceSpans, ScopeSpans, Span, Status};
use crate::transform::common::{
to_nanos,
tonic::{Attributes, ResourceAttributesWithSchema},
};
use opentelemetry::trace;
use opentelemetry::trace::{Link, SpanId, SpanKind};
use opentelemetry_sdk::trace::SpanData;
use std::collections::HashMap;
impl From<SpanKind> for span::SpanKind {
fn from(span_kind: SpanKind) -> Self {
match span_kind {
SpanKind::Client => span::SpanKind::Client,
SpanKind::Consumer => span::SpanKind::Consumer,
SpanKind::Internal => span::SpanKind::Internal,
SpanKind::Producer => span::SpanKind::Producer,
SpanKind::Server => span::SpanKind::Server,
}
}
}
impl From<&trace::Status> for status::StatusCode {
fn from(status: &trace::Status) -> Self {
match status {
trace::Status::Ok => status::StatusCode::Ok,
trace::Status::Unset => status::StatusCode::Unset,
trace::Status::Error { .. } => status::StatusCode::Error,
}
}
}
impl From<Link> for span::Link {
fn from(link: Link) -> Self {
span::Link {
trace_id: link.span_context.trace_id().to_bytes().to_vec(),
span_id: link.span_context.span_id().to_bytes().to_vec(),
trace_state: link.span_context.trace_state().header(),
attributes: Attributes::from(link.attributes).0,
dropped_attributes_count: link.dropped_attributes_count,
flags: super::build_span_flags(
link.span_context.is_remote(),
link.span_context.trace_flags().to_u8() as u32,
),
}
}
}
impl From<opentelemetry_sdk::trace::SpanData> for Span {
fn from(source_span: opentelemetry_sdk::trace::SpanData) -> Self {
let span_kind: span::SpanKind = source_span.span_kind.into();
Span {
trace_id: source_span.span_context.trace_id().to_bytes().to_vec(),
span_id: source_span.span_context.span_id().to_bytes().to_vec(),
trace_state: source_span.span_context.trace_state().header(),
parent_span_id: {
if source_span.parent_span_id != SpanId::INVALID {
source_span.parent_span_id.to_bytes().to_vec()
} else {
vec![]
}
},
flags: super::build_span_flags(
source_span.parent_span_is_remote,
source_span.span_context.trace_flags().to_u8() as u32,
),
name: source_span.name.into_owned(),
kind: span_kind as i32,
start_time_unix_nano: to_nanos(source_span.start_time),
end_time_unix_nano: to_nanos(source_span.end_time),
dropped_attributes_count: source_span.dropped_attributes_count,
attributes: Attributes::from(source_span.attributes).0,
dropped_events_count: source_span.events.dropped_count,
events: source_span
.events
.into_iter()
.map(|event| span::Event {
time_unix_nano: to_nanos(event.timestamp),
name: event.name.into(),
attributes: Attributes::from(event.attributes).0,
dropped_attributes_count: event.dropped_attributes_count,
})
.collect(),
dropped_links_count: source_span.links.dropped_count,
links: source_span.links.into_iter().map(Into::into).collect(),
status: Some(Status {
code: status::StatusCode::from(&source_span.status).into(),
message: match source_span.status {
trace::Status::Error { description } => description.to_string(),
_ => Default::default(),
},
}),
}
}
}
impl ResourceSpans {
pub fn new(source_span: SpanData, resource: &ResourceAttributesWithSchema) -> Self {
let span_kind: span::SpanKind = source_span.span_kind.into();
ResourceSpans {
resource: Some(Resource {
attributes: resource.attributes.0.clone(),
dropped_attributes_count: 0,
entity_refs: vec![],
}),
schema_url: resource.schema_url.clone().unwrap_or_default(),
scope_spans: vec![ScopeSpans {
schema_url: source_span
.instrumentation_scope
.schema_url()
.map(ToOwned::to_owned)
.unwrap_or_default(),
scope: Some((source_span.instrumentation_scope, None).into()),
spans: vec![Span {
trace_id: source_span.span_context.trace_id().to_bytes().to_vec(),
span_id: source_span.span_context.span_id().to_bytes().to_vec(),
trace_state: source_span.span_context.trace_state().header(),
parent_span_id: {
if source_span.parent_span_id != SpanId::INVALID {
source_span.parent_span_id.to_bytes().to_vec()
} else {
vec![]
}
},
flags: super::build_span_flags(
source_span.parent_span_is_remote,
source_span.span_context.trace_flags().to_u8() as u32,
),
name: source_span.name.into_owned(),
kind: span_kind as i32,
start_time_unix_nano: to_nanos(source_span.start_time),
end_time_unix_nano: to_nanos(source_span.end_time),
dropped_attributes_count: source_span.dropped_attributes_count,
attributes: Attributes::from(source_span.attributes).0,
dropped_events_count: source_span.events.dropped_count,
events: source_span
.events
.into_iter()
.map(|event| span::Event {
time_unix_nano: to_nanos(event.timestamp),
name: event.name.into(),
attributes: Attributes::from(event.attributes).0,
dropped_attributes_count: event.dropped_attributes_count,
})
.collect(),
dropped_links_count: source_span.links.dropped_count,
links: source_span.links.into_iter().map(Into::into).collect(),
status: Some(Status {
code: status::StatusCode::from(&source_span.status).into(),
message: match source_span.status {
trace::Status::Error { description } => description.to_string(),
_ => Default::default(),
},
}),
}],
}],
}
}
}
pub fn group_spans_by_resource_and_scope(
spans: Vec<SpanData>,
resource: &ResourceAttributesWithSchema,
) -> Vec<ResourceSpans> {
// Group spans by their instrumentation scope, converting each span as it
// is consumed. The batch is owned, so spans are moved rather than cloned.
let mut scope_indices: HashMap<opentelemetry::InstrumentationScope, usize> = HashMap::new();
let mut scope_spans: Vec<ScopeSpans> = Vec::new();
for span in spans {
let scope_index = match scope_indices.get(&span.instrumentation_scope) {
Some(&scope_index) => scope_index,
None => {
let instrumentation = span.instrumentation_scope.clone();
let scope_index = scope_spans.len();
scope_spans.push(ScopeSpans {
scope: Some((&instrumentation, None).into()),
schema_url: instrumentation
.schema_url()
.map(ToOwned::to_owned)
.unwrap_or_default(),
spans: Vec::new(),
});
scope_indices.insert(instrumentation, scope_index);
scope_index
}
};
scope_spans[scope_index].spans.push(span.into());
}
// Wrap ScopeSpans into a single ResourceSpans
vec![ResourceSpans {
resource: Some(Resource {
attributes: resource.attributes.0.clone(),
dropped_attributes_count: 0,
entity_refs: vec![],
}),
scope_spans,
schema_url: resource.schema_url.clone().unwrap_or_default(),
}]
}
}
#[cfg(all(test, feature = "gen-tonic-messages"))]
mod span_flags_tests {
use crate::proto::tonic::trace::v1::{Span, SpanFlags};
use opentelemetry::trace::{SpanContext, SpanId, TraceFlags, TraceId, TraceState};
use opentelemetry::InstrumentationScope;
use opentelemetry_sdk::trace::SpanData;
use std::borrow::Cow;
#[test]
fn test_build_span_flags_local_parent() {
let flags = super::build_span_flags(false, 0); // is_remote = false
assert_eq!(flags, SpanFlags::ContextHasIsRemoteMask as u32); // 0x100
}
#[test]
fn test_build_span_flags_remote_parent() {
let flags = super::build_span_flags(true, 0); // is_remote = true
assert_eq!(
flags,
(SpanFlags::ContextHasIsRemoteMask as u32) | (SpanFlags::ContextIsRemoteMask as u32)
); // 0x300
}
#[test]
fn test_build_span_flags_no_parent() {
let flags = super::build_span_flags(false, 0); // no parent = false
assert_eq!(flags, SpanFlags::ContextHasIsRemoteMask as u32); // 0x100
}
#[test]
fn test_build_span_flags_preserves_base_flags() {
let flags = super::build_span_flags(false, 0x01); // SAMPLED flag, local parent
assert_eq!(flags, 0x01 | (SpanFlags::ContextHasIsRemoteMask as u32)); // 0x101
}
#[test]
fn test_span_transformation_with_flags() {
let span_data = SpanData {
span_context: SpanContext::new(
TraceId::from(789),
SpanId::from(101112),
TraceFlags::default(),
false,
TraceState::default(),
),
parent_span_id: SpanId::from(456),
parent_span_is_remote: false,
span_kind: opentelemetry::trace::SpanKind::Internal,
name: Cow::Borrowed("test_span"),
start_time: std::time::SystemTime::now(),
end_time: std::time::SystemTime::now(),
attributes: vec![],
dropped_attributes_count: 0,
events: opentelemetry_sdk::trace::SpanEvents::default(),
links: opentelemetry_sdk::trace::SpanLinks::default(),
status: opentelemetry::trace::Status::Unset,
instrumentation_scope: InstrumentationScope::builder("test").build(),
};
let otlp_span: Span = span_data.into();
assert_eq!(otlp_span.flags, SpanFlags::ContextHasIsRemoteMask as u32); // 0x100
}
#[test]
fn test_span_transformation_with_remote_parent() {
let span_data = SpanData {
span_context: SpanContext::new(
TraceId::from(789),
SpanId::from(101112),
TraceFlags::default(),
false,
TraceState::default(),
),
parent_span_id: SpanId::from(456),
parent_span_is_remote: true,
span_kind: opentelemetry::trace::SpanKind::Internal,
name: Cow::Borrowed("test_span"),
start_time: std::time::SystemTime::now(),
end_time: std::time::SystemTime::now(),
attributes: vec![],
dropped_attributes_count: 0,
events: opentelemetry_sdk::trace::SpanEvents::default(),
links: opentelemetry_sdk::trace::SpanLinks::default(),
status: opentelemetry::trace::Status::Unset,
instrumentation_scope: InstrumentationScope::builder("test").build(),
};
let otlp_span: Span = span_data.into();
assert_eq!(
otlp_span.flags,
(SpanFlags::ContextHasIsRemoteMask as u32) | (SpanFlags::ContextIsRemoteMask as u32)
); // 0x300
}
}
#[cfg(test)]
mod tests {
use crate::tonic::common::v1::any_value::Value;
use crate::transform::common::tonic::ResourceAttributesWithSchema;
use opentelemetry::time::now;
use opentelemetry::trace::{
SpanContext, SpanId, SpanKind, Status, TraceFlags, TraceId, TraceState,
};
use opentelemetry::InstrumentationScope;
use opentelemetry::KeyValue;
use opentelemetry_sdk::resource::Resource;
use opentelemetry_sdk::trace::SpanData;
use opentelemetry_sdk::trace::{SpanEvents, SpanLinks};
use std::borrow::Cow;
use std::time::Duration;
fn create_test_span_data(instrumentation_name: &'static str) -> SpanData {
let span_context = SpanContext::new(
TraceId::from(123),
SpanId::from(456),
TraceFlags::default(),
false,
TraceState::default(),
);
SpanData {
span_context,
parent_span_id: SpanId::from(0),
parent_span_is_remote: false,
span_kind: SpanKind::Internal,
name: Cow::Borrowed("test_span"),
start_time: now(),
end_time: now() + Duration::from_secs(1),
attributes: vec![KeyValue::new("key", "value")],
dropped_attributes_count: 0,
events: SpanEvents::default(),
links: SpanLinks::default(),
status: Status::Unset,
instrumentation_scope: InstrumentationScope::builder(instrumentation_name).build(),
}
}
#[test]
fn test_group_spans_by_resource_and_scope_single_scope() {
let resource = Resource::builder_empty()
.with_attribute(KeyValue::new("resource_key", "resource_value"))
.build();
let span_data = create_test_span_data("lib1");
let spans = vec![span_data.clone()];
let resource: ResourceAttributesWithSchema = (&resource).into(); // Convert Resource to ResourceAttributesWithSchema
let grouped_spans =
crate::transform::trace::tonic::group_spans_by_resource_and_scope(spans, &resource);
assert_eq!(grouped_spans.len(), 1);
let resource_spans = &grouped_spans[0];
assert_eq!(
resource_spans.resource.as_ref().unwrap().attributes.len(),
1
);
assert_eq!(
resource_spans.resource.as_ref().unwrap().attributes[0].key,
"resource_key"
);
assert_eq!(
resource_spans.resource.as_ref().unwrap().attributes[0]
.value
.clone()
.unwrap()
.value
.unwrap(),
Value::StringValue("resource_value".to_string())
);
let scope_spans = &resource_spans.scope_spans;
assert_eq!(scope_spans.len(), 1);
let scope_span = &scope_spans[0];
assert_eq!(scope_span.scope.as_ref().unwrap().name, "lib1");
assert_eq!(scope_span.spans.len(), 1);
assert_eq!(
scope_span.spans[0].trace_id,
span_data.span_context.trace_id().to_bytes().to_vec()
);
}
#[test]
fn test_group_spans_by_resource_and_scope_multiple_scopes() {
let resource = Resource::builder_empty()
.with_attribute(KeyValue::new("resource_key", "resource_value"))
.build();
let span_data1 = create_test_span_data("lib1");
let span_data2 = create_test_span_data("lib1");
let span_data3 = create_test_span_data("lib2");
let spans = vec![span_data1.clone(), span_data2.clone(), span_data3.clone()];
let resource: ResourceAttributesWithSchema = (&resource).into(); // Convert Resource to ResourceAttributesWithSchema
let grouped_spans =
crate::transform::trace::tonic::group_spans_by_resource_and_scope(spans, &resource);
assert_eq!(grouped_spans.len(), 1);
let resource_spans = &grouped_spans[0];
assert_eq!(
resource_spans.resource.as_ref().unwrap().attributes.len(),
1
);
assert_eq!(
resource_spans.resource.as_ref().unwrap().attributes[0].key,
"resource_key"
);
assert_eq!(
resource_spans.resource.as_ref().unwrap().attributes[0]
.value
.clone()
.unwrap()
.value
.unwrap(),
Value::StringValue("resource_value".to_string())
);
let scope_spans = &resource_spans.scope_spans;
assert_eq!(scope_spans.len(), 2);
// Check the scope spans for both lib1 and lib2
let mut lib1_scope_span = None;
let mut lib2_scope_span = None;
for scope_span in scope_spans {
match scope_span.scope.as_ref().unwrap().name.as_str() {
"lib1" => lib1_scope_span = Some(scope_span),
"lib2" => lib2_scope_span = Some(scope_span),
_ => {}
}
}
let lib1_scope_span = lib1_scope_span.expect("lib1 scope span not found");
let lib2_scope_span = lib2_scope_span.expect("lib2 scope span not found");
assert_eq!(lib1_scope_span.scope.as_ref().unwrap().name, "lib1");
assert_eq!(lib2_scope_span.scope.as_ref().unwrap().name, "lib2");
assert_eq!(lib1_scope_span.spans.len(), 2);
assert_eq!(lib2_scope_span.spans.len(), 1);
assert_eq!(
lib1_scope_span.spans[0].trace_id,
span_data1.span_context.trace_id().to_bytes().to_vec()
);
assert_eq!(
lib1_scope_span.spans[1].trace_id,
span_data2.span_context.trace_id().to_bytes().to_vec()
);
assert_eq!(
lib2_scope_span.spans[0].trace_id,
span_data3.span_context.trace_id().to_bytes().to_vec()
);
}
#[test]
fn test_scope_spans_uses_instrumentation_schema_url_not_resource() {
let resource = Resource::builder_empty()
.with_schema_url(vec![], "http://resource-schema")
.build();
let instrumentation_scope = InstrumentationScope::builder("test-lib")
.with_schema_url("http://instrumentation-schema")
.build();
let span_data = SpanData {
span_context: SpanContext::new(
TraceId::from(123),
SpanId::from(456),
TraceFlags::default(),
false,
TraceState::default(),
),
parent_span_id: SpanId::from(0),
parent_span_is_remote: false,
span_kind: SpanKind::Internal,
name: Cow::Borrowed("test_span"),
start_time: now(),
end_time: now() + Duration::from_secs(1),
attributes: vec![],
dropped_attributes_count: 0,
events: SpanEvents::default(),
links: SpanLinks::default(),
status: Status::Unset,
instrumentation_scope,
};
let resource: ResourceAttributesWithSchema = (&resource).into();
let grouped_spans = crate::transform::trace::tonic::group_spans_by_resource_and_scope(
vec![span_data],
&resource,
);
assert_eq!(grouped_spans.len(), 1);
let resource_spans = &grouped_spans[0];
assert_eq!(resource_spans.schema_url, "http://resource-schema");
let scope_spans = &resource_spans.scope_spans;
assert_eq!(scope_spans.len(), 1);
assert_eq!(scope_spans[0].schema_url, "http://instrumentation-schema");
}
}
@@ -0,0 +1,72 @@
#[cfg(all(feature = "gen-tonic-messages", feature = "zpages"))]
mod tonic {
use opentelemetry::trace::{Event, Status};
use opentelemetry_sdk::trace::SpanData;
use crate::proto::tonic::{
trace::v1::{span::Event as SpanEvent, Status as SpanStatus},
tracez::v1::{ErrorData, LatencyData, RunningData},
};
use crate::transform::common::{to_nanos, tonic::Attributes};
impl From<SpanData> for LatencyData {
fn from(span_data: SpanData) -> Self {
LatencyData {
traceid: span_data.span_context.trace_id().to_bytes().to_vec(),
spanid: span_data.span_context.span_id().to_bytes().to_vec(),
parentid: span_data.parent_span_id.to_bytes().to_vec(),
starttime: to_nanos(span_data.start_time),
endtime: to_nanos(span_data.end_time),
attributes: Attributes::from(span_data.attributes).0,
events: span_data.events.iter().cloned().map(Into::into).collect(),
links: span_data.links.iter().cloned().map(Into::into).collect(),
}
}
}
impl From<SpanData> for ErrorData {
fn from(span_data: SpanData) -> Self {
ErrorData {
traceid: span_data.span_context.trace_id().to_bytes().to_vec(),
spanid: span_data.span_context.span_id().to_bytes().to_vec(),
parentid: span_data.parent_span_id.to_bytes().to_vec(),
starttime: to_nanos(span_data.start_time),
attributes: Attributes::from(span_data.attributes).0,
events: span_data.events.iter().cloned().map(Into::into).collect(),
links: span_data.links.iter().cloned().map(Into::into).collect(),
status: match span_data.status {
Status::Error { description } => Some(SpanStatus {
message: description.to_string(),
code: 2,
}),
_ => None,
},
}
}
}
impl From<SpanData> for RunningData {
fn from(span_data: SpanData) -> Self {
RunningData {
traceid: span_data.span_context.trace_id().to_bytes().to_vec(),
spanid: span_data.span_context.span_id().to_bytes().to_vec(),
parentid: span_data.parent_span_id.to_bytes().to_vec(),
starttime: to_nanos(span_data.start_time),
attributes: Attributes::from(span_data.attributes).0,
events: span_data.events.iter().cloned().map(Into::into).collect(),
links: span_data.links.iter().cloned().map(Into::into).collect(),
}
}
}
impl From<Event> for SpanEvent {
fn from(event: Event) -> Self {
SpanEvent {
time_unix_nano: to_nanos(event.timestamp),
name: event.name.to_string(),
attributes: Attributes::from(event.attributes).0,
dropped_attributes_count: event.dropped_attributes_count,
}
}
}
}
@@ -0,0 +1,265 @@
use std::collections::HashMap;
use std::path::Path;
use tempfile::TempDir;
const TONIC_OUT_DIR: &str = "src/proto/tonic";
const TONIC_PROTO_FILES: &[&str] = &[
"src/proto/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/collector/trace/v1/trace_service.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/logs/v1/logs.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/collector/logs/v1/logs_service.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/profiles/v1development/profiles.proto",
"src/proto/opentelemetry-proto/opentelemetry/proto/collector/profiles/v1development/profiles_service.proto",
"src/proto/tracez.proto",
];
const TONIC_INCLUDES: &[&str] = &["src/proto/opentelemetry-proto", "src/proto"];
#[test]
fn build_tonic() {
let before_build = build_content_map(TONIC_OUT_DIR, true);
let out_dir = TempDir::new().expect("failed to create temp dir to store the generated files");
// build the generated files into OUT_DIR for now so we don't have to touch the src unless we have to
let mut builder = tonic_prost_build::configure()
.build_server(true)
.build_client(true)
.server_mod_attribute(".", "#[cfg(feature = \"gen-tonic\")]")
.client_mod_attribute(".", "#[cfg(feature = \"gen-tonic\")]")
.type_attribute(
".",
"#[cfg_attr(feature = \"with-schemars\", derive(schemars::JsonSchema))]",
)
.type_attribute(
".",
"#[cfg_attr(feature = \"with-serde\", derive(serde::Serialize, serde::Deserialize))]",
)
.type_attribute(
".",
"#[cfg_attr(feature = \"with-serde\", serde(rename_all = \"camelCase\"))]",
);
// Optional numeric, string and array fields need to default to their default value otherwise
// JSON files without those field cannot deserialize
// we cannot add serde(default) to all generated types because enums cannot be annotated with serde(default)
for path in [
"collector.trace.v1.ExportTraceServiceRequest",
"collector.logs.v1.ExportLogsServiceRequest",
"collector.metrics.v1.ExportMetricsServiceRequest",
"collector.profiles.v1development.ExportProfilesServiceRequest",
"collector.trace.v1.ExportTracePartialSuccess",
"collector.logs.v1.ExportLogsPartialSuccess",
"collector.metrics.v1.ExportMetricsPartialSuccess",
"collector.profiles.v1development.ExportProfilesPartialSuccess",
"trace.v1.Span",
"trace.v1.Span.Link",
"trace.v1.ScopeSpans",
"trace.v1.ResourceSpans",
"common.v1.InstrumentationScope",
"resource.v1.Resource",
"trace.v1.Span.Event",
"trace.v1.Status",
"logs.v1.LogRecord",
"logs.v1.ScopeLogs",
"logs.v1.ResourceLogs",
"metrics.v1.Metric",
"metrics.v1.ResourceMetrics",
"metrics.v1.ScopeMetrics",
"metrics.v1.Gauge",
"metrics.v1.Sum",
"metrics.v1.Histogram",
"metrics.v1.ExponentialHistogram",
"metrics.v1.Summary",
"metrics.v1.NumberDataPoint",
"metrics.v1.HistogramDataPoint",
"metrics.v1.SummaryDataPoint",
"profiles.v1development.Function",
] {
builder = builder.type_attribute(
path,
"#[cfg_attr(feature = \"with-serde\", serde(default))]",
)
}
// special serializer and deserializer for traceId and spanId
// OTLP/JSON format uses hex string for traceId and spanId
// the proto file uses bytes for traceId and spanId
// Thus, special serializer and deserializer are needed
for path in [
"trace.v1.Span.trace_id",
"trace.v1.Span.span_id",
"trace.v1.Span.parent_span_id",
"trace.v1.Span.Link.trace_id",
"trace.v1.Span.Link.span_id",
"logs.v1.LogRecord.span_id",
"logs.v1.LogRecord.trace_id",
"metrics.v1.Exemplar.span_id",
"metrics.v1.Exemplar.trace_id",
"profiles.v1development.Profile.profile_id",
] {
builder = builder
.field_attribute(path, "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_to_hex_string\", deserialize_with = \"crate::proto::serializers::deserialize_from_hex_string\"))]")
}
// key_strindex is a profiling-only reference field added to common.v1.KeyValue.
// For non-profiles signals we keep the existing JSON shape by defaulting it on input
// and omitting it from output when it is zero.
builder = builder.field_attribute(
"common.v1.KeyValue.key_strindex",
"#[cfg_attr(feature = \"with-serde\", serde(default, skip_serializing_if = \"crate::proto::serializers::is_default\"))]",
);
// special serializer and deserializer for timestamp
// OTLP/JSON format may use string for timestamp
// 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\"))]")
}
for path in ["profiles.v1development.Profile.time_nanos"] {
builder = builder
.field_attribute(path, "#[cfg_attr(feature = \"with-serde\", serde(serialize_with = \"crate::proto::serializers::serialize_i64_to_string\", deserialize_with = \"crate::proto::serializers::deserialize_string_to_i64\"))]")
}
for path in ["profiles.v1development.Sample.timestamps_unix_nano"] {
builder = builder
.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 use 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 use 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.Buckets.bucket_counts",
] {
builder = builder.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 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\"))]",
);
}
// special serializer and deserializer for value
// The Value::value field must be hidden
builder = builder
.field_attribute("common.v1.AnyValue.value", "#[cfg_attr(feature =\"with-serde\", serde(flatten, serialize_with = \"crate::proto::serializers::serialize_to_value\", deserialize_with = \"crate::proto::serializers::deserialize_from_value\"))]");
// flatten
for path in ["metrics.v1.Metric.data", "metrics.v1.NumberDataPoint.value"] {
builder =
builder.field_attribute(path, "#[cfg_attr(feature =\"with-serde\", serde(flatten))]");
}
builder
.out_dir(out_dir.path())
.compile_protos(TONIC_PROTO_FILES, TONIC_INCLUDES)
.expect("cannot compile protobuf using tonic");
let after_build = build_content_map(out_dir.path(), true);
ensure_files_are_same(before_build, after_build, TONIC_OUT_DIR);
}
fn build_content_map(path: impl AsRef<Path>, normalize_line_feed: bool) -> HashMap<String, String> {
std::fs::read_dir(path)
.expect("cannot open dictionary of generated files")
.flatten()
.map(|entry| {
let path = entry.path();
let file_name = path
.file_name()
.expect("file name should always exist for generated files");
let mut file_contents = std::fs::read_to_string(path.clone())
.expect("cannot read from existing generated file");
if normalize_line_feed {
file_contents = get_platform_specific_string(file_contents);
}
(file_name.to_string_lossy().to_string(), file_contents)
})
.collect()
}
/// Returns a String which uses the platform specific new line feed character.
fn get_platform_specific_string(input: String) -> String {
if cfg!(windows) && !input.ends_with("\r\n") && input.ends_with('\n') {
return input.replace('\n', "\r\n");
}
input
}
fn ensure_files_are_same(
before_build: HashMap<String, String>,
after_build: HashMap<String, String>,
target_dir: &'static str,
) {
if after_build == before_build {
return;
}
if std::env::var("CI").is_ok() {
panic!("generated file has changed but it's a CI environment, please rerun this test locally and commit the changes");
}
// if there is at least one changes we will just copy the whole directory over
for (file_name, content) in after_build {
std::fs::write(Path::new(target_dir).join(file_name), content)
.expect("cannot write to the proto generate file. If it's happening in CI env, please return the test locally and commit the change");
}
panic!("generated file has changed, please commit the change file and rerun the test");
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
semantic-conventions/
@@ -0,0 +1,179 @@
# Changelog
## vNext
## 0.33.0
Released 2026-Sep-18
## 0.32.1
Released 2026-Jun-26
- Update to [v1.42.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.42.0) of the semantic conventions.
## 0.32.0
Released 2026-May-08
## 0.31.0
Released 2025-Sep-25
- Update to [v1.34.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.34.0) of the semantic conventions.
## 0.30.0
Released 2025-May-23
- Update to [v1.32.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.32.0) of the semantic conventions.
## 0.29.0
Released 2025-Mar-21
- Update to [v1.31.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.31.0) of the semantic conventions.
## 0.28.0
Released 2025-Feb-10
- Update to [v1.29.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.29.0) of the semantic conventions.
- Bump msrv to 1.75.0.
## 0.27.0
Released 2024-Nov-11
- Bump MSRV to 1.70 [#2179](https://github.com/open-telemetry/opentelemetry-rust/pull/2179)
- Update to [v1.28.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.28.0) of the semantic conventions.
## v0.26.0
Released 2024-Sep-30
### Changed
- Starting with this version, this crate will use Weaver for the generation of
the semantic conventions.
- **Breaking** Introduced a new feature `semconv_experimental` to enable experimental semantic conventions.
This feature is disabled by default.
## v0.25.0
### Changed
- Starting with this version, this crate will align with `opentelemetry` crate
on major,minor versions.
- Update to [v1.27.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.27.0) of the semantic conventions.
[#2000](https://github.com/open-telemetry/opentelemetry-rust/pull/2000)
## v0.16.0
### Changed
- **Breaking** Moved duplicated (and unrelated) attributes from `opentelemetry_semantic_conventions::trace` and `opentelemetry_semantic_conventions::resource` into `opentelemetry_semantic_conventions::attribute` (which now contains all semantic attributes). `trace` and `resource` now only contain references to attributes which fall under their respective category.
### Added
- Created `opentelemetry_semantic_conventions::metric` to store metric semantic conventions.
## v0.15.0
### Changed
- Update to [v1.24.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.24.0) of the semantic conventions.
[#1596](https://github.com/open-telemetry/opentelemetry-rust/pull/1596)
- Update to [v1.25.0](https://github.com/open-telemetry/semantic-conventions/releases/tag/v1.25.0) of the semantic conventions.
[#1681](https://github.com/open-telemetry/opentelemetry-rust/pull/1681)
## v0.14.0
### Changed
- **Breaking** Replaced Key constants with &'static str for tracing compatibility
[#1334](https://github.com/open-telemetry/opentelemetry-rust/pull/1334)
## v0.13.0
### Changed
- Bump MSRV to 1.65 [#1318](https://github.com/open-telemetry/opentelemetry-rust/pull/1318)
- Bump MSRV to 1.64 [#1203](https://github.com/open-telemetry/opentelemetry-rust/pull/1203)
## v0.12.0
### Changed
- Update to v1.21.0 spec
- Update to opentelemetry-api v0.20.0
## v0.11.0
### Changed
- Update to `opentelemetry` v0.19.
- Update to `opentelemetry_http` v0.8.
- Bump MSRV to 1.57 [#953](https://github.com/open-telemetry/opentelemetry-rust/pull/953).
- Update to v1.17.0 spec [#960](https://github.com/open-telemetry/opentelemetry-rust/pull/960).
- Update dependencies and bump MSRV to 1.60 [#969](https://github.com/open-telemetry/opentelemetry-rust/pull/969).
## v0.10.0
### Changed
- update to v1.9 spec #754
- Update to opentelemetry v0.18.0
## v0.9.0
### Changed
- Update to opentelemetry v0.17.0
## v0.8.0
### Changed
- Update to opentelemetry v0.16.0
## v0.7.0
### Changed
- Update to spec version 1.4.0 #570
- Update to opentelemetry v0.15.0
## v0.6.0
### Changed
- Update to spec version 1.3.0 #547
- Update to opentelemetry v0.14.0
## v0.5.0
### Changed
- Update to opentelemetry v0.13.0
### Removed
- Removed `from_env` and use environment variables to initialize the configurations by default #459
## v0.4.0
### Changed
- Update to opentelemetry v0.12.0
## v0.3.0
### Changed
- Update to opentelemetry v0.11.0
## v0.2.0
### Changed
- Update to opentelemetry v0.10.0
## v0.1.0
### Added
- Semantic conventions for [trace](https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions) and [resource](https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions).
@@ -0,0 +1,75 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.75.0"
name = "opentelemetry-semantic-conventions"
version = "0.33.0"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Semantic conventions for OpenTelemetry"
homepage = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-semantic-conventions"
readme = "README.md"
keywords = [
"opentelemetry",
"tracing",
"async",
]
categories = [
"development-tools::debugging",
"development-tools::profiling",
"asynchronous",
]
license = "Apache-2.0"
repository = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-semantic-conventions"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[features]
default = []
semconv_experimental = []
[lib]
name = "opentelemetry_semantic_conventions"
path = "src/lib.rs"
bench = false
[dev-dependencies.opentelemetry]
version = "0.33"
default-features = false
[dev-dependencies.opentelemetry_sdk]
version = "0.33"
features = ["trace"]
default-features = false
[lints.clippy.all]
level = "warn"
priority = 1
[lints.rust]
edition_2024_expr_fragment_specifier = "allow"
if_let_rescope = "allow"
tail_expr_drop_order = "allow"
[lints.rust.rust_2024_compatibility]
level = "warn"
priority = -1
@@ -0,0 +1,55 @@
# OpenTelemetry Semantic Conventions
![OpenTelemetry — An observability framework for cloud-native software.][splash]
[splash]: https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo-text.png
Semantic conventions for applications instrumented with [`OpenTelemetry`].
[![Crates.io: opentelemetry-semantic-conventions](https://img.shields.io/crates/v/opentelemetry-semantic-conventions.svg)](https://crates.io/crates/opentelemetry-semantic-conventions)
[![Documentation](https://docs.rs/opentelemetry-semantic-conventions/badge.svg)](https://docs.rs/opentelemetry-semantic-conventions)
[![LICENSE](https://img.shields.io/crates/l/opentelemetry-semantic-conventions)](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-semantic-conventions/LICENSE)
[![GitHub Actions CI](https://github.com/open-telemetry/opentelemetry-rust/workflows/CI/badge.svg)](https://github.com/open-telemetry/opentelemetry-rust/actions?query=workflow%3ACI+branch%3Amain)
[![Slack](https://img.shields.io/badge/slack-@cncf/otel/rust-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C03GDP0H023)
## Overview
[`OpenTelemetry`] is a collection of tools, APIs, and SDKs used to instrument,
generate, collect, and export telemetry data (metrics, logs, and traces) for
analysis in order to understand your software's performance and behavior. This
crate provides standardized naming patterns for attributes, and
resources to help facilitate interoperability and compatibility with processing
and visualization tools.
[`opentelemetry`]: https://crates.io/crates/opentelemetry
*[Supported Rust Versions](#supported-rust-versions)*
### What does this crate contain?
This crate contains Rust constants generated from the [OpenTelemetry Semantic
Conventions](https://github.com/open-telemetry/semantic-conventions)
specification. It provides standardized attribute keys, metric names and
resource attributes so that instrumentation produced by different libraries
remains interoperable across processing and visualization tools.
## Getting started
See [docs](https://docs.rs/opentelemetry-semantic-conventions).
## Release Notes
You can find the release notes (changelog) [here](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-semantic-conventions/CHANGELOG.md).
## Supported Rust Versions
OpenTelemetry is built against the latest stable release. The minimum supported
version is 1.75.0. The current OpenTelemetry version is NOT guaranteed to build
on Rust versions earlier than the minimum supported version.
The current stable Rust compiler and the three most recent minor versions
before it will always be supported. For example, if the current stable compiler
version is 1.49, the minimum supported version will not be increased past 1.46,
three minor versions prior. Increasing the minimum supported compiler version
is not considered a semver breaking change as long as doing so complies with
this policy.
@@ -0,0 +1,79 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CRATE_DIR="${SCRIPT_DIR}/../"
# freeze the spec version and generator version to make generation reproducible
SPEC_VERSION=1.42.0
WEAVER_VERSION=v0.24.1
cd "$CRATE_DIR"
rm -rf semantic-conventions || true
mkdir semantic-conventions
cd semantic-conventions
git init
git remote add origin https://github.com/open-telemetry/semantic-conventions.git
git fetch origin "v$SPEC_VERSION"
git reset --hard FETCH_HEAD
cd "$CRATE_DIR"
SED=(sed -i)
if [[ "$(uname)" = "Darwin" ]]; then
SED=(sed -i "")
fi
# Keep `SCHEMA_URL` key in sync with spec version
"${SED[@]}" "s/\(opentelemetry.io\/schemas\/\)[^\"]*\"/\1$SPEC_VERSION\"/" scripts/templates/registry/rust/weaver.yaml
docker run --rm \
--mount type=bind,source=$CRATE_DIR/semantic-conventions/model,target=/home/weaver/source,readonly \
--mount type=bind,source=$CRATE_DIR/scripts/templates,target=/home/weaver/templates,readonly \
--mount type=bind,source=$CRATE_DIR/src,target=/home/weaver/target \
otel/weaver:$WEAVER_VERSION \
registry generate \
--registry=/home/weaver/source \
--templates=/home/weaver/templates \
rust \
/home/weaver/target/
# handle doc generation failures
"${SED[@]}" 's/\[2\]\.$//' src/attribute.rs # remove trailing [2] from few of the doc comments
# handle escaping ranges like [0,n] / [0.0, ...] in descriptions/notes which will cause broken intra-doc links
# unescape any mistakenly escaped ranges which actually contained a link (not that we currently have any)
expression='
s/\[([a-zA-Z0-9\.\s]+,[a-zA-Z0-9\.\s]+)\]/\\[\1\\]/g
s/\\\[([^\]]+)\]\(([^)]+)\)/[\1](\2)/g
'
"${SED[@]}" -E "${expression}" src/metric.rs
"${SED[@]}" -E "${expression}" src/attribute.rs
# Fix unclosed HTML tag warnings for <key> in doc comments.
# Rustdoc treats <key> as an unclosed HTML tag and fails the build with -D warnings.
# We replace <key> with Markdown code formatting `key` to prevent the error.
# TODO: This workaround should be removed once the upstream generator handles this correctly.
"${SED[@]}" 's/<key>/`key`/g' src/attribute.rs
# Patch: rustdoc warns about bare URLs in doc comments.
# The following line wraps the specific Kubernetes ResourceRequirements URL with <...>
# as suggested by rustdoc warnings, so it becomes a clickable link and the warning goes away.
"${SED[@]}" -E 's|(/// See )(https://kubernetes.io/docs/reference/generated/kubernetes-api/v[0-9]+\.[0-9]+/#resourcerequirements-v1-core)( for details)|\1<\2>\3|g' src/metric.rs
# Tag unlabeled fenced code blocks in doc comments as `text` so rustdoc doesn't
# try to compile them as Rust. Some semconv descriptions include pseudo-code
# (e.g. with `←` arrows) that breaks `cargo test --doc`.
# TODO: Remove once the upstream generator emits language-tagged fences.
awk '
/^\/\/\/ ```$/ {
if (in_block) { print; in_block = 0 }
else { sub(/```/, "```text"); print; in_block = 1 }
next
}
{ print }
' src/attribute.rs > src/attribute.rs.tmp && mv src/attribute.rs.tmp src/attribute.rs
cargo fmt
@@ -0,0 +1,26 @@
{%- import 'macros.j2' as attr_macros -%}
// DO NOT EDIT, this is an auto-generated file
//
// If you want to update the file:
// - Edit the template at scripts/templates/registry/rust/attributes.rs.j2
// - Run the script at scripts/generate-consts-from-spec.sh
//! # Semantic Attributes
//!
//! The entire set of semantic attributes (or [conventions](https://opentelemetry.io/docs/concepts/semantic-conventions/)) defined by the project. The resource, metric, and trace modules reference these attributes.
{% for root_ns in ctx %}
{% for attr in root_ns.attributes | rejectattr("name", "in", params.excluded_attributes) %}
{# Escape any `<...>` tags to `[...]` to avoid Rustdoc warnings and HTML parsing issues. #}
{% set safe_note = attr.note | replace('<', '[') | replace('>', ']') %}
{{ [attr.brief, concat_if("\n\n## Notes\n\n", safe_note), attr_macros.examples(attr)] | comment }}
{% if attr is experimental %}
#[cfg(feature = "semconv_experimental")]
{% endif %}
{% if attr is deprecated %}
#[deprecated(note="{{ attr.deprecated | replace('\n', ' ') | replace('"', '') | trim }}")]
{% endif %}
pub const {{ attr.name | screaming_snake_case }}: &str = "{{ attr.name }}";
{% endfor %}
{% endfor %}
@@ -0,0 +1,25 @@
//! OpenTelemetry semantic conventions are agreed standardized naming patterns
//! for OpenTelemetry things. This crate aims to be the centralized place to
//! interact with these conventions.
#![warn(
future_incompatible,
missing_debug_implementations,
missing_docs,
nonstandard_style,
rust_2018_idioms,
unreachable_pub,
unused
)]
#![cfg_attr(test, deny(warnings))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo.svg"
)]
pub mod attribute;
pub mod metric;
pub mod resource;
pub mod trace;
/// The schema URL that matches the version of the semantic conventions that
/// this crate defines.
pub const SCHEMA_URL: &str = "{{ params.schema_url }}";
@@ -0,0 +1,13 @@
{%- macro examples(entity) -%}
{% if entity.examples %}
# Examples
{% if entity.examples is sequence %}
{% for example in entity.examples %}
- `{{ example | pprint }}`
{% endfor %}
{% else %}
- `{{ entity.examples | pprint }}`
{% endif %}
{% endif %}
{% endmacro %}
@@ -0,0 +1,79 @@
{%- import 'macros.j2' as metric_macros -%}
// DO NOT EDIT, this is an auto-generated file
//
// If you want to update the file:
// - Edit the template at scripts/templates/registry/rust/metric.rs.j2
// - Run the script at scripts/generate-consts-from-spec.sh
//! # Metric Semantic Conventions
//!
//! The [metric semantic conventions] define a set of standardized attributes to
//! be used in `Meter`s.
//!
//! [metric semantic conventions]: https://opentelemetry.io/docs/specs/semconv/general/metrics/
//!
//! ## Usage
//!
//! ```rust
//! use opentelemetry::{global, KeyValue};
//! use opentelemetry_semantic_conventions as semconv;
//!
//! // Assumes we already have an initialized `MeterProvider`
//! // See: https://github.com/open-telemetry/opentelemetry-rust/blob/main/examples/metrics-basic/src/main.rs
//! // for an example
//! let meter = global::meter("mylibraryname");
//! let histogram = meter
//! .u64_histogram(semconv::metric::HTTP_SERVER_REQUEST_DURATION)
//! .with_unit("s")
//! .with_description("Duration of HTTP server requests.")
//! .build();
//! ```
{% for root_ns in ctx %}
{% for metric in root_ns.metrics %}
{{ ["## Description\n\n", metric.brief, concat_if("\n\n## Notes\n\n", metric.note), metric_macros.examples(metric)] | comment }}
/// ## Metadata
/// | | |
/// |:-|:-
/// | Instrument: | `{{ metric.instrument }}` |
/// | Unit: | `{{ metric.unit }}` |
/// | Status: | `{{ metric.stability | capitalize }}` |
{% if metric.attributes %}
///
/// ## Attributes
/// | Name | Requirement |
/// |:-|:- |
{% endif %}
{% for attribute in metric.attributes | rejectattr("name", "in", params.excluded_attributes) | sort(attribute="name") %}
{% if attribute.requirement_level %}
{% if attribute.requirement_level.conditionally_required %}
{% set req_level = "Conditionally_required" %}
{% set req_message = attribute.requirement_level.conditionally_required %}
{% else %}
{% set req_level = (attribute.requirement_level | capitalize) %}
{% set req_message = attribute.requirement_level_msg %}
{% endif %}
{% else %}
{% set req_level = "Unspecified" %}
{% set req_message = '' %}
{% endif %}
/// | [`crate::attribute::{{ attribute.name | screaming_snake_case }}`] | `{{ req_level }}`{{ (': ' + req_message.replace('\n', ' ') if req_message else '') }}
{% endfor %}
{% if metric.examples %}
///
/// ## Examples
///
{% for example in metric.examples %}
/// - `{{ example }}`
{% endfor %}
{% endif %}
{% if metric is experimental %}
#[cfg(feature = "semconv_experimental")]
{% endif %}
{% if metric is deprecated %}
#[deprecated(note="{{ metric.deprecated | replace('"', '') | replace('\n', ' ') | trim }}")]
{% endif %}
pub const {{ metric.metric_name | screaming_snake_case }}: &str = "{{ metric.metric_name }}";
{% endfor %}
{% endfor %}
@@ -0,0 +1,36 @@
{%- import 'macros.j2' as attr_macros -%}
// DO NOT EDIT, this is an auto-generated file
//
// If you want to update the file:
// - Edit the template at scripts/templates/registry/rust/resource.rs.j2
// - Run the script at scripts/generate-consts-from-spec.sh
//! # Resource Semantic Conventions
//!
//! The [resource semantic conventions] define a set of standardized attributes
//! to be used in `Resource`s.
//!
//! [resource semantic conventions]: https://opentelemetry.io/docs/specs/semconv/resource/
//!
//! ## Usage
//!
//! ```rust
//! use opentelemetry::KeyValue;
//! use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};
//! use opentelemetry_semantic_conventions as semconv;
//!
//! let _tracer = SdkTracerProvider::builder()
//! .with_resource(Resource::builder_empty().with_service_name("my-service").build())
//! .build();
//! ```
{% for attr in ctx | rejectattr("name", "in", params.excluded_attributes) %}
{% if attr is experimental %}
#[cfg(feature = "semconv_experimental")]
{% endif %}
{% if attr is deprecated %}
#[allow(deprecated)]
{% endif %}
pub use crate::attribute::{{ attr.name | screaming_snake_case }};
{% endfor %}
@@ -0,0 +1,41 @@
{%- import 'macros.j2' as attr_macros -%}
// DO NOT EDIT, this is an auto-generated file
//
// If you want to update the file:
// - Edit the template at scripts/templates/registry/rust/attributes.rs.j2
// - Run the script at scripts/generate-consts-from-spec.sh
//! # Trace Semantic Conventions
//!
//! The [trace semantic conventions] define a set of standardized attributes to
//! be used in `Span`s.
//!
//! [trace semantic conventions]: https://opentelemetry.io/docs/specs/semconv/general/trace/
//!
//! ## Usage
//!
//! ```rust
//! use opentelemetry::KeyValue;
//! use opentelemetry::{global, trace::Tracer as _};
//! use opentelemetry_semantic_conventions as semconv;
//!
//! let tracer = global::tracer("my-component");
//! let _span = tracer
//! .span_builder("span-name")
//! .with_attributes([
//! KeyValue::new(semconv::trace::CLIENT_ADDRESS, "example.org"),
//! KeyValue::new(semconv::trace::CLIENT_PORT, 80i64),
//! ])
//! .start(&tracer);
//! ```
{% for attr in ctx | rejectattr("name", "in", params.excluded_attributes) %}
{% if attr is experimental %}
#[cfg(feature = "semconv_experimental")]
{% endif %}
{% if attr is deprecated %}
#[allow(deprecated)]
{% endif %}
pub use crate::attribute::{{ attr.name | screaming_snake_case }};
{% endfor %}
@@ -0,0 +1,46 @@
# Whitespace control settings to simplify the definition of templates
whitespace_control:
trim_blocks: true
lstrip_blocks: true
# Configuration for the comment formatting
comment_formats:
rust:
format: markdown
prefix: "/// "
trim: true
remove_trailing_dots: true
escape_square_brackets: true
default_comment_format: rust
params:
schema_url: "https://opentelemetry.io/schemas/1.42.0"
exclude_root_namespace: []
excluded_attributes: ["messaging.client_id"]
templates:
- pattern: attribute.rs.j2
filter: semconv_grouped_attributes($params)
application_mode: single
- pattern: metric.rs.j2
filter: semconv_grouped_metrics($params)
application_mode: single
- pattern: resource.rs.j2
filter: >
semconv_signal("resource"; $params)
| map(.attributes[])
| unique_by(.name)
| sort_by(.name)
| map({name, brief, examples, deprecated, requirement_level, stability, type})
application_mode: single
- pattern: trace.rs.j2
filter: >
(semconv_signal("span"; $params) // []) + (semconv_signal("event"; $params) // [])
| map(.attributes // [] | .[])
| unique_by(.name)
| sort_by(.name)
| map({name, brief, examples, deprecated, requirement_level, stability, type})
application_mode: single
- pattern: lib.rs.j2
filter: .
application_mode: single
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,25 @@
//! OpenTelemetry semantic conventions are agreed standardized naming patterns
//! for OpenTelemetry things. This crate aims to be the centralized place to
//! interact with these conventions.
#![warn(
future_incompatible,
missing_debug_implementations,
missing_docs,
nonstandard_style,
rust_2018_idioms,
unreachable_pub,
unused
)]
#![cfg_attr(test, deny(warnings))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo.svg"
)]
pub mod attribute;
pub mod metric;
pub mod resource;
pub mod trace;
/// The schema URL that matches the version of the semantic conventions that
/// this crate defines.
pub const SCHEMA_URL: &str = "https://opentelemetry.io/schemas/1.42.0";
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,631 @@
// DO NOT EDIT, this is an auto-generated file
//
// If you want to update the file:
// - Edit the template at scripts/templates/registry/rust/resource.rs.j2
// - Run the script at scripts/generate-consts-from-spec.sh
//! # Resource Semantic Conventions
//!
//! The [resource semantic conventions] define a set of standardized attributes
//! to be used in `Resource`s.
//!
//! [resource semantic conventions]: https://opentelemetry.io/docs/specs/semconv/resource/
//!
//! ## Usage
//!
//! ```rust
//! use opentelemetry::KeyValue;
//! use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};
//! use opentelemetry_semantic_conventions as semconv;
//!
//! let _tracer = SdkTracerProvider::builder()
//! .with_resource(Resource::builder_empty().with_service_name("my-service").build())
//! .build();
//! ```
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ANDROID_OS_API_LEVEL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_BUILD_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_INSTALLATION_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_ECS_CLUSTER_ARN;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_ECS_CONTAINER_ARN;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_ECS_LAUNCHTYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_ECS_TASK_ARN;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_ECS_TASK_FAMILY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_ECS_TASK_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_ECS_TASK_REVISION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_EKS_CLUSTER_ARN;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_LOG_GROUP_ARNS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_LOG_GROUP_NAMES;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_LOG_STREAM_ARNS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_LOG_STREAM_NAMES;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::BROWSER_BRANDS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::BROWSER_DOCUMENT_URL_FULL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::BROWSER_LANGUAGE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::BROWSER_MOBILE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::BROWSER_PLATFORM;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_RUN_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_RUN_URL_FULL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_WORKER_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_WORKER_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_WORKER_URL_FULL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUD_ACCOUNT_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUD_AVAILABILITY_ZONE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUD_PLATFORM;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUD_PROVIDER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUD_REGION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUD_RESOURCE_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_APP_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_APP_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_ORG_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_ORG_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_PROCESS_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_PROCESS_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_SPACE_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_SPACE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_SYSTEM_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUDFOUNDRY_SYSTEM_INSTANCE_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_COMMAND;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_COMMAND_ARGS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_COMMAND_LINE;
pub use crate::attribute::CONTAINER_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_IMAGE_ID;
pub use crate::attribute::CONTAINER_IMAGE_NAME;
pub use crate::attribute::CONTAINER_IMAGE_REPO_DIGESTS;
pub use crate::attribute::CONTAINER_IMAGE_TAGS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_LABEL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_RUNTIME_DESCRIPTION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_RUNTIME_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CONTAINER_RUNTIME_VERSION;
pub use crate::attribute::DEPLOYMENT_ENVIRONMENT_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DEVICE_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DEVICE_MANUFACTURER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DEVICE_MODEL_IDENTIFIER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DEVICE_MODEL_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_INSTANCE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_MAX_MEMORY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_APPLICATION_CONTAINER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_APPLICATION_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_APPLICATION_LOCATION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_SERVICE_CRITICALITY_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_SERVICE_ENVIRONMENT_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_SERVICE_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_WORKLOAD_CRITICALITY_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_WORKLOAD_ENVIRONMENT_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_APPHUB_WORKLOAD_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_CLOUD_RUN_JOB_EXECUTION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_CLOUD_RUN_JOB_TASK_INDEX;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_GCE_INSTANCE_HOSTNAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_GCE_INSTANCE_LABELS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_GCE_INSTANCE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_GCE_INSTANCE_GROUP_MANAGER_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_GCE_INSTANCE_GROUP_MANAGER_REGION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GCP_GCE_INSTANCE_GROUP_MANAGER_ZONE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HEROKU_APP_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HEROKU_RELEASE_COMMIT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HEROKU_RELEASE_CREATION_TIMESTAMP;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_ARCH;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_CPU_CACHE_L2_SIZE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_CPU_FAMILY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_CPU_MODEL_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_CPU_MODEL_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_CPU_STEPPING;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_CPU_VENDOR_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_IMAGE_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_IMAGE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_IMAGE_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_IP;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_MAC;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HOST_TYPE;
pub use crate::attribute::K8S_CLUSTER_NAME;
pub use crate::attribute::K8S_CLUSTER_UID;
pub use crate::attribute::K8S_CONTAINER_NAME;
pub use crate::attribute::K8S_CONTAINER_RESTART_COUNT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_CONTAINER_STATUS_LAST_TERMINATED_REASON;
pub use crate::attribute::K8S_CRONJOB_ANNOTATION;
pub use crate::attribute::K8S_CRONJOB_LABEL;
pub use crate::attribute::K8S_CRONJOB_NAME;
pub use crate::attribute::K8S_CRONJOB_UID;
pub use crate::attribute::K8S_DAEMONSET_ANNOTATION;
pub use crate::attribute::K8S_DAEMONSET_LABEL;
pub use crate::attribute::K8S_DAEMONSET_NAME;
pub use crate::attribute::K8S_DAEMONSET_UID;
pub use crate::attribute::K8S_DEPLOYMENT_ANNOTATION;
pub use crate::attribute::K8S_DEPLOYMENT_LABEL;
pub use crate::attribute::K8S_DEPLOYMENT_NAME;
pub use crate::attribute::K8S_DEPLOYMENT_UID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_HPA_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_HPA_SCALETARGETREF_API_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_HPA_SCALETARGETREF_KIND;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_HPA_SCALETARGETREF_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_HPA_UID;
pub use crate::attribute::K8S_JOB_ANNOTATION;
pub use crate::attribute::K8S_JOB_LABEL;
pub use crate::attribute::K8S_JOB_NAME;
pub use crate::attribute::K8S_JOB_UID;
pub use crate::attribute::K8S_NAMESPACE_ANNOTATION;
pub use crate::attribute::K8S_NAMESPACE_LABEL;
pub use crate::attribute::K8S_NAMESPACE_NAME;
pub use crate::attribute::K8S_NODE_ANNOTATION;
pub use crate::attribute::K8S_NODE_LABEL;
pub use crate::attribute::K8S_NODE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_NODE_SYSTEM_CONTAINER_NAME;
pub use crate::attribute::K8S_NODE_UID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUME_ANNOTATION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUME_LABEL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUME_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUME_RECLAIM_POLICY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUME_UID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUMECLAIM_ANNOTATION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUMECLAIM_LABEL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUMECLAIM_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_PERSISTENTVOLUMECLAIM_UID;
pub use crate::attribute::K8S_POD_ANNOTATION;
pub use crate::attribute::K8S_POD_HOSTNAME;
pub use crate::attribute::K8S_POD_IP;
pub use crate::attribute::K8S_POD_LABEL;
pub use crate::attribute::K8S_POD_NAME;
pub use crate::attribute::K8S_POD_START_TIME;
pub use crate::attribute::K8S_POD_UID;
pub use crate::attribute::K8S_REPLICASET_ANNOTATION;
pub use crate::attribute::K8S_REPLICASET_LABEL;
pub use crate::attribute::K8S_REPLICASET_NAME;
pub use crate::attribute::K8S_REPLICASET_UID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_REPLICATIONCONTROLLER_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_REPLICATIONCONTROLLER_UID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_RESOURCEQUOTA_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_RESOURCEQUOTA_UID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_SERVICE_ANNOTATION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_SERVICE_LABEL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_SERVICE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_SERVICE_PUBLISH_NOT_READY_ADDRESSES;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_SERVICE_SELECTOR;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_SERVICE_TRAFFIC_DISTRIBUTION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_SERVICE_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_SERVICE_UID;
pub use crate::attribute::K8S_STATEFULSET_ANNOTATION;
pub use crate::attribute::K8S_STATEFULSET_LABEL;
pub use crate::attribute::K8S_STATEFULSET_NAME;
pub use crate::attribute::K8S_STATEFULSET_UID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::K8S_STORAGECLASS_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::MAINFRAME_LPAR_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OCI_MANIFEST_DIGEST;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OPENSHIFT_CLUSTERQUOTA_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OPENSHIFT_CLUSTERQUOTA_UID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OS_BUILD_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OS_DESCRIPTION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OS_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OS_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OS_VERSION;
pub use crate::attribute::OTEL_SCOPE_NAME;
pub use crate::attribute::OTEL_SCOPE_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_ARGS_COUNT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_COMMAND;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_COMMAND_ARGS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_COMMAND_LINE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_CREATION_TIME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_EXECUTABLE_BUILD_ID_GNU;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_EXECUTABLE_BUILD_ID_GO;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_EXECUTABLE_BUILD_ID_HTLHASH;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_EXECUTABLE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_EXECUTABLE_PATH;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_INTERACTIVE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_LINUX_CGROUP;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_OWNER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_PARENT_PID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_PID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_RUNTIME_DESCRIPTION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_RUNTIME_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_RUNTIME_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_TITLE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_WORKING_DIRECTORY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::SERVICE_CRITICALITY;
pub use crate::attribute::SERVICE_INSTANCE_ID;
pub use crate::attribute::SERVICE_NAME;
pub use crate::attribute::SERVICE_NAMESPACE;
pub use crate::attribute::SERVICE_VERSION;
pub use crate::attribute::TELEMETRY_DISTRO_NAME;
pub use crate::attribute::TELEMETRY_DISTRO_VERSION;
pub use crate::attribute::TELEMETRY_SDK_LANGUAGE;
pub use crate::attribute::TELEMETRY_SDK_NAME;
pub use crate::attribute::TELEMETRY_SDK_VERSION;
pub use crate::attribute::USER_AGENT_ORIGINAL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::VCS_REF_HEAD_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::VCS_REF_HEAD_REVISION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::VCS_REF_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::VCS_REPOSITORY_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::VCS_REPOSITORY_URL_FULL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::WEBENGINE_DESCRIPTION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::WEBENGINE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::WEBENGINE_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ZOS_SMF_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ZOS_SYSPLEX_NAME;
@@ -0,0 +1,755 @@
// DO NOT EDIT, this is an auto-generated file
//
// If you want to update the file:
// - Edit the template at scripts/templates/registry/rust/attributes.rs.j2
// - Run the script at scripts/generate-consts-from-spec.sh
//! # Trace Semantic Conventions
//!
//! The [trace semantic conventions] define a set of standardized attributes to
//! be used in `Span`s.
//!
//! [trace semantic conventions]: https://opentelemetry.io/docs/specs/semconv/general/trace/
//!
//! ## Usage
//!
//! ```rust
//! use opentelemetry::KeyValue;
//! use opentelemetry::{global, trace::Tracer as _};
//! use opentelemetry_semantic_conventions as semconv;
//!
//! let tracer = global::tracer("my-component");
//! let _span = tracer
//! .span_builder("span-name")
//! .with_attributes([
//! KeyValue::new(semconv::trace::CLIENT_ADDRESS, "example.org"),
//! KeyValue::new(semconv::trace::CLIENT_PORT, 80i64),
//! ])
//! .start(&tracer);
//! ```
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ANDROID_APP_STATE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_BUILD_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_CRASH_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_JANK_FRAME_COUNT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_JANK_PERIOD;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_JANK_THRESHOLD;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_SCREEN_COORDINATE_X;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_SCREEN_COORDINATE_Y;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_SCREEN_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_SCREEN_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_WIDGET_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::APP_WIDGET_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_BEDROCK_GUARDRAIL_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_BEDROCK_KNOWLEDGE_BASE_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_ATTRIBUTE_DEFINITIONS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_ATTRIBUTES_TO_GET;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_CONSISTENT_READ;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_CONSUMED_CAPACITY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_COUNT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_EXCLUSIVE_START_TABLE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_GLOBAL_SECONDARY_INDEX_UPDATES;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_GLOBAL_SECONDARY_INDEXES;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_INDEX_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_ITEM_COLLECTION_METRICS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_LIMIT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_LOCAL_SECONDARY_INDEXES;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_PROJECTION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_PROVISIONED_READ_CAPACITY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_PROVISIONED_WRITE_CAPACITY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_SCAN_FORWARD;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_SCANNED_COUNT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_SEGMENT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_SELECT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_TABLE_COUNT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_TABLE_NAMES;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_DYNAMODB_TOTAL_SEGMENTS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_EXTENDED_REQUEST_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_LAMBDA_INVOKED_ARN;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_LAMBDA_RESOURCE_MAPPING_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_REQUEST_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_S3_BUCKET;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_S3_COPY_SOURCE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_S3_DELETE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_S3_KEY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_S3_PART_NUMBER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AWS_S3_UPLOAD_ID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::AZ_SERVICE_REQUEST_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_CLIENT_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_COSMOSDB_CONNECTION_MODE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_COSMOSDB_CONSISTENCY_LEVEL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_COSMOSDB_OPERATION_CONTACTED_REGIONS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_COSMOSDB_OPERATION_REQUEST_CHARGE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_COSMOSDB_REQUEST_BODY_SIZE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_COSMOSDB_RESPONSE_SUB_STATUS_CODE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_RESOURCE_PROVIDER_NAMESPACE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::AZURE_SERVICE_REQUEST_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CASSANDRA_CONSISTENCY_LEVEL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CASSANDRA_COORDINATOR_DC;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CASSANDRA_COORDINATOR_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CASSANDRA_PAGE_SIZE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CASSANDRA_QUERY_IDEMPOTENT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CASSANDRA_SPECULATIVE_EXECUTION_COUNT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_ACTION_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_RESULT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_TASK_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_TASK_RUN_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_TASK_RUN_RESULT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CICD_PIPELINE_TASK_RUN_URL_FULL;
pub use crate::attribute::CLIENT_ADDRESS;
pub use crate::attribute::CLIENT_PORT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUD_REGION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::CLOUD_RESOURCE_ID;
pub use crate::attribute::DB_COLLECTION_NAME;
pub use crate::attribute::DB_NAMESPACE;
pub use crate::attribute::DB_OPERATION_BATCH_SIZE;
pub use crate::attribute::DB_OPERATION_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DB_OPERATION_PARAMETER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DB_QUERY_PARAMETER;
pub use crate::attribute::DB_QUERY_SUMMARY;
pub use crate::attribute::DB_QUERY_TEXT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DB_RESPONSE_RETURNED_ROWS;
pub use crate::attribute::DB_RESPONSE_STATUS_CODE;
pub use crate::attribute::DB_STORED_PROCEDURE_NAME;
pub use crate::attribute::DB_SYSTEM_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DNS_ANSWERS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::DNS_QUESTION_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ELASTICSEARCH_NODE_NAME;
pub use crate::attribute::ERROR_TYPE;
#[allow(deprecated)]
pub use crate::attribute::EXCEPTION_ESCAPED;
pub use crate::attribute::EXCEPTION_MESSAGE;
pub use crate::attribute::EXCEPTION_STACKTRACE;
pub use crate::attribute::EXCEPTION_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_COLDSTART;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_CRON;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_DOCUMENT_COLLECTION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_DOCUMENT_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_DOCUMENT_OPERATION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_DOCUMENT_TIME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_INVOKED_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_INVOKED_PROVIDER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_INVOKED_REGION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_TIME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FAAS_TRIGGER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_CONTEXT_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_ERROR_MESSAGE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_KEY;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_PROVIDER_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_RESULT_REASON;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_RESULT_VALUE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_RESULT_VARIANT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_SET_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::FEATURE_FLAG_VERSION;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_AGENT_DESCRIPTION;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_AGENT_ID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_AGENT_NAME;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_AGENT_VERSION;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_CONVERSATION_ID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_DATA_SOURCE_ID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_EMBEDDINGS_DIMENSION_COUNT;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_EVALUATION_EXPLANATION;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_EVALUATION_NAME;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_EVALUATION_SCORE_LABEL;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_EVALUATION_SCORE_VALUE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_INPUT_MESSAGES;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_OPERATION_NAME;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_OUTPUT_MESSAGES;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_OUTPUT_TYPE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_PROMPT_NAME;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_PROVIDER_NAME;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_CHOICE_COUNT;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_ENCODING_FORMATS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_FREQUENCY_PENALTY;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_MAX_TOKENS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_MODEL;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_PRESENCE_PENALTY;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_SEED;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_STOP_SEQUENCES;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_STREAM;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_TEMPERATURE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_TOP_K;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_REQUEST_TOP_P;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_RESPONSE_FINISH_REASONS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_RESPONSE_ID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_RESPONSE_MODEL;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_RETRIEVAL_DOCUMENTS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_RETRIEVAL_QUERY_TEXT;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_SYSTEM;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_SYSTEM_INSTRUCTIONS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_TOOL_CALL_ARGUMENTS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_TOOL_CALL_ID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_TOOL_CALL_RESULT;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_TOOL_DEFINITIONS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_TOOL_DESCRIPTION;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_TOOL_NAME;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_TOOL_TYPE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_USAGE_INPUT_TOKENS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_USAGE_OUTPUT_TOKENS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_USAGE_REASONING_OUTPUT_TOKENS;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::GEN_AI_WORKFLOW_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GRAPHQL_DOCUMENT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GRAPHQL_OPERATION_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::GRAPHQL_OPERATION_TYPE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HTTP_REQUEST_BODY_SIZE;
pub use crate::attribute::HTTP_REQUEST_HEADER;
pub use crate::attribute::HTTP_REQUEST_METHOD;
pub use crate::attribute::HTTP_REQUEST_METHOD_ORIGINAL;
pub use crate::attribute::HTTP_REQUEST_RESEND_COUNT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HTTP_REQUEST_SIZE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HTTP_RESPONSE_BODY_SIZE;
pub use crate::attribute::HTTP_RESPONSE_HEADER;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::HTTP_RESPONSE_SIZE;
pub use crate::attribute::HTTP_RESPONSE_STATUS_CODE;
pub use crate::attribute::HTTP_ROUTE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::IOS_APP_STATE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::JSONRPC_PROTOCOL_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::JSONRPC_REQUEST_ID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::MCP_METHOD_NAME;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::MCP_PROTOCOL_VERSION;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::MCP_RESOURCE_URI;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::MCP_SESSION_ID;
pub use crate::attribute::NETWORK_LOCAL_ADDRESS;
pub use crate::attribute::NETWORK_LOCAL_PORT;
pub use crate::attribute::NETWORK_PEER_ADDRESS;
pub use crate::attribute::NETWORK_PEER_PORT;
pub use crate::attribute::NETWORK_PROTOCOL_NAME;
pub use crate::attribute::NETWORK_PROTOCOL_VERSION;
pub use crate::attribute::NETWORK_TRANSPORT;
pub use crate::attribute::NETWORK_TYPE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::OPENAI_API_TYPE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::OPENAI_REQUEST_SERVICE_TIER;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::OPENAI_RESPONSE_SERVICE_TIER;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::OPENAI_RESPONSE_SYSTEM_FINGERPRINT;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ORACLE_DB_DOMAIN;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ORACLE_DB_INSTANCE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ORACLE_DB_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ORACLE_DB_PDB;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::ORACLE_DB_SERVICE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OS_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::OS_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_COMMAND_ARGS;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_EXECUTABLE_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_EXECUTABLE_PATH;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_EXIT_CODE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::PROCESS_PID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::RPC_MESSAGE_COMPRESSED_SIZE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::RPC_MESSAGE_ID;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::RPC_MESSAGE_TYPE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::RPC_MESSAGE_UNCOMPRESSED_SIZE;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::RPC_METHOD;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::RPC_METHOD_ORIGINAL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::RPC_REQUEST_METADATA;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::RPC_RESPONSE_METADATA;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::RPC_RESPONSE_STATUS_CODE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::RPC_SERVICE;
#[cfg(feature = "semconv_experimental")]
#[allow(deprecated)]
pub use crate::attribute::RPC_SYSTEM;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::RPC_SYSTEM_NAME;
pub use crate::attribute::SERVER_ADDRESS;
pub use crate::attribute::SERVER_PORT;
pub use crate::attribute::SERVICE_VERSION;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::SESSION_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::SESSION_PREVIOUS_ID;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::TLS_PROTOCOL_NAME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::TLS_PROTOCOL_VERSION;
pub use crate::attribute::URL_FULL;
pub use crate::attribute::URL_PATH;
pub use crate::attribute::URL_QUERY;
pub use crate::attribute::URL_SCHEME;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::URL_TEMPLATE;
pub use crate::attribute::USER_AGENT_ORIGINAL;
#[cfg(feature = "semconv_experimental")]
pub use crate::attribute::USER_AGENT_SYNTHETIC_TYPE;
+952
View File
@@ -0,0 +1,952 @@
# Changelog
## vNext
## 0.33.0
Released 2026-Sep-18
- Fix `TraceState` accepting more than the 32 list-members the W3C trace-context
specification allows. `from_str`, `from_key_value` and `insert` now keep at most
32, dropping members from the end of the list as the specification prescribes, so
neither a parsed nor a locally built `tracestate` can exceed the limit.
- **Added** experimental support for a global context event observer. A
`ContextObserver` can be registered via `GlobalContextObserver::set` to be
notified of context transitions through the `on_context_enter` and
`on_context_exit` callbacks. This feature is primarily intended to publish a
different view of the current context (the `ObserverContextView`) through
alternative channels that let external readers (e.g. an eBPF profiler) track
the current context. See the associated
[OTEP](https://github.com/open-telemetry/opentelemetry-specification/pull/4947).
Gated behind the `experimental_context_observer` feature flag.
- `otel_info!`, `otel_warn!`, `otel_debug!`, and `otel_error!` macros now accept quoted-key fields
(e.g. `"otel.component.type" = "value"`) for dotted attribute names.
- **Added** `BoundGauge<T>` and `BoundUpDownCounter<T>` types (and the
corresponding `Gauge::bind()` / `UpDownCounter::bind()` methods), completing
the experimental bound-instrument API across all sync instruments
(`Counter`, `UpDownCounter`, `Histogram`, `Gauge`). Gated behind the
`experimental_metrics_bound_instruments` feature flag.
- Fix a panic when a value stored with `Context::with_value()` calls
`Context::current()` from its `Drop` implementation.
## 0.32.0
Released 2026-May-08
- **Added** `BoundCounter<T>` and `BoundHistogram<T>` types that cache resolved
aggregator references for a fixed attribute set. Created via `Counter::bind()`
and `Histogram::bind()`, bound instruments bypass per-call attribute lookup,
providing significant performance improvements for hot paths where the same
attributes are used repeatedly. Both types implement `Clone` so a single bound
state can be shared across threads or modules without re-binding. Also adds
the `SyncInstrument::bind()` trait method and `BoundSyncInstrument<T>` trait
for SDK implementors; the trait method has a no-op default so custom
`SyncInstrument` impls degrade gracefully without panicking. Gated behind the
`experimental_metrics_bound_instruments` feature flag.
- Add `reserve` method to `opentelemetry::propagation::Injector` to hint at the number of elements that will be added to avoid multiple resize operations of the underlying data structure. Has an empty default implementation.
- **Breaking** Removed the following public fields and methods from the `SpanBuilder` [#3227][3227]:
- `trace_id`, `span_id`, `end_time`, `status`, `sampling_result`
- `with_trace_id`, `with_span_id`, `with_end_time`, `with_status`, `with_sampling_result`
- **Added** `#[must_use]` attribute to `opentelemetry::metrics::AsyncInstrumentBuilder` to add compile time warning when `.build()` is not called on observable instrument builders, preventing silent failures where callbacks are never registered and metrics are never reported.
- **Breaking** Moved the following SDK sampling types from `opentelemetry::trace` to `opentelemetry_sdk::trace` [#3277][3277]:
- `SamplingDecision`, `SamplingResult`
- These types are SDK implementation details and should be imported from `opentelemetry_sdk::trace` instead.
- "spec_unstable_logs_enabled" feature flag is removed. The capability (and the
backing specification) is now stable and is enabled by default.
[3278](https://github.com/open-telemetry/opentelemetry-rust/pull/3278)
- Remove the empty "message" field from `tracing` events emitted via the `internal-logs` feature
- Fix panic when calling `Context::current()` from `Drop` implementations triggered by `ContextGuard` cleanup ([#3262][3262]).
[3227]: https://github.com/open-telemetry/opentelemetry-rust/pull/3227
[3262]: https://github.com/open-telemetry/opentelemetry-rust/pull/3262
[3277]: https://github.com/open-telemetry/opentelemetry-rust/pull/3277
## v0.31.0
Released 2025-Sep-25
- *Breaking* Change return type of `opentelemetry::global::set_tracer_provider` to Unit to align with metrics counterpart
- Add `get_all` method to `opentelemetry::propagation::Extractor` to return all values of the given propagation key and provide a default implementation.
- Add an `IntoIterator` implementation for `opentelemetry::trace::TraceState` to allow iterating through its key-value pair collection.
## 0.30.0
Released 2025-May-23
[#2821](https://github.com/open-telemetry/opentelemetry-rust/pull/2821) Context
based suppression capabilities added: Added the ability to prevent recursive
telemetry generation through new context-based suppression mechanisms. This
feature helps prevent feedback loops and excessive telemetry when OpenTelemetry
components perform their own operations.
New methods added to `Context`:
- `is_telemetry_suppressed()` - Checks if telemetry is suppressed in this
context
- `with_telemetry_suppressed()` - Creates a new context with telemetry
suppression enabled
- `is_current_telemetry_suppressed()` - Efficiently checks if the current thread's context
has telemetry suppressed
- `enter_telemetry_suppressed_scope()` - Convenience method to enter a scope where telemetry is
suppressed
These methods allow SDK components, exporters, and processors to temporarily
disable telemetry generation during their internal operations, ensuring more
predictable and efficient observability pipelines.
- re-export `tracing` for `internal-logs` feature to remove the need of adding `tracing` as a dependency
## 0.29.1
Release 2025-Apr-01
- Bug Fix: Re-export `WithContext` at `opentelemetry::trace::context::WithContext` [#2879](https://github.com/open-telemetry/opentelemetry-rust/pull/2879) to restore backwards compatibility
- The new path for `WithContext` and `FutureExt` are in `opentelemetry::context` as they are independent of the trace signal. Users should prefer this path.
## 0.29.0
Released 2025-Mar-21
- *Breaking* Moved `ExportError` trait from `opentelemetry::trace::ExportError` to `opentelemetry_sdk::export::ExportError`
- *Breaking* Moved `TraceError` enum from `opentelemetry::trace::TraceError` to `opentelemetry_sdk::trace::TraceError`
- *Breaking* Moved `TraceResult` type alias from `opentelemetry::trace::TraceResult` to `opentelemetry_sdk::trace::TraceResult`
- Bug Fix: `InstrumentationScope` implementation for `PartialEq` and `Hash` fixed to include Attributes also.
- **Breaking changes for baggage users**: [#2717](https://github.com/open-telemetry/opentelemetry-rust/issues/2717)
- Changed value type of `Baggage` from `Value` to `StringValue`
- Updated `Baggage` constants to reflect latest standard (`MAX_KEY_VALUE_PAIRS` - 180 -> 64, `MAX_BYTES_FOR_ONE_PAIR` - removed) and increased insert performance see #[2284](https://github.com/open-telemetry/opentelemetry-rust/pull/2284).
- Align `Baggage.remove()` signature with `.get()` to take the key as a reference
- `Baggage` can't be retrieved from the `Context` directly anymore and needs to be accessed via `context.baggage()`
- `with_baggage()` and `current_with_baggage()` override any existing `Baggage` in the `Context`
- `Baggage` keys can't be empty and only allow ASCII visual chars, except `"(),/:;<=>?@[\]{}` (see [RFC7230, Section 3.2.6](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6))
- `KeyValueMetadata` does not publicly expose its fields. This should be transparent change to the users.
- Changed `Context` to use a stack to properly handle out of order dropping of `ContextGuard`. This imposes a limit of `65535` nested contexts on a single thread. See #[2378](https://github.com/open-telemetry/opentelemetry-rust/pull/2284) and #[1887](https://github.com/open-telemetry/opentelemetry-rust/issues/1887).
- Added additional `name: Option<&str>` parameter to the `event_enabled` method
on the `Logger` trait. This allows implementations (SDK, processor, exporters)
to leverage this additional information to determine if an event is enabled.
## 0.28.0
Released 2025-Feb-10
- Bump msrv to 1.75.0.
- **Breaking** `opentelemetry::global::shutdown_tracer_provider()` Removed from this crate, should now use `tracer_provider.shutdown()` see [#2369](https://github.com/open-telemetry/opentelemetry-rust/pull/2369) for a migration example.
- *Breaking* Removed unused `opentelemetry::PropagationError` struct.
## 0.27.1
Released 2024-Nov-27
## 0.27.0
Released 2024-Nov-11
- Bump MSRV to 1.70 [#2179](https://github.com/open-telemetry/opentelemetry-rust/pull/2179)
- Add `LogRecord::set_trace_context`; an optional method conditional on the `trace` feature for setting trace context on a log record.
- Removed unnecessary public methods named `as_any` from `AsyncInstrument` trait and the implementing instruments: `ObservableCounter`, `ObservableGauge`, and `ObservableUpDownCounter` [#2187](https://github.com/open-telemetry/opentelemetry-rust/pull/2187)
- Introduced `SyncInstrument` trait to replace the individual synchronous instrument traits (`SyncCounter`, `SyncGauge`, `SyncHistogram`, `SyncUpDownCounter`) which are meant for SDK implementation. [#2207](https://github.com/open-telemetry/opentelemetry-rust/pull/2207)
- Ensured that `observe` method on asynchronous instruments can only be called inside a callback. This was done by removing the implementation of `AsyncInstrument` trait for each of the asynchronous instruments. [#2210](https://github.com/open-telemetry/opentelemetry-rust/pull/2210)
- Removed `PartialOrd` and `Ord` implementations for `KeyValue`. [#2215](https://github.com/open-telemetry/opentelemetry-rust/pull/2215)
- **Breaking change for exporter authors:** Marked `KeyValue` related structs and enums as `non_exhaustive`. [#2228](https://github.com/open-telemetry/opentelemetry-rust/pull/2228)
- **Breaking change for log exporter authors:** Marked `AnyValue` enum as `non_exhaustive`. [#2230](https://github.com/open-telemetry/opentelemetry-rust/pull/2230)
- **Breaking change for Metrics users:** The `init` method used to create instruments has been renamed to `build`. Also, `try_init()` method is removed from instrument builders. The return types of `InstrumentProvider` trait methods modified to return the instrument struct, instead of `Result`. [#2227](https://github.com/open-telemetry/opentelemetry-rust/pull/2227)
Before:
```rust
let counter = meter.u64_counter("my_counter").init();
```
Now:
```rust
let counter = meter.u64_counter("my_counter").build();
```
- **Breaking change**: [#2220](https://github.com/open-telemetry/opentelemetry-rust/pull/2220)
- Removed deprecated method `InstrumentationLibrary::new`
- Renamed `InstrumentationLibrary` to `InstrumentationScope`
- Renamed `InstrumentationLibraryBuilder` to `InstrumentationScopeBuilder`
- Removed deprecated methods `LoggerProvider::versioned_logger` and `TracerProvider::versioned_tracer`
- Removed methods `LoggerProvider::logger_builder`, `TracerProvider::tracer_builder` and `MeterProvider::versioned_meter`
- Replaced these methods with `LoggerProvider::logger_with_scope`, `TracerProvider::logger_with_scope`, `MeterProvider::meter_with_scope`
- Replaced `global::meter_with_version` with `global::meter_with_scope`
- Added `global::tracer_with_scope`
- Refer to PR description for migration guide.
- **Breaking change**: replaced `InstrumentationScope` public attributes by getters [#2275](https://github.com/open-telemetry/opentelemetry-rust/pull/2275)
- **Breaking change**: [#2260](https://github.com/open-telemetry/opentelemetry-rust/pull/2260)
- Removed `global::set_error_handler` and `global::handle_error`.
- `global::handle_error` usage inside the opentelemetry crates has been replaced with `global::otel_info`, `otel_warn`, `otel_debug` and `otel_error` macros based on the severity of the internal logs.
- The default behavior of `global::handle_error` was to log the error using `eprintln!`. With otel macros, the internal logs get emitted via `tracing` macros of matching severity. Users now need to configure a `tracing` layer/subscriber to capture these logs.
- Refer to PR description for migration guide. Also refer to [self-diagnostics](https://github.com/open-telemetry/opentelemetry-rust/tree/main/examples/self-diagnostics) example to learn how to view internal logs in stdout using `tracing::fmt` layer.
- **Breaking change for exporter/processor authors:** [#2266](https://github.com/open-telemetry/opentelemetry-rust/pull/2266)
- Moved `ExportError` trait from `opentelemetry::ExportError` to `opentelemetry_sdk::export::ExportError`
- Created new trait `opentelemetry::trace::ExportError` for trace API. This would be eventually be consolidated with ExportError in the SDK.
- Moved `LogError` enum from `opentelemetry::logs::LogError` to `opentelemetry_sdk::logs::LogError`
- Moved `LogResult` type alias from `opentelemetry::logs::LogResult` to `opentelemetry_sdk::logs::LogResult`
- Moved `MetricError` enum from `opentelemetry::metrics::MetricError` to `opentelemetry_sdk::metrics::MetricError`
- Moved `MetricResult` type alias from `opentelemetry::metrics::MetricResult` to `opentelemetry_sdk::metrics::MetricResult`
These changes shouldn't directly affect the users of OpenTelemetry crate, as these constructs are used in SDK and Exporters. If you are an author of an sdk component/plug-in, like an exporter etc. please use these types from sdk. Refer [CHANGELOG.md](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-sdk/CHANGELOG.md) for more details, under same version section.
- **Breaking** [2291](https://github.com/open-telemetry/opentelemetry-rust/pull/2291) Rename `logs_level_enabled flag` to `spec_unstable_logs_enabled`. Please enable this updated flag if the feature is needed. This flag will be removed once the feature is stabilized in the specifications.
## v0.26.0
Released 2024-Sep-30
- **BREAKING** Public API changes:
- **Removed**: `Key.bool()`, `Key.i64()`, `Key.f64()`, `Key.string()`, `Key.array()` [#2090](https://github.com/open-telemetry/opentelemetry-rust/issues/2090). These APIs were redundant as they didn't offer any additional functionality. The existing `KeyValue::new()` API covers all the scenarios offered by these APIs.
- **Removed**: `ObjectSafeMeterProvider` and `GlobalMeterProvider` [#2112](https://github.com/open-telemetry/opentelemetry-rust/pull/2112). These APIs were unnecessary and were mainly meant for internal use.
- **Modified**: `MeterProvider.meter()` and `MeterProvider.versioned_meter()` argument types have been updated to `&'static str` instead of `impl Into<Cow<'static, str>>>` [#2112](https://github.com/open-telemetry/opentelemetry-rust/pull/2112). These APIs were modified to enforce the Meter `name`, `version`, and `schema_url` to be `&'static str`.
- **Renamed**: `NoopMeterCore` to `NoopMeter`
- Added `with_boundaries` API to allow users to provide custom bounds for Histogram instruments. [#2135](https://github.com/open-telemetry/opentelemetry-rust/pull/2135)
## v0.25.0
- **BREAKING** [#1993](https://github.com/open-telemetry/opentelemetry-rust/pull/1993) Box complex types in AnyValue enum
Before:
```rust
#[derive(Debug, Clone, PartialEq)]
pub enum AnyValue {
/// An integer value
Int(i64),
/// A double value
Double(f64),
/// A string value
String(StringValue),
/// A boolean value
Boolean(bool),
/// A byte array
Bytes(Vec<u8>),
/// An array of `Any` values
ListAny(Vec<AnyValue>),
/// A map of string keys to `Any` values, arbitrarily nested.
Map(HashMap<Key, AnyValue>),
}
```
After:
```rust
#[derive(Debug, Clone, PartialEq)]
pub enum AnyValue {
/// An integer value
Int(i64),
/// A double value
Double(f64),
/// A string value
String(StringValue),
/// A boolean value
Boolean(bool),
/// A byte array
Bytes(Box<Vec<u8>>),
/// An array of `Any` values
ListAny(Box<Vec<AnyValue>>),
/// A map of string keys to `Any` values, arbitrarily nested.
Map(Box<HashMap<Key, AnyValue>>),
}
```
So the custom log appenders should box these types while adding them in message body, or
attribute values. Similarly, the custom exporters should dereference these complex type values
before serializing.
*Breaking* :
[#2015](https://github.com/open-telemetry/opentelemetry-rust/pull/2015) Removed
the ability to register callbacks for Observable instruments on Meter directly.
If you were using `meter.register_callback` to provide the callback, provide
them using `with_callback` method, while creating the Observable instrument
itself.
[1715](https://github.com/open-telemetry/opentelemetry-rust/pull/1715/files)
shows the exact changes needed to make this migration. If you are starting new,
refer to the
[examples](https://github.com/open-telemetry/opentelemetry-rust/blob/main/examples/metrics-basic/src/main.rs)
to learn how to provide Observable callbacks.
## v0.24.0
- Add "metrics", "logs" to default features. With this, default feature list is
"trace", "metrics" and "logs".
- When "metrics" feature is enabled, `KeyValue` implements `PartialEq`, `Eq`,
`PartialOrder`, `Order`, `Hash`. This is meant to be used for metrics
aggregation purposes only.
- Removed `Unit` struct for specifying Instrument units. Unit is treated as an
opaque string. Migration: Replace `.with_unit(Unit::new("myunit"))` with
`.with_unit("myunit")`.
- [1869](https://github.com/open-telemetry/opentelemetry-rust/pull/1869) Introduced the `LogRecord::set_target()` method in the log bridge API.
This method allows appenders to set the target/component emitting the logs.
## v0.23.0
### Added
- [#1640](https://github.com/open-telemetry/opentelemetry-rust/pull/1640) Add `PropagationError`
- [#1701](https://github.com/open-telemetry/opentelemetry-rust/pull/1701) `Gauge` no longer requires `otel-unstable` feature flag, as OpenTelemetry specification for `Gauge` instrument is stable.
### Removed
- Remove `urlencoding` crate dependency. [#1613](https://github.com/open-telemetry/opentelemetry-rust/pull/1613)
- Remove global providers for Logs [$1691](https://github.com/open-telemetry/opentelemetry-rust/pull/1691)
LoggerProviders are not meant for end users to get loggers from. It is only required for the log bridges.
Below global constructs for the logs are removed from API:
- opentelemetry::global::logger
- opentelemetry::global::set_logger_provider
- opentelemetry::global::shutdown_logger_provider
- opentelemetry::global::logger_provider
- opentelemetry::global::GlobalLoggerProvider
- opentelemetry::global::ObjectSafeLoggerProvider
For creating appenders using Logging bridge API, refer to the opentelemetry-tracing-appender [example](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-appender-tracing/examples/basic.rs)
### Changed
- **BREAKING** Moving LogRecord implementation to the SDK. [1702](https://github.com/open-telemetry/opentelemetry-rust/pull/1702).
- Relocated `LogRecord` struct to SDK.
- Introduced the `LogRecord` trait in the API for populating log records. This trait is implemented by the SDK.
This is the breaking change for the authors of Log Appenders. Refer to the [opentelemetry-appender-tracing](https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-appender-tracing) for more details.
- Deprecate `versioned_logger()` in favor of `logger_builder()` [1567](https://github.com/open-telemetry/opentelemetry-rust/pull/1567).
Before:
```rust
let logger = provider.versioned_logger(
"my-logger-name",
Some(env!("CARGO_PKG_VERSION")),
Some("https://opentelemetry.io/schemas/1.0.0"),
Some(vec![KeyValue::new("key", "value")]),
);
```
After:
```rust
let logger = provider
.logger_builder("my-logger-name")
.with_version(env!("CARGO_PKG_VERSION"))
.with_schema_url("https://opentelemetry.io/schemas/1.0.0")
.with_attributes(vec![KeyValue::new("key", "value")])
.build();
```
- Deprecate `versioned_tracer()` in favor of `tracer_builder()` [1567](https://github.com/open-telemetry/opentelemetry-rust/pull/1567).
Before:
```rust
let tracer = provider.versioned_tracer(
"my-tracer-name",
Some(env!("CARGO_PKG_VERSION")),
Some("https://opentelemetry.io/schemas/1.0.0"),
Some(vec![KeyValue::new("key", "value")]),
);
```
After:
```rust
let tracer = provider
.tracer_builder("my-tracer-name")
.with_version(env!("CARGO_PKG_VERSION"))
.with_schema_url("https://opentelemetry.io/schemas/1.0.0")
.with_attributes(vec![KeyValue::new("key", "value")])
.build();
```
## v0.22.0
### Added
- [#1410](https://github.com/open-telemetry/opentelemetry-rust/pull/1410) Add experimental synchronous gauge. This is behind the feature flag, and can be enabled by enabling the feature `otel_unstable` for opentelemetry crate.
- [#1410](https://github.com/open-telemetry/opentelemetry-rust/pull/1410) Guidelines to add new unstable/experimental features.
### Changed
- Modified `AnyValue.Map` to be backed by `HashMap` instead of custom `OrderMap`,
which internally used `IndexMap`. There was no requirement to maintain the order
of entries, so moving from `IndexMap` to `HashMap` offers slight performance
gains, and avoids `IndexMap` dependency. This affects `body` and `attributes` of
`LogRecord`.
[#1353](https://github.com/open-telemetry/opentelemetry-rust/pull/1353)
- Add `TextMapCompositePropagator` [#1373](https://github.com/open-telemetry/opentelemetry-rust/pull/1373)
- Turned off events for `NoopLogger` to save on operations
[#1455](https://github.com/open-telemetry/opentelemetry-rust/pull/1455)
### Removed
- Removed `OrderMap` type as there was no requirement to use this over regular
`HashMap`.
[#1353](https://github.com/open-telemetry/opentelemetry-rust/pull/1353)
- Remove API for Creating Histograms with signed integers. [#1371](https://github.com/open-telemetry/opentelemetry-rust/pull/1371)
- Remove `global::shutdown_meter_provider`, use `SdkMeterProvider::shutdown`
directly instead [#1412](https://github.com/open-telemetry/opentelemetry-rust/pull/1412).
## [v0.21.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.20.0...v0.21.0)
This release should been seen as 1.0-rc4 following 1.0-rc3 in v0.20.0. Refer to CHANGELOG.md in individual creates for details on changes made in different creates.
### Changed
- Bump MSRV to 1.65 [#1318](https://github.com/open-telemetry/opentelemetry-rust/pull/1318)
- Bump MSRV to 1.64 [#1203](https://github.com/open-telemetry/opentelemetry-rust/pull/1203)
- `opentelemetry` crate now only carries the API types [#1186](https://github.com/open-telemetry/opentelemetry-rust/issues/1186). Use the `opentelemetry_sdk` crate for the SDK types.
- `trace::noop::NoopSpan` no longer implements `Default` and instead exposes
a `const DEFAULT` value. [#1270](https://github.com/open-telemetry/opentelemetry-rust/pull/1270)
- Updated crate documentation and examples.
[#1256](https://github.com/open-telemetry/opentelemetry-rust/issues/1256)
- **Breaking** `SpanBuilder` attributes changed from `OrderMap<Key, Value>` to
`Vec<KeyValue>` and `with_attributes_map` method is removed from `SpanBuilder`.
This implies that OpenTelemetry API will no longer perform
de-dup of attribute Keys.
[#1293](https://github.com/open-telemetry/opentelemetry-rust/issues/1293).
Please share [feedback
here](https://github.com/open-telemetry/opentelemetry-rust/issues/1300), if
you are affected.
## [v0.20.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.19.0...v0.20.0)
This release should been seen as 1.0-rc3 following 1.0-rc2 in v0.19.0. Refer to CHANGELOG.md in individual creates for details on changes made in different creates.
### Added
- Add `new` method to `BoxedTracer` #1009
- Add js-sys as dependency for api crate when building wasm targets #1078
- Create tracer using a shared instrumentation library #1129
- Add `Context::map_current` #1140
- Add unit/doc tests for metrics #1213
- Add `opentelemetry::sdk::logs::config()` for parity with `opentelemetry::sdk::trace::config()` (#1197)
### Changed
- `OtelString::Owned` carries `Box<str>` instead of `String` #1096
### Removed
- Drop include_trace_context parameter from Logs API/SDK. [#1133](https://github.com/open-telemetry/opentelemetry-rust/issues/1133)
- Synchronous instruments no longer accepts `Context` while reporting
measurements. [#1076](https://github.com/open-telemetry/opentelemetry-rust/pull/1076).
- Fallible conversions from `futures-channel` error types to `LogError` and
`TraceError` removed.
[#1201](https://github.com/open-telemetry/opentelemetry-rust/issues/1201)
### Fixed
- Fix `SpanRef::set_attributes` mutability requirement. [#1038](https://github.com/open-telemetry/opentelemetry-rust/pull/1038)
- Move OrderMap module to root of otel-api crate. [#1061](https://github.com/open-telemetry/opentelemetry-rust/pull/1061)
- Use the browser-only js-sys workaround only when actually targeting a browser #1008
## [v0.19.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.18.0...v0.19.0)
This release should been seen as 1.0-rc2 following 1.0-rc1 in v0.18.0. Refer to CHANGELOG.md in individual creates for details on changes made in different creates.
### Added
- Add `WithContext` to public api [#893](https://github.com/open-telemetry/opentelemetry-rust/pull/893).
- Add support for instrumentation scope attributes [#1021](https://github.com/open-telemetry/opentelemetry-rust/pull/1021).
### Changed
- Implement `Display` on `Baggage` [#921](https://github.com/open-telemetry/opentelemetry-rust/pull/921).
- Bump MSRV to 1.57 [#953](https://github.com/open-telemetry/opentelemetry-rust/pull/953).
- Update dependencies and bump MSRV to 1.60 [#969](https://github.com/open-telemetry/opentelemetry-rust/pull/969).
## [v0.18.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.17.0...v0.18.0)
This release is the first beta release of the `trace` API and SDK. If no other
breaking changes are necessary, the next release will be 1.0. The `metrics` API
and SDK are still unstable.
### Added
- Pull sampling probability from `OTEL_TRACES_SAMPLER_ARG` in default sdk config #737
- Add `schema_url` to `Tracer` #743
- Add `schema_url` to `Resource` #775
- Add `Span::set_attributes` #638
- Support concurrent exports #781
- Add jaeger remote sampler #797
- Allow Custom Samplers #833
- Add `SpanExporter::force_flush` and default implementation #845
### Changed
- Deprecate metrics `ValueRecorder` in favor of `Histogram` #728
- Move `IdGenerator` to SDK, rename to `RandomIdGenerator` #742
- `meter_with_version` accepts optional parameter for `version` and `schema_url` #752
- Unify `Event` and `Link` access patterns #757
- move `SpanKind` display format impl to jaeger crate #758
- make `TraceStateError` private #755
- rename `Span::record_exception` to `Span::record_error` #756
- Replace `StatusCode` and `message` with `Status` #760
- Move `TracerProvider::force_flush` to SDK #658
- Switch to static resource references #790
- Allow O(1) get operations for `SpanBuilder::attributes` [breaking] #799
- Allow ref counted keys and values #821
- Bump MSRV from 1.49 to 1.55 #811
- bump MSRV to 1.56 #866
- Update metrics API and SDK for latest spec #819
- Switch to `pin-project-lite` #830
### Fixed
- Update dashmap to avoid soundness hole #818
- Perform sampling as explained in the specification #839
- Remove internal message queue between exporter and exporting tasks #848
- Fix span processor exporting unsampled spans #871
### Removed
- Remove `serialize` feature #738
- Remove `StatusCode::as_str` #741
- Remove `Tracer::with_span` #746
## [v0.17.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.16.0...v0.17.0)
### Changed
- Implement `Serialize` & `Deserialize` for `Sampler`, `SpanLimits` #622, #626
- Allow `&'static str` and `string` in span methods #654
- Allow `String` data in instrumentation library. #670
- Remove `std::fmt::Debug` and `'static` requirements from `TracerProvider`,
`Tracer`, and `Span` #664
- Remove unused `Tracer::invalid` method #683
- Split `TracerProvider::tracer` and `TracerProvider::versioned_tracer` methods #682
- Reduce dependency on `futures` crate #684
- Switch to parent context references #687
- Spec-compliant trace and span ids #689
- Optimize span creation internals #693
- Add instrumentation library to `ShouldSample` parameters #695
### Fixed
- Fix default resource detection for tracer provider #641
- Detect `service.name` from `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` #662
- Fix `TraceState::valid_key` crashes #665
## [v0.16.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.15.0...v0.16.0)
### Changed
- Add default resource in `TracerProvider` #571
- Rename `get_tracer` to `tracer` #586
- Extract `trace::noop` module and update docs #587
- Add `Hash` impl for span context and allow spans to clone and export current state #592
- Enforce span status code's order #593
- Make `SpanRef` public #600
- Make `SpanProcessor::on_start` take a mutable span #601
- Renamed `label` to `attribute` to align with otel specification #609
### Performance
- Small performance boost for `Resource::get` #579
## [v0.15.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.14.0...v0.15.0)
### Added
- More resource detectors #573
### Changed
- Expose the Error type to allow users to set custom error handlers #551
- Allow users to use different channels based on runtime in batch span processor #560
- Move `Unit` into `metrics` module #564
- Update trace flags to match spec #565
### Fixed
- Fix debug loop, add notes for `#[tokio::test]` #552
- `TraceState` cannot insert new key-value pairs #567
## [v0.14.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.13.0...v0.14.0)
## Added
- Adding a dynamic dispatch to Aggregator Selector #497
- Add `global::force_flush_tracer_provider` #512
- Add config `max_attributes_per_event` and `max_attributes_per_link` #521
- Add dropped attribute counts to events and links #529
## Changed
- Remove unnecessary clone in `Key` type #491
- Remove `#[must_use]` from `set_tracer_provider` #501
- Rename remaining usage of `default_sampler` to `sampler` #509
- Use current span for SDK-less context propagation #510
- Always export span batch when limit reached #519
- Rename message events to events #530
- Update resource merge behavior #537
- Ignore links with invalid context #538
## Removed
- Remove remote span context #508
- Remove metrics quantiles #525
# Fixed
- Allow users to use custom export kind selector #526
## Performance
- Improve simple span processor performance #502
- Local span perf improvements #505
- Reduce string allocations where possible #506
## [v0.13.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.12.0...v0.13.0)
Upgrade note: exporter pipelines do not return an uninstall guard as of #444,
use `opentelemetry::global::shutdown_tracer_provider` explicitly instead.
## Changed
- Pull configurations from environment variables by default when creating BatchSpanProcessor #445
- Convert doc links to intra-doc #466
- Switch to Cow for event names #471
- Use API to configure async runtime instead of features #481
- Rename trace config with_default_sampler to with_sampler #482
## Removed
- Removed tracer provider guard #444
- Removed `from_env` and use environment variables to initialize the configurations by default #459
## [v0.12.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.11.2...v0.12.0)
## Added
- Instrumentation library support #402
- Batch observer support #429
- `with_unit` methods in metrics #431
- Clone trait for noop tracer/tracer provider/span #479
- Abstracted traits for different runtimes #480
## Changed
- Dependencies updates #410
- Add `Send`, `Sync` to `AsyncInstrument` in metrics #422
- Add `Send`, `Sync` to `InstrumentCore` in metrics #423
- Replace regex with custom logic #411
- Update tokio to v1 #421
## Removed
- Moved `http` dependencies into a new opentelemetry-http crate #415
- Remove `tonic` dependency #414
## [v0.11.2](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.11.1...v0.11.2)
# Fixed
- Fix possible deadlock when dropping metric instruments #407
## [v0.11.1](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.11.0...v0.11.1)
# Fixed
- Fix remote implicit builder context sampling #405
## [v0.11.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.10.0...v0.11.0)
## Added
- Add `force_flush` method to span processors #358
- Add timeout for `force_flush` and `shutdown` #362
## Changed
- Implement Display trait for Key and Value types #353
- Remove Option from Array values #359
- Update `ShouldSample`'s parent parameter to be `Context` #368
- Consolidate error types in `trace` module into `TraceError` #371
- Add `#[must_use]` to uninstall structs #372
- Move 3rd party propagators and merge exporter into `sdk::export` #375
- Add instrumentation version to instrument config #392
- Use instrumentation library in metrics #393
- `start_from_context` renamed to `start_with_context` #399
- Removed `build_with_context` as full context is now stored in builder #399
- SpanBuilder's `with_parent` renamed to `with_parent_context` #399
# Fixed
- Fix parent based sampling in tracer #354
- StatusCode enum value ordering #377
- Counter adding the delta from last collection #395
- `HistogramAggregator` returning sum vs count #398
## [v0.10.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.9.1...v0.10.0)
## Added
- Add support for baggage metadata #287
## Changed
- Remove `api` prefix from modules #305
- Move `mark_as_active_span` and `get_active_span` functions into trace module #310
- Revert renaming of `SpanContext` to `SpanReference` #299
- Default trace propagator is now a no-op #329
- Return references to span contexts instead of clones #325
- Update exporter errors to be `Box<dyn Error + Send + Sync + 'static>` #284
- Rename `GenericProvider` to `GenericTracerProvider` #313
- Reduce `SpanStatus` enum to `Ok`, `Error`, and `Unset` variants #315
- update B3 propagator to more closely match spec #319
- Export missing pub global trace types #313
- Ensure kv array values are homogeneous #333
- Implement `Display` trait for `Key` and `Value` types #353
- Move `SpanProcessor` trait into `sdk` module #334
- Ensure `is_recording` is `false` and span is no-op after `end` #341
- Move binary propagator and base64 format to contrib #343
- Ensure metrics noop types go through constructors #345
- Change `ExportResult` to use `std::result::Result` #347
- Change `SpanExporter::export` to take `&mut self` instead of `&self` #350
- Add MSRV 1.42.0 #296
## Fixed
- Fix parent based sampling #354
## Removed
- Remove support for `u64` and `bytes` kv values #323
- Remove kv value conversion from `&str` #332
## [v0.9.1](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.9.0...v0.9.1)
## Added
- Allow metric instruments to be cloned #280
### Fixed
- Fix single threaded runtime tokio feature bug #278
## [v0.9.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.8.0...v0.9.0)
## Added
- Add resource detector #174
- Add `fields` method to TextMapFormat #178
- Add support for `tracestate` in `TraceContextPropagator` #191
- Propagate valid span context in noop tracer #197
- Add end_with_timestamp method for trace span #199
- Add ID methods for hex and byte array formatting #200
- Add AWS X-Ray ID Generator #201
- AWS X-Ray Trace Context Propagator #202
- Add instrumentation library information to spans #207
- Add keys method to extractors #209
- Add `TraceState` to `SpanContext` #217
- Add `from_env` config option for `BatchSpanProcessor` #228
- Add pipeline uninstall mechanism to shut down trace pipelines #229
### Changed
- Re-write metrics sdk to be spec compliant #179
- Rename `Sampler::Probability` to `Sampler::TraceIdRatioBased` #188
- Rename `HTTPTextPropagator` to `TextMapPropagator` #192
- Ensure extractors are case insensitive #193
- Rename `Provider` to `TracerProvider` #206
- Rename `CorrelationContext` into `Baggage` #208
- Pipeline builder for stdout trace exporter #224
- Switch to async exporters #232
- Allow `ShouldSample` implementation to modify trace state #237
- Ensure context guard is `!Send` #239
- Ensure trace noop structs use `new` constructor #240
- Switch to w3c `baggage` header #246
- Move trace module imports from `api` to `api::trace` #255
- Update `tonic` feature to use version `0.3.x` #258
- Update exporters to receive owned span data #264
- Move propagators to `sdk::propagation` #266
- Rename SpanContext to SpanReference #270
- Rename `SamplingDecision`'s `NotRecord`, `Record` and `RecordAndSampled` to
`Drop` `RecordOnly` and `RecordAndSample` #247
## [v0.8.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.7.0...v0.8.0)
## Added
- Add custom span processors to `Provider::Builder` #166
### Changed
- Separate `Carrier` into `Injector` and `Extractor` #164
- Change the default sampler to be `ParentOrElse(AlwaysOn)` #163
- Move the `Sampler` interface to the SDK #169
## [v0.7.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.6.0...v0.7.0)
### Added
- New `ParentOrElse` sampler for fallback logic if parent is not sampled. #128
- Attributes can now have array values #146
- Added `record_exception` and `record_exception_with_stacktrace` methods to `Span` #152
### Changed
- Update sampler types #128
- `Always` is now `AlwaysOn`. `Never` is now `AlwaysOff`. `Probability` now ignores parent
sampled state.
- `base64` and `binary_propagator` have been moved to `experimental` module. #134
- `Correlation-Context` header has been updated to `otcorrelations` #145
- `B3Propagator` has been updated to more closely follow the spec #148
## [v0.6.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.5.0...v0.6.0)
### Added
- Add `http` and `tonic` features to impl `Carrier` for common types.
### Changed
- Removed `span_id` from sampling parameters when implementing custom samplers.
### Fixed
- Make `Context` `Send + Sync` in #127
## [v0.5.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.4.0...v0.5.0)
### Added
- Derive `Clone` for `B3Propagator`, `SamplingResult`, and `SpanBuilder`
- Ability to configure the span id / trace id generator
- impl `From<T>` for common `Key` and `Value` types
- Add global `tracer` method
- Add `Resource` API
- Add `Context` API
- Add `Correlations` API
- Add `HttpTextCompositePropagator` for composing `HttpTextPropagator`s
- Add `GlobalPropagator` for globally configuring a propagator
- Add `TraceContextExt` to provide methods for working with trace data in a context
- Expose `EvictedQueue` constructor
### Changed
- Ensure that impls of `Span` are `Send` and `Sync` when used in `global`
- Changed `Key` and `Value` method signatures to remove `Cow` references
- Tracer's `start` now uses the implicit current context instead of an explicit span context.
`start_with_context` may be used to specify a context if desired.
- `with_span` now accepts a span for naming consistency and managing the active state of a more
complex span (likely produced by a builder), and the previous functionality that accepts a
`&str` has been renamed to `in_span`, both of which now yield a context to the provided closure.
- Tracer's `get_active_span` now accepts a closure
- The `Instrument` trait has been renamed to `FutureExt` to avoid clashing with metric instruments,
and instead accepts contexts via `with_context`.
- Span's `get_context` method has been renamed to `span_context` to avoid ambiguity.
- `HttpTextPropagators` inject the current context instead of an explicit span context. The context
can be specified with `inject_context`.
- `SpanData`'s `context` has been renamed to `span_context`
### Fixed
- Update the probability sampler to match the spec
- Rename `Traceparent` header to `traceparent`
### Removed
- `TracerGenerics` methods have been folded in to the `Tracer` trait so it is longer needed
- Tracer's `mark_span_as_inactive` has been removed
- Exporters no longer require an `as_any` method
- Span's `mark_as_active`, `mark_as_inactive`, and `as_any` have been removed
## [v0.4.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.3.0...v0.4.0)
### Added
- New async batch span processor
- New stdout exporter
- Add `trace_id` to `SpanBuilder`
### Changed
- Add `attributes` to `Event`s.
- Update `Span`'s `add_event` and `add_event_with_timestamp` to accept attributes.
- Record log fields in jaeger exporter
- Properly export span kind in jaeger exporter
- Add support for `Link`s
- Add `status_message` to `Span` and `SpanData`
- Rename `SpanStatus` to `StatusCode`
- Update `EvictedQueue` internals from LIFO to FIFO
- Switch span attributes to `EvictedHashMap`
### Fixed
- Call `shutdown` correctly when span processors and exporters are dropped
## [v0.3.0](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.2.0...v0.3.0)
### Added
- New Base64 propagator
- New SpanBuilder api
- Zipkin Exporter crate
### Changed
- Switch to `SpanId` and `TraceId` from `u64` and `u128`
- Remove `&mut self` requirements for `Span` API
### Fixed
- circular Tracer debug impl
## [v0.2.0](https://github.com/open-telemetry/opentelemetry-rust/compare/b5918251cc07f9f6957434ccddc35306f68bd791..v0.2.0)
### Added
- Make trace and metrics features optional
- ExportResult as specified in the specification
- Add Futures compatibility API
- Added serde serialize support to SpanData
- Separate OpenTelemetry Jaeger crate
### Changed
- Rename HttpTraceContextPropagator to TraceContextPropagator
- Rename HttpB3Propagator to B3Propagator
- Switch to Apache 2 license
- Resolve agent addresses to allow non-static IP
- Remove tracer name prefix from span name
### Removed
- Remove add_link from spans
## [v0.1.5](https://github.com/jtescher/opentelemetry-rust/compare/v0.1.4...v0.1.5)
### Added
- trace-context propagator
### Changed
- Prometheus API cleanup
## [v0.1.4](https://github.com/jtescher/opentelemetry-rust/compare/v0.1.3...v0.1.4)
### Added
- Parent option for default sampler
### Fixed
- SDK tracer default span id
## [v0.1.3](https://github.com/jtescher/opentelemetry-rust/compare/v0.1.2...v0.1.3)
### Changed
- Ensure spans are always send and sync
- Allow static lifetimes for span names
- Improve KeyValue ergonomics
## [v0.1.2](https://github.com/jtescher/opentelemetry-rust/compare/v0.1.1...v0.1.2)
### Added
- Implement global provider
## [v0.1.1](https://github.com/jtescher/opentelemetry-rust/compare/v0.1.0...v0.1.1)
### Added
- Documentation and API cleanup
- Tracking of active spans via thread local span stack
## [v0.1.0](https://github.com/jtescher/opentelemetry-rust/commit/ea368ea965aa035f46728d75e1be3b096b6cd6ec)
Initial debug alpha
+172
View File
@@ -0,0 +1,172 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.75.0"
name = "opentelemetry"
version = "0.33.0"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "OpenTelemetry API for Rust"
homepage = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry"
readme = "README.md"
keywords = [
"opentelemetry",
"logging",
"tracing",
"metrics",
]
categories = [
"development-tools::debugging",
"development-tools::profiling",
"api-bindings",
"asynchronous",
]
license = "Apache-2.0"
repository = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[features]
default = [
"trace",
"metrics",
"logs",
"internal-logs",
"futures",
]
experimental_context_observer = []
experimental_metrics_bound_instruments = ["metrics"]
futures = [
"futures-core",
"futures-sink",
"pin-project-lite",
]
internal-logs = ["tracing"]
logs = []
metrics = []
testing = ["trace"]
trace = [
"futures",
"thiserror",
]
[lib]
name = "opentelemetry"
path = "src/lib.rs"
bench = false
[[bench]]
name = "anyvalue"
path = "benches/anyvalue.rs"
harness = false
[[bench]]
name = "attributes"
path = "benches/attributes.rs"
harness = false
[[bench]]
name = "baggage"
path = "benches/baggage.rs"
harness = false
[[bench]]
name = "context_attach"
path = "benches/context_attach.rs"
harness = false
required-features = ["tracing"]
[[bench]]
name = "context_suppression"
path = "benches/context_suppression.rs"
harness = false
[[bench]]
name = "logs"
path = "benches/logs.rs"
harness = false
required-features = ["logs"]
[[bench]]
name = "metrics"
path = "benches/metrics.rs"
harness = false
required-features = ["metrics"]
[[bench]]
name = "trace"
path = "benches/trace.rs"
harness = false
required-features = ["trace"]
[dependencies.futures-core]
version = "0.3"
optional = true
[dependencies.futures-sink]
version = "0.3"
optional = true
[dependencies.pin-project-lite]
version = "0.2"
optional = true
[dependencies.thiserror]
version = "2"
optional = true
default-features = false
[dependencies.tracing]
version = ">=0.1.40"
optional = true
default-features = false
[dev-dependencies.criterion]
version = "0.5"
[dev-dependencies.rand]
version = "0.9"
features = [
"os_rng",
"thread_rng",
]
default-features = false
[dev-dependencies.tokio]
version = "1.0"
features = ["full"]
[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies.js-sys]
version = "0.3.63"
[lints.clippy.all]
level = "warn"
priority = 1
[lints.rust]
edition_2024_expr_fragment_specifier = "allow"
if_let_rescope = "allow"
tail_expr_drop_order = "allow"
[lints.rust.rust_2024_compatibility]
level = "warn"
priority = -1
+144
View File
@@ -0,0 +1,144 @@
# OpenTelemetry Rust API
![OpenTelemetry — An observability framework for cloud-native software.][splash]
[splash]: https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo-text.png
This crate contains the [OpenTelemetry](https://opentelemetry.io/) API for Rust.
[![Crates.io: opentelemetry](https://img.shields.io/crates/v/opentelemetry.svg)](https://crates.io/crates/opentelemetry)
[![Documentation](https://docs.rs/opentelemetry/badge.svg)](https://docs.rs/opentelemetry)
[![LICENSE](https://img.shields.io/crates/l/opentelemetry)](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/LICENSE)
[![GitHub Actions CI](https://github.com/open-telemetry/opentelemetry-rust/workflows/CI/badge.svg)](https://github.com/open-telemetry/opentelemetry-rust/actions?query=workflow%3ACI+branch%3Amain)
[![codecov](https://codecov.io/gh/open-telemetry/opentelemetry-rust/branch/main/graph/badge.svg)](https://codecov.io/gh/open-telemetry/opentelemetry-rust)
[![Slack](https://img.shields.io/badge/slack-@cncf/otel/rust-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C03GDP0H023)
## Overview
OpenTelemetry is an Observability framework and toolkit designed to create and
manage telemetry data such as traces, metrics, and logs. OpenTelemetry is
vendor- and tool-agnostic, meaning that it can be used with a broad variety of
Observability backends, including open source tools like [Jaeger] and
[Prometheus], as well as commercial offerings.
OpenTelemetry is *not* an observability backend like Jaeger, Prometheus, or other
commercial vendors. OpenTelemetry is focused on the generation, collection,
management, and export of telemetry. A major goal of OpenTelemetry is that you
can easily instrument your applications or systems, no matter their language,
infrastructure, or runtime environment. Crucially, the storage and visualization
of telemetry is intentionally left to other tools.
*[Supported Rust Versions](#supported-rust-versions)*
[Prometheus]: https://prometheus.io
[Jaeger]: https://www.jaegertracing.io
### What does this crate contain?
This crate is basic foundation for integrating OpenTelemetry into libraries and
applications, encompassing several aspects of OpenTelemetry, such as context
management and propagation, baggage, logging, tracing, and metrics. It follows
the [OpenTelemetry
specification](https://github.com/open-telemetry/opentelemetry-specification).
Here's a breakdown of its components:
- **[Context
API](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/README.md):**
Provides a way to manage and propagate context, which is essential for keeping
track of trace execution across asynchronous tasks.
- **[Propagators
API](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md):**
Defines how context can be shared across process boundaries, ensuring
continuity across microservices or distributed systems.
- **[Baggage
API](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/baggage/api.md):**
Allows for the attachment of metadata (baggage) to telemetry, which can be
used for sharing application-specific information across service boundaries.
- **[Logs Bridge
API](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/api.md):**
Allows to bridge existing logging mechanisms with OpenTelemetry logging. This
is **NOT** meant for end users to call, instead it is meant to enable writing
bridges/appenders for existing logging mechanisms such as
[log](https://crates.io/crates/log) or
[tracing](https://crates.io/crates/tracing).
- **[Tracing
API](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md):**
Offers a set of primitives to produce distributed traces to understand the
flow of a request across system boundaries.
- **[Metrics
API](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md):**
Offers a set of primitives to produce measurements of operational metrics like
latency, throughput, or error rates.
This crate serves as a facade or no-op implementation, meaning it defines the
traits for instrumentation but does not itself implement the processing or
exporting of telemetry data. This separation of concerns allows library authors
to depend on the API crate without tying themselves to a specific
implementation.
Actual implementation and the heavy lifting of telemetry data collection,
processing, and exporting are delegated to the
[opentelemetry-sdk](https://crates.io/crates/opentelemetry-sdk) crate and
various exporter crates such as
[opentelemetry-otlp](https://crates.io/crates/opentelemetry-otlp). This
architecture ensures that the final application can light up the instrumentation
by integrating an SDK implementation.
Library authors are recommended to depend on this crate *only*. This approach is
also aligned with the design philosophy of existing telemetry solutions in the
Rust ecosystem, like `tracing` or `log`, where these crates only offer a facade
and the actual functionality is enabled through additional crates.
### Related crates
Unless you are a library author, you will almost always need to use additional
crates along with this. Given this crate has no-op implementation only, an
OpenTelemetry SDK is always required.
[opentelemetry-sdk](https://crates.io/crates/opentelemetry-sdk) is the official
SDK implemented by OpenTelemetry itself, though it is possible to use a
different sdk.
Additionally one or more exporters are also required to export telemetry to a
destination. OpenTelemetry provides the following exporters:
- **[opentelemetry-stdout](https://crates.io/crates/opentelemetry-stdout):**
Prints telemetry to stdout, primarily used for learning/debugging purposes.
- **[opentelemetry-otlp](https://crates.io/crates/opentelemetry-otlp):** Exports
telemetry (logs, metrics and traces) in the [OTLP
format](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/protocol)
to an endpoint accepting OTLP. This could be the [OTel
Collector](https://github.com/open-telemetry/opentelemetry-collector),
telemetry backends like [Jaeger](https://www.jaegertracing.io/),
[Prometheus](https://prometheus.io/docs/prometheus/latest/feature_flags/#otlp-receiver)
or [vendor specific endpoints](https://opentelemetry.io/ecosystem/vendors/).
- **[opentelemetry-prometheus](https://crates.io/crates/opentelemetry-prometheus):**
Exports telemetry (metrics only) to Prometheus following [OpenTelemetry to
Prometheus
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk_exporters/prometheus.md)
OpenTelemetry Rust also has a [contrib
repo](https://github.com/open-telemetry/opentelemetry-rust-contrib), where
additional exporters could be found. Check [OpenTelemetry
Registry](https://opentelemetry.io/ecosystem/registry/?language=rust) for
additional exporters and other related components as well.
## Getting started
See [docs](https://docs.rs/opentelemetry).
## Release Notes
You can find the release notes (changelog) [here](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry/CHANGELOG.md).
## Supported Rust Versions
OpenTelemetry is built against the latest stable release. The minimum supported
version is 1.75.0. The current OpenTelemetry version is NOT guaranteed to build
on Rust versions earlier than the minimum supported version.
The current stable Rust compiler and the three most recent minor versions
before it will always be supported. For example, if the current stable compiler
version is 1.49, the minimum supported version will not be increased past 1.46,
three minor versions prior. Increasing the minimum supported compiler version
is not considered a semver breaking change as long as doing so complies with
this policy.
@@ -0,0 +1,9 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
# This is used with cargo-check-external-types to reduce the surface area of downstream crates from
# the public API. Ideally this can have a few exceptions as possible.
allowed_external_types = [
"equivalent::Equivalent",
"futures_sink::Sink", # TODO: This is a pre-1.0 crate, we can't easily stabilize with this in the public API
"futures_core::stream::Stream", # TODO: This is a pre-1.0 crate, we can't easily stabilize with this in the public API
]
@@ -0,0 +1,51 @@
use criterion::{criterion_group, criterion_main, Criterion};
use opentelemetry::{logs::AnyValue, Value};
use std::hint::black_box;
// Run this benchmark with:
// cargo bench --bench anyvalue
// Results:
// CreateOTelValueString 1-2 ns
// CreateOTelAnyValueString 15 ns
// CreateOTelValueInt 1-2 ns
// CreateOTelAnyValueInt 15 ns
fn criterion_benchmark(c: &mut Criterion) {
attributes_creation(c);
}
fn attributes_creation(c: &mut Criterion) {
c.bench_function("CreateOTelValueString", |b| {
b.iter(|| {
let _v = black_box(Value::String("value1".into()));
});
});
c.bench_function("CreateOTelAnyValueString", |b| {
b.iter(|| {
let _v = black_box(AnyValue::String("value1".into()));
});
});
c.bench_function("CreateOTelValueInt", |b| {
b.iter(|| {
let _v = black_box(Value::I64(123));
});
});
c.bench_function("CreateOTelAnyValueInt", |b| {
b.iter(|| {
let _v = black_box(AnyValue::Int(123));
});
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(2));
targets = criterion_benchmark
}
criterion_main!(benches);
@@ -0,0 +1,128 @@
/* OS: Ubuntu 22.04.4 LTS (5.15.153.1-microsoft-standard-WSL2)
Hardware: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz, 16vCPUs,
RAM: 64.0 GB
| Test | Average time|
|--------------------------------------------------|-------------|
| CreateOTelKey_Static | 1.2 ns |
| CreateOTelKey_Owned | 12.6 ns |
| CreateOTelKey_Arc | 23.35 ns |
| CreateOTelKeyValue | 3.24 ns |
| CreateTupleKeyValue | 671 ps |
| CreateOtelKeyValueArray | 18.4 ns |
| CreateOtelKeyValueArrayWithMixedValueTypes | 18.1 ns |
| CreateOtelKeyValueArrayWithNonStaticValues | 90.1 ns |
| CreateTupleKeyValueArray | 2.73 ns |
OS: macOS 15
Hardware: Apple M4 Pro, 14 cores, RAM: 24 GB
| Test | Average time|
|--------------------------------------------------|-------------|
| CreateOTelKey_Static | 0.28 ns |
| CreateOTelKey_Owned | 19.16 ns |
| CreateOTelKey_Arc | 21.76 ns |
| CreateOTelKeyValue | 1.30 ns |
| CreateTupleKeyValue | 0.26 ns |
| CreateOtelKeyValueArray | 7.41 ns |
| CreateOtelKeyValueArrayWithMixedValueTypes | 5.50 ns |
| CreateOtelKeyValueArrayWithNonStaticValues | 84.94 ns |
| CreateTupleKeyValueArray | 1.46 ns |
*/
use criterion::{criterion_group, criterion_main, Criterion};
use opentelemetry::{Key, KeyValue};
use std::hint::black_box;
use std::sync::Arc;
// Run this benchmark with:
// cargo bench --bench attributes
fn criterion_benchmark(c: &mut Criterion) {
attributes_creation(c);
}
fn attributes_creation(c: &mut Criterion) {
c.bench_function("CreateOTelKey_Static", |b| {
b.iter(|| {
let _v1 = black_box(Key::new("attribute1"));
});
});
c.bench_function("CreateOTelKey_Owned", |b| {
b.iter(|| {
let _v1 = black_box(Key::new(String::from("attribute1")));
});
});
c.bench_function("CreateOTelKey_Arc", |b| {
b.iter(|| {
let _v1 = black_box(Key::new(Arc::from("attribute1")));
});
});
c.bench_function("CreateOTelKeyValue", |b| {
b.iter(|| {
let _v1 = black_box(KeyValue::new("attribute1", "value1"));
});
});
c.bench_function("CreateTupleKeyValue", |b| {
b.iter(|| {
let _v1 = black_box(("attribute1", "value1"));
});
});
c.bench_function("CreateOtelKeyValueArray", |b| {
b.iter(|| {
let _v1 = black_box([
KeyValue::new("attribute1", "value1"),
KeyValue::new("attribute2", "value2"),
KeyValue::new("attribute3", "value3"),
KeyValue::new("attribute4", "value4"),
]);
});
});
c.bench_function("CreateOtelKeyValueArrayWithMixedValueTypes", |b| {
b.iter(|| {
let _v1 = black_box([
KeyValue::new("attribute1", true),
KeyValue::new("attribute2", 100),
KeyValue::new("attribute3", 100.5),
KeyValue::new("attribute4", "value"),
]);
});
});
let value = "value1value2value3value4".to_owned();
c.bench_function("CreateOtelKeyValueArrayWithNonStaticValues", |b| {
b.iter(|| {
let _v1 = black_box([
KeyValue::new("attribute1", value[0..6].to_owned()), // value[0..6] = "value1"
KeyValue::new("attribute2", value[6..12].to_owned()), // value[6..12] = "value2"
KeyValue::new("attribute3", value[12..18].to_owned()), // value[12..18] = "value3"
KeyValue::new("attribute4", value[18..24].to_owned()), // value[18..24] = "value4"
]);
});
});
c.bench_function("CreateTupleKeyValueArray", |b| {
b.iter(|| {
let _v1 = black_box([
("attribute1", "value1"),
("attribute2", "value2"),
("attribute3", "value3"),
("attribute4", "value4"),
]);
});
});
}
criterion_group! {
name = benches;
config = Criterion::default().warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(2));
targets = criterion_benchmark
}
criterion_main!(benches);
@@ -0,0 +1,104 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use opentelemetry::baggage::Baggage;
use rand::distr::{Alphanumeric, SampleString};
use rand::Rng;
const MAX_KEY_VALUE_PAIRS: usize = 64;
// Run this benchmark with:
// cargo bench --bench baggage
// Adding results in comments for a quick reference.
// Apple M4 Pro
// Total Number of Cores: 14 (10 performance and 4 efficiency)
// Results:
// set_baggage_static_key_value 14 ns
// set_baggage_static_key 26 ns
// set_baggage_dynamic 54 ns
// set_baggage_dynamic_with_metadata 75 ns
fn criterion_benchmark(c: &mut Criterion) {
set_baggage_static_key_value(c);
set_baggage_static_key(c);
set_baggage_dynamic(c);
set_baggage_dynamic_with_metadata(c);
}
fn set_baggage_static_key_value(c: &mut Criterion) {
let mut baggage = Baggage::new();
c.bench_function("set_baggage_static_key_value", move |b| {
b.iter(|| {
baggage.insert("key", "value");
})
});
}
fn set_baggage_static_key(c: &mut Criterion) {
let mut baggage = Baggage::new();
c.bench_function("set_baggage_static_key", move |b| {
b.iter(|| {
baggage.insert("key", "value".to_string());
})
});
}
fn set_baggage_dynamic(c: &mut Criterion) {
let mut baggage = Baggage::new();
let mut rng = rand::rng();
let key_value = (0..MAX_KEY_VALUE_PAIRS)
.map(|_| {
(
Alphanumeric.sample_string(&mut rng, 4),
Alphanumeric.sample_string(&mut rng, 4),
)
})
.collect::<Vec<(String, String)>>();
c.bench_function("set_baggage_dynamic", move |b| {
b.iter_batched(
|| rng.random_range(0..MAX_KEY_VALUE_PAIRS),
|idx| {
let (key, value) = key_value[idx].clone();
baggage.insert(key, value);
},
BatchSize::SmallInput,
)
});
}
fn set_baggage_dynamic_with_metadata(c: &mut Criterion) {
let mut baggage = Baggage::new();
let mut rng = rand::rng();
let key_value_metadata = (0..MAX_KEY_VALUE_PAIRS)
.map(|_| {
(
Alphanumeric.sample_string(&mut rng, 4),
Alphanumeric.sample_string(&mut rng, 4),
Alphanumeric.sample_string(&mut rng, 4),
)
})
.collect::<Vec<(String, String, String)>>();
c.bench_function("set_baggage_dynamic_with_metadata", move |b| {
b.iter_batched(
|| rng.random_range(0..MAX_KEY_VALUE_PAIRS),
|idx| {
let (key, value, metadata) = key_value_metadata[idx].clone();
baggage.insert_with_metadata(key, value, metadata);
},
BatchSize::SmallInput,
)
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(2));
targets = criterion_benchmark
}
criterion_main!(benches);
@@ -0,0 +1,122 @@
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, BenchmarkId, Criterion,
};
use opentelemetry::{
trace::{SpanContext, TraceContextExt, TraceState},
Context, SpanId, TraceFlags, TraceId,
};
use std::hint::black_box;
// Run this benchmark with:
// cargo bench --bench context_attach
fn criterion_benchmark(c: &mut Criterion) {
let remote_context = SpanContext::new(
TraceId::from(47),
SpanId::from(11),
TraceFlags::SAMPLED,
true,
TraceState::NONE,
);
let span_context = Context::new().with_remote_span_context(remote_context);
let contexts = vec![
("empty_cx", Context::new()),
("single_value_cx", Context::new().with_value(Value(4711))),
("span_cx", span_context),
];
for (name, cx) in contexts {
single_cx_scope(&mut group(c), name, &cx);
nested_cx_scope(&mut group(c), name, &cx);
overlapping_cx_scope(&mut group(c), name, &cx);
}
}
fn single_cx_scope(
group: &mut BenchmarkGroup<'_, WallTime>,
context_type: &str,
context: &Context,
) {
group.bench_function(BenchmarkId::new("single_cx", context_type), |b| {
b.iter_batched(
|| context.clone(),
|cx| {
single_cx(cx);
},
criterion::BatchSize::SmallInput,
);
});
}
#[inline(never)]
fn single_cx(cx: Context) {
let _cx_guard = black_box(cx.attach());
let _ = black_box(dummy_work());
}
fn nested_cx_scope(group: &mut BenchmarkGroup<'_, WallTime>, cx_type: &str, context: &Context) {
group.bench_function(BenchmarkId::new("nested_cx", cx_type), |b| {
b.iter_batched(
|| (context.clone(), context.clone()),
|(cx1, cx2)| {
nested_cx(cx1, cx2);
},
criterion::BatchSize::SmallInput,
);
});
}
#[inline(never)]
fn nested_cx(cx1: Context, cx2: Context) {
let _outer_cx_guard = black_box(cx1.attach());
let _inner_cx_guard = black_box(cx2.attach());
let _ = black_box(dummy_work());
}
fn overlapping_cx_scope(
group: &mut BenchmarkGroup<'_, WallTime>,
cx_type: &str,
context: &Context,
) {
// This is to ensure that the context is restored after the benchmark,
// see https://github.com/open-telemetry/opentelemetry-rust/issues/1887
let _restore_cx_guard = Context::current().attach();
group.bench_function(BenchmarkId::new("out_of_order_cx_drop", cx_type), |b| {
b.iter_batched(
|| (context.clone(), context.clone()),
|(cx1, cx2)| {
out_of_order_cx_drop(cx1, cx2);
},
criterion::BatchSize::SmallInput,
);
});
}
#[inline(never)]
fn out_of_order_cx_drop(cx1: Context, cx2: Context) {
let outer_cx_guard = cx1.attach();
let inner_cx_guard = cx2.attach();
let _ = black_box(dummy_work());
drop(outer_cx_guard);
drop(inner_cx_guard);
}
#[inline(never)]
fn dummy_work() -> i32 {
black_box(1 + 1)
}
fn group(c: &mut Criterion) -> BenchmarkGroup<'_, WallTime> {
c.benchmark_group("context_attach")
}
#[derive(Debug, PartialEq)]
struct Value(i32);
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(2));
targets = criterion_benchmark
}
criterion_main!(benches);
@@ -0,0 +1,66 @@
use criterion::{criterion_group, criterion_main, Criterion};
use opentelemetry::Context;
use std::hint::black_box;
// Run this benchmark with:
// cargo bench --bench context_suppression
// The benchmark results:
// criterion = "0.5.1"
// Hardware: Apple M4 Pro
// Total Number of Cores:   14 (10 performance and 4 efficiency)
// | Benchmark | Time |
// |---------------------------------------|--------|
// | enter_telemetry_suppressed_scope | 8.3 ns |
// | normal_attach | 9.1 ns |
// | is_current_telemetry_suppressed_false | 750 ps |
// | is_current_telemetry_suppressed_true | 750 ps |
fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("telemetry_suppression");
// Benchmark the cost of entering a suppressed scope
group.bench_function("enter_telemetry_suppressed_scope", |b| {
b.iter(|| {
let _guard = black_box(Context::enter_telemetry_suppressed_scope());
});
});
// For comparison - normal context attach
group.bench_function("normal_attach", |b| {
b.iter(|| {
let _guard = black_box(Context::current().attach());
});
});
// Benchmark checking if current is suppressed (when not suppressed)
group.bench_function("is_current_telemetry_suppressed_false", |b| {
// Make sure we're in a non-suppressed context
let _restore_ctx = Context::current().attach();
b.iter(|| {
let is_suppressed = black_box(Context::is_current_telemetry_suppressed());
black_box(is_suppressed);
});
});
// Benchmark checking if current is suppressed (when suppressed)
group.bench_function("is_current_telemetry_suppressed_true", |b| {
// Enter suppressed context for the duration of the benchmark
let _suppressed_guard = Context::enter_telemetry_suppressed_scope();
b.iter(|| {
let is_suppressed = black_box(Context::is_current_telemetry_suppressed());
black_box(is_suppressed);
});
});
group.finish();
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(2));
targets = criterion_benchmark
}
criterion_main!(benches);
+85
View File
@@ -0,0 +1,85 @@
/*
Benchmark Results:
criterion = "0.5.1"
OS: macOS 15
Hardware: Apple M4 Pro, 14 cores, RAM: 24 GB
| Test | Average time|
|--------------------------------------------------|-------------|
| CreateLogRecord_NoopLogger | 0.26 ns |
| CreateLogRecord_NoopLogger_WithAttributes | 0.26 ns |
| CreateLogRecord_NoopLogger_WithBody | 1.30 ns |
| CreateLogRecord_NoopLogger_Full | 1.30 ns |
| EventEnabled_NoopLogger | 0.26 ns |
*/
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use opentelemetry::logs::{
AnyValue, LogRecord, Logger, LoggerProvider, NoopLoggerProvider, Severity,
};
// Run this benchmark with:
// cargo bench --bench logs --features=logs
fn criterion_benchmark(c: &mut Criterion) {
let provider = NoopLoggerProvider::new();
let logger = provider.logger("bench");
c.bench_function("CreateLogRecord_NoopLogger", |b| {
b.iter(|| {
let record = logger.create_log_record();
logger.emit(record);
});
});
c.bench_function("CreateLogRecord_NoopLogger_WithAttributes", |b| {
b.iter(|| {
let mut record = logger.create_log_record();
record.add_attribute("key1", "value1");
record.add_attribute("key2", 123);
record.add_attribute("key3", true);
record.add_attribute("key4", 123.456);
logger.emit(record);
});
});
c.bench_function("CreateLogRecord_NoopLogger_WithBody", |b| {
b.iter(|| {
let mut record = logger.create_log_record();
record.set_body(AnyValue::String("This is a log message".into()));
record.set_severity_number(Severity::Info);
record.set_severity_text("INFO");
logger.emit(record);
});
});
c.bench_function("CreateLogRecord_NoopLogger_Full", |b| {
b.iter(|| {
let mut record = logger.create_log_record();
record.set_body(AnyValue::String("This is a log message".into()));
record.set_severity_number(Severity::Warn);
record.set_severity_text("WARN");
record.set_event_name("my.event");
record.set_target("my_crate::my_module");
record.add_attribute("key1", "value1");
record.add_attribute("key2", 123);
record.add_attribute("key3", true);
record.add_attribute("key4", 123.456);
logger.emit(record);
});
});
c.bench_function("EventEnabled_NoopLogger", |b| {
b.iter(|| {
black_box(logger.event_enabled(Severity::Info, "my_target", Some("my_event")));
});
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(2));
targets = criterion_benchmark
}
criterion_main!(benches);
@@ -0,0 +1,113 @@
/*
Benchmark Results:
criterion = "0.5.1"
OS: macOS 15
Hardware: Apple M4 Pro, 14 cores, RAM: 24 GB
| Test | Average time|
|-----------------------------------------------------|-------------|
| NoAttributes | 0.77 ns |
| AddWithInlineStaticAttributes | 8.10 ns |
| AddWithStaticArray | 0.77 ns |
| AddWithDynamicAttributes | 44.84 ns |
| AddWithDynamicAttributes_WithStringAllocation | 85.92 ns |
*/
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use opentelemetry::{global, metrics::Counter, KeyValue};
use std::hint::black_box;
// Run this benchmark with:
// cargo bench --bench metrics
fn create_counter() -> Counter<u64> {
let meter = global::meter("benchmarks");
meter.u64_counter("counter_bench").build()
}
fn criterion_benchmark(c: &mut Criterion) {
counter_add(c);
}
fn counter_add(c: &mut Criterion) {
let counter = create_counter();
c.bench_function("NoAttributes", |b| {
b.iter(|| {
counter.add(1, &[]);
});
});
c.bench_function("AddWithInlineStaticAttributes", |b| {
b.iter(|| {
counter.add(
1,
&[
KeyValue::new("attribute1", "value1"),
KeyValue::new("attribute2", "value2"),
KeyValue::new("attribute3", "value3"),
KeyValue::new("attribute4", "value4"),
],
);
});
});
let kv = [
KeyValue::new("attribute1", "value1"),
KeyValue::new("attribute2", "value2"),
KeyValue::new("attribute3", "value3"),
KeyValue::new("attribute4", "value4"),
];
c.bench_function("AddWithStaticArray", |b| {
b.iter(|| {
counter.add(1, &kv);
});
});
c.bench_function("AddWithDynamicAttributes", |b| {
b.iter_batched(
|| {
let value1 = "value1".to_string();
let value2 = "value2".to_string();
let value3 = "value3".to_string();
let value4 = "value4".to_string();
(value1, value2, value3, value4)
},
|values| {
let kv = &[
KeyValue::new("attribute1", values.0),
KeyValue::new("attribute2", values.1),
KeyValue::new("attribute3", values.2),
KeyValue::new("attribute4", values.3),
];
counter.add(1, kv);
},
BatchSize::SmallInput,
);
});
c.bench_function("AddWithDynamicAttributes_WithStringAllocation", |b| {
b.iter(|| {
let kv = &[
KeyValue::new("attribute1", black_box("value1".to_string())),
KeyValue::new("attribute2", black_box("value2".to_string())),
KeyValue::new("attribute3", black_box("value3".to_string())),
KeyValue::new("attribute4", black_box("value4".to_string())),
];
counter.add(1, kv);
});
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(2));
targets = criterion_benchmark
}
criterion_main!(benches);
+184
View File
@@ -0,0 +1,184 @@
/*
Benchmark Results:
criterion = "0.5.1"
OS: macOS 15
Hardware: Apple M4 Pro, 14 cores, RAM: 24 GB
| Test | Average time|
|--------------------------------------------------|-------------|
| CreateSpan_NoopTracer | 29.2 ns |
| CreateSpan_NoopTracer_4Attributes | 29.4 ns |
| CreateSpan_NoopTracer_AddEvent | 29.2 ns |
| CreateSpan_NoopTracer_AddLink | 29.2 ns |
| CreateSpan_NoopTracer_SetActive | 73.3 ns |
| CreateSpan_NoopTracer_WithActiveParent | 102.9 ns |
| CreateSpan_NoopTracer_InSpan | 78.0 ns |
| SpanBuilder_Creation | 22.8 ns |
| SpanBuilder_WithAttributes | 51.7 ns |
| SpanBuilder_WithLinks | 69.0 ns |
*/
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use opentelemetry::{
global,
trace::{
noop::NoopTracerProvider, Span, SpanContext, SpanId, SpanKind, TraceFlags, TraceId,
TraceState, Tracer,
},
KeyValue,
};
// Run this benchmark with:
// cargo bench --bench trace --features=trace
fn valid_span_context() -> SpanContext {
SpanContext::new(
TraceId::from(12345u128),
SpanId::from(12345u64),
TraceFlags::SAMPLED,
true,
TraceState::default(),
)
}
fn criterion_benchmark(c: &mut Criterion) {
global::set_tracer_provider(NoopTracerProvider::new());
let tracer = global::tracer("bench");
span_lifecycle(c, &tracer);
span_builder(c, &tracer);
}
fn span_lifecycle(
c: &mut Criterion,
tracer: &impl Tracer<Span = impl Span + Send + Sync + 'static>,
) {
c.bench_function("CreateSpan_NoopTracer", |b| {
b.iter(|| {
let mut span = tracer.start("test-span");
span.end();
});
});
c.bench_function("CreateSpan_NoopTracer_4Attributes", |b| {
b.iter(|| {
let mut span = tracer.start("test-span");
if span.is_recording() {
span.set_attribute(KeyValue::new("key1", false));
span.set_attribute(KeyValue::new("key2", "hello"));
span.set_attribute(KeyValue::new("key3", 123));
span.set_attribute(KeyValue::new("key4", 123.456));
}
span.end();
});
});
c.bench_function("CreateSpan_NoopTracer_AddEvent", |b| {
b.iter(|| {
let mut span = tracer.start("test-span");
if span.is_recording() {
span.add_event(
"my-event",
vec![
KeyValue::new("key1", "value1"),
KeyValue::new("key2", "value2"),
],
);
}
span.end();
});
});
c.bench_function("CreateSpan_NoopTracer_AddLink", |b| {
let link_context = valid_span_context();
b.iter(|| {
let mut span = tracer.start("test-span");
if span.is_recording() {
span.add_link(
link_context.clone(),
vec![
KeyValue::new("key1", "value1"),
KeyValue::new("key2", "value2"),
],
);
}
span.end();
});
});
c.bench_function("CreateSpan_NoopTracer_SetActive", |b| {
b.iter(|| {
let span = tracer.start("test-span");
let _guard = opentelemetry::trace::mark_span_as_active(span);
});
});
c.bench_function("CreateSpan_NoopTracer_WithActiveParent", |b| {
b.iter(|| {
let parent = tracer.start("parent");
let _guard = opentelemetry::trace::mark_span_as_active(parent);
let mut child = tracer.start("child");
child.end();
});
});
c.bench_function("CreateSpan_NoopTracer_InSpan", |b| {
b.iter(|| {
tracer.in_span("test-span", |_cx| {});
});
});
}
fn span_builder(c: &mut Criterion, tracer: &impl Tracer<Span = impl Span + Send + Sync + 'static>) {
c.bench_function("SpanBuilder_Creation", |b| {
b.iter(|| {
let span = tracer
.span_builder("test-span")
.with_kind(SpanKind::Client)
.start(tracer);
black_box(span);
});
});
c.bench_function("SpanBuilder_WithAttributes", |b| {
b.iter(|| {
let span = tracer
.span_builder("test-span")
.with_kind(SpanKind::Client)
.with_attributes(vec![
KeyValue::new("key1", false),
KeyValue::new("key2", "hello"),
KeyValue::new("key3", 123),
KeyValue::new("key4", 123.456),
])
.start(tracer);
black_box(span);
});
});
c.bench_function("SpanBuilder_WithLinks", |b| {
let link_context = valid_span_context();
b.iter(|| {
let span = tracer
.span_builder("test-span")
.with_links(vec![opentelemetry::trace::Link::new(
link_context.clone(),
vec![
KeyValue::new("key1", "value1"),
KeyValue::new("key2", "value2"),
],
0,
)])
.start(tracer);
black_box(span);
});
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(2));
targets = criterion_benchmark
}
criterion_main!(benches);
+677
View File
@@ -0,0 +1,677 @@
//! Primitives for sending name/value data across system boundaries.
//!
//! Baggage is used to annotate telemetry, adding context and information to
//! metrics, traces, and logs. It is a set of name/value pairs describing
//! user-defined properties. Each name in Baggage is associated with exactly one
//! value.
//!
//! Main types in this module are:
//!
//! * [`Baggage`]: A set of name/value pairs describing user-defined properties.
//! * [`BaggageExt`]: Extensions for managing `Baggage` in a [`Context`].
//!
//! Baggage can be sent between systems using a baggage propagator in
//! accordance with the [W3C Baggage] specification.
//!
//! Note: Baggage is not automatically added to any telemetry. Users have to
//! explicitly add baggage entries to telemetry items.
//!
//!
//! [W3C Baggage]: https://w3c.github.io/baggage
use crate::{Context, Key, KeyValue, StringValue};
use std::collections::hash_map::Entry;
use std::collections::{hash_map, HashMap};
use std::fmt;
use std::sync::OnceLock;
static DEFAULT_BAGGAGE: OnceLock<Baggage> = OnceLock::new();
const MAX_KEY_VALUE_PAIRS: usize = 64;
const MAX_LEN_OF_ALL_PAIRS: usize = 8192;
// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
const INVALID_ASCII_KEY_CHARS: [u8; 17] = *b"(),/:;<=>?@[\\]{}\"";
/// Returns the default baggage, ensuring it is initialized only once.
#[inline]
fn get_default_baggage() -> &'static Baggage {
DEFAULT_BAGGAGE.get_or_init(Baggage::default)
}
/// A set of name/value pairs describing user-defined properties.
///
/// ### Baggage Names
///
/// * ASCII strings according to the token format, defined in [RFC2616, Section 2.2]
///
/// ### Baggage Values
///
/// * URL encoded UTF-8 strings.
///
/// ### Baggage Value Metadata
///
/// Additional metadata can be added to values in the form of a property set,
/// represented as semi-colon `;` delimited list of names and/or name/value pairs,
/// e.g. `;k1=v1;k2;k3=v3`.
///
/// ### Limits
///
/// * Maximum number of name/value pairs: `64`.
/// * Maximum total length of all name/value pairs: `8192`.
///
/// <https://www.w3.org/TR/baggage/#limits>
#[derive(Debug, Default)]
pub struct Baggage {
inner: HashMap<Key, (StringValue, BaggageMetadata)>,
kv_content_len: usize, // the length of key-value-metadata string in `inner`
}
impl Baggage {
/// Creates an empty `Baggage`.
pub fn new() -> Self {
Baggage {
inner: HashMap::default(),
kv_content_len: 0,
}
}
/// Returns a reference to the value associated with a given name
///
/// # Examples
///
/// ```
/// use opentelemetry::{baggage::Baggage, StringValue};
///
/// let mut baggage = Baggage::new();
/// let _ = baggage.insert("my-name", "my-value");
///
/// assert_eq!(baggage.get("my-name"), Some(&StringValue::from("my-value")))
/// ```
pub fn get<K: AsRef<str>>(&self, key: K) -> Option<&StringValue> {
self.inner.get(key.as_ref()).map(|(value, _metadata)| value)
}
/// Returns a reference to the value and metadata associated with a given name
///
/// # Examples
/// ```
/// use opentelemetry::{baggage::{Baggage, BaggageMetadata}, StringValue};
///
/// let mut baggage = Baggage::new();
/// let _ = baggage.insert("my-name", "my-value");
///
/// // By default, the metadata is empty
/// assert_eq!(baggage.get_with_metadata("my-name"), Some(&(StringValue::from("my-value"), BaggageMetadata::from(""))))
/// ```
pub fn get_with_metadata<K: AsRef<str>>(
&self,
key: K,
) -> Option<&(StringValue, BaggageMetadata)> {
self.inner.get(key.as_ref())
}
/// Inserts a name/value pair into the baggage.
///
/// If the name was not present, [`None`] is returned. If the name was present,
/// the value is updated, and the old value is returned.
///
/// # Examples
///
/// ```
/// use opentelemetry::{baggage::Baggage, StringValue};
///
/// let mut baggage = Baggage::new();
/// let _ = baggage.insert("my-name", "my-value");
///
/// assert_eq!(baggage.get("my-name"), Some(&StringValue::from("my-value")))
/// ```
pub fn insert<K, V>(&mut self, key: K, value: V) -> Option<StringValue>
where
K: Into<Key>,
V: Into<StringValue>,
{
self.insert_with_metadata(key, value, BaggageMetadata::default())
.map(|pair| pair.0)
}
/// Inserts a name/value(+metadata) pair into the baggage.
///
/// Same with `insert`, if the name was not present, [`None`] will be returned.
/// If the name is present, the old value and metadata will be returned.
///
/// Also checks for [limits](https://w3c.github.io/baggage/#limits).
///
/// # Examples
///
/// ```
/// use opentelemetry::{baggage::{Baggage, BaggageMetadata}, StringValue};
///
/// let mut baggage = Baggage::new();
/// let _ = baggage.insert_with_metadata("my-name", "my-value", "test");
///
/// assert_eq!(baggage.get_with_metadata("my-name"), Some(&(StringValue::from("my-value"), BaggageMetadata::from("test"))))
/// ```
pub fn insert_with_metadata<K, V, S>(
&mut self,
key: K,
value: V,
metadata: S,
) -> Option<(StringValue, BaggageMetadata)>
where
K: Into<Key>,
V: Into<StringValue>,
S: Into<BaggageMetadata>,
{
let (key, value, metadata) = (key.into(), value.into(), metadata.into());
let entries_count = self.inner.len();
match self.inner.entry(key) {
Entry::Occupied(mut occupied_entry) => {
let key_str = occupied_entry.key().as_str();
let entry_content_len =
key_value_metadata_bytes_size(key_str, value.as_str(), metadata.as_str());
let prev_content_len = key_value_metadata_bytes_size(
key_str,
occupied_entry.get().0.as_str(),
occupied_entry.get().1.as_str(),
);
let new_content_len = self.kv_content_len + entry_content_len - prev_content_len;
if new_content_len > MAX_LEN_OF_ALL_PAIRS {
return None;
}
self.kv_content_len = new_content_len;
Some(occupied_entry.insert((value, metadata)))
}
Entry::Vacant(vacant_entry) => {
let key_str = vacant_entry.key().as_str();
if !Self::is_key_valid(key_str.as_bytes()) {
return None;
}
if entries_count == MAX_KEY_VALUE_PAIRS {
return None;
}
let entry_content_len =
key_value_metadata_bytes_size(key_str, value.as_str(), metadata.as_str());
let new_content_len = self.kv_content_len + entry_content_len;
if new_content_len > MAX_LEN_OF_ALL_PAIRS {
return None;
}
self.kv_content_len = new_content_len;
vacant_entry.insert((value, metadata));
None
}
}
}
/// Removes a name from the baggage, returning the value
/// corresponding to the name if the pair was previously in the map.
pub fn remove<K: AsRef<str>>(&mut self, key: K) -> Option<(StringValue, BaggageMetadata)> {
self.inner.remove(key.as_ref())
}
/// Returns the number of attributes for this baggage
pub fn len(&self) -> usize {
self.inner.len()
}
/// Returns `true` if the baggage contains no items.
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// Gets an iterator over the baggage items, in any order.
pub fn iter(&self) -> Iter<'_> {
self.into_iter()
}
fn is_key_valid(key: &[u8]) -> bool {
!key.is_empty()
&& key
.iter()
.all(|b| b.is_ascii_graphic() && !INVALID_ASCII_KEY_CHARS.contains(b))
}
}
/// Get the number of bytes for one key-value pair
fn key_value_metadata_bytes_size(key: &str, value: &str, metadata: &str) -> usize {
key.len() + value.len() + metadata.len()
}
/// An iterator over the entries of a [`Baggage`].
#[derive(Debug)]
pub struct Iter<'a>(hash_map::Iter<'a, Key, (StringValue, BaggageMetadata)>);
impl<'a> Iterator for Iter<'a> {
type Item = (&'a Key, &'a (StringValue, BaggageMetadata));
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<'a> IntoIterator for &'a Baggage {
type Item = (&'a Key, &'a (StringValue, BaggageMetadata));
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
Iter(self.inner.iter())
}
}
impl FromIterator<(Key, (StringValue, BaggageMetadata))> for Baggage {
fn from_iter<I: IntoIterator<Item = (Key, (StringValue, BaggageMetadata))>>(iter: I) -> Self {
let mut baggage = Baggage::default();
for (key, (value, metadata)) in iter.into_iter() {
baggage.insert_with_metadata(key, value, metadata);
}
baggage
}
}
impl FromIterator<KeyValue> for Baggage {
fn from_iter<I: IntoIterator<Item = KeyValue>>(iter: I) -> Self {
let mut baggage = Baggage::default();
for kv in iter.into_iter() {
baggage.insert(kv.key, kv.value);
}
baggage
}
}
impl FromIterator<KeyValueMetadata> for Baggage {
fn from_iter<I: IntoIterator<Item = KeyValueMetadata>>(iter: I) -> Self {
let mut baggage = Baggage::default();
for kvm in iter.into_iter() {
baggage.insert_with_metadata(kvm.key, kvm.value, kvm.metadata);
}
baggage
}
}
impl<I> From<I> for Baggage
where
I: IntoIterator,
I::Item: Into<KeyValueMetadata>,
{
fn from(value: I) -> Self {
value.into_iter().map(Into::into).collect()
}
}
fn encode(s: &str) -> String {
let mut encoded_string = String::with_capacity(s.len());
for byte in s.as_bytes() {
match *byte {
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'.' | b'-' | b'_' | b'~' => {
encoded_string.push(*byte as char)
}
b' ' => encoded_string.push_str("%20"),
_ => encoded_string.push_str(&format!("%{byte:02X}")),
}
}
encoded_string
}
impl fmt::Display for Baggage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, (k, v)) in self.into_iter().enumerate() {
write!(f, "{}={}", k, encode(v.0.as_str()))?;
if !v.1.as_str().is_empty() {
write!(f, ";{}", v.1)?;
}
if i < self.len() - 1 {
write!(f, ",")?;
}
}
Ok(())
}
}
/// Methods for sorting and retrieving baggage data in a context.
pub trait BaggageExt {
/// Returns a clone of the given context with the included name/value pairs.
///
/// # Examples
///
/// ```
/// use opentelemetry::{baggage::{Baggage, BaggageExt}, Context, KeyValue, StringValue};
///
/// // Explicit `Baggage` creation
/// let mut baggage = Baggage::new();
/// let _ = baggage.insert("my-name", "my-value");
///
/// let cx = Context::map_current(|cx| {
/// cx.with_baggage(baggage)
/// });
///
/// // Passing an iterator
/// let cx = Context::map_current(|cx| {
/// cx.with_baggage([KeyValue::new("my-name", "my-value")])
/// });
///
/// assert_eq!(
/// cx.baggage().get("my-name"),
/// Some(&StringValue::from("my-value")),
/// )
/// ```
fn with_baggage<T: Into<Baggage>>(&self, baggage: T) -> Self;
/// Returns a clone of the current context with the included name/value pairs.
///
/// # Examples
///
/// ```
/// use opentelemetry::{baggage::{Baggage, BaggageExt}, Context, StringValue};
///
/// let mut baggage = Baggage::new();
/// let _ = baggage.insert("my-name", "my-value");
///
/// let cx = Context::current_with_baggage(baggage);
///
/// assert_eq!(
/// cx.baggage().get("my-name"),
/// Some(&StringValue::from("my-value")),
/// )
/// ```
fn current_with_baggage<T: Into<Baggage>>(baggage: T) -> Self;
/// Returns a clone of the given context with no baggage.
///
/// # Examples
///
/// ```
/// use opentelemetry::{baggage::BaggageExt, Context};
///
/// let cx = Context::map_current(|cx| cx.with_cleared_baggage());
///
/// assert_eq!(cx.baggage().len(), 0);
/// ```
fn with_cleared_baggage(&self) -> Self;
/// Returns a reference to this context's baggage, or the default
/// empty baggage if none has been set.
fn baggage(&self) -> &Baggage;
}
/// Solely used to store `Baggage` in the `Context` without allowing direct access
#[derive(Debug)]
struct BaggageContextValue(Baggage);
impl BaggageExt for Context {
fn with_baggage<T: Into<Baggage>>(&self, baggage: T) -> Self {
self.with_value(BaggageContextValue(baggage.into()))
}
fn current_with_baggage<T: Into<Baggage>>(baggage: T) -> Self {
Context::map_current(|cx| cx.with_baggage(baggage))
}
fn with_cleared_baggage(&self) -> Self {
self.with_baggage(Baggage::new())
}
fn baggage(&self) -> &Baggage {
self.get::<BaggageContextValue>()
.map_or(get_default_baggage(), |b| &b.0)
}
}
/// An optional property set that can be added to [`Baggage`] values.
///
/// `BaggageMetadata` can be added to values in the form of a property set,
/// represented as semi-colon `;` delimited list of names and/or name/value
/// pairs, e.g. `;k1=v1;k2;k3=v3`.
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Default)]
pub struct BaggageMetadata(String);
impl BaggageMetadata {
/// Return underlying string
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl From<String> for BaggageMetadata {
fn from(s: String) -> BaggageMetadata {
BaggageMetadata(s.trim().to_string())
}
}
impl From<&str> for BaggageMetadata {
fn from(s: &str) -> Self {
BaggageMetadata(s.trim().to_string())
}
}
impl fmt::Display for BaggageMetadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(write!(f, "{}", self.as_str())?)
}
}
/// [`Baggage`] name/value pairs with their associated metadata.
#[derive(Clone, Debug, PartialEq)]
pub struct KeyValueMetadata {
/// Dimension or event key
pub(crate) key: Key,
/// Dimension or event value
pub(crate) value: StringValue,
/// Metadata associate with this key value pair
pub(crate) metadata: BaggageMetadata,
}
impl KeyValueMetadata {
/// Create a new `KeyValue` pair with metadata
pub fn new<K, V, S>(key: K, value: V, metadata: S) -> Self
where
K: Into<Key>,
V: Into<StringValue>,
S: Into<BaggageMetadata>,
{
KeyValueMetadata {
key: key.into(),
value: value.into(),
metadata: metadata.into(),
}
}
}
impl From<KeyValue> for KeyValueMetadata {
fn from(kv: KeyValue) -> Self {
KeyValueMetadata {
key: kv.key,
value: kv.value.into(),
metadata: BaggageMetadata::default(),
}
}
}
#[cfg(test)]
mod tests {
use crate::StringValue;
use super::*;
#[test]
fn insert_non_ascii_key() {
let mut baggage = Baggage::new();
baggage.insert("🚫", "not ascii key");
assert_eq!(baggage.len(), 0, "did not insert invalid key");
}
#[test]
fn test_ascii_values() {
let string1 = "test_ 123";
let string2 = "Hello123";
let string3 = "This & That = More";
let string4 = "Unicode: 😊";
let string5 = "Non-ASCII: áéíóú";
let string6 = "Unsafe: ~!@#$%^&*()_+{}[];:'\\\"<>?,./";
let string7: &str = "🚀Unicode:";
let string8 = "ΑΒΓ";
assert_eq!(encode(string1), "test_%20123");
assert_eq!(encode(string2), "Hello123");
assert_eq!(encode(string3), "This%20%26%20That%20%3D%20More");
assert_eq!(encode(string4), "Unicode%3A%20%F0%9F%98%8A");
assert_eq!(
encode(string5),
"Non-ASCII%3A%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA"
);
assert_eq!(encode(string6), "Unsafe%3A%20~%21%40%23%24%25%5E%26%2A%28%29_%2B%7B%7D%5B%5D%3B%3A%27%5C%22%3C%3E%3F%2C.%2F");
assert_eq!(encode(string7), "%F0%9F%9A%80Unicode%3A");
assert_eq!(encode(string8), "%CE%91%CE%92%CE%93");
}
#[test]
fn insert_too_much_baggage() {
// too many key pairs
let over_limit = MAX_KEY_VALUE_PAIRS + 1;
let mut data = Vec::with_capacity(over_limit);
for i in 0..over_limit {
data.push(KeyValue::new(format!("key{i}"), format!("key{i}")))
}
let baggage = data.into_iter().collect::<Baggage>();
assert_eq!(baggage.len(), MAX_KEY_VALUE_PAIRS)
}
#[test]
fn insert_pairs_length_exceed() {
let mut data = vec![];
for letter in vec!['a', 'b', 'c', 'd'].into_iter() {
data.push(KeyValue::new(
(0..MAX_LEN_OF_ALL_PAIRS / 3)
.map(|_| letter)
.collect::<String>(),
"",
));
}
let baggage = data.into_iter().collect::<Baggage>();
assert_eq!(baggage.len(), 3)
}
#[test]
fn serialize_baggage_as_string() {
// Empty baggage
let b = Baggage::default();
assert_eq!("", b.to_string());
// "single member empty value no properties"
let mut b = Baggage::default();
b.insert("foo", StringValue::from(""));
assert_eq!("foo=", b.to_string());
// "single member no properties"
let mut b = Baggage::default();
b.insert("foo", StringValue::from("1"));
assert_eq!("foo=1", b.to_string());
// "URL encoded value"
let mut b = Baggage::default();
b.insert("foo", StringValue::from("1=1"));
assert_eq!("foo=1%3D1", b.to_string());
// "single member empty value with properties"
let mut b = Baggage::default();
b.insert_with_metadata(
"foo",
StringValue::from(""),
BaggageMetadata::from("red;state=on"),
);
assert_eq!("foo=;red;state=on", b.to_string());
// "single member with properties"
let mut b = Baggage::default();
b.insert_with_metadata("foo", StringValue::from("1"), "red;state=on;z=z=z");
assert_eq!("foo=1;red;state=on;z=z=z", b.to_string());
// "two members with properties"
let mut b = Baggage::default();
b.insert_with_metadata("foo", StringValue::from("1"), "red;state=on");
b.insert_with_metadata("bar", StringValue::from("2"), "yellow");
assert!(b.to_string().contains("bar=2;yellow"));
assert!(b.to_string().contains("foo=1;red;state=on"));
}
#[test]
fn replace_existing_key() {
let half_minus2: StringValue = (0..MAX_LEN_OF_ALL_PAIRS / 2 - 2)
.map(|_| 'x')
.collect::<String>()
.into();
let mut b = Baggage::default();
b.insert("a", half_minus2.clone()); // +1 for key
b.insert("b", half_minus2); // +1 for key
b.insert("c", StringValue::from(".")); // total of 2 bytes
assert!(b.get("a").is_some());
assert!(b.get("b").is_some());
assert!(b.get("c").is_some());
assert!(b.insert("c", StringValue::from("..")).is_none()); // exceeds MAX_LEN_OF_ALL_PAIRS
assert_eq!(b.insert("c", StringValue::from("!")).unwrap(), ".".into()); // replaces existing
}
#[test]
fn test_crud_operations() {
let mut baggage = Baggage::default();
assert!(baggage.is_empty());
// create
baggage.insert("foo", "1");
assert_eq!(baggage.len(), 1);
// get
assert_eq!(baggage.get("foo"), Some(&StringValue::from("1")));
// update
baggage.insert("foo", "2");
assert_eq!(baggage.get("foo"), Some(&StringValue::from("2")));
// delete
baggage.remove("foo");
assert!(baggage.is_empty());
}
#[test]
fn test_insert_invalid_key() {
let mut baggage = Baggage::default();
// empty
baggage.insert("", "1");
assert!(baggage.is_empty());
// non-ascii
baggage.insert("Grüße", "1");
assert!(baggage.is_empty());
// invalid ascii chars
baggage.insert("(example)", "1");
assert!(baggage.is_empty());
}
#[test]
fn test_context_clear_baggage() {
let ctx = Context::new();
let ctx = ctx.with_baggage([KeyValue::new("foo", 1)]);
let _guard = ctx.attach();
{
let ctx = Context::current();
let baggage = ctx.baggage();
// At this point baggage should still contain the inital value.
assert_eq!(baggage.len(), 1);
// Baggage gets cleared.
let ctx = ctx.with_cleared_baggage();
let _guard = ctx.attach();
{
let ctx = Context::current();
let baggage = ctx.baggage();
// Baggage should contain no entries.
assert_eq!(baggage.len(), 0);
}
}
}
}
+761
View File
@@ -0,0 +1,761 @@
use std::borrow::{Borrow, Cow};
use std::sync::Arc;
use std::{fmt, hash};
use std::hash::{Hash, Hasher};
/// The key part of attribute [KeyValue] pairs.
///
/// See the [naming] spec for guidelines.
///
/// [naming]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/naming.md
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Key(OtelString);
impl Key {
/// Create a new `Key`.
///
/// # Examples
///
/// ```
/// use opentelemetry::Key;
/// use std::sync::Arc;
///
/// let key1 = Key::new("my_static_str");
/// let key2 = Key::new(String::from("my_owned_string"));
/// let key3 = Key::new(Arc::from("my_ref_counted_str"));
/// ```
pub fn new(value: impl Into<Key>) -> Self {
value.into()
}
/// Create a new const `Key`.
pub const fn from_static_str(value: &'static str) -> Self {
Key(OtelString::Static(value))
}
/// Returns a reference to the underlying key name
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl From<&'static str> for Key {
/// Convert a `&str` to a `Key`.
fn from(key_str: &'static str) -> Self {
Key(OtelString::Static(key_str))
}
}
impl From<String> for Key {
/// Convert a `String` to a `Key`.
fn from(string: String) -> Self {
Key(OtelString::Owned(string.into_boxed_str()))
}
}
impl From<Arc<str>> for Key {
/// Convert a `String` to a `Key`.
fn from(string: Arc<str>) -> Self {
Key(OtelString::RefCounted(string))
}
}
impl From<Cow<'static, str>> for Key {
/// Convert a `Cow<'static, str>` to a `Key`
fn from(string: Cow<'static, str>) -> Self {
match string {
Cow::Borrowed(s) => Key(OtelString::Static(s)),
Cow::Owned(s) => Key(OtelString::Owned(s.into_boxed_str())),
}
}
}
impl fmt::Debug for Key {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl From<Key> for String {
fn from(key: Key) -> Self {
match key.0 {
OtelString::Owned(s) => s.into_string(),
OtelString::Static(s) => s.to_owned(),
OtelString::RefCounted(s) => s.to_string(),
}
}
}
impl fmt::Display for Key {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
OtelString::Owned(s) => s.fmt(fmt),
OtelString::Static(s) => s.fmt(fmt),
OtelString::RefCounted(s) => s.fmt(fmt),
}
}
}
impl Borrow<str> for Key {
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<str> for Key {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
#[derive(Clone, Debug, Eq)]
enum OtelString {
Owned(Box<str>),
Static(&'static str),
RefCounted(Arc<str>),
}
impl OtelString {
fn as_str(&self) -> &str {
match self {
OtelString::Owned(s) => s.as_ref(),
OtelString::Static(s) => s,
OtelString::RefCounted(s) => s.as_ref(),
}
}
}
impl PartialOrd for OtelString {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for OtelString {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl PartialEq for OtelString {
fn eq(&self, other: &Self) -> bool {
self.as_str().eq(other.as_str())
}
}
impl hash::Hash for OtelString {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
/// A [Value::Array] containing homogeneous values.
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub enum Array {
/// Array of bools
Bool(Vec<bool>),
/// Array of integers
I64(Vec<i64>),
/// Array of floats
F64(Vec<f64>),
/// Array of strings
String(Vec<StringValue>),
}
impl fmt::Display for Array {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Array::Bool(values) => display_array_str(values, fmt),
Array::I64(values) => display_array_str(values, fmt),
Array::F64(values) => display_array_str(values, fmt),
Array::String(values) => {
write!(fmt, "[")?;
for (i, t) in values.iter().enumerate() {
if i > 0 {
write!(fmt, ",")?;
}
write!(fmt, "\"{t}\"")?;
}
write!(fmt, "]")
}
}
}
}
fn display_array_str<T: fmt::Display>(slice: &[T], fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "[")?;
for (i, t) in slice.iter().enumerate() {
if i > 0 {
write!(fmt, ",")?;
}
write!(fmt, "{t}")?;
}
write!(fmt, "]")
}
macro_rules! into_array {
($(($t:ty, $val:expr),)+) => {
$(
impl From<$t> for Array {
fn from(t: $t) -> Self {
$val(t)
}
}
)+
}
}
into_array!(
(Vec<bool>, Array::Bool),
(Vec<i64>, Array::I64),
(Vec<f64>, Array::F64),
(Vec<StringValue>, Array::String),
);
/// The value part of attribute [KeyValue] pairs.
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
/// bool values
Bool(bool),
/// i64 values
I64(i64),
/// f64 values
F64(f64),
/// String values
String(StringValue),
/// Array of homogeneous values
Array(Array),
}
/// Wrapper for string-like values
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct StringValue(OtelString);
impl fmt::Debug for StringValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Display for StringValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
OtelString::Owned(s) => s.fmt(f),
OtelString::Static(s) => s.fmt(f),
OtelString::RefCounted(s) => s.fmt(f),
}
}
}
impl AsRef<str> for StringValue {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl StringValue {
/// Returns a string slice to this value
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl From<StringValue> for String {
fn from(s: StringValue) -> Self {
match s.0 {
OtelString::Owned(s) => s.into_string(),
OtelString::Static(s) => s.to_owned(),
OtelString::RefCounted(s) => s.to_string(),
}
}
}
impl From<&'static str> for StringValue {
fn from(s: &'static str) -> Self {
StringValue(OtelString::Static(s))
}
}
impl From<String> for StringValue {
fn from(s: String) -> Self {
StringValue(OtelString::Owned(s.into_boxed_str()))
}
}
impl From<Arc<str>> for StringValue {
fn from(s: Arc<str>) -> Self {
StringValue(OtelString::RefCounted(s))
}
}
impl From<Cow<'static, str>> for StringValue {
fn from(s: Cow<'static, str>) -> Self {
match s {
Cow::Owned(s) => StringValue(OtelString::Owned(s.into_boxed_str())),
Cow::Borrowed(s) => StringValue(OtelString::Static(s)),
}
}
}
impl From<Value> for StringValue {
fn from(s: Value) -> Self {
match s {
Value::Bool(v) => format!("{v}").into(),
Value::I64(v) => format!("{v}").into(),
Value::F64(v) => format!("{v}").into(),
Value::String(v) => v,
Value::Array(v) => format!("{v}").into(),
}
}
}
impl Value {
/// String representation of the `Value`
///
/// This will allocate if the underlying value is not a `String`.
pub fn as_str(&self) -> Cow<'_, str> {
match self {
Value::Bool(v) => format!("{v}").into(),
Value::I64(v) => format!("{v}").into(),
Value::F64(v) => format!("{v}").into(),
Value::String(v) => Cow::Borrowed(v.as_str()),
Value::Array(v) => format!("{v}").into(),
}
}
}
macro_rules! from_values {
(
$(
($t:ty, $val:expr);
)+
) => {
$(
impl From<$t> for Value {
fn from(t: $t) -> Self {
$val(t)
}
}
)+
}
}
from_values!(
(bool, Value::Bool);
(i64, Value::I64);
(f64, Value::F64);
(StringValue, Value::String);
);
impl From<&'static str> for Value {
fn from(s: &'static str) -> Self {
Value::String(s.into())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::String(s.into())
}
}
impl From<Arc<str>> for Value {
fn from(s: Arc<str>) -> Self {
Value::String(s.into())
}
}
impl From<Cow<'static, str>> for Value {
fn from(s: Cow<'static, str>) -> Self {
Value::String(s.into())
}
}
impl fmt::Display for Value {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Bool(v) => v.fmt(fmt),
Value::I64(v) => v.fmt(fmt),
Value::F64(v) => v.fmt(fmt),
Value::String(v) => fmt.write_str(v.as_str()),
Value::Array(v) => v.fmt(fmt),
}
}
}
/// A key-value pair describing an attribute.
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct KeyValue {
/// The attribute name
pub key: Key,
/// The attribute value
pub value: Value,
}
impl KeyValue {
/// Create a new `KeyValue` pair.
pub fn new<K, V>(key: K, value: V) -> Self
where
K: Into<Key>,
V: Into<Value>,
{
KeyValue {
key: key.into(),
value: value.into(),
}
}
}
struct F64Hashable(f64);
impl PartialEq for F64Hashable {
fn eq(&self, other: &Self) -> bool {
self.0.to_bits() == other.0.to_bits()
}
}
impl Eq for F64Hashable {}
impl Hash for F64Hashable {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl Hash for KeyValue {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key.hash(state);
match &self.value {
Value::F64(f) => F64Hashable(*f).hash(state),
Value::Array(a) => match a {
Array::Bool(b) => b.hash(state),
Array::I64(i) => i.hash(state),
Array::F64(f) => f.iter().for_each(|f| F64Hashable(*f).hash(state)),
Array::String(s) => s.hash(state),
},
Value::Bool(b) => b.hash(state),
Value::I64(i) => i.hash(state),
Value::String(s) => s.hash(state),
};
}
}
impl Eq for KeyValue {}
/// Information about a library or crate providing instrumentation.
///
/// An instrumentation scope should be named to follow any naming conventions
/// of the instrumented library (e.g. 'middleware' for a web framework).
///
/// See the [instrumentation libraries] spec for more information.
///
/// [instrumentation libraries]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/overview.md#instrumentation-libraries
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct InstrumentationScope {
/// The library name.
///
/// This should be the name of the crate providing the instrumentation.
name: Cow<'static, str>,
/// The library version.
version: Option<Cow<'static, str>>,
/// [Schema URL] used by this library.
///
/// [Schema URL]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
schema_url: Option<Cow<'static, str>>,
/// Specifies the instrumentation scope attributes to associate with emitted telemetry.
attributes: Vec<KeyValue>,
}
impl PartialEq for InstrumentationScope {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.version == other.version
&& self.schema_url == other.schema_url
&& self.attributes.len() == other.attributes.len()
&& {
let mut self_attrs = Vec::from_iter(&self.attributes);
let mut other_attrs = Vec::from_iter(&other.attributes);
self_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
other_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
self_attrs == other_attrs
}
}
}
impl Eq for InstrumentationScope {}
impl hash::Hash for InstrumentationScope {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.version.hash(state);
self.schema_url.hash(state);
let mut sorted_attrs = Vec::from_iter(&self.attributes);
sorted_attrs.sort_unstable_by(|a, b| a.key.cmp(&b.key));
for attribute in sorted_attrs {
attribute.hash(state);
}
}
}
impl InstrumentationScope {
/// Create a new builder to create an [InstrumentationScope]
pub fn builder<T: Into<Cow<'static, str>>>(name: T) -> InstrumentationScopeBuilder {
InstrumentationScopeBuilder {
name: name.into(),
version: None,
schema_url: None,
attributes: None,
}
}
/// Returns the instrumentation library name.
#[inline]
pub fn name(&self) -> &str {
&self.name
}
/// Returns the instrumentation library version.
#[inline]
pub fn version(&self) -> Option<&str> {
self.version.as_deref()
}
/// Returns the [Schema URL] used by this library.
///
/// [Schema URL]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/schemas/overview.md#schema-url
#[inline]
pub fn schema_url(&self) -> Option<&str> {
self.schema_url.as_deref()
}
/// Returns the instrumentation scope attributes to associate with emitted telemetry.
#[inline]
pub fn attributes(&self) -> impl Iterator<Item = &KeyValue> {
self.attributes.iter()
}
}
/// Configuration options for [InstrumentationScope].
///
/// An instrumentation scope is a library or crate providing instrumentation.
/// It should be named to follow any naming conventions of the instrumented
/// library (e.g. 'middleware' for a web framework).
///
/// Apart from the name, all other fields are optional.
///
/// See the [instrumentation libraries] spec for more information.
///
/// [instrumentation libraries]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.9.0/specification/overview.md#instrumentation-libraries
#[derive(Debug)]
pub struct InstrumentationScopeBuilder {
name: Cow<'static, str>,
version: Option<Cow<'static, str>>,
schema_url: Option<Cow<'static, str>>,
attributes: Option<Vec<KeyValue>>,
}
impl InstrumentationScopeBuilder {
/// Configure the version for the instrumentation scope
///
/// # Examples
///
/// ```
/// let scope = opentelemetry::InstrumentationScope::builder("my-crate")
/// .with_version("v0.1.0")
/// .build();
/// ```
pub fn with_version(mut self, version: impl Into<Cow<'static, str>>) -> Self {
self.version = Some(version.into());
self
}
/// Configure the Schema URL for the instrumentation scope
///
/// # Examples
///
/// ```
/// let scope = opentelemetry::InstrumentationScope::builder("my-crate")
/// .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
/// .build();
/// ```
pub fn with_schema_url(mut self, schema_url: impl Into<Cow<'static, str>>) -> Self {
self.schema_url = Some(schema_url.into());
self
}
/// Configure the attributes for the instrumentation scope
///
/// # Examples
///
/// ```
/// use opentelemetry::KeyValue;
///
/// let scope = opentelemetry::InstrumentationScope::builder("my-crate")
/// .with_attributes([KeyValue::new("k", "v")])
/// .build();
/// ```
pub fn with_attributes<I>(mut self, attributes: I) -> Self
where
I: IntoIterator<Item = KeyValue>,
{
self.attributes = Some(attributes.into_iter().collect());
self
}
/// Create a new [InstrumentationScope] from this configuration
pub fn build(self) -> InstrumentationScope {
InstrumentationScope {
name: self.name,
version: self.version,
schema_url: self.schema_url,
attributes: self.attributes.unwrap_or_default(),
}
}
}
#[cfg(test)]
mod tests {
use std::hash::{Hash, Hasher};
use crate::{InstrumentationScope, KeyValue};
use rand::random;
use std::collections::hash_map::DefaultHasher;
#[test]
fn kv_float_equality() {
let kv1 = KeyValue::new("key", 1.0);
let kv2 = KeyValue::new("key", 1.0);
assert_eq!(kv1, kv2);
let kv1 = KeyValue::new("key", 1.0);
let kv2 = KeyValue::new("key", 1.01);
assert_ne!(kv1, kv2);
let kv1 = KeyValue::new("key", f64::NAN);
let kv2 = KeyValue::new("key", f64::NAN);
assert_ne!(kv1, kv2, "NAN is not equal to itself");
for float_val in [
f64::INFINITY,
f64::NEG_INFINITY,
f64::MAX,
f64::MIN,
f64::MIN_POSITIVE,
]
.iter()
{
let kv1 = KeyValue::new("key", *float_val);
let kv2 = KeyValue::new("key", *float_val);
assert_eq!(kv1, kv2);
}
for _ in 0..100 {
let random_value = random::<f64>();
let kv1 = KeyValue::new("key", random_value);
let kv2 = KeyValue::new("key", random_value);
assert_eq!(kv1, kv2);
}
}
#[test]
fn kv_float_hash() {
for float_val in [
f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
f64::MAX,
f64::MIN,
f64::MIN_POSITIVE,
]
.iter()
{
let kv1 = KeyValue::new("key", *float_val);
let kv2 = KeyValue::new("key", *float_val);
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
}
for _ in 0..100 {
let random_value = random::<f64>();
let kv1 = KeyValue::new("key", random_value);
let kv2 = KeyValue::new("key", random_value);
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
}
}
fn hash_helper<T: Hash>(item: &T) -> u64 {
let mut hasher = DefaultHasher::new();
item.hash(&mut hasher);
hasher.finish()
}
#[test]
fn instrumentation_scope_equality() {
let scope1 = InstrumentationScope::builder("my-crate")
.with_version("v0.1.0")
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
.with_attributes([KeyValue::new("k", "v")])
.build();
let scope2 = InstrumentationScope::builder("my-crate")
.with_version("v0.1.0")
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
.with_attributes([KeyValue::new("k", "v")])
.build();
assert_eq!(scope1, scope2);
}
#[test]
fn instrumentation_scope_equality_attributes_diff_order() {
let scope1 = InstrumentationScope::builder("my-crate")
.with_version("v0.1.0")
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
.with_attributes([KeyValue::new("k1", "v1"), KeyValue::new("k2", "v2")])
.build();
let scope2 = InstrumentationScope::builder("my-crate")
.with_version("v0.1.0")
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
.with_attributes([KeyValue::new("k2", "v2"), KeyValue::new("k1", "v1")])
.build();
assert_eq!(scope1, scope2);
// assert hash are same for both scopes
let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
scope1.hash(&mut hasher1);
let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
scope2.hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish());
}
#[test]
fn instrumentation_scope_equality_different_attributes() {
let scope1 = InstrumentationScope::builder("my-crate")
.with_version("v0.1.0")
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
.with_attributes([KeyValue::new("k1", "v1"), KeyValue::new("k2", "v2")])
.build();
let scope2 = InstrumentationScope::builder("my-crate")
.with_version("v0.1.0")
.with_schema_url("https://opentelemetry.io/schemas/1.17.0")
.with_attributes([KeyValue::new("k2", "v3"), KeyValue::new("k4", "v5")])
.build();
assert_ne!(scope1, scope2);
// assert hash are same for both scopes
let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
scope1.hash(&mut hasher1);
let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
scope2.hash(&mut hasher2);
assert_ne!(hasher1.finish(), hasher2.finish());
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,108 @@
use crate::Context;
use futures_core::Stream;
use futures_sink::Sink;
use pin_project_lite::pin_project;
use std::pin::Pin;
use std::task::Context as TaskContext;
use std::task::Poll;
impl<T: Sized> FutureExt for T {}
impl<T: std::future::Future> std::future::Future for WithContext<T> {
type Output = T::Output;
fn poll(self: Pin<&mut Self>, task_cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
let this = self.project();
let _guard = this.otel_cx.clone().attach();
this.inner.poll(task_cx)
}
}
impl<T: Stream> Stream for WithContext<T> {
type Item = T::Item;
fn poll_next(self: Pin<&mut Self>, task_cx: &mut TaskContext<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
let _guard = this.otel_cx.clone().attach();
T::poll_next(this.inner, task_cx)
}
}
pin_project! {
/// A future, stream, or sink that has an associated context.
#[derive(Clone, Debug)]
pub struct WithContext<T> {
#[pin]
inner: T,
otel_cx: Context,
}
}
impl<I, T: Sink<I>> Sink<I> for WithContext<T>
where
T: Sink<I>,
{
type Error = T::Error;
fn poll_ready(
self: Pin<&mut Self>,
task_cx: &mut TaskContext<'_>,
) -> Poll<Result<(), Self::Error>> {
let this = self.project();
let _guard = this.otel_cx.clone().attach();
T::poll_ready(this.inner, task_cx)
}
fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> {
let this = self.project();
let _guard = this.otel_cx.clone().attach();
T::start_send(this.inner, item)
}
fn poll_flush(
self: Pin<&mut Self>,
task_cx: &mut TaskContext<'_>,
) -> Poll<Result<(), Self::Error>> {
let this = self.project();
let _guard = this.otel_cx.clone().attach();
T::poll_flush(this.inner, task_cx)
}
fn poll_close(
self: Pin<&mut Self>,
task_cx: &mut TaskContext<'_>,
) -> Poll<Result<(), Self::Error>> {
let this = self.project();
let _enter = this.otel_cx.clone().attach();
T::poll_close(this.inner, task_cx)
}
}
/// Extension trait allowing futures, streams, and sinks to be traced with a span.
pub trait FutureExt: Sized {
/// Attaches the provided [`Context`] to this type, returning a `WithContext`
/// wrapper.
///
/// When the wrapped type is a future, stream, or sink, the attached context
/// will be set as current while it is being polled.
///
/// [`Context`]: Context
fn with_context(self, otel_cx: Context) -> WithContext<Self> {
WithContext {
inner: self,
otel_cx,
}
}
/// Attaches the current [`Context`] to this type, returning a `WithContext`
/// wrapper.
///
/// When the wrapped type is a future, stream, or sink, the attached context
/// will be set as the default while it is being polled.
///
/// [`Context`]: Context
fn with_current_context(self) -> WithContext<Self> {
let otel_cx = Context::current();
self.with_context(otel_cx)
}
}
@@ -0,0 +1,227 @@
#![allow(unused_macros)]
///
/// **Note**: These macros (`otel_info!`, `otel_warn!`, `otel_debug!`, and `otel_error!`) are intended to be used
/// **internally within OpenTelemetry code** or for **custom exporters, processors and other plugins**. They are not designed
/// for general application logging and should not be used for that purpose.
///
/// When running tests with `--nocapture`, these macros will print their output to stdout. This is useful for debugging
/// test failures and understanding the flow of operations during testing.
///
/// Macro for logging informational messages in OpenTelemetry.
///
/// # Fields:
/// - `name`: The operation or action being logged.
/// - Additional optional key-value pairs can be passed as attributes.
///
/// # Example:
/// ```rust
/// use opentelemetry::otel_info;
/// otel_info!(name: "sdk_start", version = "1.0.0", schema_url = "http://example.com");
/// ```
///
// TODO: Remove `name` attribute duplication in logging macros below once `tracing::Fmt` supports displaying `name`.
// See issue: https://github.com/tokio-rs/tracing/issues/2774
#[macro_export]
macro_rules! otel_info {
(name: $name:expr $(,)?) => {
#[cfg(feature = "internal-logs")]
{
$crate::_private::info!( name: $name, target: env!("CARGO_PKG_NAME"), name = $name);
}
#[cfg(test)]
{
print!("otel_info: name={}\n", $name);
}
#[cfg(all(not(feature = "internal-logs"), not(test)))]
{
let _ = $name; // Compiler will optimize this out as it's unused.
}
};
(name: $name:expr, $($key:tt = $value:expr),+ $(,)?) => {
#[cfg(feature = "internal-logs")]
{
$crate::_private::info!(name: $name, target: env!("CARGO_PKG_NAME"), name = $name, $($key = $value),+);
}
#[cfg(test)]
{
print!("otel_info: name={}", $name);
$(
print!(", {}={}", stringify!($key), $value);
)+
print!("\n");
}
#[cfg(all(not(feature = "internal-logs"), not(test)))]
{
let _ = ($name, $($value),+); // Compiler will optimize this out as it's unused.
}
};
}
/// Macro for logging warning messages in OpenTelemetry.
///
/// # Fields:
/// - `name`: The operation or action being logged.
/// - Additional optional key-value pairs can be passed as attributes.
///
/// # Example:
/// ```rust
/// use opentelemetry::otel_warn;
/// otel_warn!(name: "export_warning", error_code = 404, version = "1.0.0");
/// ```
#[macro_export]
macro_rules! otel_warn {
(name: $name:expr $(,)?) => {
#[cfg(feature = "internal-logs")]
{
$crate::_private::warn!(name: $name, target: env!("CARGO_PKG_NAME"), name = $name);
}
#[cfg(test)]
{
print!("otel_warn: name={}\n", $name);
}
#[cfg(all(not(feature = "internal-logs"), not(test)))]
{
let _ = $name; // Compiler will optimize this out as it's unused.
}
};
(name: $name:expr, $($key:tt = $value:expr),+ $(,)?) => {
#[cfg(feature = "internal-logs")]
{
$crate::_private::warn!(name: $name,
target: env!("CARGO_PKG_NAME"),
name = $name,
$($key = {
$value
}),+,
)
}
#[cfg(test)]
{
print!("otel_warn: name={}", $name);
$(
print!(", {}={}", stringify!($key), $value);
)+
print!("\n");
}
#[cfg(all(not(feature = "internal-logs"), not(test)))]
{
let _ = ($name, $($value),+); // Compiler will optimize this out as it's unused.
}
};
}
/// Macro for logging debug messages in OpenTelemetry.
///
/// # Fields:
/// - `name`: The operation or action being logged.
/// - Additional optional key-value pairs can be passed as attributes.
///
/// # Example:
/// ```rust
/// use opentelemetry::otel_debug;
/// otel_debug!(name: "debug_operation", debug_level = "high", version = "1.0.0");
/// ```
#[macro_export]
macro_rules! otel_debug {
(name: $name:expr $(,)?) => {
#[cfg(feature = "internal-logs")]
{
$crate::_private::debug!(name: $name, target: env!("CARGO_PKG_NAME"), name = $name);
}
#[cfg(test)]
{
print!("otel_debug: name={}\n", $name);
}
#[cfg(all(not(feature = "internal-logs"), not(test)))]
{
let _ = $name; // Compiler will optimize this out as it's unused.
}
};
(name: $name:expr, $($key:tt = $value:expr),+ $(,)?) => {
#[cfg(feature = "internal-logs")]
{
$crate::_private::debug!(name: $name, target: env!("CARGO_PKG_NAME"), name = $name, $($key = $value),+);
}
#[cfg(test)]
{
print!("otel_debug: name={}", $name);
$(
print!(", {}={}", stringify!($key), $value);
)+
print!("\n");
}
#[cfg(all(not(feature = "internal-logs"), not(test)))]
{
let _ = ($name, $($value),+); // Compiler will optimize this out as it's unused.
}
};
}
/// Macro for logging error messages in OpenTelemetry.
///
/// # Fields:
/// - `name`: The operation or action being logged.
/// - Additional optional key-value pairs can be passed as attributes.
///
/// # Example:
/// ```rust
/// use opentelemetry::otel_error;
/// otel_error!(name: "export_failure", error_code = 500, version = "1.0.0");
/// ```
#[macro_export]
macro_rules! otel_error {
(name: $name:expr $(,)?) => {
#[cfg(feature = "internal-logs")]
{
$crate::_private::error!(name: $name, target: env!("CARGO_PKG_NAME"), name = $name);
}
#[cfg(test)]
{
print!("otel_error: name={}\n", $name);
}
#[cfg(all(not(feature = "internal-logs"), not(test)))]
{
let _ = $name; // Compiler will optimize this out as it's unused.
}
};
(name: $name:expr, $($key:tt = $value:expr),+ $(,)?) => {
#[cfg(feature = "internal-logs")]
{
$crate::_private::error!(name: $name,
target: env!("CARGO_PKG_NAME"),
name = $name,
$($key = {
$value
}),+,
)
}
#[cfg(test)]
{
print!("otel_error: name={}", $name);
$(
print!(", {}={}", stringify!($key), $value);
)+
print!("\n");
}
#[cfg(all(not(feature = "internal-logs"), not(test)))]
{
let _ = ($name, $($value),+); // Compiler will optimize this out as it's unused.
}
};
}
@@ -0,0 +1,82 @@
use crate::metrics::{self, Meter, MeterProvider};
use crate::{otel_error, otel_info, InstrumentationScope};
use std::sync::{Arc, OnceLock, RwLock};
type GlobalMeterProvider = Arc<dyn MeterProvider + Send + Sync>;
/// The global `MeterProvider` singleton.
static GLOBAL_METER_PROVIDER: OnceLock<RwLock<GlobalMeterProvider>> = OnceLock::new();
#[inline]
fn global_meter_provider() -> &'static RwLock<GlobalMeterProvider> {
GLOBAL_METER_PROVIDER
.get_or_init(|| RwLock::new(Arc::new(crate::metrics::noop::NoopMeterProvider::new())))
}
/// Sets the given [`MeterProvider`] instance as the current global meter
/// provider.
/// Libraries should NOT call this function. It is intended for applications/executables.
///
/// **NOTE:** This function should be called before getting [`Meter`] instances via [`meter()`] or [`meter_with_scope()`]. Otherwise, you could get no-op [`Meter`] instances.
pub fn set_meter_provider<P>(new_provider: P)
where
P: metrics::MeterProvider + Send + Sync + 'static,
{
// Try to set the global meter provider. If the RwLock is poisoned, we'll log an error.
let mut global_provider = global_meter_provider().write();
if let Ok(ref mut provider) = global_provider {
**provider = Arc::new(new_provider);
otel_info!(name: "MeterProvider.GlobalSet", message = "Global meter provider is set. Meters can now be created using global::meter() or global::meter_with_scope().");
} else {
otel_error!(name: "MeterProvider.GlobalSetFailed", message = "Setting global meter provider failed. Meters created using global::meter() or global::meter_with_scope() will not function. Report this issue in OpenTelemetry repo.");
}
}
/// Returns an instance of the currently configured global [`MeterProvider`].
pub fn meter_provider() -> GlobalMeterProvider {
// Try to get the global meter provider. If the RwLock is poisoned, we'll log an error and return a NoopMeterProvider.
let global_provider = global_meter_provider().read();
if let Ok(provider) = global_provider {
provider.clone()
} else {
otel_error!(name: "MeterProvider.GlobalGetFailed", message = "Getting global meter provider failed. Meters created using global::meter() or global::meter_with_scope() will not function. Report this issue in OpenTelemetry repo.");
Arc::new(crate::metrics::noop::NoopMeterProvider::new())
}
}
/// Creates a named [`Meter`] via the currently configured global [`MeterProvider`].
///
/// This is a more convenient way of expressing `global::meter_provider().meter(name)`.
///
/// **NOTE:** Calls to [`meter()`] return a [`Meter`] backed by the global [`MeterProvider`] configured during the method invocation.
/// If the global [`MeterProvider`] is changed after getting [`Meter`] instances from these calls, the [`Meter`] instances returned will not reflect the change.
pub fn meter(name: &'static str) -> Meter {
meter_provider().meter(name)
}
/// Creates a [`Meter`] with the given instrumentation scope.
///
/// This is a simpler alternative to `global::meter_provider().meter_with_scope(...)`
///
/// **NOTE:** Calls to [`meter_with_scope()`] return a [`Meter`] backed by the global [`MeterProvider`] configured during the method invocation.
/// If the global [`MeterProvider`] is changed after getting [`Meter`] instances from these calls, the [`Meter`] instances returned will not reflect the change.
///
/// # Example
///
/// ```
/// use std::sync::Arc;
/// use opentelemetry::global::meter_with_scope;
/// use opentelemetry::InstrumentationScope;
/// use opentelemetry::KeyValue;
///
/// let scope = InstrumentationScope::builder("io.opentelemetry")
/// .with_version("0.17")
/// .with_schema_url("https://opentelemetry.io/schemas/1.2.0")
/// .with_attributes(vec![(KeyValue::new("key", "value"))])
/// .build();
///
/// let meter = meter_with_scope(scope);
/// ```
pub fn meter_with_scope(scope: InstrumentationScope) -> Meter {
meter_provider().meter_with_scope(scope)
}
@@ -0,0 +1,149 @@
//! Utilities for working with global telemetry primitives
//!
//! ## Global Trace API
//!
//! The global trace API **provides applications access to their configured
//! [`TracerProvider`] instance from anywhere in the codebase**. This allows
//! applications to be less coupled to the specific Open Telemetry SDK while not
//! manually passing references to each part of the code that needs to create
//! [`Span`]s. Additionally, **3rd party middleware** or **library code** can be
//! written against this generic API and not constrain users to a specific
//! implementation choice.
//!
//! ### Usage in Applications
//!
//! Applications configure their tracer either by installing a trace pipeline,
//! or calling [`set_tracer_provider`].
//!
//! ```
//! # #[cfg(feature="trace")]
//! # {
//! use opentelemetry::trace::{Tracer, noop::NoopTracerProvider};
//! use opentelemetry::global;
//!
//! fn init_tracer() {
//! // Swap this no-op provider for your tracing service of choice (jaeger, zipkin, etc)
//! let provider = NoopTracerProvider::new();
//!
//! // Configure the global `TracerProvider` singleton when your app starts
//! // (there is a no-op default if this is not set by your application)
//! let _ = global::set_tracer_provider(provider);
//! }
//!
//! fn do_something_tracked() {
//! // Then you can get a named tracer instance anywhere in your codebase.
//! let tracer = global::tracer("my-component");
//!
//! tracer.in_span("doing_work", |cx| {
//! // Traced app logic here...
//! });
//! }
//!
//! // in main or other app start
//! init_tracer();
//! do_something_tracked();
//! # }
//! ```
//!
//! ### Usage in Libraries
//!
//! ```
//! # #[cfg(feature="trace")]
//! # {
//! use std::sync::Arc;
//! use opentelemetry::trace::Tracer;
//! use opentelemetry::global;
//! use opentelemetry::InstrumentationScope;
//!
//! pub fn my_traced_library_function() {
//! // End users of your library will configure their global tracer provider
//! // so you can use the global tracer without any setup
//!
//! let scope = InstrumentationScope::builder("my_library-name")
//! .with_version(env!("CARGO_PKG_VERSION"))
//! .with_schema_url("https://opentelemetry.io/schemas/1.17.0")
//! .build();
//!
//! let tracer = global::tracer_with_scope(scope);
//!
//! tracer.in_span("doing_library_work", |cx| {
//! // Traced library logic here...
//! });
//! }
//! # }
//! ```
//!
//! [`TracerProvider`]: crate::trace::TracerProvider
//! [`Span`]: crate::trace::Span
//!
//! ## Global Metrics API
//!
//! The global metrics API **provides applications access to their configured
//! [`MeterProvider`] instance from anywhere in the codebase**. This allows
//! applications to be less coupled to the specific Open Telemetry SDK while not
//! manually passing references to each part of the code that needs to create
//! metric instruments. Additionally, **3rd party middleware** or **library code** can be
//! written against this generic API and not constrain users to a specific
//! implementation choice.
//!
//! ### Usage in Applications and libraries
//!
//! Applications and libraries can obtain meter from the global meter provider,
//! and use the meter to create instruments to emit measurements.
//!
//! ```
//! # #[cfg(feature="metrics")]
//! # {
//! use opentelemetry::metrics::{Meter};
//! use opentelemetry::{global, KeyValue};
//!
//! fn do_something_instrumented() {
//! let meter = global::meter("my-component");
//! // It is recommended to reuse the same counter instance for the
//! // lifetime of the application
//! let counter = meter.u64_counter("my_counter").build();
//!
//! // record measurements
//! counter.add(1, &[KeyValue::new("mykey", "myvalue")]);
//! }
//! }
//! ```
//!
//! ### Usage in Applications
//! Application owners have the responsibility to set the global meter provider.
//! The global meter provider can be set using the [`set_meter_provider`] function.
//! As set_meter_provider takes ownership of the provider, it is recommended to
//! provide a clone of the provider, if the application needs to use the provider
//! later to perform operations like shutdown.
//! ```
//! # #[cfg(feature="metrics")]
//! # {
//! use opentelemetry::{global, KeyValue};
//!
//! fn main() {
//! // Set the global meter provider
//! // global::set_meter_provider(my_meter_provider().clone());
//! }
//! # }
//! ```
//!
//! [`MeterProvider`]: crate::metrics::MeterProvider
//! [`set_meter_provider`]: crate::global::set_meter_provider
mod internal_logging;
#[cfg(feature = "metrics")]
mod metrics;
#[cfg(feature = "trace")]
mod propagation;
#[cfg(feature = "trace")]
mod trace;
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub use metrics::*;
#[cfg(feature = "trace")]
#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
pub use propagation::*;
#[cfg(feature = "trace")]
#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
pub use trace::*;
@@ -0,0 +1,43 @@
use crate::propagation::TextMapPropagator;
use crate::trace::noop::NoopTextMapPropagator;
use std::sync::{OnceLock, RwLock};
/// The current global `TextMapPropagator` propagator.
static GLOBAL_TEXT_MAP_PROPAGATOR: OnceLock<RwLock<Box<dyn TextMapPropagator + Send + Sync>>> =
OnceLock::new();
/// The global default `TextMapPropagator` propagator.
static DEFAULT_TEXT_MAP_PROPAGATOR: OnceLock<NoopTextMapPropagator> = OnceLock::new();
/// Ensures the `GLOBAL_TEXT_MAP_PROPAGATOR` is initialized with a `NoopTextMapPropagator`.
#[inline]
fn global_text_map_propagator() -> &'static RwLock<Box<dyn TextMapPropagator + Send + Sync>> {
GLOBAL_TEXT_MAP_PROPAGATOR.get_or_init(|| RwLock::new(Box::new(NoopTextMapPropagator::new())))
}
/// Ensures the `DEFAULT_TEXT_MAP_PROPAGATOR` is initialized.
#[inline]
fn default_text_map_propagator() -> &'static NoopTextMapPropagator {
DEFAULT_TEXT_MAP_PROPAGATOR.get_or_init(NoopTextMapPropagator::new)
}
/// Sets the given [`TextMapPropagator`] propagator as the current global propagator.
pub fn set_text_map_propagator<P: TextMapPropagator + Send + Sync + 'static>(propagator: P) {
let _lock = global_text_map_propagator()
.write()
.map(|mut global_propagator| *global_propagator = Box::new(propagator));
}
/// Executes a closure with a reference to the current global [`TextMapPropagator`] propagator.
pub fn get_text_map_propagator<T, F>(mut f: F) -> T
where
F: FnMut(&dyn TextMapPropagator) -> T,
{
global_text_map_propagator()
.read()
.map(|propagator| f(&**propagator))
.unwrap_or_else(|_| {
let default_propagator = default_text_map_propagator();
f(default_propagator as &dyn TextMapPropagator)
})
}
@@ -0,0 +1,440 @@
use crate::trace::{noop::NoopTracerProvider, SpanContext, Status};
use crate::{otel_error, otel_info, InstrumentationScope};
use crate::{trace, trace::TracerProvider, Context, KeyValue};
use std::borrow::Cow;
use std::fmt;
use std::sync::{Arc, OnceLock, RwLock};
use std::time::SystemTime;
/// Allows a specific [`crate::trace::Span`] to be used generically by [`BoxedSpan`]
/// instances by mirroring the interface and boxing the return types.
pub trait ObjectSafeSpan {
/// An API to record events at a specific time in the context of a given `Span`.
///
/// Events SHOULD preserve the order in which they're set. This will typically match
/// the ordering of the events' timestamps.
///
/// Note that the OpenTelemetry project documents certain ["standard event names and
/// keys"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
/// which have prescribed semantic meanings.
fn add_event_with_timestamp(
&mut self,
name: Cow<'static, str>,
timestamp: SystemTime,
attributes: Vec<KeyValue>,
);
/// Returns the `SpanContext` for the given `Span`. The returned value may be used even after
/// the `Span is finished. The returned value MUST be the same for the entire `Span` lifetime.
fn span_context(&self) -> &SpanContext;
/// Returns true if this `Span` is recording information like events with the `add_event`
/// operation, attributes using `set_attributes`, status with `set_status`, etc.
///
/// This flag SHOULD be used to avoid expensive computations of a `Span` attributes or events in
/// case when a `Span` is definitely not recorded. Note that any child span's recording is
/// determined independently from the value of this flag (typically based on the sampled flag of
/// a `TraceFlag` on `SpanContext`).
///
/// This flag may be true despite the entire trace being sampled out. This allows to record and
/// process information about the individual Span without sending it to the backend. An example
/// of this scenario may be recording and processing of all incoming requests for the processing
/// and building of SLA/SLO latency charts while sending only a subset - sampled spans - to the
/// backend. See also the sampling section of SDK design.
///
/// Users of the API should only access the `is_recording` property when instrumenting code and
/// never access `SampledFlag` unless used in context propagators.
fn is_recording(&self) -> bool;
/// An API to set a single `Attribute` where the attribute properties are passed
/// as arguments. To avoid extra allocations some implementations may offer a separate API for
/// each of the possible value types.
///
/// An `Attribute` is defined as a `KeyValue` pair.
///
/// Attributes SHOULD preserve the order in which they're set. Setting an attribute
/// with the same key as an existing attribute SHOULD overwrite the existing
/// attribute's value.
///
/// Note that the OpenTelemetry project documents certain ["standard
/// attributes"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
/// that have prescribed semantic meanings.
fn set_attribute(&mut self, attribute: KeyValue);
/// Sets the status of the `Span`. `message` MUST be ignored when the status is `OK` or
/// `Unset`.
///
/// The order of status is `Ok` > `Error` > `Unset`. That's means set the status
/// to `Unset` will always be ignore, set the status to `Error` only works when current
/// status is `Unset`, set the status to `Ok` will be consider final and any further call
/// to this function will be ignore.
fn set_status(&mut self, status: Status);
/// Updates the `Span`'s name. After this update, any sampling behavior based on the
/// name will depend on the implementation.
///
/// It is highly discouraged to update the name of a `Span` after its creation.
/// `Span` name is often used to group, filter and identify the logical groups of
/// spans. Often, filtering logic will be implemented before the `Span` creation
/// for performance reasons, and the name update may interfere with this logic.
///
/// The method name is called `update_name` to differentiate this method from the
/// regular property. It emphasizes that this operation signifies a
/// major change for a `Span` and may lead to re-calculation of sampling or
/// filtering decisions made previously depending on the implementation.
fn update_name(&mut self, new_name: Cow<'static, str>);
/// Adds a link to this span
///
fn add_link(&mut self, span_context: SpanContext, attributes: Vec<KeyValue>);
/// Finishes the `Span`.
///
/// Implementations MUST ignore all subsequent calls to `end` (there might be
/// exceptions when the tracer is streaming events and has no mutable state
/// associated with the Span).
///
/// Calls to `end` a Span MUST not have any effects on child `Span`s as they may
/// still be running and can be ended later.
///
/// This API MUST be non-blocking.
fn end(&mut self) {
self.end_with_timestamp(crate::time::now());
}
/// Finishes the `Span` with given timestamp
///
/// For more details, refer to [`Span::end`]
///
/// [`Span::end`]: trace::Span::end
fn end_with_timestamp(&mut self, timestamp: SystemTime);
}
impl<T: trace::Span> ObjectSafeSpan for T {
fn add_event_with_timestamp(
&mut self,
name: Cow<'static, str>,
timestamp: SystemTime,
attributes: Vec<KeyValue>,
) {
self.add_event_with_timestamp(name, timestamp, attributes)
}
fn span_context(&self) -> &SpanContext {
self.span_context()
}
fn is_recording(&self) -> bool {
self.is_recording()
}
fn set_attribute(&mut self, attribute: KeyValue) {
self.set_attribute(attribute)
}
fn set_status(&mut self, status: Status) {
self.set_status(status)
}
fn update_name(&mut self, new_name: Cow<'static, str>) {
self.update_name(new_name)
}
fn add_link(&mut self, span_context: SpanContext, attributes: Vec<KeyValue>) {
self.add_link(span_context, attributes)
}
fn end_with_timestamp(&mut self, timestamp: SystemTime) {
self.end_with_timestamp(timestamp)
}
}
/// Wraps the [`BoxedTracer`]'s [`Span`] so it can be used generically by
/// applications without knowing the underlying type.
///
/// [`Span`]: crate::trace::Span
pub struct BoxedSpan(Box<dyn ObjectSafeSpan + Send + Sync>);
impl BoxedSpan {
pub(crate) fn new<T>(span: T) -> Self
where
T: ObjectSafeSpan + Send + Sync + 'static,
{
BoxedSpan(Box::new(span))
}
}
impl fmt::Debug for BoxedSpan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("BoxedSpan")
}
}
impl trace::Span for BoxedSpan {
/// Records events at a specific time in the context of a given `Span`.
///
/// Note that the OpenTelemetry project documents certain ["standard event names and
/// keys"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
/// which have prescribed semantic meanings.
fn add_event_with_timestamp<T>(
&mut self,
name: T,
timestamp: SystemTime,
attributes: Vec<KeyValue>,
) where
T: Into<Cow<'static, str>>,
{
self.0
.add_event_with_timestamp(name.into(), timestamp, attributes)
}
/// Returns the `SpanContext` for the given `Span`.
fn span_context(&self) -> &trace::SpanContext {
self.0.span_context()
}
/// Returns true if this `Span` is recording information like events with the `add_event`
/// operation, attributes using `set_attributes`, status with `set_status`, etc.
fn is_recording(&self) -> bool {
self.0.is_recording()
}
/// Sets a single `Attribute` where the attribute properties are passed as arguments.
///
/// Note that the OpenTelemetry project documents certain ["standard
/// attributes"](https://github.com/open-telemetry/opentelemetry-specification/tree/v0.5.0/specification/trace/semantic_conventions/README.md)
/// that have prescribed semantic meanings.
fn set_attribute(&mut self, attribute: KeyValue) {
self.0.set_attribute(attribute)
}
/// Sets the status of the `Span`. If used, this will override the default `Span`
/// status, which is `Unset`.
fn set_status(&mut self, status: trace::Status) {
self.0.set_status(status)
}
/// Updates the `Span`'s name.
fn update_name<T>(&mut self, new_name: T)
where
T: Into<Cow<'static, str>>,
{
self.0.update_name(new_name.into())
}
/// Adds a link to this span
///
fn add_link(&mut self, span_context: trace::SpanContext, attributes: Vec<KeyValue>) {
self.0.add_link(span_context, attributes)
}
/// Finishes the span with given timestamp.
fn end_with_timestamp(&mut self, timestamp: SystemTime) {
self.0.end_with_timestamp(timestamp);
}
}
/// Wraps the [`GlobalTracerProvider`]'s [`Tracer`] so it can be used generically by
/// applications without knowing the underlying type.
///
/// [`Tracer`]: crate::trace::Tracer
/// [`GlobalTracerProvider`]: crate::global::GlobalTracerProvider
pub struct BoxedTracer(Box<dyn ObjectSafeTracer + Send + Sync>);
impl BoxedTracer {
/// Create a `BoxedTracer` from an object-safe tracer.
pub fn new(tracer: Box<dyn ObjectSafeTracer + Send + Sync>) -> Self {
BoxedTracer(tracer)
}
}
impl fmt::Debug for BoxedTracer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("BoxedTracer")
}
}
impl trace::Tracer for BoxedTracer {
/// Global tracer uses `BoxedSpan`s so that it can be a global singleton,
/// which is not possible if it takes generic type parameters.
type Span = BoxedSpan;
/// Create a span from a `SpanBuilder`
fn build_with_context(&self, builder: trace::SpanBuilder, parent_cx: &Context) -> Self::Span {
BoxedSpan(self.0.build_with_context_boxed(builder, parent_cx))
}
}
/// Allows a specific [`Tracer`] to be used generically by [`BoxedTracer`]
/// instances by mirroring the interface and boxing the return types.
///
/// [`Tracer`]: crate::trace::Tracer
pub trait ObjectSafeTracer {
/// Returns a trait object so the underlying implementation can be swapped
/// out at runtime.
fn build_with_context_boxed(
&self,
builder: trace::SpanBuilder,
parent_cx: &Context,
) -> Box<dyn ObjectSafeSpan + Send + Sync>;
}
impl<S, T> ObjectSafeTracer for T
where
S: trace::Span + Send + Sync + 'static,
T: trace::Tracer<Span = S>,
{
/// Returns a trait object so the underlying implementation can be swapped
/// out at runtime.
fn build_with_context_boxed(
&self,
builder: trace::SpanBuilder,
parent_cx: &Context,
) -> Box<dyn ObjectSafeSpan + Send + Sync> {
Box::new(self.build_with_context(builder, parent_cx))
}
}
/// Allows a specific [`TracerProvider`] to be used generically by the
/// [`GlobalTracerProvider`] by mirroring the interface and boxing the return types.
///
/// [`TracerProvider`]: crate::trace::TracerProvider
/// [`GlobalTracerProvider`]: crate::global::GlobalTracerProvider
pub trait ObjectSafeTracerProvider {
/// Creates a versioned named tracer instance that is a trait object through the underlying
/// `TracerProvider`.
fn boxed_tracer(&self, scope: InstrumentationScope) -> Box<dyn ObjectSafeTracer + Send + Sync>;
}
impl<S, T, P> ObjectSafeTracerProvider for P
where
S: trace::Span + Send + Sync + 'static,
T: trace::Tracer<Span = S> + Send + Sync + 'static,
P: trace::TracerProvider<Tracer = T>,
{
/// Return a versioned boxed tracer
fn boxed_tracer(&self, scope: InstrumentationScope) -> Box<dyn ObjectSafeTracer + Send + Sync> {
Box::new(self.tracer_with_scope(scope))
}
}
/// Represents the globally configured [`TracerProvider`] instance for this
/// application. This allows generic tracing through the returned
/// [`BoxedTracer`] instances.
///
/// [`TracerProvider`]: crate::trace::TracerProvider
#[derive(Clone)]
pub struct GlobalTracerProvider {
provider: Arc<dyn ObjectSafeTracerProvider + Send + Sync>,
}
impl fmt::Debug for GlobalTracerProvider {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("GlobalTracerProvider")
}
}
impl GlobalTracerProvider {
/// Create a new GlobalTracerProvider instance from a struct that implements `TracerProvider`.
fn new<P, T, S>(provider: P) -> Self
where
S: trace::Span + Send + Sync + 'static,
T: trace::Tracer<Span = S> + Send + Sync + 'static,
P: trace::TracerProvider<Tracer = T> + Send + Sync + 'static,
{
GlobalTracerProvider {
provider: Arc::new(provider),
}
}
}
impl trace::TracerProvider for GlobalTracerProvider {
type Tracer = BoxedTracer;
/// Create a tracer using the global provider.
fn tracer_with_scope(&self, scope: InstrumentationScope) -> Self::Tracer {
BoxedTracer(self.provider.boxed_tracer(scope))
}
}
/// The global `Tracer` provider singleton.
static GLOBAL_TRACER_PROVIDER: OnceLock<RwLock<GlobalTracerProvider>> = OnceLock::new();
#[inline]
fn global_tracer_provider() -> &'static RwLock<GlobalTracerProvider> {
GLOBAL_TRACER_PROVIDER
.get_or_init(|| RwLock::new(GlobalTracerProvider::new(NoopTracerProvider::new())))
}
/// Returns an instance of the currently configured global [`TracerProvider`] through
/// [`GlobalTracerProvider`].
///
/// [`TracerProvider`]: crate::trace::TracerProvider
/// [`GlobalTracerProvider`]: crate::global::GlobalTracerProvider
pub fn tracer_provider() -> GlobalTracerProvider {
// Try to get the global tracer provider. If the RwLock is poisoned, we'll log an error and return a NoopTracerProvider.
let global_provider = global_tracer_provider().read();
if let Ok(provider) = global_provider {
provider.clone()
} else {
otel_error!(name: "TracerProvider.GlobalGetFailed", message = "Getting global tracer provider failed. Traces created using global::tracer() or global::tracer_with_scope() will not function. Report this issue in OpenTelemetry repo.");
GlobalTracerProvider::new(NoopTracerProvider::new())
}
}
/// Creates a named instance of [`Tracer`] via the configured [`GlobalTracerProvider`].
///
/// If the name is an empty string, the provider will use a default name.
///
/// This is a more convenient way of expressing `global::tracer_provider().tracer(name)`.
///
/// [`Tracer`]: crate::trace::Tracer
pub fn tracer(name: impl Into<Cow<'static, str>>) -> BoxedTracer {
tracer_provider().tracer(name.into())
}
/// Creates a [`Tracer`] with the given instrumentation scope
/// via the configured [`GlobalTracerProvider`].
///
/// This is a simpler alternative to `global::tracer_provider().tracer_with_scope(...)`
///
/// # Example
///
/// ```
/// use std::sync::Arc;
/// use opentelemetry::global::tracer_with_scope;
/// use opentelemetry::InstrumentationScope;
/// use opentelemetry::KeyValue;
///
/// let scope = InstrumentationScope::builder("io.opentelemetry")
/// .with_version("0.17")
/// .with_schema_url("https://opentelemetry.io/schemas/1.2.0")
/// .with_attributes(vec![(KeyValue::new("key", "value"))])
/// .build();
///
/// let tracer = tracer_with_scope(scope);
/// ```
///
/// [`Tracer`]: crate::trace::Tracer
pub fn tracer_with_scope(scope: InstrumentationScope) -> BoxedTracer {
tracer_provider().tracer_with_scope(scope)
}
/// Sets the given [`TracerProvider`] instance as the current global provider.
///
/// Libraries should NOT call this function. It is intended for applications/executables.
/// [`TracerProvider`]: crate::trace::TracerProvider
pub fn set_tracer_provider<P, T, S>(new_provider: P)
where
S: trace::Span + Send + Sync + 'static,
T: trace::Tracer<Span = S> + Send + Sync + 'static,
P: trace::TracerProvider<Tracer = T> + Send + Sync + 'static,
{
let mut global_provider = global_tracer_provider().write();
if let Ok(ref mut provider) = global_provider {
**provider = GlobalTracerProvider::new(new_provider);
otel_info!(name: "TracerProvider.GlobalSet", message = "Global tracer provider is set. Traces can now be created using global::tracer() or global::tracer_with_scope().");
} else {
otel_error!(name: "TracerProvider.GlobalSetFailed", message = "Setting global tracer provider failed. Traces created using global::tracer() or global::tracer_with_scope() will not function. Report this issue in OpenTelemetry repo.");
}
}
+305
View File
@@ -0,0 +1,305 @@
//! Implements the [`API`] component of [OpenTelemetry].
//!
//! *[Supported Rust Versions](#supported-rust-versions)*
//!
//! [`API`]: https://opentelemetry.io/docs/specs/otel/overview/#api
//! [OpenTelemetry]: https://opentelemetry.io/docs/what-is-opentelemetry/
//!
//! # Getting Started with Traces
//!
//! The [`trace`] module includes types for tracking the progression of a single
//! request while it is handled by services that make up an application. A trace
//! is a tree of [`Span`]s which are objects that represent the work being done
//! by individual services or components involved in a request as it flows
//! through a system.
//!
//! ```
//! # #[cfg(feature = "trace")]
//! # {
//! use opentelemetry::{global, trace::{Span, Tracer}, KeyValue};
//!
//! // get a tracer from a provider
//! let tracer = global::tracer("my_service");
//!
//! // start a new span
//! let mut span = tracer.start("my_span");
//!
//! // set some attributes
//! span.set_attribute(KeyValue::new("http.client_ip", "83.164.160.102"));
//!
//! // perform some more work...
//!
//! // end or drop the span to export
//! span.end();
//! # }
//! ```
//!
//! For a runnable getting-started example, see
//! [`examples/tracing-grpc`](https://github.com/open-telemetry/opentelemetry-rust/tree/main/examples/tracing-grpc),
//! which demonstrates creating spans and propagating trace context across a
//! gRPC client and server. See the
//! [examples](https://github.com/open-telemetry/opentelemetry-rust/tree/main/examples)
//! directory for additional integration patterns.
//!
//! See the [`trace`] module docs for more information on creating and managing
//! spans.
//!
//! [`Span`]: crate::trace::Span
//!
//! # Getting Started with Metrics
//!
//! The [`metrics`] module provides types for recording measurements about a
//! service at runtime. Below are the key steps to report measurements using
//! OpenTelemetry Metrics:
//!
//! 1. **Obtain a Meter:** Get a `Meter` from a `MeterProvider`.
//! 2. **Create Instruments:** Use the `Meter` to create one or more instruments
//! (e.g., counters, histograms).
//! 3. **Record Measurements:** Use the instruments to record measurement values
//! along with optional attributes.
//!
//! For a runnable getting-started example, see
//! [`examples/metrics-basic`](https://github.com/open-telemetry/opentelemetry-rust/tree/main/examples/metrics-basic),
//! which exercises every instrument type.
//!
//! ## How Metrics work in OpenTelemetry
//! In OpenTelemetry, raw measurements recorded using instruments are
//! **aggregated in memory** to form metrics. These aggregated metrics are
//! periodically exported by the [`opentelemetry_sdk`] at fixed intervals (e.g.,
//! every 60 seconds) via exporters such as [`opentelemetry-stdout`] or
//! [`opentelemetry-otlp`]. This reduces reporting overhead while ensuring
//! up-to-date data. The aggregation strategy and export interval can be
//! customized in the [`opentelemetry_sdk`] based on your use case.
//!
//! ## Choosing the Right Instrument
//! Selecting the correct instrument is critical for accurately representing
//! your metrics data:
//!
//! - Use **Counters** for values that only increase, such as the number of
//! requests served or errors encountered.
//! - Use **UpDownCounters** for values that can increase or decrease, such as
//! the number of active connections, number of items in a queue etc.
//! - **Gauges:** Use for values that can go up or down and represent the
//! current state, such as CPU usage, temperature etc.
//! - Use **Histograms** for measuring the distribution of a value, such as
//! response times or payload sizes.
//!
//! ### Observable Instruments
//!
//! Counters, UpDownCounters, and Gauges have Observable variants that allow
//! values to be reported through a callback function. Observable instruments
//! are ideal when the metric value is managed elsewhere and needs to be
//! observed by OpenTelemetry instrumentation. The callbacks are automatically
//! invoked by the OpenTelemetry SDK before every export (e.g., every 60
//! seconds).
//!
//! For example:
//! - An **ObservableCounter** can monitor the number of page faults in a
//! process as reported by the operating system.
//! - An **ObservableUpDownCounter** can monitor the size of an in-memory queue
//! by reporting the size using queue's len() method within the callback
//! function.
//! - An **ObservableGauge** can monitor the CPU temperature by using
//! temperature sensor APIs within the callback function.
//!
//! For detailed guidance, refer to [OpenTelemetry Metrics API - Instrumentation
//! Guidance](https://opentelemetry.io/docs/specs/otel/metrics/supplementary-guidelines/#instrument-selection).
//!
//! ## Best Practices
//! - **Re-use Instruments:** Instruments are designed for
//! reuse. Avoid creating new instruments repeatedly.
//! - **Clone for Sharing:** If the same instrument needs to be used across
//! multiple parts of your code, you can safely clone it to share.
//!
//! ## Example Usage
//! ```
//! use opentelemetry::{global, KeyValue};
//!
//! // Get a meter from a provider.
//! let meter = global::meter("my_service");
//!
//! // Create an instrument (in this case, a Counter).
//! let counter = meter.u64_counter("request.count").build();
//!
//! // Record a measurement by passing the value and a set of attributes.
//! counter.add(1, &[KeyValue::new("http.client_ip", "83.164.160.102")]);
//!
//! // Create an ObservableCounter and register a callback that reports the measurement.
//! let _observable_counter = meter
//! .u64_observable_counter("bytes_received")
//! .with_callback(|observer| {
//! observer.observe(
//! 100,
//! &[
//! KeyValue::new("protocol", "udp"),
//! ],
//! )
//! })
//! .build();
//! ```
//!
//! See the [`metrics`] module docs for more information on creating and
//! managing instruments.
//!
//!
//! # Getting Started with Logs
//!
//! The [`logs`] module contains the Logs Bridge API. It is not intended to be
//! called by application developers directly. It is provided for logging
//! library authors to build log appenders, that bridges existing logging
//! systems with OpenTelemetry. Bridges for
//! [`log`](https://crates.io/crates/log) and
//! [`tracing`](https://crates.io/crates/tracing) libraries are provided via
//! the
//! [`opentelemetry-appender-log`](https://crates.io/crates/opentelemetry-appender-log)
//! and
//! [`opentelemetry-appender-tracing`](https://crates.io/crates/opentelemetry-appender-tracing)
//! crates.
//!
//! For a runnable getting-started example, see
//! [`examples/logs-basic`](https://github.com/open-telemetry/opentelemetry-rust/tree/main/examples/logs-basic),
//! which uses the `tracing` appender to bridge logs to OpenTelemetry.
//!
//! # Crate Feature Flags
//!
//! The following core crate feature flags are available:
//!
//! * `trace`: Includes the trace API.
//! * `metrics`: Includes the metrics API.
//! * `logs`: Includes the logs bridge API.
//! * `internal-logs`: Includes internal logging for the OpenTelemetry library via `tracing`.
//!
//! The default feature flags are ["trace", "metrics", "logs", "internal-logs"].
//!
//!
//! The following feature flags enable APIs defined in OpenTelemetry specification that is in experimental phase:
//! * `otel_unstable`: Includes unstable APIs. There are no features behind this flag at the moment.
//!
//! # Related Crates
//!
//! In addition to `opentelemetry`, the [`open-telemetry/opentelemetry-rust`]
//! repository contains several additional crates designed to be used with the
//! `opentelemetry` ecosystem. This includes exporters, samplers, as well as
//! utility and adapter crates to assist in propagating context and
//! instrumenting applications.
//!
//! In particular, the following crates are likely to be of interest:
//!
//! - [`opentelemetry_sdk`] provides the OpenTelemetry SDK used to configure providers.
//! - [`opentelemetry-http`] provides an interface for injecting and extracting
//! trace information from [`http`] headers.
//! - [`opentelemetry-otlp`] exporter for sending telemetry in the
//! OTLP format.
//! - [`opentelemetry-stdout`] provides ability to output telemetry to stdout,
//! primarily used for learning/debugging purposes.
//! - [`opentelemetry-prometheus`] provides a pipeline and exporter for sending
//! metrics information to [`Prometheus`].
//!
//! In addition, there are several other useful crates in the [OTel Rust
//! Contrib
//! repo](https://github.com/open-telemetry/opentelemetry-rust-contrib). A lot
//! of crates maintained outside OpenTelemetry owned repos can be found in the
//! [OpenTelemetry
//! Registry](https://opentelemetry.io/ecosystem/registry/?language=rust).
//!
//! [`http`]: https://crates.io/crates/http
//! [`open-telemetry/opentelemetry-rust`]: https://github.com/open-telemetry/opentelemetry-rust
//! [`opentelemetry_sdk`]: https://crates.io/crates/opentelemetry_sdk
//! [`opentelemetry-stdout`]: https://crates.io/crates/opentelemetry_stdout
//! [`opentelemetry-http`]: https://crates.io/crates/opentelemetry-http
//! [`opentelemetry-otlp`]: https://crates.io/crates/opentelemetry-otlp
//! [`opentelemetry-prometheus`]: https://crates.io/crates/opentelemetry-prometheus
//! [`Prometheus`]: https://prometheus.io
//!
//! # Supported Rust Versions
//!
//! OpenTelemetry is built against the latest stable release. The minimum
//! supported version is 1.70. The current OpenTelemetry version is not
//! guaranteed to build on Rust versions earlier than the minimum supported
//! version.
//!
//! The current stable Rust compiler and the three most recent minor versions
//! before it will always be supported. For example, if the current stable
//! compiler version is 1.49, the minimum supported version will not be
//! increased past 1.46, three minor versions prior. Increasing the minimum
//! supported compiler version is not considered a semver breaking change as
//! long as doing so complies with this policy.
#![warn(
future_incompatible,
missing_debug_implementations,
missing_docs,
nonstandard_style,
rust_2018_idioms,
unreachable_pub,
unused
)]
#![allow(clippy::needless_doctest_main)]
#![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo.svg"
)]
#![cfg_attr(test, deny(warnings))]
pub mod global;
pub mod baggage;
pub mod context;
pub use context::{Context, ContextGuard};
mod trace_context;
pub use trace_context::{SpanId, TraceFlags, TraceId};
mod common;
#[cfg(any(feature = "testing", test))]
#[doc(hidden)]
pub mod testing;
pub use common::{
Array, InstrumentationScope, InstrumentationScopeBuilder, Key, KeyValue, StringValue, Value,
};
#[cfg(feature = "metrics")]
#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
pub mod metrics;
#[cfg(feature = "trace")]
#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
pub mod propagation;
#[cfg(feature = "trace")]
#[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
pub mod trace;
#[cfg(feature = "logs")]
#[cfg_attr(docsrs, doc(cfg(feature = "logs")))]
pub mod logs;
#[doc(hidden)]
#[cfg(any(feature = "metrics", feature = "trace", feature = "logs"))]
pub mod time {
use std::time::SystemTime;
#[doc(hidden)]
#[cfg(any(
not(target_arch = "wasm32"),
all(target_arch = "wasm32", target_os = "wasi")
))]
pub fn now() -> SystemTime {
SystemTime::now()
}
#[doc(hidden)]
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
pub fn now() -> SystemTime {
SystemTime::UNIX_EPOCH + std::time::Duration::from_millis(js_sys::Date::now() as u64)
}
}
#[doc(hidden)]
pub mod _private {
#[cfg(feature = "internal-logs")]
pub use tracing::{debug, error, info, warn}; // re-export
}
@@ -0,0 +1,62 @@
use std::borrow::Cow;
use crate::{logs::LogRecord, InstrumentationScope};
use super::Severity;
/// The interface for emitting [`LogRecord`]s.
pub trait Logger {
/// Specifies the `LogRecord` type associated with this logger.
type LogRecord: LogRecord;
/// Creates a new log record builder.
fn create_log_record(&self) -> Self::LogRecord;
/// Emit a [`LogRecord`]. If there is active current thread's [`Context`],
/// the logger will set the record's `TraceContext` to the active trace context,
///
/// [`Context`]: crate::Context
fn emit(&self, record: Self::LogRecord);
/// Check if the given log is enabled.
fn event_enabled(&self, level: Severity, target: &str, name: Option<&str>) -> bool;
}
/// Interfaces that can create [`Logger`] instances.
pub trait LoggerProvider {
/// The [`Logger`] type that this provider will return.
type Logger: Logger;
/// Returns a new logger with the given instrumentation scope.
///
/// # Examples
///
/// ```
/// use opentelemetry::InstrumentationScope;
/// use opentelemetry::logs::LoggerProvider;
/// use opentelemetry_sdk::logs::SdkLoggerProvider;
///
/// let provider = SdkLoggerProvider::builder().build();
///
/// // logger used in applications/binaries
/// let logger = provider.logger("my_app");
///
/// // logger used in libraries/crates that optionally includes version and schema url
/// let scope = InstrumentationScope::builder(env!("CARGO_PKG_NAME"))
/// .with_version(env!("CARGO_PKG_VERSION"))
/// .with_schema_url("https://opentelemetry.io/schemas/1.0.0")
/// .build();
///
/// let logger = provider.logger_with_scope(scope);
/// ```
fn logger_with_scope(&self, scope: InstrumentationScope) -> Self::Logger;
/// Returns a new logger with the given name.
///
/// The `name` should be the application name or the name of the library
/// providing instrumentation.
fn logger(&self, name: impl Into<Cow<'static, str>>) -> Self::Logger {
let scope = InstrumentationScope::builder(name).build();
self.logger_with_scope(scope)
}
}
+11
View File
@@ -0,0 +1,11 @@
//! # OpenTelemetry Logs Bridge API
/// This API is not intended to be called by application developers directly.
/// It is provided for logging library authors to build log appenders, that
/// bridges existing logging systems with OpenTelemetry.
mod logger;
mod noop;
mod record;
pub use logger::{Logger, LoggerProvider};
pub use noop::NoopLoggerProvider;
pub use record::{AnyValue, LogRecord, Severity};
@@ -0,0 +1,85 @@
use std::{borrow::Cow, time::SystemTime};
use crate::{
logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity},
InstrumentationScope, Key,
};
/// A no-op implementation of a [`LoggerProvider`].
#[derive(Clone, Debug, Default)]
pub struct NoopLoggerProvider(());
impl NoopLoggerProvider {
/// Create a new no-op logger provider.
pub fn new() -> Self {
NoopLoggerProvider(())
}
}
impl LoggerProvider for NoopLoggerProvider {
type Logger = NoopLogger;
fn logger_with_scope(&self, _scope: InstrumentationScope) -> Self::Logger {
NoopLogger(())
}
}
#[derive(Debug, Clone, Default)]
/// A no-operation log record that implements the LogRecord trait.
pub struct NoopLogRecord;
impl LogRecord for NoopLogRecord {
// Implement the LogRecord trait methods with empty bodies.
#[inline]
fn set_event_name(&mut self, _name: &'static str) {}
#[inline]
fn set_timestamp(&mut self, _timestamp: SystemTime) {}
#[inline]
fn set_observed_timestamp(&mut self, _timestamp: SystemTime) {}
#[inline]
fn set_severity_text(&mut self, _text: &'static str) {}
#[inline]
fn set_severity_number(&mut self, _number: Severity) {}
#[inline]
fn set_body(&mut self, _body: AnyValue) {}
#[inline]
fn add_attributes<I, K, V>(&mut self, _attributes: I)
where
I: IntoIterator<Item = (K, V)>,
K: Into<Key>,
V: Into<AnyValue>,
{
}
#[inline]
fn add_attribute<K, V>(&mut self, _key: K, _value: V)
where
K: Into<Key>,
V: Into<AnyValue>,
{
}
#[inline]
// Sets the `target` of a record
fn set_target<T>(&mut self, _target: T)
where
T: Into<Cow<'static, str>>,
{
}
}
/// A no-op implementation of a [`Logger`]
#[derive(Clone, Debug)]
pub struct NoopLogger(());
impl Logger for NoopLogger {
type LogRecord = NoopLogRecord;
fn create_log_record(&self) -> Self::LogRecord {
NoopLogRecord {}
}
fn emit(&self, _record: Self::LogRecord) {}
fn event_enabled(&self, _level: super::Severity, _target: &str, _name: Option<&str>) -> bool {
false
}
}
@@ -0,0 +1,230 @@
use crate::{Key, StringValue};
use crate::{SpanId, TraceFlags, TraceId};
use std::{borrow::Cow, collections::HashMap, time::SystemTime};
/// SDK implemented trait for managing log records
pub trait LogRecord {
/// Sets the `event_name` of a record
fn set_event_name(&mut self, name: &'static str);
/// Sets the `target` of a record.
/// Currently, both `opentelemetry-appender-tracing` and `opentelemetry-appender-log` create a single logger
/// with a scope that doesn't accurately reflect the component emitting the logs.
/// Exporters MAY use this field to override the `instrumentation_scope.name`.
fn set_target<T>(&mut self, _target: T)
where
T: Into<Cow<'static, str>>;
/// Sets the time when the event occurred measured by the origin clock, i.e. the time at the source.
fn set_timestamp(&mut self, timestamp: SystemTime);
/// Sets the observed event timestamp.
fn set_observed_timestamp(&mut self, timestamp: SystemTime);
/// Sets severity as text.
fn set_severity_text(&mut self, text: &'static str);
/// Sets severity as a numeric value.
fn set_severity_number(&mut self, number: Severity);
/// Sets the message body of the log.
fn set_body(&mut self, body: AnyValue);
/// Adds multiple attributes.
fn add_attributes<I, K, V>(&mut self, attributes: I)
where
I: IntoIterator<Item = (K, V)>,
K: Into<Key>,
V: Into<AnyValue>;
/// Adds a single attribute.
fn add_attribute<K, V>(&mut self, key: K, value: V)
where
K: Into<Key>,
V: Into<AnyValue>;
/// Sets the trace context of the log.
fn set_trace_context(
&mut self,
trace_id: TraceId,
span_id: SpanId,
trace_flags: Option<TraceFlags>,
) {
let _ = trace_id;
let _ = span_id;
let _ = trace_flags;
}
}
/// Value types for representing arbitrary values in a log record.
/// Note: The `tracing` and `log` crates only support basic types that can be
/// converted to these core variants: `i64`, `f64`, `StringValue`, and `bool`.
/// Any complex and custom types are supported through their Debug implementation,
/// and converted to String. More complex types (`Bytes`, `ListAny`, and `Map`) are
/// included here to meet specification requirements and are available to support
/// custom appenders that may be implemented for other logging crates.
/// These types allow for handling dynamic data structures, so keep in mind the
/// potential performance overhead of using boxed vectors and maps in appenders.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum AnyValue {
/// An integer value
Int(i64),
/// A double value
Double(f64),
/// A string value
String(StringValue),
/// A boolean value
Boolean(bool),
/// A byte array
Bytes(Box<Vec<u8>>),
/// An array of `Any` values
ListAny(Box<Vec<AnyValue>>),
/// A map of string keys to `Any` values, arbitrarily nested.
Map(Box<HashMap<Key, AnyValue>>),
}
macro_rules! impl_trivial_from {
($t:ty, $variant:path) => {
impl From<$t> for AnyValue {
fn from(val: $t) -> AnyValue {
$variant(val.into())
}
}
};
}
impl_trivial_from!(i8, AnyValue::Int);
impl_trivial_from!(i16, AnyValue::Int);
impl_trivial_from!(i32, AnyValue::Int);
impl_trivial_from!(i64, AnyValue::Int);
impl_trivial_from!(u8, AnyValue::Int);
impl_trivial_from!(u16, AnyValue::Int);
impl_trivial_from!(u32, AnyValue::Int);
impl_trivial_from!(f64, AnyValue::Double);
impl_trivial_from!(f32, AnyValue::Double);
impl_trivial_from!(String, AnyValue::String);
impl_trivial_from!(Cow<'static, str>, AnyValue::String);
impl_trivial_from!(&'static str, AnyValue::String);
impl_trivial_from!(StringValue, AnyValue::String);
impl_trivial_from!(bool, AnyValue::Boolean);
impl From<&[u8]> for AnyValue {
fn from(val: &[u8]) -> AnyValue {
AnyValue::Bytes(Box::new(val.to_vec()))
}
}
impl<T: Into<AnyValue>> FromIterator<T> for AnyValue {
/// Creates an [`AnyValue::ListAny`] value from a sequence of `Into<AnyValue>` values.
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
AnyValue::ListAny(Box::new(iter.into_iter().map(Into::into).collect()))
}
}
impl<K: Into<Key>, V: Into<AnyValue>> FromIterator<(K, V)> for AnyValue {
/// Creates an [`AnyValue::Map`] value from a sequence of key-value pairs
/// that can be converted into a `Key` and `AnyValue` respectively.
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
AnyValue::Map(Box::new(HashMap::from_iter(
iter.into_iter().map(|(k, v)| (k.into(), v.into())),
)))
}
}
/// A normalized severity value.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub enum Severity {
/// TRACE
Trace = 1,
/// TRACE2
Trace2 = 2,
/// TRACE3
Trace3 = 3,
/// TRACE4
Trace4 = 4,
/// DEBUG
Debug = 5,
/// DEBUG2
Debug2 = 6,
/// DEBUG3
Debug3 = 7,
/// DEBUG4
Debug4 = 8,
/// INFO
Info = 9,
/// INFO2
Info2 = 10,
/// INFO3
Info3 = 11,
/// INFO4
Info4 = 12,
/// WARN
Warn = 13,
/// WARN2
Warn2 = 14,
/// WARN3
Warn3 = 15,
/// WARN4
Warn4 = 16,
/// ERROR
Error = 17,
/// ERROR2
Error2 = 18,
/// ERROR3
Error3 = 19,
/// ERROR4
Error4 = 20,
/// FATAL
Fatal = 21,
/// FATAL2
Fatal2 = 22,
/// FATAL3
Fatal3 = 23,
/// FATAL4
Fatal4 = 24,
}
impl Severity {
/// Return the string representing the short name for the `Severity`
/// value as specified by the OpenTelemetry logs data model.
pub const fn name(&self) -> &'static str {
match &self {
Severity::Trace => "TRACE",
Severity::Trace2 => "TRACE2",
Severity::Trace3 => "TRACE3",
Severity::Trace4 => "TRACE4",
Severity::Debug => "DEBUG",
Severity::Debug2 => "DEBUG2",
Severity::Debug3 => "DEBUG3",
Severity::Debug4 => "DEBUG4",
Severity::Info => "INFO",
Severity::Info2 => "INFO2",
Severity::Info3 => "INFO3",
Severity::Info4 => "INFO4",
Severity::Warn => "WARN",
Severity::Warn2 => "WARN2",
Severity::Warn3 => "WARN3",
Severity::Warn4 => "WARN4",
Severity::Error => "ERROR",
Severity::Error2 => "ERROR2",
Severity::Error3 => "ERROR3",
Severity::Error4 => "ERROR4",
Severity::Fatal => "FATAL",
Severity::Fatal2 => "FATAL2",
Severity::Fatal3 => "FATAL3",
Severity::Fatal4 => "FATAL4",
}
}
}
Loaded 100 of 213 files, more files were not shown because too many files have changed in this diff. Show more