From 46e5ebfa83695f5e112487457dbcf8c5e74e8e50 Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 17 May 2024 21:06:49 -0700 Subject: [PATCH 1/2] Remove opentelemetry-jaeger --- .github/codecov.yaml | 3 - .github/workflows/ci.yml | 1 - opentelemetry-jaeger/CHANGELOG.md | 244 ---- opentelemetry-jaeger/CODEOWNERS | 5 - opentelemetry-jaeger/Cargo.toml | 110 -- opentelemetry-jaeger/LICENSE | 201 --- opentelemetry-jaeger/README.md | 183 +-- .../examples/actix-udp/Cargo.toml | 14 - .../examples/actix-udp/README.md | 23 - .../examples/actix-udp/src/main.rs | 55 - .../examples/remote-sampler/Cargo.toml | 12 - .../examples/remote-sampler/README.md | 48 - .../remote-sampler/docker-compose.yaml | 26 - .../remote-sampler/otel-collector.yaml | 17 - .../examples/remote-sampler/src/main.rs | 40 - .../examples/remote-sampler/strategies.json | 37 - opentelemetry-jaeger/src/exporter/agent.rs | 207 --- .../src/exporter/collector.rs | 232 ---- .../src/exporter/config/agent.rs | 465 ------- .../exporter/config/collector/http_client.rs | 192 --- .../src/exporter/config/collector/mod.rs | 706 ----------- .../src/exporter/config/mod.rs | 131 -- opentelemetry-jaeger/src/exporter/mod.rs | 473 ------- opentelemetry-jaeger/src/exporter/runtime.rs | 89 -- .../src/exporter/thrift/agent.rs | 305 ----- .../src/exporter/thrift/jaeger.rs | 1116 ----------------- .../src/exporter/thrift/mod.rs | 69 - .../src/exporter/thrift/zipkincore.rs | 1093 ---------------- .../src/exporter/transport/buffer.rs | 54 - .../src/exporter/transport/mod.rs | 6 - .../src/exporter/transport/noop.rs | 10 - opentelemetry-jaeger/src/exporter/uploader.rs | 79 -- opentelemetry-jaeger/src/lib.rs | 330 ----- .../src/testing/jaeger_api_v2.rs | 448 ------- opentelemetry-jaeger/src/testing/mod.rs | 85 -- opentelemetry-jaeger/tests/Dockerfile | 4 - .../tests/docker-compose.yaml | 23 - .../tests/integration_test.rs | 243 ---- opentelemetry-jaeger/trace.png | Bin 86393 -> 0 bytes scripts/lint.sh | 11 - scripts/publish.sh | 1 - scripts/test.sh | 1 - 42 files changed, 8 insertions(+), 7384 deletions(-) delete mode 100644 opentelemetry-jaeger/CHANGELOG.md delete mode 100644 opentelemetry-jaeger/CODEOWNERS delete mode 100644 opentelemetry-jaeger/Cargo.toml delete mode 100644 opentelemetry-jaeger/LICENSE delete mode 100644 opentelemetry-jaeger/examples/actix-udp/Cargo.toml delete mode 100644 opentelemetry-jaeger/examples/actix-udp/README.md delete mode 100644 opentelemetry-jaeger/examples/actix-udp/src/main.rs delete mode 100644 opentelemetry-jaeger/examples/remote-sampler/Cargo.toml delete mode 100644 opentelemetry-jaeger/examples/remote-sampler/README.md delete mode 100644 opentelemetry-jaeger/examples/remote-sampler/docker-compose.yaml delete mode 100644 opentelemetry-jaeger/examples/remote-sampler/otel-collector.yaml delete mode 100644 opentelemetry-jaeger/examples/remote-sampler/src/main.rs delete mode 100644 opentelemetry-jaeger/examples/remote-sampler/strategies.json delete mode 100644 opentelemetry-jaeger/src/exporter/agent.rs delete mode 100644 opentelemetry-jaeger/src/exporter/collector.rs delete mode 100644 opentelemetry-jaeger/src/exporter/config/agent.rs delete mode 100644 opentelemetry-jaeger/src/exporter/config/collector/http_client.rs delete mode 100644 opentelemetry-jaeger/src/exporter/config/collector/mod.rs delete mode 100644 opentelemetry-jaeger/src/exporter/config/mod.rs delete mode 100644 opentelemetry-jaeger/src/exporter/mod.rs delete mode 100644 opentelemetry-jaeger/src/exporter/runtime.rs delete mode 100644 opentelemetry-jaeger/src/exporter/thrift/agent.rs delete mode 100644 opentelemetry-jaeger/src/exporter/thrift/jaeger.rs delete mode 100644 opentelemetry-jaeger/src/exporter/thrift/mod.rs delete mode 100644 opentelemetry-jaeger/src/exporter/thrift/zipkincore.rs delete mode 100644 opentelemetry-jaeger/src/exporter/transport/buffer.rs delete mode 100644 opentelemetry-jaeger/src/exporter/transport/mod.rs delete mode 100644 opentelemetry-jaeger/src/exporter/transport/noop.rs delete mode 100644 opentelemetry-jaeger/src/exporter/uploader.rs delete mode 100644 opentelemetry-jaeger/src/lib.rs delete mode 100644 opentelemetry-jaeger/src/testing/jaeger_api_v2.rs delete mode 100644 opentelemetry-jaeger/src/testing/mod.rs delete mode 100644 opentelemetry-jaeger/tests/Dockerfile delete mode 100644 opentelemetry-jaeger/tests/docker-compose.yaml delete mode 100644 opentelemetry-jaeger/tests/integration_test.rs delete mode 100644 opentelemetry-jaeger/trace.png diff --git a/.github/codecov.yaml b/.github/codecov.yaml index 317422a6f2..2bfdd7105a 100644 --- a/.github/codecov.yaml +++ b/.github/codecov.yaml @@ -13,13 +13,10 @@ coverage: ignore: - "opentelemetry/src/testing" # test harnesses - - "opentelemetry-jaeger/src/testing" # test harness - - "opentelemetry-jaeger/src/exporter/thrift" # auto generated files - "opentelemetry-otlp/src/proto" # auto generated files - "opentelemetry-proto/src/proto" # auto generated files # examples below - "examples" - - "opentelemetry-jaeger/examples" - "opentelemetry-zipkin/examples" - "opentelemetry-otlp/examples" - "opentelemetry-http/examples" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c009b40c8d..295c69bc6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,7 +113,6 @@ jobs: - name: Run tests run: cargo --version && cargo test --manifest-path=opentelemetry/Cargo.toml --features trace,metrics,testing && - cargo test --manifest-path=opentelemetry-jaeger/Cargo.toml --features rt-tokio && cargo test --manifest-path=opentelemetry-zipkin/Cargo.toml cargo-deny: runs-on: ubuntu-latest # This uses the step `EmbarkStudios/cargo-deny-action@v1` which is only supported on Linux diff --git a/opentelemetry-jaeger/CHANGELOG.md b/opentelemetry-jaeger/CHANGELOG.md deleted file mode 100644 index 179a8e874d..0000000000 --- a/opentelemetry-jaeger/CHANGELOG.md +++ /dev/null @@ -1,244 +0,0 @@ -# Changelog - -## Deprecation Notice - -Starting with [Jaeger v1.38](https://github.com/jaegertracing/jaeger/releases/tag/v1.38.0) Jaeger supports the OpenTelemetry Protocol (OTLP). -[OpenTelemetry has recommended](https://opentelemetry.io/blog/2022/jaeger-native-otlp/) migrating to OTLP. - -Please check the [README](https://crates.io/crates/opentelemetry-jaeger) for more information. - -## v0.22.0 - -- **This is the last release of this crate.** - Jaeger propagator is part of [opentelemetry-jaeger-propagator](../opentelemetry-jaeger-propagator/). - For exporting to Jaeger, use [opentelemetry-otlp](../opentelemetry-otlp/). -- 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-semantic-conventions` dependency version to 0.15 - -## v0.21.0 - -### Changed - -- Update to tonic 0.11 and prost 0.12 [#1536](https://github.com/open-telemetry/opentelemetry-rust/pull/1536) - -### Removed - -- **Breaking** Jaeger propagator functionality has been moved to a new crate [opentelemetry-jaeger-propagator](../opentelemetry-jaeger-propagator/) - to prepare for opentelemetry-jaeger exporter deprecation. [#1487](https://github.com/open-telemetry/opentelemetry-rust/pull/1487) -- **Breaking** Remove support for surf HTTP client [#1537](https://github.com/open-telemetry/opentelemetry-rust/pull/1537) - -## v0.20.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) -- Prioritize environment variables over compiling time variables [#1323](https://github.com/open-telemetry/opentelemetry-rust/pull/1323) - -## v0.19.0 - -### Changed - -- Add warning to jaeger docs about future deprecation #996 -- Update to opentelemetry-api v0.20.0 - -### Fixed -- allow span id to be less than 16 characters in propagator [#1084](https://github.com/open-telemetry/opentelemetry-rust/pull/1084) -- `reqwest_rustls_collector_client` now includes `with_reqwest` [#1159](https://github.com/open-telemetry/opentelemetry-rust/pull/1159) - -## v0.18.0 - -### Added - -- Added `CollectorPipeline::build_collector_exporter` [#894](https://github.com/open-telemetry/opentelemetry-rust/pull/894). -- Support IPv6 in sync uploader [#938](https://github.com/open-telemetry/opentelemetry-rust/pull/938). - -### Changed -- Update `opentelemetry` to 0.19 -- Update `opentelemetry-http` to 0.8 -- Update `opentelemetry-semantic-conventions` to 0.11. -- Bump MSRV to 1.57 [#953](https://github.com/open-telemetry/opentelemetry-rust/pull/953). -- Include packet length for `SizeLimit` error messages [#938](https://github.com/open-telemetry/opentelemetry-rust/pull/938). -- Update dependencies and bump MSRV to 1.60 [#969](https://github.com/open-telemetry/opentelemetry-rust/pull/969). -- Make `JaegerRemoteSampler` public, revise doc [#975](https://github.com/open-telemetry/opentelemetry-rust/pull/975). -- Add warnings to docs about future deprecation [#996](https://github.com/open-telemetry/opentelemetry-rust/pull/996). -- Fix array encoding length of datadog version v5 exporter(#1002)(https://github.com/open-telemetry/opentelemetry-rust/pull/1002). - -## v0.17.0 - -### Added - -- Support rustls in jaeger reqwest collector #834 -- Customisation support in Jaeger Propagator. #852 -- Add IPv6 support for Jaeger agent addresses #856 -- Add `with_batch_processor_config` for jaeger pipline #869 - -### Changed - -- Consolidate the config errors #762 -- Better configuration pipeline #748 -- Add Timeout Environment Var #729 -- add propagator initialisation with custom headers and baggage prefix #852 -- Update to opentelemetry v0.18.0 -- Update to opentelemetry-http v0.7.0 -- Update to opentelemetry-semantic-conventions v0.10.0 - -### Fixed - -- Fix clearing span context in Propagator #810 -- Fix reqwest client runs inside a non-tokio runtime #829 - -## v0.16.0 - -### Changed - -- try split batch if payload size larger than max_package_size #619 -- update to thrift 0.15 #697 -- Update to opentelemetry v0.17.0 -- Update to opentelemetry-http v0.6.0 -- Update to opentelemetry-semantic-conventions v0.9.0 - -### Fixed - -- Mapping between Jaeger processes and Otel process. #663 - -## v0.15.0 - -### Changed - -- Set client-to-agent UDP comm based on runtime #599. Users should change their `tokio` feature to `rt-tokio`. Similar with async-std -- Update to opentelemetry v0.16.0 - -## v0.14.0 - -### Changed - -- Update to opentelemetry v0.15.0 - -## v0.13.0 - -### Changed - -- Use follows from instead of child of for links #524 -- Remove default surf features #546 -- Update to opentelemetry v0.14.0 - -## v0.12.1 - -### Fixed - -- jaeger span error reporting and spec compliance #489 - -## v0.12.0 - -### Added -- Add max packet size constraint #457 - -### Fixed -- Allow user to use hostname like `localhost` in the `OTEL_EXPORTER_JAEGER_AGENT_HOST` environment variable. #448 - -### Removed -- Removed `from_env` and use environment variables to initialize the configurations by default #459 - -### Changed -- Update to opentelemetry v0.13.0 -- Rename trace config with_default_sampler to with_sampler #482 - -## v0.11.0 - -### Changed - -- Update to opentelemetry v0.12.0 -- Update tokio to v1 #421 -- Make `with_collector_endpoint` function less error prune #428 -- Use opentelemetry-http for http integration #415 - -## v0.10.0 - -### Added - -- Add wasm support #365 -- Allow user to use their own http clients or use 4 of the default implementation - (`surf_collector_client`, `reqwest_collector_client`, `reqwest_blocking_collector_client`, `isahc_collector_client`) -- Set `otel.status_code` and `otel.status_description` values #383 - -### Changed - -- Update to opentelemetry v0.11.0 -- Use http client trait #378 - -## v0.9.0 - -### Added - -- Option to disable exporting instrumentation library information #288 - -### Changed - -- Update to opentelemetry v0.10.0 -- Update mapping otel events to Jaeger logs attributes #285 -- Add MSRV 1.42.0 #296 - -## v0.8.0 - -### Added - -- Map `Resource`s to jaeger process tags #215 -- Export instrument library information #243 - -### Changed - -- Switch to pipeline configuration #189 -- Update to opentelemetry v0.9.0 - -## v0.7.0 - -### Changed - -- Update to opentelemetry v0.8.0 - -## v0.6.0 - -### Changed -- Update to opentelemetry v0.7.0 - -### Fixed -- Do not add `span.kind` tag if it has been set as an attribute #140 - -## v0.5.0 - -### Changed -- Update to opentelemetry v0.6.0 - -### Fixed -- Switch internally to `ureq` from `reqwest` to fix #106 -- Fix exported link span id format #118 - -## v0.4.0 - -### Added -- Support for resource attributes - -### Changed -- Update to opentelemetry v0.5.0 - -### Removed -- `as_any` method on exporter - -## v0.3.0 - -### Changed -- Update to opentelemetry v0.4.0 - -## v0.2.0 - -### Changed -- Update to opentelemetry v0.3.0 - -## v0.1.0 - -### Added -- Jaeger agent Thrift UDP client -- Jaeger collector Thrift HTTP client diff --git a/opentelemetry-jaeger/CODEOWNERS b/opentelemetry-jaeger/CODEOWNERS deleted file mode 100644 index d6962a905a..0000000000 --- a/opentelemetry-jaeger/CODEOWNERS +++ /dev/null @@ -1,5 +0,0 @@ -# Code owners file. -# This file controls who is tagged for review for any given pull request. - -# For anything not explicitly taken by someone else: -* @open-telemetry/rust-approvers diff --git a/opentelemetry-jaeger/Cargo.toml b/opentelemetry-jaeger/Cargo.toml deleted file mode 100644 index eabfa4ee45..0000000000 --- a/opentelemetry-jaeger/Cargo.toml +++ /dev/null @@ -1,110 +0,0 @@ -[package] -name = "opentelemetry-jaeger" -version = "0.22.0" -description = "Jaeger exporter for OpenTelemetry" -homepage = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-jaeger" -repository = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-jaeger" -readme = "README.md" -categories = [ - "development-tools::debugging", - "development-tools::profiling", - "asynchronous", -] -keywords = ["opentelemetry", "jaeger", "tracing", "async"] -license = "Apache-2.0" -edition = "2021" -rust-version = "1.65" - -[badges] -maintenance = { status = "deprecated" } - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[dependencies] -async-std = { workspace = true, optional = true } -async-trait = { workspace = true } -base64 = { version = "0.21.0", optional = true } -headers = { version = "0.3.2", optional = true } -http = { workspace = true, optional = true } -hyper = { workspace = true, features = ["client"], optional = true } -hyper-tls = { version = "0.5.0", default-features = false, optional = true } -isahc = { workspace = true, optional = true } -js-sys = { version = "0.3", optional = true } -opentelemetry = { version = "0.23", default-features = false, features = ["trace"], path = "../opentelemetry" } -opentelemetry_sdk = { version = "0.23", default-features = false, features = ["trace"], path = "../opentelemetry-sdk" } -opentelemetry-http = { version = "0.12", path = "../opentelemetry-http", optional = true } -opentelemetry-semantic-conventions = { version = "0.15", path = "../opentelemetry-semantic-conventions" } -pin-project-lite = { workspace = true, optional = true } -reqwest = { workspace = true, optional = true } -thrift = "0.17.0" -tokio = { workspace = true, features = ["net", "sync"], optional = true } -wasm-bindgen = { version = "0.2", optional = true } -wasm-bindgen-futures = { version = "0.4.18", optional = true } - -tonic = { workspace = true, optional = true, features = ["transport", "codegen", "prost"] } -prost = { workspace = true, optional = true } -prost-types = { workspace = true, optional = true } - -# Futures -futures-executor = { workspace = true, features = ["std"], optional = true } -futures-core = { workspace = true } -futures-util = { workspace = true, features = ["std", "alloc"]} - -[dev-dependencies] -tokio = { workspace = true, features = ["net", "sync"] } -bytes = { workspace = true } -futures-executor = { workspace = true } -opentelemetry-jaeger-propagator = { path = "../opentelemetry-jaeger-propagator" } # for doctests -opentelemetry_sdk = { features = ["trace", "testing", "rt-tokio"], path = "../opentelemetry-sdk" } - -[dependencies.web-sys] -version = "0.3.4" -features = [ - 'Headers', - 'Request', - 'RequestCredentials', - 'RequestInit', - 'RequestMode', - 'Response', - 'Window', -] -optional = true - -[features] -full = [ - "collector_client", - "hyper_collector_client", - "hyper_tls_collector_client", - "isahc_collector_client", - "reqwest_collector_client", - "reqwest_blocking_collector_client", - "reqwest_rustls_collector_client", - "wasm_collector_client", - "rt-tokio", - "rt-tokio-current-thread", - "rt-async-std", - "integration_test", -] -default = [] -collector_client = ["http", "opentelemetry-http"] -hyper_collector_client = ["collector_client", "headers", "http", "hyper", "opentelemetry-http/tokio", "opentelemetry-http/hyper"] -hyper_tls_collector_client = ["hyper_collector_client", "hyper-tls"] -isahc_collector_client = ["isahc", "opentelemetry-http/isahc"] -reqwest_blocking_collector_client = ["reqwest/blocking", "collector_client", "headers", "opentelemetry-http/reqwest"] -reqwest_collector_client = ["reqwest", "collector_client", "headers", "opentelemetry-http/reqwest"] -reqwest_rustls_collector_client = ["reqwest_collector_client", "reqwest/rustls-tls-native-roots"] -wasm_collector_client = [ - "base64", - "http", - "js-sys", - "pin-project-lite", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] -rt-tokio = ["tokio", "opentelemetry_sdk/rt-tokio"] -rt-tokio-current-thread = ["tokio", "opentelemetry_sdk/rt-tokio-current-thread"] -rt-async-std = ["async-std", "opentelemetry_sdk/rt-async-std"] -integration_test = ["tonic", "prost", "prost-types", "rt-tokio", "collector_client", "hyper_collector_client", "hyper_tls_collector_client", "reqwest_collector_client", "isahc_collector_client"] diff --git a/opentelemetry-jaeger/LICENSE b/opentelemetry-jaeger/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/opentelemetry-jaeger/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - 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. diff --git a/opentelemetry-jaeger/README.md b/opentelemetry-jaeger/README.md index d6b0cb639d..7e2f9f68a2 100644 --- a/opentelemetry-jaeger/README.md +++ b/opentelemetry-jaeger/README.md @@ -1,178 +1,11 @@ -![OpenTelemetry — An observability framework for cloud-native software.][splash] - -[splash]: https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo-text.png - # OpenTelemetry Jaeger (Deprecated) -**WARNING** -As of [Jaeger 1.35.0], released in Sept 2022, ingesting the OpenTelemetry Protocol (OTLP) is stable and -as a result, language specific Jaeger exporters within OpenTelemetry SDKs are [recommended for deprecation by the OpenTelemetry project][jaeger-deprecation]. -More information and examples of using OTLP with Jaeger can be found in [Introducing native support for OpenTelemetry in Jaeger][jaeger-otlp] -and [Exporting OTLP traces to Jaeger][exporting-otlp]. - -The opentelemetry-jaeger crate previously contained both a Jaeger exporter and a Jaeger propagator. -To prepare for the deprecation of the Jaeger exporter, the Jaeger propagator implementation has been migrated to -[opentelemetry-jaeger-propagator](../opentelemetry-jaeger-propagator/). - -The 0.22.0 is the last release of the Jaeger exporter. This means that future versions of the OpenTelemetry -SDK will not work with the exporter. - -If you have any questions please comment on the [Jaeger Deprecation Issue][deprecation-issue]. - -[`Jaeger`] integration for applications instrumented with [`OpenTelemetry`]. This includes a jaeger exporter and a jaeger propagator. - -[![Crates.io: opentelemetry-jaeger](https://img.shields.io/crates/v/opentelemetry-jaeger.svg)](https://crates.io/crates/opentelemetry-jaeger) -[![Documentation](https://docs.rs/opentelemetry-jaeger/badge.svg)](https://docs.rs/opentelemetry-jaeger) -[![LICENSE](https://img.shields.io/crates/l/opentelemetry-jaeger)](./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 a trace pipeline and exporter for sending span information to a -Jaeger `agent` or `collector` endpoint for processing and visualization. - -*Compiler support: [requires `rustc` 1.65+][msrv]* - -[`Jaeger`]: https://www.jaegertracing.io/ -[jaeger-otlp]: https://medium.com/jaegertracing/introducing-native-support-for-opentelemetry-in-jaeger-eb661be8183c -[jaeger-deprecation]: https://opentelemetry.io/blog/2022/jaeger-native-otlp/ -[exporting-otlp]: https://github.com/open-telemetry/opentelemetry-rust/tree/main/examples/tracing-jaeger -[Jaeger 1.35.0]: https://github.com/jaegertracing/jaeger/releases/tag/v1.35.0 -[deprecation-issue]: https://github.com/open-telemetry/opentelemetry-rust/issues/995 -[`OpenTelemetry`]: https://crates.io/crates/opentelemetry -[msrv]: #supported-rust-versions - -### Quickstart - -First make sure you have a running version of the Jaeger instance you want to -send data to: - -```shell -$ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 -p14268:14268 jaegertracing/all-in-one:latest -``` - -Then install a new jaeger pipeline with the recommended defaults to start -exporting telemetry: - -```rust -use opentelemetry::global; -use opentelemetry::trace::Tracer; -use opentelemetry_jaeger_propagator; - -fn main() -> Result<(), Box> { - global::set_text_map_propagator(opentelemetry_jaeger_propagator::Propagator::new()); - let tracer = opentelemetry_jaeger::new_agent_pipeline().install_simple()?; - - tracer.in_span("doing_work", |cx| { - // Traced app logic here... - }); - - global::shutdown_tracer_provider(); // sending remaining spans - - Ok(()) -} -``` - -![Jaeger UI](https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/opentelemetry-jaeger/trace.png) - -## Performance - -For optimal performance, a batch exporter is recommended as the simple exporter -will export each span synchronously on drop. You can enable the [`rt-tokio`], -[`rt-tokio-current-thread`] or [`rt-async-std`] features and specify a runtime -on the pipeline builder to have a batch exporter configured for you -automatically. - -```toml -[dependencies] -opentelemetry_sdk = { version = "*", features = ["rt-tokio"] } -opentelemetry-jaeger = { version = "*", features = ["rt-tokio"] } -``` - -```rust -let tracer = opentelemetry_jaeger::new_agent_pipeline() - .install_batch(opentelemetry_sdk::runtime::Tokio)?; -``` - -[`rt-tokio`]: https://tokio.rs -[`rt-tokio-current-thread`]: https://tokio.rs -[`rt-async-std`]: https://async.rs - -### Jaeger Exporter From Environment Variables - -The jaeger pipeline builder can be configured dynamically via environment -variables. All variables are optional, a full list of accepted options can be -found in the [jaeger variables spec]. - -[jaeger variables spec]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md - -### Jaeger Collector Example - -If you want to skip the agent and submit spans directly to a Jaeger collector, -you can enable the optional `collector_client` feature for this crate. This -example expects a Jaeger collector running on `http://localhost:14268`. - -```toml -[dependencies] -opentelemetry-jaeger = { version = "..", features = ["isahc_collector_client"] } -``` - -Then you can use the [`with_collector_endpoint`] method to specify the endpoint: - -[`with_collector_endpoint`]: https://docs.rs/opentelemetry-jaeger/latest/opentelemetry_jaeger/config/collector/struct.CollectorPipeline.html#method.with_endpoint - -```rust -// Note that this requires one of the following features enabled so that there is a default http client implementation -// * hyper_collector_client -// * reqwest_collector_client -// * reqwest_blocking_collector_client -// * reqwest_rustls_collector_client -// * isahc_collector_client - -// You can also provide your own implementation by enable -// `collector_client` and set it with -// new_pipeline().with_http_client() method. -use opentelemetry::trace::Tracer; - -fn main() -> Result<(), Box> { - let tracer = opentelemetry_jaeger::new_collector_pipeline() - .with_endpoint("http://localhost:14268/api/traces") - // optionally set username and password as well. - .with_username("username") - .with_password("s3cr3t") - .install_batch()?; - - tracer.in_span("doing_work", |cx| { - // Traced app logic here... - }); - - opentelemetry::global::shutdown_tracer_provider(); // sending remaining spans - - Ok(()) -} -``` - -## Kitchen Sink Full Configuration - -[`Example`] showing how to override all configuration options. See the -[`AgentPipeline`] docs for details of each option. - -[`Example`]: https://docs.rs/opentelemetry-jaeger/latest/opentelemetry_jaeger/#kitchen-sink-full-configuration -[`AgentPipeline`]: https://docs.rs/opentelemetry-jaeger/latest/opentelemetry_jaeger/config/agent/struct.AgentPipeline.html - -## Supported Rust Versions - -OpenTelemetry is built against the latest stable release. The minimum supported -version is 1.65. The current OpenTelemetry version is not guaranteed to build -on Rust versions earlier than the minimum supported version. +This component is deleted. The release of opentelemetry-jaeger, 0.22.0, can be +found [here](https://crates.io/crates/opentelemetry-jaeger) If you are looking +to export traces to Jaeger, use +[opentelemetry-otlp](https://crates.io/crates/opentelemetry-otlp), as Jaeger +natively supports OTLP. See [this](../examples/tracing-jaeger/README.md) for an +example. -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. +If you are looking for Jaeger propagator, it is available in the crate +[opentelemetry-jaeger-propagator](https://crates.io/crates/opentelemetry-jaeger-propagator). diff --git a/opentelemetry-jaeger/examples/actix-udp/Cargo.toml b/opentelemetry-jaeger/examples/actix-udp/Cargo.toml deleted file mode 100644 index 2b282cc4ff..0000000000 --- a/opentelemetry-jaeger/examples/actix-udp/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "actix-udp-example" -version = "0.1.0" -edition = "2021" -license = "Apache-2.0" -publish = false - -[dependencies] -opentelemetry = { path = "../../../opentelemetry" } -opentelemetry-jaeger = { path = "../.." } -opentelemetry_sdk = { path = "../../../opentelemetry-sdk" } -actix-web = "4.1" -actix-service = "2" -env_logger = "0.10.0" diff --git a/opentelemetry-jaeger/examples/actix-udp/README.md b/opentelemetry-jaeger/examples/actix-udp/README.md deleted file mode 100644 index 5afeeaa41b..0000000000 --- a/opentelemetry-jaeger/examples/actix-udp/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Actix-web - Jaeger example with UDP agent - -This example shows how to export spans from an actix-web application and ship them - to the Jaeger agent over UDP. - -## Usage - -Launch the application: -```shell -# Run jaeger in background -$ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 -p14268:14268 jaegertracing/all-in-one:latest - -# Start the actix-web server -$ cargo run - -# View spans -$ firefox http://localhost:16686/ -``` - -Fire a request: -```bash -curl http://localhost:8080 -``` diff --git a/opentelemetry-jaeger/examples/actix-udp/src/main.rs b/opentelemetry-jaeger/examples/actix-udp/src/main.rs deleted file mode 100644 index 6f861f1d35..0000000000 --- a/opentelemetry-jaeger/examples/actix-udp/src/main.rs +++ /dev/null @@ -1,55 +0,0 @@ -#![allow(deprecated)] -use actix_service::Service; -use actix_web::middleware::Logger; -use actix_web::{web, App, HttpServer}; -use opentelemetry::{ - global, - trace::{FutureExt, TraceContextExt, TraceError, Tracer}, - Key, KeyValue, -}; -use opentelemetry_sdk::{trace::config, Resource}; - -fn init_tracer() -> Result { - opentelemetry_jaeger::new_agent_pipeline() - .with_endpoint("localhost:6831") - .with_service_name("trace-udp-demo") - .with_trace_config(config().with_resource(Resource::new(vec![ - KeyValue::new("service.name", "my-service"), // this will not override the trace-udp-demo - KeyValue::new("service.namespace", "my-namespace"), - KeyValue::new("exporter", "jaeger"), - ]))) - .install_simple() -} - -async fn index() -> &'static str { - let tracer = global::tracer("request"); - tracer.in_span("index", |ctx| { - ctx.span().set_attribute(Key::new("parameter").i64(10)); - "Index" - }) -} - -#[actix_web::main] -async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "debug"); - env_logger::init(); - let _tracer = init_tracer().expect("Failed to initialise tracer."); - - HttpServer::new(|| { - App::new() - .wrap(Logger::default()) - .wrap_fn(|req, srv| { - let tracer = global::tracer("request"); - tracer.in_span("middleware", move |cx| { - cx.span() - .set_attribute(Key::new("path").string(req.path().to_string())); - srv.call(req).with_context(cx) - }) - }) - .route("/", web::get().to(index)) - }) - .bind("127.0.0.1:8080") - .unwrap() - .run() - .await -} diff --git a/opentelemetry-jaeger/examples/remote-sampler/Cargo.toml b/opentelemetry-jaeger/examples/remote-sampler/Cargo.toml deleted file mode 100644 index 170fca5f3d..0000000000 --- a/opentelemetry-jaeger/examples/remote-sampler/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "remote-sampler" -version = "0.1.0" -license = "Apache-2.0" -edition = "2021" - -[dependencies] -opentelemetry = { path = "../../../opentelemetry" } -opentelemetry_sdk = { path = "../../../opentelemetry-sdk", features = ["rt-tokio", "jaeger_remote_sampler"] } -opentelemetry-stdout = { path = "../../../opentelemetry-stdout", features = ["trace"] } -reqwest = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/opentelemetry-jaeger/examples/remote-sampler/README.md b/opentelemetry-jaeger/examples/remote-sampler/README.md deleted file mode 100644 index 69fba687eb..0000000000 --- a/opentelemetry-jaeger/examples/remote-sampler/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Jaeger remote sampler - -When services generate too many spans. We need to sample some spans to save cost and speed up the queries. - -Adaptive sampling works in the Jaeger collector by observing the spans received from services and recalculating sampling -probabilities for each service/endpoint combination to ensure that the volume is relatively constant. - -For a full list of configurations. See SDK docs of [JaegerRemoteSamplerBuilder](https://docs.rs/opentelemetry_sdk/latest/opentelemetry_sdk/trace/struct.JaegerRemoteSamplerBuilder.html). - -## Setup - -Start a jaeger collector and an opentelemetry collector locally using docker - -``` -docker-compose run -d -``` - -It will allow you to - -- query sampling strategies from jaeger collect at port 5578. `http://localhost:5778/sampling?service=foo` -- query sampling strategies from opentelemetry collector at port 5579. `http://localhost:5779/sampling?service=foo` - -## Run the example - -After start the jaeger remote sampling server successfully. We can run - -`cargo run` - -command to start the example, you should only see one span is printed out. - -Looking at the example, you will notice we use `AlwaysOff` as our default sampler. It means before the SDK get the sampling strategy from remote server, no span will be sampled. - -Once the SDK fetched the remote strategy, we will start a probability sampler internally. In this case, we set the probability to 1.0 for all spans. This is defined by - -``` -"service": "foo", -"type": "probabilistic", -"param": 1, -``` - -Feel free to tune the `param` and see if the probability of sampling changes. - -## Strategies - -The sampling strategies is defined in `srategies.json` files. It defines two set of strategies. - -The first strategy is returned for `foo` service. The second strategy is catch all default strategy for all other -services. diff --git a/opentelemetry-jaeger/examples/remote-sampler/docker-compose.yaml b/opentelemetry-jaeger/examples/remote-sampler/docker-compose.yaml deleted file mode 100644 index 8f41f07319..0000000000 --- a/opentelemetry-jaeger/examples/remote-sampler/docker-compose.yaml +++ /dev/null @@ -1,26 +0,0 @@ -version: "3" -services: - - # jaeger collector - jaeger-all-in-one: - image: jaegertracing/all-in-one:latest - ports: - - "16686:16686" - - "14268" - - "14250" - - "5778:5778" - container_name: jaeger-collector - volumes: - - ./strategies.json:/etc/jaeger/custom_strategies.json - environment: - - SAMPLING_STRATEGIES_FILE=/etc/jaeger/custom_strategies.json - - # opentelemetry collector - otel-collector: - image: otel/opentelemetry-collector:latest - command: [ "--config=/etc/otel-collector.yaml" ] - volumes: - - ./otel-collector.yaml:/etc/otel-collector.yaml - - ./strategies.json:/etc/strategies.json - ports: - - "5779:5778" # default jaeger remote sampling port \ No newline at end of file diff --git a/opentelemetry-jaeger/examples/remote-sampler/otel-collector.yaml b/opentelemetry-jaeger/examples/remote-sampler/otel-collector.yaml deleted file mode 100644 index e5f1280a2e..0000000000 --- a/opentelemetry-jaeger/examples/remote-sampler/otel-collector.yaml +++ /dev/null @@ -1,17 +0,0 @@ -receivers: - jaeger: - protocols: - grpc: - remote_sampling: - host_endpoint: "0.0.0.0:5778" # default port - insecure: true - strategy_file: "/etc/strategies.json" - -exporters: - logging: - -service: - pipelines: - traces: - receivers: [ jaeger ] - exporters: [ logging ] \ No newline at end of file diff --git a/opentelemetry-jaeger/examples/remote-sampler/src/main.rs b/opentelemetry-jaeger/examples/remote-sampler/src/main.rs deleted file mode 100644 index 140c4d059f..0000000000 --- a/opentelemetry-jaeger/examples/remote-sampler/src/main.rs +++ /dev/null @@ -1,40 +0,0 @@ -use opentelemetry::global; -use opentelemetry::trace::Tracer; -use opentelemetry_sdk::runtime; -use opentelemetry_sdk::trace::{Sampler, TracerProvider as SdkTracerProvider}; -use std::time::Duration; - -fn setup() { - let client = reqwest::Client::new(); - - let sampler = Sampler::jaeger_remote(runtime::Tokio, client, Sampler::AlwaysOff, "foo") - .with_endpoint("http://localhost:5778/sampling") // setup jaeger remote sampler endpoint - .with_update_interval(Duration::from_secs(5)) // will call jaeger sampling endpoint every 5 secs. - .build() - .unwrap(); - - let config = opentelemetry_sdk::trace::config().with_sampler(sampler); - - let provider = SdkTracerProvider::builder() - .with_config(config) - .with_simple_exporter(opentelemetry_stdout::SpanExporter::default()) - .build(); - - global::set_tracer_provider(provider); -} - -#[tokio::main] -async fn main() { - setup(); - let tracer = global::tracer("test"); - - { - let _not_sampled_span = tracer.start("test"); - } - - tokio::time::sleep(Duration::from_secs(10)).await; - - { - let _sampled_span = tracer.start("should_record"); - } -} diff --git a/opentelemetry-jaeger/examples/remote-sampler/strategies.json b/opentelemetry-jaeger/examples/remote-sampler/strategies.json deleted file mode 100644 index 3ba614e48e..0000000000 --- a/opentelemetry-jaeger/examples/remote-sampler/strategies.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "service_strategies": [ - { - "service": "foo", - "type": "probabilistic", - "param": 1, - "operation_strategies": [ - { - "operation": "op1", - "type": "probabilistic", - "param": 0.2 - }, - { - "operation": "op2", - "type": "probabilistic", - "param": 0.4 - } - ] - } - ], - "default_strategy": { - "type": "probabilistic", - "param": 0.5, - "operation_strategies": [ - { - "operation": "/health", - "type": "probabilistic", - "param": 0.0 - }, - { - "operation": "/metrics", - "type": "probabilistic", - "param": 0.0 - } - ] - } -} diff --git a/opentelemetry-jaeger/src/exporter/agent.rs b/opentelemetry-jaeger/src/exporter/agent.rs deleted file mode 100644 index 8982d526c8..0000000000 --- a/opentelemetry-jaeger/src/exporter/agent.rs +++ /dev/null @@ -1,207 +0,0 @@ -//! # UDP Jaeger Agent Client -use crate::exporter::address_family; -use crate::exporter::runtime::JaegerTraceRuntime; -use crate::exporter::thrift::{ - agent::{self, TAgentSyncClient}, - jaeger, -}; -use crate::exporter::transport::{TBufferChannel, TNoopChannel}; -use std::fmt; -use std::net::{SocketAddr, UdpSocket}; -use thrift::{ - protocol::{TCompactInputProtocol, TCompactOutputProtocol}, - transport::{ReadHalf, TIoChannel, WriteHalf}, -}; - -struct BufferClient { - buffer: ReadHalf, - client: agent::AgentSyncClient< - TCompactInputProtocol, - TCompactOutputProtocol>, - >, -} - -impl fmt::Debug for BufferClient { - /// Debug info - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("BufferClient") - .field("buffer", &self.buffer) - .field("client", &"AgentSyncClient") - .finish() - } -} - -/// `AgentSyncClientUDP` implements a version of the `TAgentSyncClient` -/// interface over UDP. -#[derive(Debug)] -pub(crate) struct AgentSyncClientUdp { - conn: UdpSocket, - buffer_client: BufferClient, - max_packet_size: usize, - auto_split: bool, -} - -impl AgentSyncClientUdp { - /// Create a new UDP agent client - pub(crate) fn new( - max_packet_size: usize, - auto_split: bool, - agent_address: Vec, - ) -> thrift::Result { - let (buffer, write) = TBufferChannel::with_capacity(max_packet_size).split()?; - let client = agent::AgentSyncClient::new( - TCompactInputProtocol::new(TNoopChannel), - TCompactOutputProtocol::new(write), - ); - - let conn = UdpSocket::bind(address_family(agent_address.as_slice()))?; - conn.connect(agent_address.as_slice())?; - - Ok(AgentSyncClientUdp { - conn, - buffer_client: BufferClient { buffer, client }, - max_packet_size, - auto_split, - }) - } - - /// Emit standard Jaeger batch - pub(crate) fn emit_batch(&mut self, batch: jaeger::Batch) -> thrift::Result<()> { - if !self.auto_split { - let payload = serialize_batch(&mut self.buffer_client, batch, self.max_packet_size)?; - self.conn.send(&payload)?; - return Ok(()); - } - - let mut buffers = vec![]; - serialize_batch_vectored( - &mut self.buffer_client, - batch, - self.max_packet_size, - &mut buffers, - )?; - for payload in buffers { - self.conn.send(&payload)?; - } - - Ok(()) - } -} - -/// `AgentAsyncClientUDP` implements an async version of the `TAgentSyncClient` -/// interface over UDP. -#[derive(Debug)] -pub(crate) struct AgentAsyncClientUdp { - runtime: R, - conn: ::Socket, - buffer_client: BufferClient, - max_packet_size: usize, - auto_split: bool, -} - -impl AgentAsyncClientUdp { - /// Create a new UDP agent client - pub(crate) fn new( - max_packet_size: usize, - runtime: R, - auto_split: bool, - agent_address: Vec, - ) -> thrift::Result { - let (buffer, write) = TBufferChannel::with_capacity(max_packet_size).split()?; - let client = agent::AgentSyncClient::new( - TCompactInputProtocol::new(TNoopChannel), - TCompactOutputProtocol::new(write), - ); - - let conn = runtime.create_socket(agent_address.as_slice())?; - - Ok(AgentAsyncClientUdp { - runtime, - conn, - buffer_client: BufferClient { buffer, client }, - max_packet_size, - auto_split, - }) - } - - /// Emit standard Jaeger batch - pub(crate) async fn emit_batch(&mut self, batch: jaeger::Batch) -> thrift::Result<()> { - if !self.auto_split { - let payload = serialize_batch(&mut self.buffer_client, batch, self.max_packet_size)?; - self.runtime.write_to_socket(&self.conn, payload).await?; - return Ok(()); - } - - let mut buffers = vec![]; - serialize_batch_vectored( - &mut self.buffer_client, - batch, - self.max_packet_size, - &mut buffers, - )?; - for payload in buffers { - self.runtime.write_to_socket(&self.conn, payload).await?; - } - - Ok(()) - } -} - -fn serialize_batch( - client: &mut BufferClient, - batch: jaeger::Batch, - max_packet_size: usize, -) -> thrift::Result> { - client.client.emit_batch(batch)?; - let payload = client.buffer.take_bytes(); - - if payload.len() > max_packet_size { - return Err(thrift::ProtocolError::new( - thrift::ProtocolErrorKind::SizeLimit, - format!( - "jaeger exporter payload size of {} bytes over max UDP packet size of {} bytes. Try setting a smaller batch size or turn auto split on.", - payload.len(), - max_packet_size, - ), - ) - .into()); - } - - Ok(payload) -} - -fn serialize_batch_vectored( - client: &mut BufferClient, - mut batch: jaeger::Batch, - max_packet_size: usize, - output: &mut Vec>, -) -> thrift::Result<()> { - client.client.emit_batch(batch.clone())?; - let payload = client.buffer.take_bytes(); - - if payload.len() <= max_packet_size { - output.push(payload); - return Ok(()); - } - - if batch.spans.len() <= 1 { - return Err(thrift::ProtocolError::new( - thrift::ProtocolErrorKind::SizeLimit, - format!( - "single span's jaeger exporter payload size of {} bytes over max UDP packet size of {} bytes", - payload.len(), - max_packet_size, - ), - ) - .into()); - } - - let mid = batch.spans.len() / 2; - let new_spans = batch.spans.drain(mid..).collect::>(); - let new_batch = jaeger::Batch::new(batch.process.clone(), new_spans); - - serialize_batch_vectored(client, batch, max_packet_size, output)?; - serialize_batch_vectored(client, new_batch, max_packet_size, output)?; - - Ok(()) -} diff --git a/opentelemetry-jaeger/src/exporter/collector.rs b/opentelemetry-jaeger/src/exporter/collector.rs deleted file mode 100644 index dc8800d874..0000000000 --- a/opentelemetry-jaeger/src/exporter/collector.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! # HTTP Jaeger Collector Client -//! -#[cfg(feature = "collector_client")] -use http::Uri; -#[cfg(feature = "collector_client")] -use opentelemetry_http::{HttpClient, ResponseExt as _}; - -#[cfg(feature = "collector_client")] -pub(crate) use collector_client::AsyncHttpClient; -#[cfg(feature = "wasm_collector_client")] -pub(crate) use wasm_collector_client::WasmCollector; - -#[cfg(feature = "collector_client")] -mod collector_client { - use super::*; - use crate::exporter::thrift::jaeger; - use opentelemetry_sdk::export::trace::ExportResult; - use std::io::Cursor; - use std::sync::atomic::{AtomicUsize, Ordering}; - use thrift::protocol::TBinaryOutputProtocol; - - /// `AsyncHttpClient` implements an async version of the - /// `TCollectorSyncClient` interface over HTTP - #[derive(Debug)] - pub(crate) struct AsyncHttpClient { - endpoint: Uri, - http_client: Box, - payload_size_estimate: AtomicUsize, - } - - impl AsyncHttpClient { - /// Create a new HTTP collector client - pub(crate) fn new(endpoint: Uri, client: Box) -> Self { - let payload_size_estimate = AtomicUsize::new(512); - - AsyncHttpClient { - endpoint, - http_client: client, - payload_size_estimate, - } - } - - /// Submit list of Jaeger batches - pub(crate) async fn submit_batch(&self, batch: jaeger::Batch) -> ExportResult { - // estimate transport capacity based on last request - let estimate = self.payload_size_estimate.load(Ordering::Relaxed); - - // Write payload to transport buffer - let transport = Cursor::new(Vec::with_capacity(estimate)); - let mut protocol = TBinaryOutputProtocol::new(transport, true); - batch - .write_to_out_protocol(&mut protocol) - .map_err(crate::Error::from)?; - - // Use current batch capacity as new estimate - self.payload_size_estimate - .store(protocol.transport.get_ref().len(), Ordering::Relaxed); - - // Build collector request - let req = http::Request::builder() - .method(http::Method::POST) - .uri(&self.endpoint) - .header("Content-Type", "application/vnd.apache.thrift.binary") - .body(protocol.transport.into_inner()) - .expect("request should always be valid"); - - // Send request to collector - let _ = self.http_client.send(req).await?.error_for_status()?; - Ok(()) - } - } -} - -#[cfg(feature = "wasm_collector_client")] -mod wasm_collector_client { - use crate::exporter::thrift::jaeger; - use base64::engine::general_purpose; - use base64::Engine; - use futures_util::future; - use http::Uri; - use js_sys::Uint8Array; - use pin_project_lite::pin_project; - use std::future::Future; - use std::io::{self, Cursor}; - use std::pin::Pin; - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::task::{Context, Poll}; - use thrift::protocol::TBinaryOutputProtocol; - use wasm_bindgen::JsCast; - use wasm_bindgen_futures::JsFuture; - use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response}; - - #[derive(Debug)] - pub(crate) struct WasmCollector { - endpoint: Uri, - payload_size_estimate: AtomicUsize, - client: WasmHttpClient, - } - - #[derive(Debug, Default)] - struct WasmHttpClient { - auth: Option, - } - - impl WasmCollector { - /// Create a new HTTP collector client - pub(crate) fn new( - endpoint: Uri, - username: Option, - password: Option, - ) -> thrift::Result { - let auth = if let (Some(username), Some(password)) = (username, password) { - let mut auth = String::from("Basic "); - general_purpose::STANDARD.encode_string(username, &mut auth); - auth.push(':'); - general_purpose::STANDARD.encode_string(password, &mut auth); - Some(auth) - } else { - None - }; - let payload_size_estimate = AtomicUsize::new(512); - - Ok(Self { - endpoint, - client: WasmHttpClient { auth }, - payload_size_estimate, - }) - } - - /// Submit list of Jaeger batches - pub(crate) fn submit_batch( - &self, - batch: jaeger::Batch, - ) -> impl Future> + Send + 'static - { - self.build_request(batch) - .map(post_request) - .map(|fut| future::Either::Left(SubmitBatchFuture { fut })) - .unwrap_or_else(|e| future::Either::Right(future::err(e))) - } - - fn build_request(&self, batch: jaeger::Batch) -> thrift::Result { - // estimate transport capacity based on last request - let estimate = self.payload_size_estimate.load(Ordering::Relaxed); - - // Write payload to transport buffer - let transport = Cursor::new(Vec::with_capacity(estimate)); - let mut protocol = TBinaryOutputProtocol::new(transport, true); - batch.write_to_out_protocol(&mut protocol)?; - - // Use current batch capacity as new estimate - self.payload_size_estimate - .store(protocol.transport.get_ref().len(), Ordering::Relaxed); - - // Build collector request - let mut options = RequestInit::new(); - options.method("POST"); - options.mode(RequestMode::Cors); - - let body: Uint8Array = protocol.transport.get_ref().as_slice().into(); - options.body(Some(body.as_ref())); - - if self.client.auth.is_some() { - options.credentials(RequestCredentials::Include); - } - - let request = Request::new_with_str_and_init(&self.endpoint.to_string(), &options) - .map_err(jsvalue_into_ioerror)?; - let headers = request.headers(); - headers - .set("Content-Type", "application/vnd.apache.thrift.binary") - .map_err(jsvalue_into_ioerror)?; - if let Some(auth) = self.client.auth.as_ref() { - headers - .set("Authorization", auth) - .map_err(jsvalue_into_ioerror)?; - } - - Ok(request) - } - } - - async fn post_request(request: Request) -> thrift::Result { - // Send request to collector - let window = web_sys::window().unwrap(); - let res_value = JsFuture::from(window.fetch_with_request(&request)) - .await - .map_err(jsvalue_into_ioerror)?; - let res: Response = res_value.dyn_into().unwrap(); - - if !res.ok() { - return Err(thrift::Error::from(io::Error::new( - io::ErrorKind::Other, - format!( - "Expected success response, got {} ({})", - res.status(), - res.status_text() - ), - ))); - } - - Ok(jaeger::BatchSubmitResponse { ok: true }) - } - - pin_project! { - /// Wrapper of web fetch API future marked as Send. - /// - /// At the moment, the web APIs are single threaded. Since all opentelemetry futures are - /// required to be Send, we mark this future as Send. - struct SubmitBatchFuture { - #[pin] fut: F - } - } - - unsafe impl Send for SubmitBatchFuture {} - - impl Future for SubmitBatchFuture { - type Output = F::Output; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().fut.poll(cx) - } - } - - fn jsvalue_into_ioerror(value: wasm_bindgen::JsValue) -> io::Error { - io::Error::new( - io::ErrorKind::Other, - js_sys::JSON::stringify(&value) - .map(String::from) - .unwrap_or_else(|_| "unknown error".to_string()), - ) - } -} diff --git a/opentelemetry-jaeger/src/exporter/config/agent.rs b/opentelemetry-jaeger/src/exporter/config/agent.rs deleted file mode 100644 index 1c523307f3..0000000000 --- a/opentelemetry-jaeger/src/exporter/config/agent.rs +++ /dev/null @@ -1,465 +0,0 @@ -use std::borrow::BorrowMut; -use std::net::ToSocketAddrs; -use std::sync::Arc; -use std::{env, net}; - -use opentelemetry::trace::TraceError; -use opentelemetry_sdk::trace::{BatchConfig, Config, TracerProvider}; -use opentelemetry_sdk::trace::{BatchSpanProcessor, Tracer}; - -use crate::exporter::agent::{AgentAsyncClientUdp, AgentSyncClientUdp}; -use crate::exporter::config::{ - build_config_and_process, install_tracer_provider_and_get_tracer, HasRequiredConfig, - TransformationConfig, -}; -use crate::exporter::uploader::{AsyncUploader, SyncUploader, Uploader}; -use crate::{Error, Exporter, JaegerTraceRuntime}; - -/// The max size of UDP packet we want to send, synced with jaeger-agent -const UDP_PACKET_MAX_LENGTH: usize = 65_000; - -/// The hostname for the Jaeger agent. -/// e.g. "localhost" -const ENV_AGENT_HOST: &str = "OTEL_EXPORTER_JAEGER_AGENT_HOST"; - -/// The port for the Jaeger agent. -/// e.g. 6832 -const ENV_AGENT_PORT: &str = "OTEL_EXPORTER_JAEGER_AGENT_PORT"; - -/// Default agent host if none is provided -const DEFAULT_AGENT_ENDPOINT_HOST: &str = "127.0.0.1"; - -/// Default agent port if none is provided -const DEFAULT_AGENT_ENDPOINT_PORT: &str = "6831"; - -/// Deprecation Notice: -/// Ingestion of OTLP is now supported in Jaeger please check [crates.io] for more details. -/// -/// AgentPipeline config and build a exporter targeting a jaeger agent using UDP as transport layer protocol. -/// -/// ## UDP packet max length -/// The exporter uses UDP to communicate with the agent. UDP requests may be rejected if it's too long. -/// See [UDP packet size] for details. -/// -/// Users can utilise [`with_max_packet_size`] and [`with_auto_split_batch`] to avoid spans loss or UDP requests failure. -/// -/// The default `max_packet_size` is `65000`([why 65000]?). If your platform has a smaller limit on UDP packet. -/// You will need to adjust the `max_packet_size` accordingly. -/// -/// Set `auto_split_batch` to true will config the exporter to split the batch based on `max_packet_size` -/// automatically. Note that it has a performance overhead as every batch could require multiple requests to export. -/// -/// For example, OSX UDP packet limit is 9216 by default. You can configure the pipeline as following -/// to avoid UDP packet breaches the limit. -/// -/// ```no_run -/// # use opentelemetry_sdk::runtime::Tokio; -/// # fn main() { -/// let tracer = opentelemetry_jaeger::new_agent_pipeline() -/// .with_endpoint("localhost:6831") -/// .with_service_name("my_app") -/// .with_max_packet_size(9_216) -/// .with_auto_split_batch(true) -/// .install_batch(Tokio).unwrap(); -/// # } -/// ``` -/// -/// [`with_auto_split_batch`]: AgentPipeline::with_auto_split_batch -/// [`with_max_packet_size`]: AgentPipeline::with_max_packet_size -/// [UDP packet size]: https://stackoverflow.com/questions/1098897/what-is-the-largest-safe-udp-packet-size-on-the-internet -/// [why 65000]: https://serverfault.com/questions/246508/how-is-the-mtu-is-65535-in-udp-but-ethernet-does-not-allow-frame-size-more-than -/// [crates.io]: https://crates.io/crates/opentelemetry-jaeger -/// -/// ## Environment variables -/// The following environment variables are available to configure the agent exporter. -/// -/// - `OTEL_EXPORTER_JAEGER_AGENT_HOST`, set the host of the agent. If the `OTEL_EXPORTER_JAEGER_AGENT_HOST` -/// is not set, the value will be ignored. -/// - `OTEL_EXPORTER_JAEGER_AGENT_PORT`, set the port of the agent. If the `OTEL_EXPORTER_JAEGER_AGENT_HOST` -/// is not set, the exporter will use 127.0.0.1 as the host. -#[derive(Debug)] -#[deprecated( - since = "0.21.0", - note = "Please migrate to opentelemetry-otlp exporter." -)] -pub struct AgentPipeline { - transformation_config: TransformationConfig, - trace_config: Option, - batch_config: Option, - agent_endpoint: Option, - max_packet_size: usize, - auto_split_batch: bool, -} - -impl Default for AgentPipeline { - fn default() -> Self { - AgentPipeline { - transformation_config: Default::default(), - trace_config: Default::default(), - batch_config: Some(Default::default()), - agent_endpoint: Some(format!( - "{DEFAULT_AGENT_ENDPOINT_HOST}:{DEFAULT_AGENT_ENDPOINT_PORT}" - )), - max_packet_size: UDP_PACKET_MAX_LENGTH, - auto_split_batch: false, - } - } -} - -// implement the seal trait -impl HasRequiredConfig for AgentPipeline { - fn set_transformation_config(&mut self, f: T) - where - T: FnOnce(&mut TransformationConfig), - { - f(self.transformation_config.borrow_mut()) - } - - fn set_trace_config(&mut self, config: Config) { - self.trace_config = Some(config) - } - - fn set_batch_config(&mut self, config: BatchConfig) { - self.batch_config = Some(config) - } -} - -/// Start a new pipeline to configure a exporter that target a jaeger agent. -/// -/// See details for each configurations at [`AgentPipeline`] -/// -/// Deprecation Notice: -/// Ingestion of OTLP is now supported in Jaeger please check [crates.io] for more details. -/// -/// [`AgentPipeline`]: crate::config::agent::AgentPipeline -/// [crates.io]: https://crates.io/crates/opentelemetry-jaeger -#[deprecated( - since = "0.21.0", - note = "Please migrate to opentelemetry-otlp exporter." -)] -pub fn new_agent_pipeline() -> AgentPipeline { - AgentPipeline::default() -} - -impl AgentPipeline { - /// set the endpoint of the agent. - /// - /// It usually composed by host ip and the port number. - /// Any valid socket address can be used. - /// - /// Default to be `127.0.0.1:6831`. - pub fn with_endpoint>(self, agent_endpoint: T) -> Self { - AgentPipeline { - agent_endpoint: Some(agent_endpoint.into()), - ..self - } - } - - /// Assign the max packet size in bytes. - /// - /// It should be consistent with the limit of platforms. Otherwise, UDP requests maybe reject with - /// error like `thrift agent failed with transport error` or `thrift agent failed with message too long`. - /// - /// The exporter will cut off spans if the batch is long. To avoid this, set [auto_split_batch](AgentPipeline::with_auto_split_batch) to `true` - /// to split a batch into multiple UDP packets. - /// - /// Default to be `65000`. - pub fn with_max_packet_size(self, max_packet_size: usize) -> Self { - AgentPipeline { - max_packet_size, - ..self - } - } - - /// Config whether to auto split batches. - /// - /// When auto split is set to `true`, the exporter will try to split the - /// batch into smaller ones so that there will be minimal data loss. It - /// will impact the performance. - /// - /// Note that if the length of one serialized span is longer than the `max_packet_size`. - /// The exporter will return an error as it cannot export the span. Use jaeger collector - /// instead of jaeger agent may be help in this case as the exporter will use HTTP to communicate - /// with jaeger collector. - /// - /// Default to be `false`. - pub fn with_auto_split_batch(mut self, should_auto_split: bool) -> Self { - self.auto_split_batch = should_auto_split; - self - } - - /// Set the service name of the application. It generally is the name of application. - /// Critically, Jaeger backend depends on `Span.Process.ServiceName` to identify the service - /// that produced the spans. - /// - /// Opentelemetry allows set the service name using multiple methods. - /// This functions takes priority over all other methods. - /// - /// If the service name is not set. It will default to be `unknown_service`. - pub fn with_service_name>(mut self, service_name: T) -> Self { - self.set_transformation_config(|config| { - config.service_name = Some(service_name.into()); - }); - self - } - - /// Config whether to export information of instrumentation library. - /// - /// It's required to [report instrumentation library as span tags]. - /// However it does have a overhead on performance, performance sensitive applications can - /// use this function to opt out reporting instrumentation library. - /// - /// Default to be `true`. - /// - /// [report instrumentation library as span tags]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/non-otlp.md#instrumentationscope - pub fn with_instrumentation_library_tags(mut self, should_export: bool) -> Self { - self.set_transformation_config(|config| { - config.export_instrument_library = should_export; - }); - self - } - - /// Assign the opentelemetry SDK configurations for the exporter pipeline. - /// - /// For mapping between opentelemetry configurations and Jaeger spans. Please refer [the spec]. - /// - /// [the spec]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#mappings - /// # Examples - /// Set service name via resource. - /// ```rust - /// use opentelemetry::KeyValue; - /// use opentelemetry_sdk::{Resource, trace::Config}; - /// - /// let pipeline = opentelemetry_jaeger::new_agent_pipeline() - /// .with_trace_config( - /// Config::default() - /// .with_resource(Resource::new(vec![KeyValue::new("service.name", "my-service")])) - /// ); - /// - /// ``` - pub fn with_trace_config(mut self, config: Config) -> Self { - self.set_trace_config(config); - self - } - - /// Assign the batch span processor for the exporter pipeline. - /// - /// If a simple span processor is used by [`install_simple`][AgentPipeline::install_simple] - /// or [`build_simple`][AgentPipeline::install_simple], then this config will not be ignored. - /// - /// # Examples - /// Set max queue size. - /// ```rust - /// use opentelemetry_sdk::trace::BatchConfigBuilder; - /// - /// let pipeline = opentelemetry_jaeger::new_agent_pipeline() - /// .with_batch_processor_config( - /// BatchConfigBuilder::default() - /// .with_max_queue_size(200) - /// .build() - /// ); - /// - /// ``` - pub fn with_batch_processor_config(mut self, config: BatchConfig) -> Self { - self.set_batch_config(config); - self - } - - /// Build a `TracerProvider` using a blocking exporter and configurations from the pipeline. - /// - /// The exporter will send each span to the agent upon the span ends. - pub fn build_simple(mut self) -> Result { - let mut builder = TracerProvider::builder(); - - let (config, process) = build_config_and_process( - self.trace_config.take(), - self.transformation_config.service_name.take(), - ); - let exporter = Exporter::new( - process.into(), - self.transformation_config.export_instrument_library, - self.build_sync_agent_uploader()?, - ); - - builder = builder.with_simple_exporter(exporter); - builder = builder.with_config(config); - - Ok(builder.build()) - } - - /// Build a `TracerProvider` using a async exporter and configurations from the pipeline. - /// - /// The exporter will collect spans in a batch and send them to the agent. - /// - /// It's possible to lose spans up to a batch when the application shuts down. So users should - /// use [`shut_down_tracer_provider`] to block the shut down process until - /// all remaining spans have been sent. - /// - /// Commonly used runtime are provided via `rt-tokio`, `rt-tokio-current-thread`, `rt-async-std` - /// features. - /// - /// [`shut_down_tracer_provider`]: opentelemetry::global::shutdown_tracer_provider - pub fn build_batch(mut self, runtime: R) -> Result - where - R: JaegerTraceRuntime, - { - let mut builder = TracerProvider::builder(); - - let export_instrument_library = self.transformation_config.export_instrument_library; - // build sdk trace config and jaeger process. - // some attributes like service name has attributes like service name - let (config, process) = build_config_and_process( - self.trace_config.take(), - self.transformation_config.service_name.take(), - ); - let batch_config = self.batch_config.take(); - let uploader = self.build_async_agent_uploader(runtime.clone())?; - let exporter = Exporter::new(process.into(), export_instrument_library, uploader); - let batch_processor = BatchSpanProcessor::builder(exporter, runtime) - .with_batch_config(batch_config.unwrap_or_default()) - .build(); - - builder = builder.with_span_processor(batch_processor); - builder = builder.with_config(config); - - Ok(builder.build()) - } - - /// Similar to [`build_simple`][AgentPipeline::build_simple] but also returns a tracer from the - /// tracer provider. - /// - /// The tracer name is `opentelemetry-jaeger`. The tracer version will be the version of this crate. - pub fn install_simple(self) -> Result { - let tracer_provider = self.build_simple()?; - install_tracer_provider_and_get_tracer(tracer_provider) - } - - /// Similar to [`build_batch`][AgentPipeline::build_batch] but also returns a tracer from the - /// tracer provider. - /// - /// The tracer name is `opentelemetry-jaeger`. The tracer version will be the version of this crate. - pub fn install_batch(self, runtime: R) -> Result - where - R: JaegerTraceRuntime, - { - let tracer_provider = self.build_batch(runtime)?; - install_tracer_provider_and_get_tracer(tracer_provider) - } - - /// Build an jaeger exporter targeting a jaeger agent and running on the async runtime. - pub fn build_async_agent_exporter( - mut self, - runtime: R, - ) -> Result - where - R: JaegerTraceRuntime, - { - let export_instrument_library = self.transformation_config.export_instrument_library; - // build sdk trace config and jaeger process. - // some attributes like service name has attributes like service name - let (_, process) = build_config_and_process( - self.trace_config.take(), - self.transformation_config.service_name.take(), - ); - let uploader = self.build_async_agent_uploader(runtime)?; - Ok(Exporter::new( - process.into(), - export_instrument_library, - uploader, - )) - } - - /// Build an jaeger exporter targeting a jaeger agent and running on the sync runtime. - pub fn build_sync_agent_exporter(mut self) -> Result { - let (_, process) = build_config_and_process( - self.trace_config.take(), - self.transformation_config.service_name.take(), - ); - Ok(Exporter::new( - process.into(), - self.transformation_config.export_instrument_library, - self.build_sync_agent_uploader()?, - )) - } - - fn build_async_agent_uploader(self, runtime: R) -> Result, TraceError> - where - R: JaegerTraceRuntime, - { - let agent = AgentAsyncClientUdp::new( - self.max_packet_size, - runtime, - self.auto_split_batch, - self.resolve_endpoint()?, - ) - .map_err::(Into::into)?; - Ok(Arc::new(AsyncUploader::Agent( - futures_util::lock::Mutex::new(agent), - ))) - } - - fn build_sync_agent_uploader(self) -> Result, TraceError> { - let agent = AgentSyncClientUdp::new( - self.max_packet_size, - self.auto_split_batch, - self.resolve_endpoint()?, - ) - .map_err::(Into::into)?; - Ok(Arc::new(SyncUploader::Agent(std::sync::Mutex::new(agent)))) - } - - // resolve the agent endpoint from the environment variables or the builder - // if only one of the environment variables is set, the other one will be set to the default value - // if no environment variable is set, the builder value will be used. - fn resolve_endpoint(self) -> Result, TraceError> { - let endpoint_str = match (env::var(ENV_AGENT_HOST), env::var(ENV_AGENT_PORT)) { - (Ok(host), Ok(port)) => format!("{}:{}", host.trim(), port.trim()), - (Ok(host), _) => format!("{}:{DEFAULT_AGENT_ENDPOINT_PORT}", host.trim()), - (_, Ok(port)) => format!("{DEFAULT_AGENT_ENDPOINT_HOST}:{}", port.trim()), - (_, _) => self.agent_endpoint.unwrap_or(format!( - "{DEFAULT_AGENT_ENDPOINT_HOST}:{DEFAULT_AGENT_ENDPOINT_PORT}" - )), - }; - endpoint_str - .to_socket_addrs() - .map(|addrs| addrs.collect()) - .map_err(|io_err| { - Error::ConfigError { - pipeline_name: "agent", - config_name: "endpoint", - reason: io_err.to_string(), - } - .into() - }) - } -} - -#[cfg(test)] -mod tests { - use crate::config::agent::AgentPipeline; - - #[test] - fn set_socket_address() { - let test_cases = vec![ - // invalid inputs - ("invalid_endpoint", false), - ("0.0.0.0.0:9123", false), - ("127.0.0.1", false), // port is needed - // valid inputs - ("[::0]:9123", true), - ("127.0.0.1:1001", true), - ]; - for (socket_str, is_ok) in test_cases.into_iter() { - let resolved_endpoint = AgentPipeline::default() - .with_endpoint(socket_str) - .resolve_endpoint(); - assert_eq!( - resolved_endpoint.is_ok(), - // if is_ok is true, use socket_str, otherwise use the default endpoint - is_ok, - "endpoint string {}", - socket_str - ); - } - } -} diff --git a/opentelemetry-jaeger/src/exporter/config/collector/http_client.rs b/opentelemetry-jaeger/src/exporter/config/collector/http_client.rs deleted file mode 100644 index 69bafcc985..0000000000 --- a/opentelemetry-jaeger/src/exporter/config/collector/http_client.rs +++ /dev/null @@ -1,192 +0,0 @@ -use opentelemetry_http::HttpClient; -use std::time::Duration; - -#[derive(Debug)] -pub(crate) enum CollectorHttpClient { - None, - Custom(Box), - #[cfg(feature = "hyper_collector_client")] - Hyper, - #[cfg(feature = "isahc_collector_client")] - Isahc, - #[cfg(feature = "reqwest_collector_client")] - Reqwest, - #[cfg(feature = "reqwest_blocking_collector_client")] - ReqwestBlocking, -} - -impl CollectorHttpClient { - // try to build a build in http client if users chose one. If none available return NoHttpClient error - #[allow(unused_variables)] // if the user enabled no build in client features. all parameters are unused. - pub(crate) fn build_client( - self, - collector_username: Option, - collector_password: Option, - collector_timeout: Duration, - ) -> Result, crate::Error> { - match self { - CollectorHttpClient::Custom(client) => Ok(client), - CollectorHttpClient::None => Err(crate::Error::ConfigError { - pipeline_name: "http_client", - config_name: "collector", - reason: - "No http client provided. Consider enable one of the `hyper_collector_client`, `surf_collector_client`, \ - `reqwest_collector_client`, `reqwest_blocking_collector_client`, `isahc_collector_client` \ - features to use a build in http client. Or use `with_http_client` method in pipeline to \ - provide your own implementation." - .to_string(), - }), - #[cfg(feature = "isahc_collector_client")] - CollectorHttpClient::Isahc => { - use isahc::config::Configurable; - - let mut builder = isahc::HttpClient::builder().timeout(collector_timeout); - - if let (Some(username), Some(password)) = (collector_username, collector_password) { - builder = builder - .authentication(isahc::auth::Authentication::basic()) - .credentials(isahc::auth::Credentials::new(username, password)); - } - - let client = builder.build().map_err(|err| crate::Error::ConfigError { - config_name: "http_client", - pipeline_name: "collector", - reason: format!("cannot create isahc http client, {}", err), - })?; - Ok(Box::new(client)) - } - #[cfg(feature = "reqwest_blocking_collector_client")] - CollectorHttpClient::ReqwestBlocking => { - use headers::authorization::Credentials; - - let mut builder = - reqwest::blocking::ClientBuilder::new().timeout(collector_timeout); - if let (Some(username), Some(password)) = (collector_username, collector_password) { - let mut map = http::HeaderMap::with_capacity(1); - let auth_header_val = - headers::Authorization::basic(username.as_str(), password.as_str()); - map.insert(http::header::AUTHORIZATION, auth_header_val.0.encode()); - builder = builder.default_headers(map); - } - let client = builder.build().map_err::(|err| { - crate::Error::ConfigError { - pipeline_name: "http_client", - config_name: "collector", - reason: format!("cannot create reqwest blocking http client, {}", err), - } - })?; - Ok(Box::new(client)) - } - #[cfg(feature = "reqwest_collector_client")] - CollectorHttpClient::Reqwest => { - use headers::authorization::Credentials; - - let mut builder = reqwest::ClientBuilder::new().timeout(collector_timeout); - if let (Some(username), Some(password)) = (collector_username, collector_password) { - let mut map = http::HeaderMap::with_capacity(1); - let auth_header_val = - headers::Authorization::basic(username.as_str(), password.as_str()); - map.insert(http::header::AUTHORIZATION, auth_header_val.0.encode()); - builder = builder.default_headers(map); - } - let client = builder.build().map_err::(|err| { - crate::Error::ConfigError { - pipeline_name: "http_client", - config_name: "collector", - reason: format!("cannot create reqwest http client, {}", err), - } - })?; - Ok(Box::new(client)) - } - #[cfg(any(feature = "hyper_collector_client", feature = "hyper_tls_collector_client"))] - CollectorHttpClient::Hyper => { - use headers::authorization::Credentials; - use opentelemetry_http::hyper::HyperClient; - use hyper::{Client, Body}; - - #[cfg(feature = "hyper_tls_collector_client")] - let inner: Client<_, Body> = Client::builder().build(hyper_tls::HttpsConnector::new()); - #[cfg(feature = "hyper_collector_client")] - let inner: Client<_, Body> = Client::new(); - - let client = if let (Some(username), Some(password)) = - (collector_username, collector_password) - { - let auth_header_val = - headers::Authorization::basic(username.as_str(), password.as_str()); - HyperClient::new_with_timeout_and_authorization_header( - inner, - collector_timeout, - auth_header_val.0.encode(), - ) - } else { - HyperClient::new_with_timeout(inner, collector_timeout) - }; - Ok(Box::new(client)) - } - } - } -} - -#[cfg(test)] -#[cfg(feature = "rt-tokio")] -pub(crate) mod test_http_client { - use async_trait::async_trait; - use bytes::Bytes; - use http::{Request, Response}; - use opentelemetry_http::{HttpClient, HttpError}; - use std::fmt::Debug; - - pub(crate) struct TestHttpClient; - - impl Debug for TestHttpClient { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("test http client") - } - } - - #[async_trait] - impl HttpClient for TestHttpClient { - async fn send(&self, _request: Request>) -> Result, HttpError> { - Err("wrong uri set in http client".into()) - } - } -} - -#[cfg(test)] -#[cfg(all(feature = "collector_client", feature = "rt-tokio"))] -mod collector_client_tests { - use crate::config::build_config_and_process; - use crate::config::collector::http_client::test_http_client; - use crate::exporter::thrift::jaeger::Batch; - use crate::new_collector_pipeline; - use opentelemetry::trace::TraceError; - use opentelemetry_sdk::runtime::Tokio; - - // Ignore this test as it is flaky and the opentelemetry-jaeger is on-track for deprecation - #[ignore] - #[test] - fn test_bring_your_own_client() -> Result<(), TraceError> { - let invalid_uri_builder = new_collector_pipeline() - .with_endpoint("localhost:6831") - .with_http_client(test_http_client::TestHttpClient); - let (_, process) = build_config_and_process(None, None); - let uploader = invalid_uri_builder.build_uploader::()?; - let res = futures_executor::block_on(async { - uploader - .upload(Batch::new(process.into(), Vec::new())) - .await - }); - assert_eq!( - format!("{:?}", res.err().unwrap()), - "Other(\"wrong uri set in http client\")" - ); - - let valid_uri_builder = new_collector_pipeline() - .with_http_client(test_http_client::TestHttpClient) - .build_uploader::(); - - assert!(valid_uri_builder.is_ok()); - Ok(()) - } -} diff --git a/opentelemetry-jaeger/src/exporter/config/collector/mod.rs b/opentelemetry-jaeger/src/exporter/config/collector/mod.rs deleted file mode 100644 index 9400beadf2..0000000000 --- a/opentelemetry-jaeger/src/exporter/config/collector/mod.rs +++ /dev/null @@ -1,706 +0,0 @@ -use std::borrow::BorrowMut; -use std::env; -use std::sync::Arc; -#[cfg(feature = "collector_client")] -use std::time::Duration; - -use http::Uri; - -use opentelemetry::trace::TraceError; -#[cfg(feature = "collector_client")] -use opentelemetry_http::HttpClient; -use opentelemetry_sdk::trace::{BatchConfig, BatchSpanProcessor, Config, Tracer, TracerProvider}; - -#[cfg(feature = "collector_client")] -use crate::config::collector::http_client::CollectorHttpClient; -#[cfg(feature = "collector_client")] -use crate::exporter::collector::AsyncHttpClient; -#[cfg(feature = "wasm_collector_client")] -use crate::exporter::collector::WasmCollector; -use crate::exporter::config::{ - build_config_and_process, install_tracer_provider_and_get_tracer, HasRequiredConfig, - TransformationConfig, -}; -use crate::exporter::uploader::{AsyncUploader, Uploader}; -use crate::{Exporter, JaegerTraceRuntime}; - -#[cfg(feature = "collector_client")] -mod http_client; - -/// HTTP endpoint for Jaeger collector. -/// e.g. "http://localhost:14250" -const ENV_ENDPOINT: &str = "OTEL_EXPORTER_JAEGER_ENDPOINT"; - -const DEFAULT_ENDPOINT: &str = "http://localhost:14250/api/trace"; - -/// Timeout for Jaeger collector. -#[cfg(feature = "collector_client")] -const ENV_TIMEOUT: &str = "OTEL_EXPORTER_JAEGER_TIMEOUT"; - -/// Default of 10s -#[cfg(feature = "collector_client")] -const DEFAULT_COLLECTOR_TIMEOUT: Duration = Duration::from_secs(10); - -/// Username to send as part of "Basic" authentication to the collector endpoint. -const ENV_USERNAME: &str = "OTEL_EXPORTER_JAEGER_USER"; - -/// Password to send as part of "Basic" authentication to the collector endpoint. -const ENV_PASSWORD: &str = "OTEL_EXPORTER_JAEGER_PASSWORD"; - -/// CollectorPipeline config and build a exporter targeting a jaeger collector using HTTP protocol. -/// -/// Deprecation Notice: -/// Ingestion of OTLP is now supported in Jaeger please check [crates.io] for more details. -/// -/// ## Environment variables -/// -/// - `OTEL_EXPORTER_JAEGER_ENDPOINT`: set the endpoint of the collector. Usually starts with `http://` or `https://` -/// -/// - `OTEL_EXPORTER_JAEGER_TIMEOUT`: set the timeout of the http client timeout. It only applies to build in http clients. -/// -/// - `OTEL_EXPORTER_JAEGER_USER`: set the username. Part of the authentication for the collector. It only applies to build in http clients. -/// -/// - `OTEL_EXPORTER_JAEGER_PASSWORD`: set the password. Part of the authentication for the collector. It only applies to build in http clients. -/// -/// ## Built-in http clients -/// To help user setup the exporter, `opentelemetry-jaeger` provides the following build in http client -/// implementation and relative configurations. -/// -/// - [hyper], requires `hyper_collector_client` feature enabled, use [`with_hyper`][CollectorPipeline::with_hyper] function to setup. -/// - [isahc], requires `isahc_collector_client` feature enabled, use [`with_isahc`][CollectorPipeline::with_isahc] function to setup. -/// - [reqwest], requires `reqwest_collector_client` feature enabled, use [`with_reqwest`][CollectorPipeline::with_reqwest] function to setup. -/// - [reqwest blocking client], requires `reqwest_blocking_collector_client` feature enabled, use [`with_reqwest_blocking`][CollectorPipeline::with_reqwest_blocking] function to setup. -/// -/// Additionally you can enable https -/// -/// Note that the functions to setup build in http clients override each other. That means if you have a pipeline with the following setup -/// -/// ```no_run -/// # use opentelemetry::trace::TraceError; -/// # #[cfg(all(feature="reqwest_collector_client", feature="surf_collector_client"))] -/// let tracer = opentelemetry_jaeger::new_collector_pipeline() -/// .with_surf() -/// .with_reqwest() -/// .install_batch(opentelemetry_sdk::runtime::Tokio) -/// # .unwrap(); -/// ``` -/// -/// The pipeline will use [reqwest] http client. -/// -/// [reqwest]: reqwest::Client -/// [reqwest blocking client]: reqwest::blocking::Client -/// [crates.io]: https://crates.io/crates/opentelemetry-jaeger -#[derive(Debug)] -#[deprecated( - since = "0.21.0", - note = "Please migrate to opentelemetry-otlp exporter." -)] -pub struct CollectorPipeline { - transformation_config: TransformationConfig, - trace_config: Option, - batch_config: Option, - - #[cfg(feature = "collector_client")] - collector_timeout: Duration, - // only used by builtin http clients. - collector_endpoint: Option, - collector_username: Option, - collector_password: Option, - - client_config: ClientConfig, -} - -impl Default for CollectorPipeline { - fn default() -> Self { - Self { - #[cfg(feature = "collector_client")] - collector_timeout: DEFAULT_COLLECTOR_TIMEOUT, - collector_endpoint: None, - collector_username: None, - collector_password: None, - client_config: ClientConfig::default(), - transformation_config: Default::default(), - trace_config: Default::default(), - batch_config: Some(Default::default()), - } - } -} - -// implement the seal trait -impl HasRequiredConfig for CollectorPipeline { - fn set_transformation_config(&mut self, f: T) - where - T: FnOnce(&mut TransformationConfig), - { - f(self.transformation_config.borrow_mut()) - } - - fn set_trace_config(&mut self, config: Config) { - self.trace_config = Some(config) - } - - fn set_batch_config(&mut self, config: BatchConfig) { - self.batch_config = Some(config) - } -} - -#[derive(Debug)] -enum ClientConfig { - #[cfg(feature = "collector_client")] - Http { client_type: CollectorHttpClient }, - #[cfg(feature = "wasm_collector_client")] - Wasm, // no config is available for wasm for now. But we can add in the future -} - -#[allow(clippy::derivable_impls)] -impl Default for ClientConfig { - fn default() -> Self { - // as long as collector is enabled, we will in favor of it - #[cfg(feature = "collector_client")] - { - ClientConfig::Http { - client_type: CollectorHttpClient::None, - } - } - // when collector_client is disabled and wasm_collector_client is enabled - #[cfg(not(feature = "collector_client"))] - ClientConfig::Wasm - } -} - -/// Start a new pipeline to configure a exporter that target a jaeger collector. -/// -/// See details for each configurations at [`CollectorPipeline`]. -/// -/// Deprecation Notice: -/// Ingestion of OTLP is now supported in Jaeger please check [crates.io] for more details. -/// -/// [`CollectorPipeline`]: crate::config::collector::CollectorPipeline -/// [crates.io]: https://crates.io/crates/opentelemetry-jaeger -#[cfg(feature = "collector_client")] -#[deprecated( - since = "0.21.0", - note = "Please migrate to opentelemetry-otlp exporter." -)] -pub fn new_collector_pipeline() -> CollectorPipeline { - CollectorPipeline::default() -} - -/// Similar to [`new_collector_pipeline`] but the exporter is configured to run with wasm. -/// -/// Deprecation Notice: -/// Ingestion of OTLP is now supported in Jaeger please check [crates.io] for more details. -/// -/// [crates.io]: https://crates.io/crates/opentelemetry-jaeger -#[cfg(feature = "wasm_collector_client")] -#[allow(clippy::field_reassign_with_default)] -// make sure when collector_cilent and wasm_collector_client are both set. We will create a wasm type client -#[deprecated( - since = "0.21.0", - note = "Please migrate to opentelemetry-otlp exporter." -)] -pub fn new_wasm_collector_pipeline() -> CollectorPipeline { - let mut pipeline = CollectorPipeline::default(); - pipeline.client_config = ClientConfig::Wasm; - pipeline -} - -impl CollectorPipeline { - /// Set the http client timeout. - /// - /// This function only applies to build in http clients. - /// - /// Default to be 10s. - #[cfg(feature = "collector_client")] - pub fn with_timeout(self, collector_timeout: Duration) -> Self { - Self { - collector_timeout, - ..self - } - } - - /// Set the collector endpoint. - /// - /// E.g. "http://localhost:14268/api/traces" - pub fn with_endpoint>(self, collector_endpoint: T) -> Self { - Self { - collector_endpoint: Some(collector_endpoint.into()), - ..self - } - } - - /// Set the username used in authentication to communicate with the collector. - /// - /// *Note* that if the password is not set by calling `with_password` or set `OTEL_EXPORTER_JAEGER_PASSWORD` - /// environment variables. The username will be ignored. - /// - /// This function only applies to build in http clients. - pub fn with_username>(self, collector_username: S) -> Self { - Self { - collector_username: Some(collector_username.into()), - ..self - } - } - - /// Set the password used in authentication to communicate with the collector. - /// - /// *Note* that if the username is not set by calling `with_username` or set `OTEL_EXPORTER_JAEGER_USER` - /// environment variables. The username will be ignored. - /// - /// This function only applies to build in http clients. - pub fn with_password>(self, collector_password: S) -> Self { - Self { - collector_password: Some(collector_password.into()), - ..self - } - } - - /// Get collector's username set in the builder. Default to be the value of - /// `OTEL_EXPORTER_JAEGER_USER` environment variable. - /// - /// If users uses custom http client. This function can help retrieve the value of - /// `OTEL_EXPORTER_JAEGER_USER` environment variable. - pub fn collector_username(&self) -> Option { - self.collector_username.clone() - } - - /// Get the collector's password set in the builder. Default to be the value of - /// `OTEL_EXPORTER_JAEGER_PASSWORD` environment variable. - /// - /// If users uses custom http client. This function can help retrieve the value of - /// `OTEL_EXPORTER_JAEGER_PASSWORD` environment variable. - pub fn collector_password(&self) -> Option { - self.collector_password.clone() - } - - /// Custom http client used to send spans. - /// - /// **Note** that all configuration other than the [`endpoint`][CollectorPipeline::with_endpoint] are not - /// applicable to custom clients. - #[cfg(feature = "collector_client")] - pub fn with_http_client(mut self, client: T) -> Self { - self.client_config = match self.client_config { - ClientConfig::Http { .. } => ClientConfig::Http { - client_type: CollectorHttpClient::Custom(Box::new(client)), - }, - // noop for wasm - #[cfg(feature = "wasm_collector_client")] - ClientConfig::Wasm => ClientConfig::Wasm, - }; - self - } - - /// Use isahc http client in the exporter. - #[cfg(feature = "isahc_collector_client")] - pub fn with_isahc(self) -> Self { - Self { - client_config: ClientConfig::Http { - client_type: CollectorHttpClient::Isahc, - }, - ..self - } - } - - /// Use reqwest http client in the exporter. - #[cfg(feature = "reqwest_collector_client")] - pub fn with_reqwest(self) -> Self { - Self { - client_config: ClientConfig::Http { - client_type: CollectorHttpClient::Reqwest, - }, - ..self - } - } - - /// Use reqwest blocking http client in the exporter. - #[cfg(feature = "reqwest_blocking_collector_client")] - pub fn with_reqwest_blocking(self) -> Self { - Self { - client_config: ClientConfig::Http { - client_type: CollectorHttpClient::ReqwestBlocking, - }, - ..self - } - } - - /// Use hyper http client in the exporter. - #[cfg(feature = "hyper_collector_client")] - pub fn with_hyper(self) -> Self { - Self { - client_config: ClientConfig::Http { - client_type: CollectorHttpClient::Hyper, - }, - ..self - } - } - - /// Set the service name of the application. It generally is the name of application. - /// Critically, Jaeger backend depends on `Span.Process.ServiceName` to identify the service - /// that produced the spans. - /// - /// Opentelemetry allows set the service name using multiple methods. - /// This functions takes priority over all other methods. - /// - /// If the service name is not set. It will default to be `unknown_service`. - pub fn with_service_name>(mut self, service_name: T) -> Self { - self.set_transformation_config(|config| { - config.service_name = Some(service_name.into()); - }); - self - } - - /// Config whether to export information of instrumentation library. - /// - /// It's required to [report instrumentation library as span tags]. - /// However it does have a overhead on performance, performance sensitive applications can - /// use this function to opt out reporting instrumentation library. - /// - /// Default to be `true`. - /// - /// [report instrumentation library as span tags]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/non-otlp.md#instrumentationscope - pub fn with_instrumentation_library_tags(mut self, should_export: bool) -> Self { - self.set_transformation_config(|config| { - config.export_instrument_library = should_export; - }); - self - } - - /// Assign the opentelemetry SDK configurations for the exporter pipeline. - /// - /// For mapping between opentelemetry configurations and Jaeger spans. Please refer [the spec]. - /// - /// [the spec]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#mappings - /// # Examples - /// Set service name via resource. - /// ```rust - /// use opentelemetry::KeyValue; - /// use opentelemetry_sdk::{Resource, trace::Config}; - /// - /// let pipeline = opentelemetry_jaeger::new_collector_pipeline() - /// .with_trace_config( - /// Config::default() - /// .with_resource(Resource::new(vec![KeyValue::new("service.name", "my-service")])) - /// ); - /// - /// ``` - pub fn with_trace_config(mut self, config: Config) -> Self { - self.set_trace_config(config); - self - } - - /// Assign the batch span processor for the exporter pipeline. - /// - /// # Examples - /// Set max queue size. - /// ```rust - /// use opentelemetry_sdk::trace::BatchConfigBuilder; - /// - /// let pipeline = opentelemetry_jaeger::new_collector_pipeline() - /// .with_batch_processor_config( - /// BatchConfigBuilder::default() - /// .with_max_queue_size(200) - /// .build() - /// ); - /// - /// ``` - pub fn with_batch_processor_config(mut self, config: BatchConfig) -> Self { - self.set_batch_config(config); - self - } - - /// Build a `TracerProvider` using a async exporter and configurations from the pipeline. - /// - /// The exporter will collect spans in a batch and send them to the agent. - /// - /// It's possible to lose spans up to a batch when the application shuts down. So users should - /// use [`shut_down_tracer_provider`] to block the shut down process until - /// all remaining spans have been sent. - /// - /// Commonly used runtime are provided via `rt-tokio`, `rt-tokio-current-thread`, `rt-async-std` - /// features. - /// - /// [`shut_down_tracer_provider`]: opentelemetry::global::shutdown_tracer_provider - // todo: we don't need JaegerTraceRuntime, we only need otel runtime - pub fn build_batch( - mut self, - runtime: R, - ) -> Result { - let mut builder = TracerProvider::builder(); - // build sdk trace config and jaeger process. - // some attributes like service name has attributes like service name - let export_instrument_library = self.transformation_config.export_instrument_library; - let (config, process) = build_config_and_process( - self.trace_config.take(), - self.transformation_config.service_name.take(), - ); - let batch_config = self.batch_config.take(); - let uploader = self.build_uploader::()?; - let exporter = Exporter::new(process.into(), export_instrument_library, uploader); - let batch_processor = BatchSpanProcessor::builder(exporter, runtime) - .with_batch_config(batch_config.unwrap_or_default()) - .build(); - - builder = builder.with_span_processor(batch_processor); - builder = builder.with_config(config); - - Ok(builder.build()) - } - - /// Similar to [`build_batch`][CollectorPipeline::build_batch] but also returns a tracer from the - /// tracer provider. - /// - /// The tracer name is `opentelemetry-jaeger`. The tracer version will be the version of this crate. - pub fn install_batch(self, runtime: R) -> Result { - let tracer_provider = self.build_batch(runtime)?; - install_tracer_provider_and_get_tracer(tracer_provider) - } - - /// Build an jaeger exporter targeting a jaeger collector. - pub fn build_collector_exporter(mut self) -> Result - where - R: JaegerTraceRuntime, - { - let export_instrument_library = self.transformation_config.export_instrument_library; - let (_, process) = build_config_and_process( - self.trace_config.take(), - self.transformation_config.service_name.take(), - ); - let uploader = self.build_uploader::()?; - let exporter = Exporter::new(process.into(), export_instrument_library, uploader); - Ok(exporter) - } - - fn build_uploader(self) -> Result, crate::Error> - where - R: JaegerTraceRuntime, - { - let endpoint = self.resolve_endpoint()?; - let username = self.resolve_username(); - let password = self.resolve_password(); - #[cfg(feature = "collector_client")] - let timeout = self.resolve_timeout(); - match self.client_config { - #[cfg(feature = "collector_client")] - ClientConfig::Http { client_type } => { - let client = client_type.build_client(username, password, timeout)?; - - let collector = AsyncHttpClient::new(endpoint, client); - Ok(Arc::new(AsyncUploader::::Collector(collector))) - } - #[cfg(feature = "wasm_collector_client")] - ClientConfig::Wasm => { - let collector = WasmCollector::new(endpoint, username, password) - .map_err::(Into::into)?; - Ok(Arc::new(AsyncUploader::::WasmCollector(collector))) - } - } - } - - fn resolve_env_var(env_var: &'static str) -> Option { - env::var(env_var).ok().filter(|var| !var.is_empty()) - } - - // if provided value from environment variable or the builder is invalid, return error - fn resolve_endpoint(&self) -> Result { - let endpoint_from_env = Self::resolve_env_var(ENV_ENDPOINT) - .map(|endpoint| { - Uri::try_from(endpoint.as_str()).map_err::(|err| { - crate::Error::ConfigError { - pipeline_name: "collector", - config_name: "collector_endpoint", - reason: format!("invalid uri from environment variable, {}", err), - } - }) - }) - .transpose()?; - - Ok(match endpoint_from_env { - Some(endpoint) => endpoint, - None => { - if let Some(endpoint) = &self.collector_endpoint { - Uri::try_from(endpoint.as_str()).map_err::(|err| { - crate::Error::ConfigError { - pipeline_name: "collector", - config_name: "collector_endpoint", - reason: format!("invalid uri from the builder, {}", err), - } - })? - } else { - Uri::try_from(DEFAULT_ENDPOINT).unwrap() // default endpoint should always valid - } - } - }) - } - - #[cfg(feature = "collector_client")] - fn resolve_timeout(&self) -> Duration { - match Self::resolve_env_var(ENV_TIMEOUT) { - Some(timeout) => match timeout.parse() { - Ok(timeout) => Duration::from_millis(timeout), - Err(e) => { - eprintln!("{} malformed default to 10s: {}", ENV_TIMEOUT, e); - self.collector_timeout - } - }, - None => self.collector_timeout, - } - } - - fn resolve_username(&self) -> Option { - Self::resolve_env_var(ENV_USERNAME).or_else(|| self.collector_username.clone()) - } - - fn resolve_password(&self) -> Option { - Self::resolve_env_var(ENV_PASSWORD).or_else(|| self.collector_password.clone()) - } -} - -#[cfg(test)] -#[cfg(feature = "rt-tokio")] -mod tests { - use super::*; - - #[test] - fn test_resolve_endpoint() { - struct TestCase<'a> { - description: &'a str, - env_var: &'a str, - builder_endpoint: Option<&'a str>, - expected_result: Result, - } - let test_cases = vec![ - TestCase { - description: "Positive: Endpoint from environment variable exists", - env_var: "http://example.com", - builder_endpoint: None, - expected_result: Ok(Uri::try_from("http://example.com").unwrap()), - }, - TestCase { - description: "Positive: Endpoint from builder", - env_var: "", - builder_endpoint: Some("http://example.com"), - expected_result: Ok(Uri::try_from("http://example.com").unwrap()), - }, - TestCase { - description: "Negative: Invalid URI from environment variable", - env_var: "invalid random uri", - builder_endpoint: None, - expected_result: Err(crate::Error::ConfigError { - pipeline_name: "collector", - config_name: "collector_endpoint", - reason: "invalid uri from environment variable, invalid uri character" - .to_string(), - }), - }, - TestCase { - description: "Negative: Invalid URI from builder", - env_var: "", - builder_endpoint: Some("invalid random uri"), - expected_result: Err(crate::Error::ConfigError { - pipeline_name: "collector", - config_name: "collector_endpoint", - reason: "invalid uri from the builder, invalid uri character".to_string(), - }), - }, - TestCase { - description: "Positive: Default endpoint (no environment variable set)", - env_var: "", - builder_endpoint: None, - expected_result: Ok(Uri::try_from(DEFAULT_ENDPOINT).unwrap()), - }, - ]; - for test_case in test_cases { - env::set_var(ENV_ENDPOINT, test_case.env_var); - let builder = CollectorPipeline { - collector_endpoint: test_case.builder_endpoint.map(|s| s.to_string()), - ..Default::default() - }; - let result = builder.resolve_endpoint(); - match test_case.expected_result { - Ok(expected) => { - assert_eq!(result.unwrap(), expected, "{}", test_case.description); - } - Err(expected_err) => { - assert!( - result.is_err(), - "{}, expected error, get {}", - test_case.description, - result.unwrap() - ); - match (result.unwrap_err(), expected_err) { - ( - crate::Error::ConfigError { - pipeline_name: result_pipeline_name, - config_name: result_config_name, - reason: result_reason, - }, - crate::Error::ConfigError { - pipeline_name: expected_pipeline_name, - config_name: expected_config_name, - reason: expected_reason, - }, - ) => { - assert_eq!( - result_pipeline_name, expected_pipeline_name, - "{}", - test_case.description - ); - assert_eq!( - result_config_name, expected_config_name, - "{}", - test_case.description - ); - assert_eq!(result_reason, expected_reason, "{}", test_case.description); - } - _ => panic!("we don't expect collector to return other error"), - } - } - } - env::remove_var(ENV_ENDPOINT); - } - } - - #[test] - fn test_resolve_timeout() { - struct TestCase<'a> { - description: &'a str, - env_var: &'a str, - builder_var: Option, - expected_duration: Duration, - } - let test_cases = vec![ - TestCase { - description: "Valid environment variable", - env_var: "5000", - builder_var: None, - expected_duration: Duration::from_millis(5000), - }, - TestCase { - description: "Invalid environment variable", - env_var: "invalid", - builder_var: None, - expected_duration: DEFAULT_COLLECTOR_TIMEOUT, - }, - TestCase { - description: "Missing environment variable", - env_var: "", - builder_var: Some(Duration::from_millis(5000)), - expected_duration: Duration::from_millis(5000), - }, - ]; - for test_case in test_cases { - env::set_var(ENV_TIMEOUT, test_case.env_var); - let mut builder = CollectorPipeline::default(); - if let Some(timeout) = test_case.builder_var { - builder = builder.with_timeout(timeout); - } - let result = builder.resolve_timeout(); - assert_eq!( - result, test_case.expected_duration, - "{}", - test_case.description - ); - env::remove_var(ENV_TIMEOUT); - } - } -} diff --git a/opentelemetry-jaeger/src/exporter/config/mod.rs b/opentelemetry-jaeger/src/exporter/config/mod.rs deleted file mode 100644 index 93218867af..0000000000 --- a/opentelemetry-jaeger/src/exporter/config/mod.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Configurations to build a jaeger exporter. -//! -//! The jaeger exporter can send spans to [jaeger agent] or [jaeger collector]. The agent is usually -//! deployed along with the application like a sidecar. The collector is usually deployed a stand alone -//! application and receive spans from multiple sources. The exporter will use UDP to send spans to -//! agents and use HTTP/TCP to send spans to collectors. See [jaeger deployment guide] for more details. -//! -//! [jaeger agent]: https://www.jaegertracing.io/docs/1.31/deployment/#agent -//! [jaeger collector]: https://www.jaegertracing.io/docs/1.31/deployment/#collector -//! [jaeger deployment guide]: https://www.jaegertracing.io/docs/1.31/deployment - -use crate::Process; -use opentelemetry::{global, trace::TraceError, KeyValue}; -use opentelemetry_sdk::trace::{BatchConfig, Config, Tracer, TracerProvider}; -use opentelemetry_semantic_conventions as semcov; - -/// Config a exporter that sends the spans to a [jaeger agent](https://www.jaegertracing.io/docs/1.31/deployment/#agent). -pub mod agent; -/// Config a exporter that bypass the agent and send spans directly to [jaeger collector](https://www.jaegertracing.io/docs/1.31/deployment/#collector). -#[cfg(any(feature = "collector_client", feature = "wasm_collector_client"))] -pub mod collector; - -// configurations and overrides on how to transform OTLP spans to Jaeger spans. -#[derive(Debug)] -struct TransformationConfig { - export_instrument_library: bool, - service_name: Option, -} - -impl Default for TransformationConfig { - fn default() -> Self { - TransformationConfig { - export_instrument_library: true, - service_name: None, - } - } -} - -// pipeline must have transformation config, trace config and batch config. -trait HasRequiredConfig { - fn set_transformation_config(&mut self, f: T) - where - T: FnOnce(&mut TransformationConfig); - - fn set_trace_config(&mut self, config: Config); - - fn set_batch_config(&mut self, config: BatchConfig); -} - -// To reduce the overhead of copying service name in every spans. We convert resource into jaeger tags -// and store them into process. And set the resource in trace config to empty. -// -// There are multiple ways to set the service name. A `service.name` tag will be always added -// to the process tags. -fn build_config_and_process( - config: Option, - service_name_opt: Option, -) -> (Config, Process) { - let config = config.unwrap_or_default(); - - let service_name = service_name_opt.unwrap_or_else(|| { - config - .resource - .get(semcov::resource::SERVICE_NAME.into()) - .map(|v| v.to_string()) - .unwrap_or_else(|| "unknown_service".to_string()) - }); - - // merge the tags and resource. Resources take priority. - let mut tags = config - .resource - .iter() - .filter(|(key, _)| key.as_str() != semcov::resource::SERVICE_NAME) - .map(|(key, value)| KeyValue::new(key.clone(), value.clone())) - .collect::>(); - - tags.push(KeyValue::new( - semcov::resource::SERVICE_NAME, - service_name.clone(), - )); - - (config, Process { service_name, tags }) -} - -pub(crate) fn install_tracer_provider_and_get_tracer( - tracer_provider: TracerProvider, -) -> Result { - let tracer = opentelemetry::trace::TracerProvider::tracer_builder( - &tracer_provider, - "opentelemetry-jaeger", - ) - .with_version(env!("CARGO_PKG_VERSION")) - .with_schema_url(semcov::SCHEMA_URL) - .build(); - let _ = global::set_tracer_provider(tracer_provider); - Ok(tracer) -} - -#[cfg(test)] -mod tests { - use crate::exporter::config::build_config_and_process; - use crate::new_agent_pipeline; - use opentelemetry::KeyValue; - use opentelemetry_sdk::{trace::Config, Resource}; - use std::env; - - #[test] - fn test_set_service_name() { - let service_name = "halloween_service".to_string(); - - // set via builder's service name, it has highest priority - let (_, process) = build_config_and_process(None, Some(service_name.clone())); - assert_eq!(process.service_name, service_name); - - // make sure the tags in resource are moved to process - let trace_config = Config::default() - .with_resource(Resource::new(vec![KeyValue::new("test-key", "test-value")])); - let (_, process) = build_config_and_process(Some(trace_config), Some(service_name)); - assert_eq!(process.tags.len(), 2); - } - - #[tokio::test] - async fn test_read_from_env() { - // OTEL_SERVICE_NAME env var also works - env::set_var("OTEL_SERVICE_NAME", "test service"); - let builder = new_agent_pipeline(); - let exporter = builder.build_sync_agent_exporter().unwrap(); - assert_eq!(exporter.process.service_name, "test service"); - env::set_var("OTEL_SERVICE_NAME", "") - } -} diff --git a/opentelemetry-jaeger/src/exporter/mod.rs b/opentelemetry-jaeger/src/exporter/mod.rs deleted file mode 100644 index 38aea1c411..0000000000 --- a/opentelemetry-jaeger/src/exporter/mod.rs +++ /dev/null @@ -1,473 +0,0 @@ -//! # Jaeger Exporter -//! -// Linting isn't detecting that it's used seems like linting bug. -#[allow(unused_imports)] -use std::convert::TryInto; -use std::fmt::Display; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; -use std::sync::Arc; -use std::time::{Duration, SystemTime}; - -use futures_core::future::BoxFuture; -#[cfg(feature = "isahc_collector_client")] -#[allow(unused_imports)] // this is actually used to configure authentication -use isahc::prelude::Configurable; - -use opentelemetry::{ - trace::{Event, Link, SpanKind, Status}, - InstrumentationLibrary, Key, KeyValue, -}; -use opentelemetry_sdk::export::{ - trace::{ExportResult, SpanData, SpanExporter}, - ExportError, -}; -use opentelemetry_sdk::trace::SpanEvents; - -use crate::exporter::uploader::Uploader; - -use self::runtime::JaegerTraceRuntime; -use self::thrift::jaeger; - -mod agent; -#[cfg(any(feature = "collector_client", feature = "wasm_collector_client"))] -mod collector; -pub(crate) mod runtime; -#[allow(clippy::all, unreachable_pub, dead_code)] -#[rustfmt::skip] // don't format generated files -mod thrift; -pub mod config; -pub(crate) mod transport; -mod uploader; - -/// Instrument Library name MUST be reported in Jaeger Span tags with the following key -const INSTRUMENTATION_LIBRARY_NAME: &str = "otel.library.name"; - -/// Instrument Library version MUST be reported in Jaeger Span tags with the following key -const INSTRUMENTATION_LIBRARY_VERSION: &str = "otel.library.version"; - -/// Jaeger span exporter -/// -/// Deprecation Notice: -/// Ingestion of OTLP is now supported in Jaeger please check [crates.io] for more details. -/// -/// [crates.io]: https://crates.io/crates/opentelemetry-jaeger -#[deprecated( - since = "0.21.0", - note = "Please migrate to opentelemetry-otlp exporter." -)] -#[derive(Debug)] -pub struct Exporter { - /// Whether or not to export instrumentation information. - export_instrumentation_lib: bool, - uploader: Arc, - process: jaeger::Process, -} - -impl Exporter { - fn new( - process: jaeger::Process, - export_instrumentation_lib: bool, - uploader: Arc, - ) -> Exporter { - Exporter { - export_instrumentation_lib, - uploader, - process, - } - } -} - -/// Jaeger process configuration -/// -/// Deprecation Notice: -/// Ingestion of OTLP is now supported in Jaeger please check [crates.io] for more details. -/// -/// [crates.io]: https://crates.io/crates/opentelemetry-jaeger -#[deprecated( - since = "0.21.0", - note = "Please migrate to opentelemetry-otlp exporter." -)] -#[derive(Debug, Default)] -pub struct Process { - /// Jaeger service name - pub service_name: String, - /// Jaeger tags - pub tags: Vec, -} - -impl SpanExporter for Exporter { - fn export(&mut self, batch: Vec) -> BoxFuture<'static, ExportResult> { - let mut jaeger_spans: Vec = Vec::with_capacity(batch.len()); - let process = self.process.clone(); - - for span in batch.into_iter() { - jaeger_spans.push(convert_otel_span_into_jaeger_span( - span, - self.export_instrumentation_lib, - )); - } - - let uploader = self.uploader.clone(); - Box::pin(async move { - uploader - .upload(jaeger::Batch::new(process, jaeger_spans)) - .await - }) - } -} - -fn links_to_references(links: &[Link]) -> Option> { - if !links.is_empty() { - let refs = links - .iter() - .map(|link| { - let span_context = &link.span_context; - let trace_id_bytes = span_context.trace_id().to_bytes(); - let (high, low) = trace_id_bytes.split_at(8); - let trace_id_high = i64::from_be_bytes(high.try_into().unwrap()); - let trace_id_low = i64::from_be_bytes(low.try_into().unwrap()); - - jaeger::SpanRef::new( - jaeger::SpanRefType::FollowsFrom, - trace_id_low, - trace_id_high, - i64::from_be_bytes(span_context.span_id().to_bytes()), - ) - }) - .collect(); - Some(refs) - } else { - None - } -} - -/// Convert spans to jaeger thrift span for exporting. -fn convert_otel_span_into_jaeger_span(span: SpanData, export_instrument_lib: bool) -> jaeger::Span { - let trace_id_bytes = span.span_context.trace_id().to_bytes(); - let (high, low) = trace_id_bytes.split_at(8); - let trace_id_high = i64::from_be_bytes(high.try_into().unwrap()); - let trace_id_low = i64::from_be_bytes(low.try_into().unwrap()); - jaeger::Span { - trace_id_low, - trace_id_high, - span_id: i64::from_be_bytes(span.span_context.span_id().to_bytes()), - parent_span_id: i64::from_be_bytes(span.parent_span_id.to_bytes()), - operation_name: span.name.into_owned(), - references: links_to_references(span.links.as_ref()), - flags: span.span_context.trace_flags().to_u8() as i32, - start_time: span - .start_time - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap_or_else(|_| Duration::from_secs(0)) - .as_micros() as i64, - duration: span - .end_time - .duration_since(span.start_time) - .unwrap_or_else(|_| Duration::from_secs(0)) - .as_micros() as i64, - tags: Some(build_span_tags( - span.attributes, - if export_instrument_lib { - Some(span.instrumentation_lib) - } else { - None - }, - span.status, - span.span_kind, - )), - logs: events_to_logs(span.events), - } -} - -fn build_span_tags( - attrs: Vec, - instrumentation_lib: Option, - status: Status, - kind: SpanKind, -) -> Vec { - let mut user_overrides = UserOverrides::default(); - // TODO determine if namespacing is required to avoid collisions with set attributes - let mut tags = attrs - .into_iter() - .map(|kv| { - user_overrides.record_attr(kv.key.as_str()); - kv.into() - }) - .collect::>(); - - if let Some(instrumentation_lib) = instrumentation_lib { - // Set instrument library tags - tags.push(KeyValue::new(INSTRUMENTATION_LIBRARY_NAME, instrumentation_lib.name).into()); - if let Some(version) = instrumentation_lib.version { - tags.push(KeyValue::new(INSTRUMENTATION_LIBRARY_VERSION, version).into()) - } - } - - if !user_overrides.span_kind && kind != SpanKind::Internal { - tags.push(Key::new(SPAN_KIND).string(format_span_kind(kind)).into()); - } - - match status { - Status::Unset => {} - Status::Ok => { - if !user_overrides.status_code { - tags.push(KeyValue::new(OTEL_STATUS_CODE, "OK").into()); - } - } - Status::Error { - description: message, - } => { - if !user_overrides.error { - tags.push(Key::new(ERROR).bool(true).into()); - } - - if !user_overrides.status_code { - tags.push(KeyValue::new(OTEL_STATUS_CODE, "ERROR").into()); - } - - if !message.is_empty() && !user_overrides.status_description { - tags.push(Key::new(OTEL_STATUS_DESCRIPTION).string(message).into()); - } - } - } - - tags -} - -fn format_span_kind(kind: SpanKind) -> &'static str { - match kind { - SpanKind::Client => "client", - SpanKind::Server => "server", - SpanKind::Producer => "producer", - SpanKind::Consumer => "consumer", - SpanKind::Internal => "internal", - } -} - -const ERROR: &str = "error"; -const SPAN_KIND: &str = "span.kind"; -const OTEL_STATUS_CODE: &str = "otel.status_code"; -const OTEL_STATUS_DESCRIPTION: &str = "otel.status_description"; - -#[derive(Default)] -struct UserOverrides { - error: bool, - span_kind: bool, - status_code: bool, - status_description: bool, -} - -impl UserOverrides { - fn record_attr(&mut self, attr: &str) { - match attr { - ERROR => self.error = true, - SPAN_KIND => self.span_kind = true, - OTEL_STATUS_CODE => self.status_code = true, - OTEL_STATUS_DESCRIPTION => self.status_description = true, - _ => (), - } - } -} - -fn events_to_logs(events: SpanEvents) -> Option> { - if events.is_empty() { - None - } else { - Some(events.into_iter().map(Into::into).collect()) - } -} - -/// Wrap type for errors from opentelemetry jaeger -#[derive(Debug)] -pub enum Error { - /// Error from thrift agents. - /// - /// If the spans was sent to jaeger agent. Refer [AgentPipeline](config::agent::AgentPipeline) for more details. - /// If the spans was sent to jaeger collector. Refer [CollectorPipeline](config::collector::CollectorPipeline) for more details. - ThriftAgentError(::thrift::Error), - - /// Pipeline fails because one of the configurations is invalid. - ConfigError { - /// the name of the pipeline. It can be `agent`, `collector` or `wasm collector` - pipeline_name: &'static str, - /// config name that has the error. - config_name: &'static str, - /// the underlying error message. - reason: String, - }, -} - -impl std::error::Error for Error {} - -impl From<::thrift::Error> for Error { - fn from(value: ::thrift::Error) -> Self { - Error::ThriftAgentError(value) - } -} - -impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::ThriftAgentError(err) => match err { - ::thrift::Error::Transport(transport_error) => { - write!( - f, - "thrift agent failed on transportation layer, {}, {}", - transport_error, transport_error.message - ) - } - ::thrift::Error::Protocol(protocol_error) => { - write!( - f, - "thrift agent failed on protocol layer, {}, {}", - protocol_error, protocol_error.message - ) - } - ::thrift::Error::Application(application_error) => { - write!( - f, - "thrift agent failed on application layer, {}, {}", - application_error, application_error.message - ) - } - ::thrift::Error::User(error) => { - write!(f, "thrift agent failed, {}", error) - } - }, - Error::ConfigError { - pipeline_name, - config_name, - reason, - } => write!( - f, - "{} pipeline fails because one of the configuration {} is invalid. {}", - pipeline_name, config_name, reason - ), - } - } -} - -impl ExportError for Error { - fn exporter_name(&self) -> &'static str { - "jaeger" - } -} - -/// Sample the first address provided to designate which IP family to bind the socket to. -/// IP families returned be INADDR_ANY as [`Ipv4Addr::UNSPECIFIED`] or -/// IN6ADDR_ANY as [`Ipv6Addr::UNSPECIFIED`]. -fn address_family(addrs: &[SocketAddr]) -> SocketAddr { - match addrs.first() { - Some(SocketAddr::V4(_)) | None => SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)), - Some(SocketAddr::V6(_)) => SocketAddr::from((Ipv6Addr::UNSPECIFIED, 0)), - } -} - -#[cfg(test)] -mod tests { - use opentelemetry::{ - trace::{SpanKind, Status}, - KeyValue, - }; - - use crate::exporter::thrift::jaeger::Tag; - use crate::exporter::{build_span_tags, OTEL_STATUS_CODE, OTEL_STATUS_DESCRIPTION}; - - use super::SPAN_KIND; - - fn assert_tag_contains(tags: Vec, key: &'static str, expect_val: &'static str) { - assert_eq!( - tags.into_iter() - .filter(|tag| tag.key.as_str() == key - && tag.v_str.as_deref().unwrap_or("") == expect_val) - .count(), - 1, - "Expect a tag {} with value {} but found nothing", - key, - expect_val - ); - } - - fn assert_tag_not_contains(tags: Vec, key: &'static str) { - assert_eq!( - tags.into_iter() - .filter(|tag| tag.key.as_str() == key) - .count(), - 0, - "Not expect tag {}, but found something", - key - ); - } - - #[rustfmt::skip] - fn get_error_tag_test_data() -> Vec<(Status, Option<&'static str>, Option<&'static str>)> - { - // Status, OTEL_STATUS_CODE tag value, OTEL_STATUS_DESCRIPTION tag value - vec![ - (Status::error(""), Some("ERROR"), None), - (Status::Unset, None, None), - // When status is ok, no description should be in span data. This should be ensured by Otel API - (Status::Ok, Some("OK"), None), - (Status::error("have message"), Some("ERROR"), Some("have message")), - (Status::Unset, None, None), - ] - } - - #[test] - fn test_set_status() { - for (status, status_tag_val, msg_tag_val) in get_error_tag_test_data() { - let tags = build_span_tags(Vec::new(), None, status, SpanKind::Client); - if let Some(val) = status_tag_val { - assert_tag_contains(tags.clone(), OTEL_STATUS_CODE, val); - } else { - assert_tag_not_contains(tags.clone(), OTEL_STATUS_CODE); - } - - if let Some(val) = msg_tag_val { - assert_tag_contains(tags.clone(), OTEL_STATUS_DESCRIPTION, val); - } else { - assert_tag_not_contains(tags.clone(), OTEL_STATUS_DESCRIPTION); - } - } - } - - #[test] - fn ignores_user_set_values() { - let mut attributes = Vec::new(); - let user_error = true; - let user_kind = "server"; - let user_status_description = "Something bad happened"; - let user_status = Status::Error { - description: user_status_description.into(), - }; - attributes.push(KeyValue::new("error", user_error)); - attributes.push(KeyValue::new(SPAN_KIND, user_kind)); - attributes.push(KeyValue::new(OTEL_STATUS_CODE, "ERROR")); - attributes.push(KeyValue::new( - OTEL_STATUS_DESCRIPTION, - user_status_description, - )); - let tags = build_span_tags(attributes, None, user_status, SpanKind::Client); - - assert!(tags - .iter() - .filter(|tag| tag.key.as_str() == "error") - .all(|tag| tag.v_bool.unwrap())); - assert_tag_contains(tags.clone(), SPAN_KIND, user_kind); - assert_tag_contains(tags.clone(), OTEL_STATUS_CODE, "ERROR"); - assert_tag_contains(tags, OTEL_STATUS_DESCRIPTION, user_status_description); - } - - #[test] - fn error_message_should_contain_details() { - let size_limit_err = - crate::Error::from(::thrift::Error::Protocol(thrift::ProtocolError::new( - thrift::ProtocolErrorKind::SizeLimit, - "the error message should contain details".to_string(), - ))); - assert_eq!( - format!("{}", size_limit_err), - "thrift agent failed on protocol layer, message too long, the error message should contain details" - ); - } -} diff --git a/opentelemetry-jaeger/src/exporter/runtime.rs b/opentelemetry-jaeger/src/exporter/runtime.rs deleted file mode 100644 index 576eabc2e7..0000000000 --- a/opentelemetry-jaeger/src/exporter/runtime.rs +++ /dev/null @@ -1,89 +0,0 @@ -#[cfg(any( - feature = "rt-async-std", - feature = "rt-tokio", - feature = "rt-tokio-current-thread" -))] -use crate::exporter::address_family; -use async_trait::async_trait; -use opentelemetry_sdk::runtime::RuntimeChannel; -use std::net::ToSocketAddrs; - -/// Jaeger Trace Runtime is an extension to [`RuntimeChannel`]. -/// -/// Deprecation Notice: -/// Ingestion of OTLP is now supported in Jaeger please check [crates.io] for more details. -/// -/// [`RuntimeChannel`]: opentelemetry_sdk::runtime::RuntimeChannel -/// [crates.io]: https://crates.io/crates/opentelemetry-jaeger -#[async_trait] -#[deprecated( - since = "0.21.0", - note = "Please migrate to opentelemetry-otlp exporter." -)] -pub trait JaegerTraceRuntime: RuntimeChannel + std::fmt::Debug { - /// A communication socket between Jaeger client and agent. - type Socket: std::fmt::Debug + Send + Sync; - - /// Create a new communication socket. - fn create_socket(&self, endpoint: T) -> thrift::Result; - - /// Send payload over the socket. - async fn write_to_socket(&self, socket: &Self::Socket, payload: Vec) -> thrift::Result<()>; -} - -#[cfg(feature = "rt-tokio")] -#[async_trait] -impl JaegerTraceRuntime for opentelemetry_sdk::runtime::Tokio { - type Socket = tokio::net::UdpSocket; - - fn create_socket(&self, endpoint: T) -> thrift::Result { - let addrs = endpoint.to_socket_addrs()?.collect::>(); - let conn = std::net::UdpSocket::bind(address_family(addrs.as_slice()))?; - conn.connect(addrs.as_slice())?; - Ok(tokio::net::UdpSocket::from_std(conn)?) - } - - async fn write_to_socket(&self, socket: &Self::Socket, payload: Vec) -> thrift::Result<()> { - socket.send(&payload).await?; - - Ok(()) - } -} - -#[cfg(feature = "rt-tokio-current-thread")] -#[async_trait] -impl JaegerTraceRuntime for opentelemetry_sdk::runtime::TokioCurrentThread { - type Socket = tokio::net::UdpSocket; - - fn create_socket(&self, endpoint: T) -> thrift::Result { - let addrs = endpoint.to_socket_addrs()?.collect::>(); - let conn = std::net::UdpSocket::bind(address_family(addrs.as_slice()))?; - conn.connect(addrs.as_slice())?; - Ok(tokio::net::UdpSocket::from_std(conn)?) - } - - async fn write_to_socket(&self, socket: &Self::Socket, payload: Vec) -> thrift::Result<()> { - socket.send(&payload).await?; - - Ok(()) - } -} - -#[cfg(feature = "rt-async-std")] -#[async_trait] -impl JaegerTraceRuntime for opentelemetry_sdk::runtime::AsyncStd { - type Socket = async_std::net::UdpSocket; - - fn create_socket(&self, endpoint: T) -> thrift::Result { - let addrs = endpoint.to_socket_addrs()?.collect::>(); - let conn = std::net::UdpSocket::bind(address_family(addrs.as_slice()))?; - conn.connect(addrs.as_slice())?; - Ok(async_std::net::UdpSocket::from(conn)) - } - - async fn write_to_socket(&self, socket: &Self::Socket, payload: Vec) -> thrift::Result<()> { - socket.send(&payload).await?; - - Ok(()) - } -} diff --git a/opentelemetry-jaeger/src/exporter/thrift/agent.rs b/opentelemetry-jaeger/src/exporter/thrift/agent.rs deleted file mode 100644 index 06b6fce22d..0000000000 --- a/opentelemetry-jaeger/src/exporter/thrift/agent.rs +++ /dev/null @@ -1,305 +0,0 @@ -// Autogenerated by Thrift Compiler (0.13.0) -// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING - -#![allow(unused_imports)] -#![allow(unused_extern_crates)] -#![allow(clippy::too_many_arguments, clippy::type_complexity)] -#![cfg_attr(rustfmt, rustfmt_skip)] - -extern crate thrift; - -use thrift::OrderedFloat; -use std::cell::RefCell; -use std::collections::{BTreeMap, BTreeSet}; -use std::convert::{From, TryFrom}; -use std::default::Default; -use std::error::Error; -use std::fmt; -use std::fmt::{Display, Formatter}; -use std::rc::Rc; - -use thrift::{ApplicationError, ApplicationErrorKind, ProtocolError, ProtocolErrorKind, TThriftClient}; -use thrift::protocol::{TFieldIdentifier, TListIdentifier, TMapIdentifier, TMessageIdentifier, TMessageType, TInputProtocol, TOutputProtocol, TSetIdentifier, TStructIdentifier, TType}; -use thrift::protocol::field_id; -use thrift::protocol::verify_expected_message_type; -use thrift::protocol::verify_expected_sequence_number; -use thrift::protocol::verify_expected_service_call; -use thrift::protocol::verify_required_field_exists; -use thrift::server::TProcessor; - -use super::jaeger; -use super::zipkincore; - -// -// Agent service client -// - -pub trait TAgentSyncClient { - fn emit_zipkin_batch(&mut self, spans: Vec) -> thrift::Result<()>; - fn emit_batch(&mut self, batch: jaeger::Batch) -> thrift::Result<()>; -} - -pub trait TAgentSyncClientMarker {} - -pub struct AgentSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - _i_prot: IP, - _o_prot: OP, - _sequence_number: i32, -} - -impl AgentSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - pub fn new(input_protocol: IP, output_protocol: OP) -> AgentSyncClient { - AgentSyncClient { _i_prot: input_protocol, _o_prot: output_protocol, _sequence_number: 0 } - } -} - -impl TThriftClient for AgentSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - fn i_prot_mut(&mut self) -> &mut dyn TInputProtocol { &mut self._i_prot } - fn o_prot_mut(&mut self) -> &mut dyn TOutputProtocol { &mut self._o_prot } - fn sequence_number(&self) -> i32 { self._sequence_number } - fn increment_sequence_number(&mut self) -> i32 { self._sequence_number += 1; self._sequence_number } -} - -impl TAgentSyncClientMarker for AgentSyncClient where IP: TInputProtocol, OP: TOutputProtocol {} - -impl TAgentSyncClient for C { - fn emit_zipkin_batch(&mut self, spans: Vec) -> thrift::Result<()> { - ( - { - self.increment_sequence_number(); - let message_ident = TMessageIdentifier::new("emitZipkinBatch", TMessageType::OneWay, self.sequence_number()); - let call_args = AgentEmitZipkinBatchArgs { spans }; - self.o_prot_mut().write_message_begin(&message_ident)?; - call_args.write_to_out_protocol(self.o_prot_mut())?; - self.o_prot_mut().write_message_end()?; - self.o_prot_mut().flush() - } - )?; - Ok(()) - } - fn emit_batch(&mut self, batch: jaeger::Batch) -> thrift::Result<()> { - ( - { - self.increment_sequence_number(); - let message_ident = TMessageIdentifier::new("emitBatch", TMessageType::OneWay, self.sequence_number()); - let call_args = AgentEmitBatchArgs { batch }; - self.o_prot_mut().write_message_begin(&message_ident)?; - call_args.write_to_out_protocol(self.o_prot_mut())?; - self.o_prot_mut().write_message_end()?; - self.o_prot_mut().flush() - } - )?; - Ok(()) - } -} - -// -// Agent service processor -// - -pub trait AgentSyncHandler { - fn handle_emit_zipkin_batch(&self, spans: Vec) -> thrift::Result<()>; - fn handle_emit_batch(&self, batch: jaeger::Batch) -> thrift::Result<()>; -} - -pub struct AgentSyncProcessor { - handler: H, -} - -impl AgentSyncProcessor { - pub fn new(handler: H) -> AgentSyncProcessor { - AgentSyncProcessor { - handler, - } - } - fn process_emit_zipkin_batch(&self, incoming_sequence_number: i32, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - TAgentProcessFunctions::process_emit_zipkin_batch(&self.handler, incoming_sequence_number, i_prot, o_prot) - } - fn process_emit_batch(&self, incoming_sequence_number: i32, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - TAgentProcessFunctions::process_emit_batch(&self.handler, incoming_sequence_number, i_prot, o_prot) - } -} - -pub struct TAgentProcessFunctions; - -impl TAgentProcessFunctions { - pub fn process_emit_zipkin_batch(handler: &H, _: i32, i_prot: &mut dyn TInputProtocol, _: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let args = AgentEmitZipkinBatchArgs::read_from_in_protocol(i_prot)?; - match handler.handle_emit_zipkin_batch(args.spans) { - Ok(_) => { - Ok(()) - }, - Err(e) => { - match e { - thrift::Error::Application(app_err) => { - Err(thrift::Error::Application(app_err)) - }, - _ => { - let ret_err = { - ApplicationError::new( - ApplicationErrorKind::Unknown, - e.to_string() - ) - }; - Err(thrift::Error::Application(ret_err)) - }, - } - }, - } - } - pub fn process_emit_batch(handler: &H, _: i32, i_prot: &mut dyn TInputProtocol, _: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let args = AgentEmitBatchArgs::read_from_in_protocol(i_prot)?; - match handler.handle_emit_batch(args.batch) { - Ok(_) => { - Ok(()) - }, - Err(e) => { - match e { - thrift::Error::Application(app_err) => { - Err(thrift::Error::Application(app_err)) - }, - _ => { - let ret_err = { - ApplicationError::new( - ApplicationErrorKind::Unknown, - e.to_string() - ) - }; - Err(thrift::Error::Application(ret_err)) - }, - } - }, - } - } -} - -impl TProcessor for AgentSyncProcessor { - fn process(&self, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let message_ident = i_prot.read_message_begin()?; - let res = match &*message_ident.name { - "emitZipkinBatch" => { - self.process_emit_zipkin_batch(message_ident.sequence_number, i_prot, o_prot) - }, - "emitBatch" => { - self.process_emit_batch(message_ident.sequence_number, i_prot, o_prot) - }, - method => { - Err( - thrift::Error::Application( - ApplicationError::new( - ApplicationErrorKind::UnknownMethod, - format!("unknown method {}", method) - ) - ) - ) - }, - }; - thrift::server::handle_process_result(&message_ident, res, o_prot) - } -} - -// -// AgentEmitZipkinBatchArgs -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -struct AgentEmitZipkinBatchArgs { - spans: Vec, -} - -impl AgentEmitZipkinBatchArgs { - fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_0 = zipkincore::Span::read_from_in_protocol(i_prot)?; - val.push(list_elem_0); - } - i_prot.read_list_end()?; - f_1 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("AgentEmitZipkinBatchArgs.spans", &f_1)?; - let ret = AgentEmitZipkinBatchArgs { - spans: f_1.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("emitZipkinBatch_args"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("spans", TType::List, 1))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, self.spans.len() as i32))?; - for e in &self.spans { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// AgentEmitBatchArgs -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -struct AgentEmitBatchArgs { - batch: jaeger::Batch, -} - -impl AgentEmitBatchArgs { - fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = jaeger::Batch::read_from_in_protocol(i_prot)?; - f_1 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("AgentEmitBatchArgs.batch", &f_1)?; - let ret = AgentEmitBatchArgs { - batch: f_1.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("emitBatch_args"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("batch", TType::Struct, 1))?; - self.batch.write_to_out_protocol(o_prot)?; - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} diff --git a/opentelemetry-jaeger/src/exporter/thrift/jaeger.rs b/opentelemetry-jaeger/src/exporter/thrift/jaeger.rs deleted file mode 100644 index 60e21e9e78..0000000000 --- a/opentelemetry-jaeger/src/exporter/thrift/jaeger.rs +++ /dev/null @@ -1,1116 +0,0 @@ -// Autogenerated by Thrift Compiler (0.13.0) -// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING - -#![allow(unused_imports)] -#![allow(unused_extern_crates)] -#![allow(clippy::too_many_arguments, clippy::type_complexity)] -#![cfg_attr(rustfmt, rustfmt_skip)] - -extern crate thrift; - -use thrift::OrderedFloat; -use std::cell::RefCell; -use std::collections::{BTreeMap, BTreeSet}; -use std::convert::{From, TryFrom}; -use std::default::Default; -use std::error::Error; -use std::fmt; -use std::fmt::{Display, Formatter}; -use std::rc::Rc; - -use thrift::{ApplicationError, ApplicationErrorKind, ProtocolError, ProtocolErrorKind, TThriftClient}; -use thrift::protocol::{TFieldIdentifier, TListIdentifier, TMapIdentifier, TMessageIdentifier, TMessageType, TInputProtocol, TOutputProtocol, TSetIdentifier, TStructIdentifier, TType}; -use thrift::protocol::field_id; -use thrift::protocol::verify_expected_message_type; -use thrift::protocol::verify_expected_sequence_number; -use thrift::protocol::verify_expected_service_call; -use thrift::protocol::verify_required_field_exists; -use thrift::server::TProcessor; - -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub enum TagType { - String = 0, - Double = 1, - Bool = 2, - Long = 3, - Binary = 4, -} - -impl TagType { - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - o_prot.write_i32(*self as i32) - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - let enum_value = i_prot.read_i32()?; - TagType::try_from(enum_value) } -} - -impl TryFrom for TagType { - type Error = thrift::Error; fn try_from(i: i32) -> Result { - match i { - 0 => Ok(TagType::String), - 1 => Ok(TagType::Double), - 2 => Ok(TagType::Bool), - 3 => Ok(TagType::Long), - 4 => Ok(TagType::Binary), - _ => { - Err( - thrift::Error::Protocol( - ProtocolError::new( - ProtocolErrorKind::InvalidData, - format!("cannot convert enum constant {} to TagType", i) - ) - ) - ) - }, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub enum SpanRefType { - ChildOf = 0, - FollowsFrom = 1, -} - -impl SpanRefType { - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - o_prot.write_i32(*self as i32) - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - let enum_value = i_prot.read_i32()?; - SpanRefType::try_from(enum_value) } -} - -impl TryFrom for SpanRefType { - type Error = thrift::Error; fn try_from(i: i32) -> Result { - match i { - 0 => Ok(SpanRefType::ChildOf), - 1 => Ok(SpanRefType::FollowsFrom), - _ => { - Err( - thrift::Error::Protocol( - ProtocolError::new( - ProtocolErrorKind::InvalidData, - format!("cannot convert enum constant {} to SpanRefType", i) - ) - ) - ) - }, - } - } -} - -// -// Tag -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Tag { - pub key: String, - pub v_type: TagType, - pub v_str: Option, - pub v_double: Option>, - pub v_bool: Option, - pub v_long: Option, - pub v_binary: Option>, -} - -impl Tag { - pub fn new(key: String, v_type: TagType, v_str: F3, v_double: F4, v_bool: F5, v_long: F6, v_binary: F7) -> Tag where F3: Into>, F4: Into>>, F5: Into>, F6: Into>, F7: Into>> { - Tag { - key, - v_type, - v_str: v_str.into(), - v_double: v_double.into(), - v_bool: v_bool.into(), - v_long: v_long.into(), - v_binary: v_binary.into(), - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - let mut f_2: Option = None; - let mut f_3: Option = None; - let mut f_4: Option> = None; - let mut f_5: Option = None; - let mut f_6: Option = None; - let mut f_7: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_string()?; - f_1 = Some(val); - }, - 2 => { - let val = TagType::read_from_in_protocol(i_prot)?; - f_2 = Some(val); - }, - 3 => { - let val = i_prot.read_string()?; - f_3 = Some(val); - }, - 4 => { - let val = OrderedFloat::from(i_prot.read_double()?); - f_4 = Some(val); - }, - 5 => { - let val = i_prot.read_bool()?; - f_5 = Some(val); - }, - 6 => { - let val = i_prot.read_i64()?; - f_6 = Some(val); - }, - 7 => { - let val = i_prot.read_bytes()?; - f_7 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("Tag.key", &f_1)?; - verify_required_field_exists("Tag.v_type", &f_2)?; - let ret = Tag { - key: f_1.expect("auto-generated code should have checked for presence of required fields"), - v_type: f_2.expect("auto-generated code should have checked for presence of required fields"), - v_str: f_3, - v_double: f_4, - v_bool: f_5, - v_long: f_6, - v_binary: f_7, - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Tag"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("key", TType::String, 1))?; - o_prot.write_string(&self.key)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("vType", TType::I32, 2))?; - self.v_type.write_to_out_protocol(o_prot)?; - o_prot.write_field_end()?; - if let Some(ref fld_var) = self.v_str { - o_prot.write_field_begin(&TFieldIdentifier::new("vStr", TType::String, 3))?; - o_prot.write_string(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.v_double { - o_prot.write_field_begin(&TFieldIdentifier::new("vDouble", TType::Double, 4))?; - o_prot.write_double(fld_var.into())?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.v_bool { - o_prot.write_field_begin(&TFieldIdentifier::new("vBool", TType::Bool, 5))?; - o_prot.write_bool(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.v_long { - o_prot.write_field_begin(&TFieldIdentifier::new("vLong", TType::I64, 6))?; - o_prot.write_i64(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.v_binary { - o_prot.write_field_begin(&TFieldIdentifier::new("vBinary", TType::String, 7))?; - o_prot.write_bytes(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// Log -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Log { - pub timestamp: i64, - pub fields: Vec, -} - -impl Log { - pub fn new(timestamp: i64, fields: Vec) -> Log { - Log { - timestamp, - fields, - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - let mut f_2: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_i64()?; - f_1 = Some(val); - }, - 2 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_0 = Tag::read_from_in_protocol(i_prot)?; - val.push(list_elem_0); - } - i_prot.read_list_end()?; - f_2 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("Log.timestamp", &f_1)?; - verify_required_field_exists("Log.fields", &f_2)?; - let ret = Log { - timestamp: f_1.expect("auto-generated code should have checked for presence of required fields"), - fields: f_2.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Log"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("timestamp", TType::I64, 1))?; - o_prot.write_i64(self.timestamp)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("fields", TType::List, 2))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, self.fields.len() as i32))?; - for e in &self.fields { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// SpanRef -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct SpanRef { - pub ref_type: SpanRefType, - pub trace_id_low: i64, - pub trace_id_high: i64, - pub span_id: i64, -} - -impl SpanRef { - pub fn new(ref_type: SpanRefType, trace_id_low: i64, trace_id_high: i64, span_id: i64) -> SpanRef { - SpanRef { - ref_type, - trace_id_low, - trace_id_high, - span_id, - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - let mut f_2: Option = None; - let mut f_3: Option = None; - let mut f_4: Option = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = SpanRefType::read_from_in_protocol(i_prot)?; - f_1 = Some(val); - }, - 2 => { - let val = i_prot.read_i64()?; - f_2 = Some(val); - }, - 3 => { - let val = i_prot.read_i64()?; - f_3 = Some(val); - }, - 4 => { - let val = i_prot.read_i64()?; - f_4 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("SpanRef.ref_type", &f_1)?; - verify_required_field_exists("SpanRef.trace_id_low", &f_2)?; - verify_required_field_exists("SpanRef.trace_id_high", &f_3)?; - verify_required_field_exists("SpanRef.span_id", &f_4)?; - let ret = SpanRef { - ref_type: f_1.expect("auto-generated code should have checked for presence of required fields"), - trace_id_low: f_2.expect("auto-generated code should have checked for presence of required fields"), - trace_id_high: f_3.expect("auto-generated code should have checked for presence of required fields"), - span_id: f_4.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("SpanRef"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("refType", TType::I32, 1))?; - self.ref_type.write_to_out_protocol(o_prot)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("traceIdLow", TType::I64, 2))?; - o_prot.write_i64(self.trace_id_low)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("traceIdHigh", TType::I64, 3))?; - o_prot.write_i64(self.trace_id_high)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("spanId", TType::I64, 4))?; - o_prot.write_i64(self.span_id)?; - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// Span -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Span { - pub trace_id_low: i64, - pub trace_id_high: i64, - pub span_id: i64, - pub parent_span_id: i64, - pub operation_name: String, - pub references: Option>, - pub flags: i32, - pub start_time: i64, - pub duration: i64, - pub tags: Option>, - pub logs: Option>, -} - -impl Span { - pub fn new(trace_id_low: i64, trace_id_high: i64, span_id: i64, parent_span_id: i64, operation_name: String, references: F6, flags: i32, start_time: i64, duration: i64, tags: F10, logs: F11) -> Span where F6: Into>>, F10: Into>>, F11: Into>> { - Span { - trace_id_low, - trace_id_high, - span_id, - parent_span_id, - operation_name, - references: references.into(), - flags, - start_time, - duration, - tags: tags.into(), - logs: logs.into(), - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - let mut f_2: Option = None; - let mut f_3: Option = None; - let mut f_4: Option = None; - let mut f_5: Option = None; - let mut f_6: Option> = None; - let mut f_7: Option = None; - let mut f_8: Option = None; - let mut f_9: Option = None; - let mut f_10: Option> = None; - let mut f_11: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_i64()?; - f_1 = Some(val); - }, - 2 => { - let val = i_prot.read_i64()?; - f_2 = Some(val); - }, - 3 => { - let val = i_prot.read_i64()?; - f_3 = Some(val); - }, - 4 => { - let val = i_prot.read_i64()?; - f_4 = Some(val); - }, - 5 => { - let val = i_prot.read_string()?; - f_5 = Some(val); - }, - 6 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_1 = SpanRef::read_from_in_protocol(i_prot)?; - val.push(list_elem_1); - } - i_prot.read_list_end()?; - f_6 = Some(val); - }, - 7 => { - let val = i_prot.read_i32()?; - f_7 = Some(val); - }, - 8 => { - let val = i_prot.read_i64()?; - f_8 = Some(val); - }, - 9 => { - let val = i_prot.read_i64()?; - f_9 = Some(val); - }, - 10 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_2 = Tag::read_from_in_protocol(i_prot)?; - val.push(list_elem_2); - } - i_prot.read_list_end()?; - f_10 = Some(val); - }, - 11 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_3 = Log::read_from_in_protocol(i_prot)?; - val.push(list_elem_3); - } - i_prot.read_list_end()?; - f_11 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("Span.trace_id_low", &f_1)?; - verify_required_field_exists("Span.trace_id_high", &f_2)?; - verify_required_field_exists("Span.span_id", &f_3)?; - verify_required_field_exists("Span.parent_span_id", &f_4)?; - verify_required_field_exists("Span.operation_name", &f_5)?; - verify_required_field_exists("Span.flags", &f_7)?; - verify_required_field_exists("Span.start_time", &f_8)?; - verify_required_field_exists("Span.duration", &f_9)?; - let ret = Span { - trace_id_low: f_1.expect("auto-generated code should have checked for presence of required fields"), - trace_id_high: f_2.expect("auto-generated code should have checked for presence of required fields"), - span_id: f_3.expect("auto-generated code should have checked for presence of required fields"), - parent_span_id: f_4.expect("auto-generated code should have checked for presence of required fields"), - operation_name: f_5.expect("auto-generated code should have checked for presence of required fields"), - references: f_6, - flags: f_7.expect("auto-generated code should have checked for presence of required fields"), - start_time: f_8.expect("auto-generated code should have checked for presence of required fields"), - duration: f_9.expect("auto-generated code should have checked for presence of required fields"), - tags: f_10, - logs: f_11, - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Span"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("traceIdLow", TType::I64, 1))?; - o_prot.write_i64(self.trace_id_low)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("traceIdHigh", TType::I64, 2))?; - o_prot.write_i64(self.trace_id_high)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("spanId", TType::I64, 3))?; - o_prot.write_i64(self.span_id)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("parentSpanId", TType::I64, 4))?; - o_prot.write_i64(self.parent_span_id)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("operationName", TType::String, 5))?; - o_prot.write_string(&self.operation_name)?; - o_prot.write_field_end()?; - if let Some(ref fld_var) = self.references { - o_prot.write_field_begin(&TFieldIdentifier::new("references", TType::List, 6))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, fld_var.len() as i32))?; - for e in fld_var { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_begin(&TFieldIdentifier::new("flags", TType::I32, 7))?; - o_prot.write_i32(self.flags)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("startTime", TType::I64, 8))?; - o_prot.write_i64(self.start_time)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("duration", TType::I64, 9))?; - o_prot.write_i64(self.duration)?; - o_prot.write_field_end()?; - if let Some(ref fld_var) = self.tags { - o_prot.write_field_begin(&TFieldIdentifier::new("tags", TType::List, 10))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, fld_var.len() as i32))?; - for e in fld_var { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.logs { - o_prot.write_field_begin(&TFieldIdentifier::new("logs", TType::List, 11))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, fld_var.len() as i32))?; - for e in fld_var { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// Process -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Process { - pub service_name: String, - pub tags: Option>, -} - -impl Process { - pub fn new(service_name: String, tags: F2) -> Process where F2: Into>> { - Process { - service_name, - tags: tags.into(), - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - let mut f_2: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_string()?; - f_1 = Some(val); - }, - 2 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_4 = Tag::read_from_in_protocol(i_prot)?; - val.push(list_elem_4); - } - i_prot.read_list_end()?; - f_2 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("Process.service_name", &f_1)?; - let ret = Process { - service_name: f_1.expect("auto-generated code should have checked for presence of required fields"), - tags: f_2, - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Process"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("serviceName", TType::String, 1))?; - o_prot.write_string(&self.service_name)?; - o_prot.write_field_end()?; - if let Some(ref fld_var) = self.tags { - o_prot.write_field_begin(&TFieldIdentifier::new("tags", TType::List, 2))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, fld_var.len() as i32))?; - for e in fld_var { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// Batch -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Batch { - pub process: Process, - pub spans: Vec, -} - -impl Batch { - pub fn new(process: Process, spans: Vec) -> Batch { - Batch { - process, - spans, - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - let mut f_2: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = Process::read_from_in_protocol(i_prot)?; - f_1 = Some(val); - }, - 2 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_5 = Span::read_from_in_protocol(i_prot)?; - val.push(list_elem_5); - } - i_prot.read_list_end()?; - f_2 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("Batch.process", &f_1)?; - verify_required_field_exists("Batch.spans", &f_2)?; - let ret = Batch { - process: f_1.expect("auto-generated code should have checked for presence of required fields"), - spans: f_2.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Batch"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("process", TType::Struct, 1))?; - self.process.write_to_out_protocol(o_prot)?; - o_prot.write_field_end()?; - o_prot.write_field_begin(&TFieldIdentifier::new("spans", TType::List, 2))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, self.spans.len() as i32))?; - for e in &self.spans { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// BatchSubmitResponse -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct BatchSubmitResponse { - pub ok: bool, -} - -impl BatchSubmitResponse { - pub fn new(ok: bool) -> BatchSubmitResponse { - BatchSubmitResponse { - ok, - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_bool()?; - f_1 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("BatchSubmitResponse.ok", &f_1)?; - let ret = BatchSubmitResponse { - ok: f_1.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("BatchSubmitResponse"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("ok", TType::Bool, 1))?; - o_prot.write_bool(self.ok)?; - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// Collector service client -// - -pub trait TCollectorSyncClient { - fn submit_batches(&mut self, batches: Vec) -> thrift::Result>; -} - -pub trait TCollectorSyncClientMarker {} - -pub struct CollectorSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - _i_prot: IP, - _o_prot: OP, - _sequence_number: i32, -} - -impl CollectorSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - pub fn new(input_protocol: IP, output_protocol: OP) -> CollectorSyncClient { - CollectorSyncClient { _i_prot: input_protocol, _o_prot: output_protocol, _sequence_number: 0 } - } -} - -impl TThriftClient for CollectorSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - fn i_prot_mut(&mut self) -> &mut dyn TInputProtocol { &mut self._i_prot } - fn o_prot_mut(&mut self) -> &mut dyn TOutputProtocol { &mut self._o_prot } - fn sequence_number(&self) -> i32 { self._sequence_number } - fn increment_sequence_number(&mut self) -> i32 { self._sequence_number += 1; self._sequence_number } -} - -impl TCollectorSyncClientMarker for CollectorSyncClient where IP: TInputProtocol, OP: TOutputProtocol {} - -impl TCollectorSyncClient for C { - fn submit_batches(&mut self, batches: Vec) -> thrift::Result> { - ( - { - self.increment_sequence_number(); - let message_ident = TMessageIdentifier::new("submitBatches", TMessageType::Call, self.sequence_number()); - let call_args = CollectorSubmitBatchesArgs { batches }; - self.o_prot_mut().write_message_begin(&message_ident)?; - call_args.write_to_out_protocol(self.o_prot_mut())?; - self.o_prot_mut().write_message_end()?; - self.o_prot_mut().flush() - } - )?; - { - let message_ident = self.i_prot_mut().read_message_begin()?; - verify_expected_sequence_number(self.sequence_number(), message_ident.sequence_number)?; - verify_expected_service_call("submitBatches", &message_ident.name)?; - if message_ident.message_type == TMessageType::Exception { - let remote_error = thrift::Error::read_application_error_from_in_protocol(self.i_prot_mut())?; - self.i_prot_mut().read_message_end()?; - return Err(thrift::Error::Application(remote_error)) - } - verify_expected_message_type(TMessageType::Reply, message_ident.message_type)?; - let result = CollectorSubmitBatchesResult::read_from_in_protocol(self.i_prot_mut())?; - self.i_prot_mut().read_message_end()?; - result.ok_or() - } - } -} - -// -// Collector service processor -// - -pub trait CollectorSyncHandler { - fn handle_submit_batches(&self, batches: Vec) -> thrift::Result>; -} - -pub struct CollectorSyncProcessor { - handler: H, -} - -impl CollectorSyncProcessor { - pub fn new(handler: H) -> CollectorSyncProcessor { - CollectorSyncProcessor { - handler, - } - } - fn process_submit_batches(&self, incoming_sequence_number: i32, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - TCollectorProcessFunctions::process_submit_batches(&self.handler, incoming_sequence_number, i_prot, o_prot) - } -} - -pub struct TCollectorProcessFunctions; - -impl TCollectorProcessFunctions { - pub fn process_submit_batches(handler: &H, incoming_sequence_number: i32, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let args = CollectorSubmitBatchesArgs::read_from_in_protocol(i_prot)?; - match handler.handle_submit_batches(args.batches) { - Ok(handler_return) => { - let message_ident = TMessageIdentifier::new("submitBatches", TMessageType::Reply, incoming_sequence_number); - o_prot.write_message_begin(&message_ident)?; - let ret = CollectorSubmitBatchesResult { result_value: Some(handler_return) }; - ret.write_to_out_protocol(o_prot)?; - o_prot.write_message_end()?; - o_prot.flush() - }, - Err(e) => { - match e { - thrift::Error::Application(app_err) => { - let message_ident = TMessageIdentifier::new("submitBatches", TMessageType::Exception, incoming_sequence_number); - o_prot.write_message_begin(&message_ident)?; - thrift::Error::write_application_error_to_out_protocol(&app_err, o_prot)?; - o_prot.write_message_end()?; - o_prot.flush() - }, - _ => { - let ret_err = { - ApplicationError::new( - ApplicationErrorKind::Unknown, - e.to_string() - ) - }; - let message_ident = TMessageIdentifier::new("submitBatches", TMessageType::Exception, incoming_sequence_number); - o_prot.write_message_begin(&message_ident)?; - thrift::Error::write_application_error_to_out_protocol(&ret_err, o_prot)?; - o_prot.write_message_end()?; - o_prot.flush() - }, - } - }, - } - } -} - -impl TProcessor for CollectorSyncProcessor { - fn process(&self, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let message_ident = i_prot.read_message_begin()?; - let res = match &*message_ident.name { - "submitBatches" => { - self.process_submit_batches(message_ident.sequence_number, i_prot, o_prot) - }, - method => { - Err( - thrift::Error::Application( - ApplicationError::new( - ApplicationErrorKind::UnknownMethod, - format!("unknown method {}", method) - ) - ) - ) - }, - }; - thrift::server::handle_process_result(&message_ident, res, o_prot) - } -} - -// -// CollectorSubmitBatchesArgs -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -struct CollectorSubmitBatchesArgs { - batches: Vec, -} - -impl CollectorSubmitBatchesArgs { - fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_6 = Batch::read_from_in_protocol(i_prot)?; - val.push(list_elem_6); - } - i_prot.read_list_end()?; - f_1 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("CollectorSubmitBatchesArgs.batches", &f_1)?; - let ret = CollectorSubmitBatchesArgs { - batches: f_1.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("submitBatches_args"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("batches", TType::List, 1))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, self.batches.len() as i32))?; - for e in &self.batches { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// CollectorSubmitBatchesResult -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -struct CollectorSubmitBatchesResult { - result_value: Option>, -} - -impl CollectorSubmitBatchesResult { - fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_0: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 0 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_7 = BatchSubmitResponse::read_from_in_protocol(i_prot)?; - val.push(list_elem_7); - } - i_prot.read_list_end()?; - f_0 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - let ret = CollectorSubmitBatchesResult { - result_value: f_0, - }; - Ok(ret) - } - fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("CollectorSubmitBatchesResult"); - o_prot.write_struct_begin(&struct_ident)?; - if let Some(ref fld_var) = self.result_value { - o_prot.write_field_begin(&TFieldIdentifier::new("result_value", TType::List, 0))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, fld_var.len() as i32))?; - for e in fld_var { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } - fn ok_or(self) -> thrift::Result> { - if self.result_value.is_some() { - Ok(self.result_value.unwrap()) - } else { - Err( - thrift::Error::Application( - ApplicationError::new( - ApplicationErrorKind::MissingResult, - "no result received for CollectorSubmitBatches" - ) - ) - ) - } - } -} - diff --git a/opentelemetry-jaeger/src/exporter/thrift/mod.rs b/opentelemetry-jaeger/src/exporter/thrift/mod.rs deleted file mode 100644 index c6916fb592..0000000000 --- a/opentelemetry-jaeger/src/exporter/thrift/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Thrift generated Jaeger client -//! -//! Definitions: - -use opentelemetry::{trace::Event, Key, KeyValue, Value}; -use std::time::{Duration, SystemTime}; - -pub(crate) mod agent; -pub(crate) mod jaeger; -pub(crate) mod zipkincore; - -impl From for jaeger::Process { - fn from(process: super::Process) -> jaeger::Process { - jaeger::Process::new( - process.service_name, - Some(process.tags.into_iter().map(Into::into).collect()), - ) - } -} - -impl From for jaeger::Log { - fn from(event: crate::exporter::Event) -> jaeger::Log { - let timestamp = event - .timestamp - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap_or_else(|_| Duration::from_secs(0)) - .as_micros() as i64; - let mut event_set_via_attribute = false; - let mut fields = event - .attributes - .into_iter() - .map(|attr| { - if attr.key.as_str() == "event" { - event_set_via_attribute = true; - }; - attr.into() - }) - .collect::>(); - - if !event_set_via_attribute { - fields.push(Key::new("event").string(event.name).into()); - } - - if event.dropped_attributes_count != 0 { - fields.push( - Key::new("otel.event.dropped_attributes_count") - .i64(i64::from(event.dropped_attributes_count)) - .into(), - ); - } - - jaeger::Log::new(timestamp, fields) - } -} - -#[rustfmt::skip] -impl From for jaeger::Tag { - fn from(kv: KeyValue) -> jaeger::Tag { - let KeyValue { key, value } = kv; - match value { - Value::String(s) => jaeger::Tag::new(key.into(), jaeger::TagType::String, Some(s.into()), None, None, None, None), - Value::F64(f) => jaeger::Tag::new(key.into(), jaeger::TagType::Double, None, Some(f.into()), None, None, None), - Value::Bool(b) => jaeger::Tag::new(key.into(), jaeger::TagType::Bool, None, None, Some(b), None, None), - Value::I64(i) => jaeger::Tag::new(key.into(), jaeger::TagType::Long, None, None, None, Some(i), None), - // TODO: better Array handling, jaeger thrift doesn't support arrays - v @ Value::Array(_) => jaeger::Tag::new(key.into(), jaeger::TagType::String, Some(v.to_string()), None, None, None, None), - } - } -} diff --git a/opentelemetry-jaeger/src/exporter/thrift/zipkincore.rs b/opentelemetry-jaeger/src/exporter/thrift/zipkincore.rs deleted file mode 100644 index f9d54118db..0000000000 --- a/opentelemetry-jaeger/src/exporter/thrift/zipkincore.rs +++ /dev/null @@ -1,1093 +0,0 @@ -// Autogenerated by Thrift Compiler (0.13.0) -// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING - -#![allow(unused_imports)] -#![allow(unused_extern_crates)] -#![allow(clippy::too_many_arguments, clippy::type_complexity)] -#![cfg_attr(rustfmt, rustfmt_skip)] - -extern crate thrift; - -use thrift::OrderedFloat; -use std::cell::RefCell; -use std::collections::{BTreeMap, BTreeSet}; -use std::convert::{From, TryFrom}; -use std::default::Default; -use std::error::Error; -use std::fmt; -use std::fmt::{Display, Formatter}; -use std::rc::Rc; - -use thrift::{ApplicationError, ApplicationErrorKind, ProtocolError, ProtocolErrorKind, TThriftClient}; -use thrift::protocol::{TFieldIdentifier, TListIdentifier, TMapIdentifier, TMessageIdentifier, TMessageType, TInputProtocol, TOutputProtocol, TSetIdentifier, TStructIdentifier, TType}; -use thrift::protocol::field_id; -use thrift::protocol::verify_expected_message_type; -use thrift::protocol::verify_expected_sequence_number; -use thrift::protocol::verify_expected_service_call; -use thrift::protocol::verify_required_field_exists; -use thrift::server::TProcessor; - -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub enum AnnotationType { - Bool = 0, - Bytes = 1, - I16 = 2, - I32 = 3, - I64 = 4, - Double = 5, - String = 6, -} - -impl AnnotationType { - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - o_prot.write_i32(*self as i32) - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - let enum_value = i_prot.read_i32()?; - AnnotationType::try_from(enum_value) } -} - -impl TryFrom for AnnotationType { - type Error = thrift::Error; fn try_from(i: i32) -> Result { - match i { - 0 => Ok(AnnotationType::Bool), - 1 => Ok(AnnotationType::Bytes), - 2 => Ok(AnnotationType::I16), - 3 => Ok(AnnotationType::I32), - 4 => Ok(AnnotationType::I64), - 5 => Ok(AnnotationType::Double), - 6 => Ok(AnnotationType::String), - _ => { - Err( - thrift::Error::Protocol( - ProtocolError::new( - ProtocolErrorKind::InvalidData, - format!("cannot convert enum constant {} to AnnotationType", i) - ) - ) - ) - }, - } - } -} - -// -// Endpoint -// - -/// Indicates the network context of a service recording an annotation with two -/// exceptions. -/// -/// When a BinaryAnnotation, and key is CLIENT_ADDR or SERVER_ADDR, -/// the endpoint indicates the source or destination of an RPC. This exception -/// allows zipkin to display network context of uninstrumented services, or -/// clients such as web browsers. -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Endpoint { - /// IPv4 host address packed into 4 bytes. - /// - /// Ex for the ip 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4 - pub ipv4: Option, - /// IPv4 port - /// - /// Note: this is to be treated as an unsigned integer, so watch for negatives. - /// - /// Conventionally, when the port isn't known, port = 0. - pub port: Option, - /// Service name in lowercase, such as "memcache" or "zipkin-web" - /// - /// Conventionally, when the service name isn't known, service_name = "unknown". - pub service_name: Option, - /// IPv6 host address packed into 16 bytes. Ex Inet6Address.getBytes() - pub ipv6: Option>, -} - -impl Endpoint { - pub fn new(ipv4: F1, port: F2, service_name: F3, ipv6: F4) -> Endpoint where F1: Into>, F2: Into>, F3: Into>, F4: Into>> { - Endpoint { - ipv4: ipv4.into(), - port: port.into(), - service_name: service_name.into(), - ipv6: ipv6.into(), - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = Some(0); - let mut f_2: Option = Some(0); - let mut f_3: Option = Some("".to_owned()); - let mut f_4: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_i32()?; - f_1 = Some(val); - }, - 2 => { - let val = i_prot.read_i16()?; - f_2 = Some(val); - }, - 3 => { - let val = i_prot.read_string()?; - f_3 = Some(val); - }, - 4 => { - let val = i_prot.read_bytes()?; - f_4 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - let ret = Endpoint { - ipv4: f_1, - port: f_2, - service_name: f_3, - ipv6: f_4, - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Endpoint"); - o_prot.write_struct_begin(&struct_ident)?; - if let Some(fld_var) = self.ipv4 { - o_prot.write_field_begin(&TFieldIdentifier::new("ipv4", TType::I32, 1))?; - o_prot.write_i32(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.port { - o_prot.write_field_begin(&TFieldIdentifier::new("port", TType::I16, 2))?; - o_prot.write_i16(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.service_name { - o_prot.write_field_begin(&TFieldIdentifier::new("service_name", TType::String, 3))?; - o_prot.write_string(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.ipv6 { - o_prot.write_field_begin(&TFieldIdentifier::new("ipv6", TType::String, 4))?; - o_prot.write_bytes(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -impl Default for Endpoint { - fn default() -> Self { - Endpoint{ - ipv4: Some(0), - port: Some(0), - service_name: Some("".to_owned()), - ipv6: Some(Vec::new()), - } - } -} - -// -// Annotation -// - -/// An annotation is similar to a log statement. It includes a host field which -/// allows these events to be attributed properly, and also aggregatable. -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Annotation { - /// Microseconds from epoch. - /// - /// This value should use the most precise value possible. For example, - /// gettimeofday or syncing nanoTime against a tick of currentTimeMillis. - pub timestamp: Option, - pub value: Option, - /// Always the host that recorded the event. By specifying the host you allow - /// rollup of all events (such as client requests to a service) by IP address. - pub host: Option, -} - -impl Annotation { - pub fn new(timestamp: F1, value: F2, host: F3) -> Annotation where F1: Into>, F2: Into>, F3: Into> { - Annotation { - timestamp: timestamp.into(), - value: value.into(), - host: host.into(), - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = Some(0); - let mut f_2: Option = Some("".to_owned()); - let mut f_3: Option = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_i64()?; - f_1 = Some(val); - }, - 2 => { - let val = i_prot.read_string()?; - f_2 = Some(val); - }, - 3 => { - let val = Endpoint::read_from_in_protocol(i_prot)?; - f_3 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - let ret = Annotation { - timestamp: f_1, - value: f_2, - host: f_3, - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Annotation"); - o_prot.write_struct_begin(&struct_ident)?; - if let Some(fld_var) = self.timestamp { - o_prot.write_field_begin(&TFieldIdentifier::new("timestamp", TType::I64, 1))?; - o_prot.write_i64(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.value { - o_prot.write_field_begin(&TFieldIdentifier::new("value", TType::String, 2))?; - o_prot.write_string(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.host { - o_prot.write_field_begin(&TFieldIdentifier::new("host", TType::Struct, 3))?; - fld_var.write_to_out_protocol(o_prot)?; - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -impl Default for Annotation { - fn default() -> Self { - Annotation{ - timestamp: Some(0), - value: Some("".to_owned()), - host: None, - } - } -} - -// -// BinaryAnnotation -// - -/// Binary annotations are tags applied to a Span to give it context. For -/// example, a binary annotation of "http.uri" could the path to a resource in a -/// RPC call. -/// -/// Binary annotations of type STRING are always queryable, though more a -/// historical implementation detail than a structural concern. -/// -/// Binary annotations can repeat, and vary on the host. Similar to Annotation, -/// the host indicates who logged the event. This allows you to tell the -/// difference between the client and server side of the same key. For example, -/// the key "http.uri" might be different on the client and server side due to -/// rewriting, like "/api/v1/myresource" vs "/myresource. Via the host field, -/// you can see the different points of view, which often help in debugging. -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct BinaryAnnotation { - pub key: Option, - pub value: Option>, - pub annotation_type: Option, - /// The host that recorded tag, which allows you to differentiate between - /// multiple tags with the same key. There are two exceptions to this. - /// - /// When the key is CLIENT_ADDR or SERVER_ADDR, host indicates the source or - /// destination of an RPC. This exception allows zipkin to display network - /// context of uninstrumented services, or clients such as web browsers. - pub host: Option, -} - -impl BinaryAnnotation { - pub fn new(key: F1, value: F2, annotation_type: F3, host: F4) -> BinaryAnnotation where F1: Into>, F2: Into>>, F3: Into>, F4: Into> { - BinaryAnnotation { - key: key.into(), - value: value.into(), - annotation_type: annotation_type.into(), - host: host.into(), - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = Some("".to_owned()); - let mut f_2: Option> = Some(Vec::new()); - let mut f_3: Option = None; - let mut f_4: Option = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_string()?; - f_1 = Some(val); - }, - 2 => { - let val = i_prot.read_bytes()?; - f_2 = Some(val); - }, - 3 => { - let val = AnnotationType::read_from_in_protocol(i_prot)?; - f_3 = Some(val); - }, - 4 => { - let val = Endpoint::read_from_in_protocol(i_prot)?; - f_4 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - let ret = BinaryAnnotation { - key: f_1, - value: f_2, - annotation_type: f_3, - host: f_4, - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("BinaryAnnotation"); - o_prot.write_struct_begin(&struct_ident)?; - if let Some(ref fld_var) = self.key { - o_prot.write_field_begin(&TFieldIdentifier::new("key", TType::String, 1))?; - o_prot.write_string(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.value { - o_prot.write_field_begin(&TFieldIdentifier::new("value", TType::String, 2))?; - o_prot.write_bytes(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.annotation_type { - o_prot.write_field_begin(&TFieldIdentifier::new("annotation_type", TType::I32, 3))?; - fld_var.write_to_out_protocol(o_prot)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.host { - o_prot.write_field_begin(&TFieldIdentifier::new("host", TType::Struct, 4))?; - fld_var.write_to_out_protocol(o_prot)?; - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -impl Default for BinaryAnnotation { - fn default() -> Self { - BinaryAnnotation{ - key: Some("".to_owned()), - value: Some(Vec::new()), - annotation_type: None, - host: None, - } - } -} - -// -// Span -// - -/// A trace is a series of spans (often RPC calls) which form a latency tree. -/// -/// The root span is where trace_id = id and parent_id = Nil. The root span is -/// usually the longest interval in the trace, starting with a SERVER_RECV -/// annotation and ending with a SERVER_SEND. -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Span { - pub trace_id: Option, - /// Span name in lowercase, rpc method for example - /// - /// Conventionally, when the span name isn't known, name = "unknown". - pub name: Option, - pub id: Option, - pub parent_id: Option, - pub annotations: Option>, - pub binary_annotations: Option>, - pub debug: Option, - /// Microseconds from epoch of the creation of this span. - /// - /// This value should be set directly by instrumentation, using the most - /// precise value possible. For example, gettimeofday or syncing nanoTime - /// against a tick of currentTimeMillis. - /// - /// For compatibilty with instrumentation that precede this field, collectors - /// or span stores can derive this via Annotation.timestamp. - /// For example, SERVER_RECV.timestamp or CLIENT_SEND.timestamp. - /// - /// This field is optional for compatibility with old data: first-party span - /// stores are expected to support this at time of introduction. - pub timestamp: Option, - /// Measurement of duration in microseconds, used to support queries. - /// - /// This value should be set directly, where possible. Doing so encourages - /// precise measurement decoupled from problems of clocks, such as skew or NTP - /// updates causing time to move backwards. - /// - /// For compatibilty with instrumentation that precede this field, collectors - /// or span stores can derive this by subtracting Annotation.timestamp. - /// For example, SERVER_SEND.timestamp - SERVER_RECV.timestamp. - /// - /// If this field is persisted as unset, zipkin will continue to work, except - /// duration query support will be implementation-specific. Similarly, setting - /// this field non-atomically is implementation-specific. - /// - /// This field is i64 vs i32 to support spans longer than 35 minutes. - pub duration: Option, - /// Optional unique 8-byte additional identifier for a trace. If non zero, this - /// means the trace uses 128 bit traceIds instead of 64 bit. - pub trace_id_high: Option, -} - -impl Span { - pub fn new(trace_id: F1, name: F3, id: F4, parent_id: F5, annotations: F6, binary_annotations: F8, debug: F9, timestamp: F10, duration: F11, trace_id_high: F12) -> Span where F1: Into>, F3: Into>, F4: Into>, F5: Into>, F6: Into>>, F8: Into>>, F9: Into>, F10: Into>, F11: Into>, F12: Into> { - Span { - trace_id: trace_id.into(), - name: name.into(), - id: id.into(), - parent_id: parent_id.into(), - annotations: annotations.into(), - binary_annotations: binary_annotations.into(), - debug: debug.into(), - timestamp: timestamp.into(), - duration: duration.into(), - trace_id_high: trace_id_high.into(), - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = Some(0); - let mut f_3: Option = Some("".to_owned()); - let mut f_4: Option = Some(0); - let mut f_5: Option = None; - let mut f_6: Option> = Some(Vec::new()); - let mut f_8: Option> = Some(Vec::new()); - let mut f_9: Option = None; - let mut f_10: Option = None; - let mut f_11: Option = None; - let mut f_12: Option = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_i64()?; - f_1 = Some(val); - }, - 3 => { - let val = i_prot.read_string()?; - f_3 = Some(val); - }, - 4 => { - let val = i_prot.read_i64()?; - f_4 = Some(val); - }, - 5 => { - let val = i_prot.read_i64()?; - f_5 = Some(val); - }, - 6 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_0 = Annotation::read_from_in_protocol(i_prot)?; - val.push(list_elem_0); - } - i_prot.read_list_end()?; - f_6 = Some(val); - }, - 8 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_1 = BinaryAnnotation::read_from_in_protocol(i_prot)?; - val.push(list_elem_1); - } - i_prot.read_list_end()?; - f_8 = Some(val); - }, - 9 => { - let val = i_prot.read_bool()?; - f_9 = Some(val); - }, - 10 => { - let val = i_prot.read_i64()?; - f_10 = Some(val); - }, - 11 => { - let val = i_prot.read_i64()?; - f_11 = Some(val); - }, - 12 => { - let val = i_prot.read_i64()?; - f_12 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - let ret = Span { - trace_id: f_1, - name: f_3, - id: f_4, - parent_id: f_5, - annotations: f_6, - binary_annotations: f_8, - debug: f_9, - timestamp: f_10, - duration: f_11, - trace_id_high: f_12, - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Span"); - o_prot.write_struct_begin(&struct_ident)?; - if let Some(fld_var) = self.trace_id { - o_prot.write_field_begin(&TFieldIdentifier::new("trace_id", TType::I64, 1))?; - o_prot.write_i64(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.name { - o_prot.write_field_begin(&TFieldIdentifier::new("name", TType::String, 3))?; - o_prot.write_string(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.id { - o_prot.write_field_begin(&TFieldIdentifier::new("id", TType::I64, 4))?; - o_prot.write_i64(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.parent_id { - o_prot.write_field_begin(&TFieldIdentifier::new("parent_id", TType::I64, 5))?; - o_prot.write_i64(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.annotations { - o_prot.write_field_begin(&TFieldIdentifier::new("annotations", TType::List, 6))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, fld_var.len() as i32))?; - for e in fld_var { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(ref fld_var) = self.binary_annotations { - o_prot.write_field_begin(&TFieldIdentifier::new("binary_annotations", TType::List, 8))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, fld_var.len() as i32))?; - for e in fld_var { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.debug { - o_prot.write_field_begin(&TFieldIdentifier::new("debug", TType::Bool, 9))?; - o_prot.write_bool(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.timestamp { - o_prot.write_field_begin(&TFieldIdentifier::new("timestamp", TType::I64, 10))?; - o_prot.write_i64(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.duration { - o_prot.write_field_begin(&TFieldIdentifier::new("duration", TType::I64, 11))?; - o_prot.write_i64(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - if let Some(fld_var) = self.trace_id_high { - o_prot.write_field_begin(&TFieldIdentifier::new("trace_id_high", TType::I64, 12))?; - o_prot.write_i64(fld_var)?; - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -impl Default for Span { - fn default() -> Self { - Span{ - trace_id: Some(0), - name: Some("".to_owned()), - id: Some(0), - parent_id: Some(0), - annotations: Some(Vec::new()), - binary_annotations: Some(Vec::new()), - debug: Some(false), - timestamp: Some(0), - duration: Some(0), - trace_id_high: Some(0), - } - } -} - -// -// Response -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Response { - pub ok: bool, -} - -impl Response { - pub fn new(ok: bool) -> Response { - Response { - ok, - - } - } - pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let val = i_prot.read_bool()?; - f_1 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("Response.ok", &f_1)?; - let ret = Response { - ok: f_1.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("Response"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("ok", TType::Bool, 1))?; - o_prot.write_bool(self.ok)?; - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -pub const C_L_I_E_N_T_S_E_N_D: &str = "cs"; - -pub const C_L_I_E_N_T_R_E_C_V: &str = "cr"; - -pub const S_E_R_V_E_R_S_E_N_D: &str = "ss"; - -pub const S_E_R_V_E_R_R_E_C_V: &str = "sr"; - -pub const M_E_S_S_A_G_E_S_E_N_D: &str = "ms"; - -pub const M_E_S_S_A_G_E_R_E_C_V: &str = "mr"; - -pub const W_I_R_E_S_E_N_D: &str = "ws"; - -pub const W_I_R_E_R_E_C_V: &str = "wr"; - -pub const C_L_I_E_N_T_S_E_N_D_F_R_A_G_M_E_N_T: &str = "csf"; - -pub const C_L_I_E_N_T_R_E_C_V_F_R_A_G_M_E_N_T: &str = "crf"; - -pub const S_E_R_V_E_R_S_E_N_D_F_R_A_G_M_E_N_T: &str = "ssf"; - -pub const S_E_R_V_E_R_R_E_C_V_F_R_A_G_M_E_N_T: &str = "srf"; - -pub const L_O_C_A_L_C_O_M_P_O_N_E_N_T: &str = "lc"; - -pub const C_L_I_E_N_T_A_D_D_R: &str = "ca"; - -pub const S_E_R_V_E_R_A_D_D_R: &str = "sa"; - -pub const M_E_S_S_A_G_E_A_D_D_R: &str = "ma"; - -// -// ZipkinCollector service client -// - -pub trait TZipkinCollectorSyncClient { - fn submit_zipkin_batch(&mut self, spans: Vec) -> thrift::Result>; -} - -pub trait TZipkinCollectorSyncClientMarker {} - -pub struct ZipkinCollectorSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - _i_prot: IP, - _o_prot: OP, - _sequence_number: i32, -} - -impl ZipkinCollectorSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - pub fn new(input_protocol: IP, output_protocol: OP) -> ZipkinCollectorSyncClient { - ZipkinCollectorSyncClient { _i_prot: input_protocol, _o_prot: output_protocol, _sequence_number: 0 } - } -} - -impl TThriftClient for ZipkinCollectorSyncClient where IP: TInputProtocol, OP: TOutputProtocol { - fn i_prot_mut(&mut self) -> &mut dyn TInputProtocol { &mut self._i_prot } - fn o_prot_mut(&mut self) -> &mut dyn TOutputProtocol { &mut self._o_prot } - fn sequence_number(&self) -> i32 { self._sequence_number } - fn increment_sequence_number(&mut self) -> i32 { self._sequence_number += 1; self._sequence_number } -} - -impl TZipkinCollectorSyncClientMarker for ZipkinCollectorSyncClient where IP: TInputProtocol, OP: TOutputProtocol {} - -impl TZipkinCollectorSyncClient for C { - fn submit_zipkin_batch(&mut self, spans: Vec) -> thrift::Result> { - ( - { - self.increment_sequence_number(); - let message_ident = TMessageIdentifier::new("submitZipkinBatch", TMessageType::Call, self.sequence_number()); - let call_args = ZipkinCollectorSubmitZipkinBatchArgs { spans }; - self.o_prot_mut().write_message_begin(&message_ident)?; - call_args.write_to_out_protocol(self.o_prot_mut())?; - self.o_prot_mut().write_message_end()?; - self.o_prot_mut().flush() - } - )?; - { - let message_ident = self.i_prot_mut().read_message_begin()?; - verify_expected_sequence_number(self.sequence_number(), message_ident.sequence_number)?; - verify_expected_service_call("submitZipkinBatch", &message_ident.name)?; - if message_ident.message_type == TMessageType::Exception { - let remote_error = thrift::Error::read_application_error_from_in_protocol(self.i_prot_mut())?; - self.i_prot_mut().read_message_end()?; - return Err(thrift::Error::Application(remote_error)) - } - verify_expected_message_type(TMessageType::Reply, message_ident.message_type)?; - let result = ZipkinCollectorSubmitZipkinBatchResult::read_from_in_protocol(self.i_prot_mut())?; - self.i_prot_mut().read_message_end()?; - result.ok_or() - } - } -} - -// -// ZipkinCollector service processor -// - -pub trait ZipkinCollectorSyncHandler { - fn handle_submit_zipkin_batch(&self, spans: Vec) -> thrift::Result>; -} - -pub struct ZipkinCollectorSyncProcessor { - handler: H, -} - -impl ZipkinCollectorSyncProcessor { - pub fn new(handler: H) -> ZipkinCollectorSyncProcessor { - ZipkinCollectorSyncProcessor { - handler, - } - } - fn process_submit_zipkin_batch(&self, incoming_sequence_number: i32, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - TZipkinCollectorProcessFunctions::process_submit_zipkin_batch(&self.handler, incoming_sequence_number, i_prot, o_prot) - } -} - -pub struct TZipkinCollectorProcessFunctions; - -impl TZipkinCollectorProcessFunctions { - pub fn process_submit_zipkin_batch(handler: &H, incoming_sequence_number: i32, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let args = ZipkinCollectorSubmitZipkinBatchArgs::read_from_in_protocol(i_prot)?; - match handler.handle_submit_zipkin_batch(args.spans) { - Ok(handler_return) => { - let message_ident = TMessageIdentifier::new("submitZipkinBatch", TMessageType::Reply, incoming_sequence_number); - o_prot.write_message_begin(&message_ident)?; - let ret = ZipkinCollectorSubmitZipkinBatchResult { result_value: Some(handler_return) }; - ret.write_to_out_protocol(o_prot)?; - o_prot.write_message_end()?; - o_prot.flush() - }, - Err(e) => { - match e { - thrift::Error::Application(app_err) => { - let message_ident = TMessageIdentifier::new("submitZipkinBatch", TMessageType::Exception, incoming_sequence_number); - o_prot.write_message_begin(&message_ident)?; - thrift::Error::write_application_error_to_out_protocol(&app_err, o_prot)?; - o_prot.write_message_end()?; - o_prot.flush() - }, - _ => { - let ret_err = { - ApplicationError::new( - ApplicationErrorKind::Unknown, - e.to_string() - ) - }; - let message_ident = TMessageIdentifier::new("submitZipkinBatch", TMessageType::Exception, incoming_sequence_number); - o_prot.write_message_begin(&message_ident)?; - thrift::Error::write_application_error_to_out_protocol(&ret_err, o_prot)?; - o_prot.write_message_end()?; - o_prot.flush() - }, - } - }, - } - } -} - -impl TProcessor for ZipkinCollectorSyncProcessor { - fn process(&self, i_prot: &mut dyn TInputProtocol, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let message_ident = i_prot.read_message_begin()?; - let res = match &*message_ident.name { - "submitZipkinBatch" => { - self.process_submit_zipkin_batch(message_ident.sequence_number, i_prot, o_prot) - }, - method => { - Err( - thrift::Error::Application( - ApplicationError::new( - ApplicationErrorKind::UnknownMethod, - format!("unknown method {}", method) - ) - ) - ) - }, - }; - thrift::server::handle_process_result(&message_ident, res, o_prot) - } -} - -// -// ZipkinCollectorSubmitZipkinBatchArgs -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -struct ZipkinCollectorSubmitZipkinBatchArgs { - spans: Vec, -} - -impl ZipkinCollectorSubmitZipkinBatchArgs { - fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_1: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 1 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_2 = Span::read_from_in_protocol(i_prot)?; - val.push(list_elem_2); - } - i_prot.read_list_end()?; - f_1 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - verify_required_field_exists("ZipkinCollectorSubmitZipkinBatchArgs.spans", &f_1)?; - let ret = ZipkinCollectorSubmitZipkinBatchArgs { - spans: f_1.expect("auto-generated code should have checked for presence of required fields"), - }; - Ok(ret) - } - fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("submitZipkinBatch_args"); - o_prot.write_struct_begin(&struct_ident)?; - o_prot.write_field_begin(&TFieldIdentifier::new("spans", TType::List, 1))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, self.spans.len() as i32))?; - for e in &self.spans { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } -} - -// -// ZipkinCollectorSubmitZipkinBatchResult -// - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -struct ZipkinCollectorSubmitZipkinBatchResult { - result_value: Option>, -} - -impl ZipkinCollectorSubmitZipkinBatchResult { - fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result { - i_prot.read_struct_begin()?; - let mut f_0: Option> = None; - loop { - let field_ident = i_prot.read_field_begin()?; - if field_ident.field_type == TType::Stop { - break; - } - let field_id = field_id(&field_ident)?; - match field_id { - 0 => { - let list_ident = i_prot.read_list_begin()?; - let mut val: Vec = Vec::with_capacity(list_ident.size as usize); - for _ in 0..list_ident.size { - let list_elem_3 = Response::read_from_in_protocol(i_prot)?; - val.push(list_elem_3); - } - i_prot.read_list_end()?; - f_0 = Some(val); - }, - _ => { - i_prot.skip(field_ident.field_type)?; - }, - }; - i_prot.read_field_end()?; - } - i_prot.read_struct_end()?; - let ret = ZipkinCollectorSubmitZipkinBatchResult { - result_value: f_0, - }; - Ok(ret) - } - fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> { - let struct_ident = TStructIdentifier::new("ZipkinCollectorSubmitZipkinBatchResult"); - o_prot.write_struct_begin(&struct_ident)?; - if let Some(ref fld_var) = self.result_value { - o_prot.write_field_begin(&TFieldIdentifier::new("result_value", TType::List, 0))?; - o_prot.write_list_begin(&TListIdentifier::new(TType::Struct, fld_var.len() as i32))?; - for e in fld_var { - e.write_to_out_protocol(o_prot)?; - o_prot.write_list_end()?; - } - o_prot.write_field_end()?; - () - } else { - () - } - o_prot.write_field_stop()?; - o_prot.write_struct_end() - } - fn ok_or(self) -> thrift::Result> { - if self.result_value.is_some() { - Ok(self.result_value.unwrap()) - } else { - Err( - thrift::Error::Application( - ApplicationError::new( - ApplicationErrorKind::MissingResult, - "no result received for ZipkinCollectorSubmitZipkinBatch" - ) - ) - ) - } - } -} - diff --git a/opentelemetry-jaeger/src/exporter/transport/buffer.rs b/opentelemetry-jaeger/src/exporter/transport/buffer.rs deleted file mode 100644 index f6d63f7d50..0000000000 --- a/opentelemetry-jaeger/src/exporter/transport/buffer.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::io; -use std::sync::{Arc, Mutex}; -use thrift::transport::{ReadHalf, TIoChannel, WriteHalf}; - -/// Custom TBufferChannel that can be dynamically grown and split off of. -#[derive(Debug, Clone)] -pub(crate) struct TBufferChannel { - inner: Arc>>, -} - -impl TBufferChannel { - /// Create a new buffer channel with the given initial capacity - pub(crate) fn with_capacity(capacity: usize) -> Self { - TBufferChannel { - inner: Arc::new(Mutex::new(Vec::with_capacity(capacity))), - } - } - - /// Take the accumulated bytes from the buffer, leaving capacity unchanged. - pub(crate) fn take_bytes(&mut self) -> Vec { - self.inner - .lock() - .map(|mut write| write.split_off(0)) - .unwrap_or_default() - } -} - -impl io::Read for TBufferChannel { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - unreachable!("jaeger protocol never reads") - } -} - -impl io::Write for TBufferChannel { - fn write(&mut self, buf: &[u8]) -> io::Result { - if let Ok(mut inner) = self.inner.lock() { - inner.extend_from_slice(buf); - } - Ok(buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl TIoChannel for TBufferChannel { - fn split(self) -> thrift::Result<(ReadHalf, WriteHalf)> - where - Self: Sized, - { - Ok((ReadHalf::new(self.clone()), WriteHalf::new(self))) - } -} diff --git a/opentelemetry-jaeger/src/exporter/transport/mod.rs b/opentelemetry-jaeger/src/exporter/transport/mod.rs deleted file mode 100644 index 302927e4d4..0000000000 --- a/opentelemetry-jaeger/src/exporter/transport/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Additional Thrift transport implementations -mod buffer; -mod noop; - -pub(crate) use buffer::TBufferChannel; -pub(crate) use noop::TNoopChannel; diff --git a/opentelemetry-jaeger/src/exporter/transport/noop.rs b/opentelemetry-jaeger/src/exporter/transport/noop.rs deleted file mode 100644 index 5c51e0fb09..0000000000 --- a/opentelemetry-jaeger/src/exporter/transport/noop.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::io; - -#[derive(Debug)] -pub(crate) struct TNoopChannel; - -impl io::Read for TNoopChannel { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Ok(0) - } -} diff --git a/opentelemetry-jaeger/src/exporter/uploader.rs b/opentelemetry-jaeger/src/exporter/uploader.rs deleted file mode 100644 index ce5a46b79b..0000000000 --- a/opentelemetry-jaeger/src/exporter/uploader.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! # Jaeger Span Uploader -#[cfg(any(feature = "collector_client", feature = "wasm_collector_client"))] -use crate::exporter::collector; -use crate::exporter::{agent, jaeger}; -use async_trait::async_trait; -use opentelemetry_sdk::export::trace::ExportResult; -use std::fmt::Debug; - -use crate::exporter::thrift::jaeger::Batch; -use crate::exporter::JaegerTraceRuntime; - -#[async_trait] -pub(crate) trait Uploader: Debug + Send + Sync { - async fn upload(&self, batch: jaeger::Batch) -> ExportResult; -} - -#[derive(Debug)] -pub(crate) enum SyncUploader { - Agent(std::sync::Mutex), -} - -#[async_trait] -impl Uploader for SyncUploader { - async fn upload(&self, batch: jaeger::Batch) -> ExportResult { - match self { - SyncUploader::Agent(client) => { - // TODO Implement retry behavior - client - .lock() - .expect("Failed to lock agent client") - .emit_batch(batch) - .map_err::(Into::into)?; - } - } - Ok(()) - } -} - -/// Uploads a batch of spans to Jaeger -#[derive(Debug)] -pub(crate) enum AsyncUploader { - /// Agent async client - Agent(futures_util::lock::Mutex>), - /// Collector sync client - #[cfg(feature = "collector_client")] - Collector(collector::AsyncHttpClient), - #[cfg(feature = "wasm_collector_client")] - WasmCollector(collector::WasmCollector), -} - -#[async_trait] -impl Uploader for AsyncUploader { - async fn upload(&self, batch: Batch) -> ExportResult { - match self { - Self::Agent(client) => { - // TODO Implement retry behaviour - client - .lock() - .await - .emit_batch(batch) - .await - .map_err::(Into::into)?; - } - #[cfg(feature = "collector_client")] - Self::Collector(collector) => { - // TODO Implement retry behaviour - collector.submit_batch(batch).await?; - } - #[cfg(feature = "wasm_collector_client")] - Self::WasmCollector(collector) => { - collector - .submit_batch(batch) - .await - .map_err::(Into::into)?; - } - } - Ok(()) - } -} diff --git a/opentelemetry-jaeger/src/lib.rs b/opentelemetry-jaeger/src/lib.rs deleted file mode 100644 index de35884f69..0000000000 --- a/opentelemetry-jaeger/src/lib.rs +++ /dev/null @@ -1,330 +0,0 @@ -//! Collects OpenTelemetry spans and reports them to a given Jaeger -//! `agent` or `collector` endpoint, propagate the tracing context between the applications using [Jaeger propagation format]. -//! -//! *Warning*: Note that the exporter component from this crate will be [deprecated][jaeger-deprecation] -//! in the future. Users are advised to move to [opentelemetry_otlp][otlp-exporter] instead as [Jaeger][jaeger-otlp] -//! supports accepting data in the OTLP protocol. -//! See the [Jaeger Docs] for details about Jaeger and deployment information. -//! -//! *Compiler support: [requires `rustc` 1.64+][msrv]* -//! -//! [Jaeger Docs]: https://www.jaegertracing.io/docs/ -//! [jaeger-deprecation]: https://github.com/open-telemetry/opentelemetry-specification/pull/2858/files -//! [jaeger-otlp]: https://www.jaegertracing.io/docs/1.38/apis/#opentelemetry-protocol-stable -//! [otlp-exporter]: https://docs.rs/opentelemetry-otlp/latest/opentelemetry_otlp/ -//! [msrv]: #supported-rust-versions -//! [jaeger propagation format]: https://www.jaegertracing.io/docs/1.18/client-libraries/#propagation-format -//! -//! ## Quickstart -//! -//! First make sure you have a running version of the Jaeger instance -//! you want to send data to: -//! -//! ```shell -//! $ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 -p14268:14268 jaegertracing/all-in-one:latest -//! ``` -//! -//! Then install a new jaeger pipeline with the recommended defaults to start -//! exporting telemetry: -//! -//! ```no_run -//! use opentelemetry::{global, trace::{Tracer, TraceError}}; -//! use opentelemetry_jaeger_propagator; -//! -//! #[tokio::main] -//! async fn main() -> Result<(), TraceError> { -//! global::set_text_map_propagator(opentelemetry_jaeger_propagator::Propagator::new()); -//! let tracer = opentelemetry_jaeger::new_agent_pipeline().install_simple()?; -//! -//! tracer.in_span("doing_work", |cx| { -//! // Traced app logic here... -//! }); -//! -//! global::shutdown_tracer_provider(); // export remaining spans -//! -//! Ok(()) -//! } -//! ``` -//! -//! Or if you are running on an async runtime like Tokio and want to report spans in batches -//! ```no_run -//! use opentelemetry::{global, trace::{Tracer, TraceError}}; -//! use opentelemetry_sdk::runtime::Tokio; -//! use opentelemetry_jaeger_propagator; -//! -//! fn main() -> Result<(), TraceError> { -//! global::set_text_map_propagator(opentelemetry_jaeger_propagator::Propagator::new()); -//! let tracer = opentelemetry_jaeger::new_agent_pipeline().install_batch(Tokio)?; -//! -//! tracer.in_span("doing_work", |cx| { -//! // Traced app logic here... -//! }); -//! -//! global::shutdown_tracer_provider(); // export remaining spans -//! -//! Ok(()) -//! } -//! ``` -//! ## Performance -//! -//! For optimal performance, a batch exporter is recommended as the simple exporter -//! will export each span synchronously on drop. You can enable the `rt-tokio`, -//! `rt-tokio-current-thread` or `rt-async-std` features and specify a runtime -//! on the pipeline builder to have a batch exporter configured for you -//! automatically. -//! -//! ```toml -//! [dependencies] -//! opentelemetry_sdk = { version = "*", features = ["rt-tokio"] } -//! opentelemetry-jaeger = { version = "*", features = ["rt-tokio"] } -//! ``` -//! -//! ```no_run -//! # fn main() -> Result<(), opentelemetry::trace::TraceError> { -//! let tracer = opentelemetry_jaeger::new_agent_pipeline() -//! .install_batch(opentelemetry_sdk::runtime::Tokio)?; -//! # Ok(()) -//! # } -//! ``` -//! -//! [`tokio`]: https://tokio.rs -//! [`async-std`]: https://async.rs -//! -//! ## Jaeger Exporter From Environment Variables -//! -//! The jaeger pipeline builder can be configured dynamically via environment -//! variables. All variables are optional, a full list of accepted options can -//! be found in the [jaeger variables spec]. -//! -//! [jaeger variables spec]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md -//! -//! ## Jaeger Collector Example -//! -//! If you want to skip the agent and submit spans directly to a Jaeger collector, -//! you can enable the optional `collector_client` feature for this crate. This -//! example expects a Jaeger collector running on `http://localhost:14268`. -//! -//! ```toml -//! [dependencies] -//! opentelemetry-jaeger = { version = "..", features = ["collector_client", "isahc_collector_client"] } -//! ``` -//! -//! Then you can use the [`with_endpoint`] method to specify the endpoint: -//! -//! [`with_endpoint`]: exporter::config::collector::CollectorPipeline::with_endpoint -//! -//! ```ignore -//! // Note that this requires the `collector_client` feature. -//! // We enabled the `isahc_collector_client` feature for a default isahc http client. -//! // You can also provide your own implementation via .with_http_client() method. -//! use opentelemetry::trace::{Tracer, TraceError}; -//! -//! fn main() -> Result<(), TraceError> { -//! let tracer = opentelemetry_jaeger::new_collector_pipeline() -//! .with_endpoint("http://localhost:14268/api/traces") -//! // optionally set username and password for authentication of the exporter. -//! .with_username("username") -//! .with_password("s3cr3t") -//! .with_isahc() -//! //.with_http_client() provide custom http client implementation -//! .install_batch(opentelemetry_sdk::runtime::Tokio)?; -//! -//! tracer.in_span("doing_work", |cx| { -//! // Traced app logic here... -//! }); -//! -//! Ok(()) -//! } -//! ``` -//! ## Resource, tags and service name -//! In order to export the spans in different format. opentelemetry uses its own -//! model internally. Most of the jaeger spans' concept can be found in this model. -//! The full list of this mapping can be found in [OpenTelemetry to Jaeger Transformation]. -//! -//! The **process tags** in jaeger spans will be mapped as resource in opentelemetry. You can -//! set it through `OTEL_RESOURCE_ATTRIBUTES` environment variable or using [`with_trace_config`]. -//! -//! Note that to avoid copying data multiple times. Jaeger exporter will uses resource stored in [`Exporter`]. -//! -//! The **tags** in jaeger spans will be mapped as attributes in opentelemetry spans. You can -//! set it through [`set_attribute`] method. -//! -//! Each jaeger span requires a **service name**. This will be mapped as a resource with `service.name` key. -//! You can set it using one of the following methods from highest priority to lowest priority. -//! 1. [`with_service_name`]. -//! 2. include a `service.name` key value pairs when configure resource using [`with_trace_config`]. -//! 3. set the service name as `OTEL_SERVICE_NAME` environment variable. -//! 4. set the `service.name` attributes in `OTEL_RESOURCE_ATTRIBUTES`. -//! 5. if the service name is not provided by the above method. `unknown_service` will be used. -//! -//! Based on the service name, we update/append the `service.name` process tags in jaeger spans. -//! -//! [`with_service_name`]: crate::exporter::config::agent::AgentPipeline::with_service_name -//! [`with_trace_config`]: crate::exporter::config::agent::AgentPipeline::with_trace_config -//! [`set_attribute`]: opentelemetry::trace::Span::set_attribute -//! [OpenTelemetry to Jaeger Transformation]:https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md -//! -//! ## Kitchen Sink Full Configuration -//! -//! Example showing how to override all configuration options. See the -//! [`CollectorPipeline`] and [`AgentPipeline`] docs for details of each option. -//! -//! [`CollectorPipeline`]: config::collector::CollectorPipeline -//! [`AgentPipeline`]: config::agent::AgentPipeline -//! -//! ### Export to agents -//! ```no_run -//! use opentelemetry::{global, KeyValue, trace::{Tracer, TraceError}}; -//! use opentelemetry_sdk::{trace::{config, RandomIdGenerator, Sampler}, Resource}; -//! use opentelemetry_jaeger_propagator; -//! -//! fn main() -> Result<(), TraceError> { -//! global::set_text_map_propagator(opentelemetry_jaeger_propagator::Propagator::new()); -//! let tracer = opentelemetry_jaeger::new_agent_pipeline() -//! .with_endpoint("localhost:6831") -//! .with_service_name("my_app") -//! .with_max_packet_size(9_216) -//! .with_auto_split_batch(true) -//! .with_instrumentation_library_tags(false) -//! .with_trace_config( -//! config() -//! .with_sampler(Sampler::AlwaysOn) -//! .with_id_generator(RandomIdGenerator::default()) -//! .with_max_events_per_span(64) -//! .with_max_attributes_per_span(16) -//! // resources will translated to tags in jaeger spans -//! .with_resource(Resource::new(vec![KeyValue::new("key", "value"), -//! KeyValue::new("process_key", "process_value")])), -//! ) -//! .install_batch(opentelemetry_sdk::runtime::Tokio)?; -//! -//! tracer.in_span("doing_work", |cx| { -//! // Traced app logic here... -//! }); -//! -//! // export remaining spans. It's optional if you can accept spans loss for the last batch. -//! global::shutdown_tracer_provider(); -//! -//! Ok(()) -//! } -//! ``` -//! -//! ### Export to collectors -//! Note that this example requires `collector_client` and `isahc_collector_client` feature. -//! ```ignore -//! use opentelemetry::{global, KeyValue, trace::{Tracer, TraceError}}; -//! use opentelemetry_sdk::{trace::{config, RandomIdGenerator, Sampler}, Resource}; -//! use opentelemetry_jaeger_propagator; -//! -//! fn main() -> Result<(), TraceError> { -//! global::set_text_map_propagator(opentelemetry_jaeger_propagator::Propagator::new()); -//! let tracer = opentelemetry_jaeger::new_collector_pipeline() -//! .with_endpoint("http://localhost:14250/api/trace") // set collector endpoint -//! .with_service_name("my_app") // the name of the application -//! .with_trace_config( -//! config() -//! .with_sampler(Sampler::AlwaysOn) -//! .with_id_generator(RandomIdGenerator::default()) -//! .with_max_events_per_span(64) -//! .with_max_attributes_per_span(16) -//! .with_max_events_per_span(16) -//! // resources will translated to tags in jaeger spans -//! .with_resource(Resource::new(vec![KeyValue::new("key", "value"), -//! KeyValue::new("process_key", "process_value")])), -//! ) -//! .with_username("username") -//! .with_password("s3cr3t") -//! .with_timeout(std::time::Duration::from_secs(2)) -//! .install_batch(opentelemetry_sdk::runtime::Tokio)?; -//! -//! tracer.in_span("doing_work", |cx| { -//! // Traced app logic here... -//! }); -//! -//! // export remaining spans. It's optional if you can accept spans loss for the last batch. -//! global::shutdown_tracer_provider(); -//! -//! Ok(()) -//! } -//! ``` -//! -//! # Crate Feature Flags -//! -//! The following crate feature flags are available: -//! -//! * `collector_client`: Export span data directly to a Jaeger collector. User MUST provide the http client. -//! -//! * `hyper_collector_client`: Export span data with Jaeger collector backed by a hyper default http client. -//! -//! * `reqwest_collector_client`: Export span data with Jaeger collector backed by a reqwest http client. -//! -//! * `reqwest_blocking_collector_client`: Export span data with Jaeger collector backed by a reqwest blocking http client. -//! -//! * `isahc_collector_client`: Export span data with Jaeger collector backed by a isahc http client. -//! -//! * `wasm_collector_client`: Enable collector in wasm. -//! -//! Support for recording and exporting telemetry asynchronously can be added -//! via the following flags, it extends the [`opentelemetry`] feature: -//! -//! * `rt-tokio`: Enable sending UDP packets to Jaeger agent asynchronously when the tokio -//! [`Multi-Threaded Scheduler`] is used. -//! -//! * `rt-tokio-current-thread`: Enable sending UDP packets to Jaeger agent asynchronously when the -//! tokio [`Current-Thread Scheduler`] is used. -//! -//! * `rt-async-std`: Enable sending UDP packets to Jaeger agent asynchronously when the -//! [`async-std`] runtime is used. -//! -//! [`Multi-Threaded Scheduler`]: https://docs.rs/tokio/latest/tokio/runtime/index.html#multi-thread-scheduler -//! [`Current-Thread Scheduler`]: https://docs.rs/tokio/latest/tokio/runtime/index.html#current-thread-scheduler -//! [`async-std`]: https://async.rs -//! [`opentelemetry`]: https://crates.io/crates/opentelemetry -//! -//! # Supported Rust Versions -//! -//! OpenTelemetry is built against the latest stable release. The minimum -//! supported version is 1.64. 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.64, 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(deprecated)] -#![cfg_attr( - docsrs, - feature(doc_cfg, doc_auto_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 use exporter::config; -#[cfg(feature = "collector_client")] -pub use exporter::config::collector::new_collector_pipeline; -#[cfg(feature = "wasm_collector_client")] -pub use exporter::config::collector::new_wasm_collector_pipeline; -pub use exporter::{ - config::agent::new_agent_pipeline, runtime::JaegerTraceRuntime, Error, Exporter, Process, -}; - -mod exporter; - -#[cfg(feature = "integration_test")] -#[doc(hidden)] -pub mod testing; diff --git a/opentelemetry-jaeger/src/testing/jaeger_api_v2.rs b/opentelemetry-jaeger/src/testing/jaeger_api_v2.rs deleted file mode 100644 index 467ef9f2b1..0000000000 --- a/opentelemetry-jaeger/src/testing/jaeger_api_v2.rs +++ /dev/null @@ -1,448 +0,0 @@ -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct KeyValue { - #[prost(string, tag = "1")] - pub key: ::prost::alloc::string::String, - #[prost(enumeration = "ValueType", tag = "2")] - pub v_type: i32, - #[prost(string, tag = "3")] - pub v_str: ::prost::alloc::string::String, - #[prost(bool, tag = "4")] - pub v_bool: bool, - #[prost(int64, tag = "5")] - pub v_int64: i64, - #[prost(double, tag = "6")] - pub v_float64: f64, - #[prost(bytes = "vec", tag = "7")] - pub v_binary: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Log { - #[prost(message, optional, tag = "1")] - pub timestamp: ::core::option::Option<::prost_types::Timestamp>, - #[prost(message, repeated, tag = "2")] - pub fields: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct SpanRef { - #[prost(bytes = "vec", tag = "1")] - pub trace_id: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "2")] - pub span_id: ::prost::alloc::vec::Vec, - #[prost(enumeration = "SpanRefType", tag = "3")] - pub ref_type: i32, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Process { - #[prost(string, tag = "1")] - pub service_name: ::prost::alloc::string::String, - #[prost(message, repeated, tag = "2")] - pub tags: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Span { - #[prost(bytes = "vec", tag = "1")] - pub trace_id: ::prost::alloc::vec::Vec, - #[prost(bytes = "vec", tag = "2")] - pub span_id: ::prost::alloc::vec::Vec, - #[prost(string, tag = "3")] - pub operation_name: ::prost::alloc::string::String, - #[prost(message, repeated, tag = "4")] - pub references: ::prost::alloc::vec::Vec, - #[prost(uint32, tag = "5")] - pub flags: u32, - #[prost(message, optional, tag = "6")] - pub start_time: ::core::option::Option<::prost_types::Timestamp>, - #[prost(message, optional, tag = "7")] - pub duration: ::core::option::Option<::prost_types::Duration>, - #[prost(message, repeated, tag = "8")] - pub tags: ::prost::alloc::vec::Vec, - #[prost(message, repeated, tag = "9")] - pub logs: ::prost::alloc::vec::Vec, - #[prost(message, optional, tag = "10")] - pub process: ::core::option::Option, - #[prost(string, tag = "11")] - pub process_id: ::prost::alloc::string::String, - #[prost(string, repeated, tag = "12")] - pub warnings: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Trace { - #[prost(message, repeated, tag = "1")] - pub spans: ::prost::alloc::vec::Vec, - #[prost(message, repeated, tag = "2")] - pub process_map: ::prost::alloc::vec::Vec, - #[prost(string, repeated, tag = "3")] - pub warnings: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -/// Nested message and enum types in `Trace`. -pub mod trace { - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Message)] - pub struct ProcessMapping { - #[prost(string, tag = "1")] - pub process_id: ::prost::alloc::string::String, - #[prost(message, optional, tag = "2")] - pub process: ::core::option::Option, - } -} -/// Note that both Span and Batch may contain a Process. -/// This is different from the Thrift model which was only used -/// for transport, because Proto model is also used by the backend -/// as the domain model, where once a batch is received it is split -/// into individual spans which are all processed independently, -/// and therefore they all need a Process. As far as on-the-wire -/// semantics, both Batch and Spans in the same message may contain -/// their own instances of Process, with span.Process taking priority -/// over batch.Process. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Batch { - #[prost(message, repeated, tag = "1")] - pub spans: ::prost::alloc::vec::Vec, - #[prost(message, optional, tag = "2")] - pub process: ::core::option::Option, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct DependencyLink { - #[prost(string, tag = "1")] - pub parent: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub child: ::prost::alloc::string::String, - #[prost(uint64, tag = "3")] - pub call_count: u64, - #[prost(string, tag = "4")] - pub source: ::prost::alloc::string::String, -} -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum ValueType { - String = 0, - Bool = 1, - Int64 = 2, - Float64 = 3, - Binary = 4, -} -impl ValueType { - /// 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 { - ValueType::String => "STRING", - ValueType::Bool => "BOOL", - ValueType::Int64 => "INT64", - ValueType::Float64 => "FLOAT64", - ValueType::Binary => "BINARY", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "STRING" => Some(Self::String), - "BOOL" => Some(Self::Bool), - "INT64" => Some(Self::Int64), - "FLOAT64" => Some(Self::Float64), - "BINARY" => Some(Self::Binary), - _ => None, - } - } -} -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum SpanRefType { - ChildOf = 0, - FollowsFrom = 1, -} -impl SpanRefType { - /// 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 { - SpanRefType::ChildOf => "CHILD_OF", - SpanRefType::FollowsFrom => "FOLLOWS_FROM", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "CHILD_OF" => Some(Self::ChildOf), - "FOLLOWS_FROM" => Some(Self::FollowsFrom), - _ => None, - } - } -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetTraceRequest { - #[prost(bytes = "vec", tag = "1")] - pub trace_id: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct SpansResponseChunk { - #[prost(message, repeated, tag = "1")] - pub spans: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ArchiveTraceRequest { - #[prost(bytes = "vec", tag = "1")] - pub trace_id: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ArchiveTraceResponse {} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct TraceQueryParameters { - #[prost(string, tag = "1")] - pub service_name: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub operation_name: ::prost::alloc::string::String, - #[prost(map = "string, string", tag = "3")] - pub tags: - ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, - #[prost(message, optional, tag = "4")] - pub start_time_min: ::core::option::Option<::prost_types::Timestamp>, - #[prost(message, optional, tag = "5")] - pub start_time_max: ::core::option::Option<::prost_types::Timestamp>, - #[prost(message, optional, tag = "6")] - pub duration_min: ::core::option::Option<::prost_types::Duration>, - #[prost(message, optional, tag = "7")] - pub duration_max: ::core::option::Option<::prost_types::Duration>, - #[prost(int32, tag = "8")] - pub search_depth: i32, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct FindTracesRequest { - #[prost(message, optional, tag = "1")] - pub query: ::core::option::Option, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetServicesRequest {} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetServicesResponse { - #[prost(string, repeated, tag = "1")] - pub services: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetOperationsRequest { - #[prost(string, tag = "1")] - pub service: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub span_kind: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Operation { - #[prost(string, tag = "1")] - pub name: ::prost::alloc::string::String, - #[prost(string, tag = "2")] - pub span_kind: ::prost::alloc::string::String, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetOperationsResponse { - /// deprecated - #[prost(string, repeated, tag = "1")] - pub operation_names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(message, repeated, tag = "2")] - pub operations: ::prost::alloc::vec::Vec, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetDependenciesRequest { - #[prost(message, optional, tag = "1")] - pub start_time: ::core::option::Option<::prost_types::Timestamp>, - #[prost(message, optional, tag = "2")] - pub end_time: ::core::option::Option<::prost_types::Timestamp>, -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetDependenciesResponse { - #[prost(message, repeated, tag = "1")] - pub dependencies: ::prost::alloc::vec::Vec, -} -/// Generated client implementations. -pub mod query_service_client { - #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] - use tonic::codegen::http::Uri; - use tonic::codegen::*; - #[derive(Debug, Clone)] - pub struct QueryServiceClient { - inner: tonic::client::Grpc, - } - impl QueryServiceClient { - /// Attempt to create a new client by connecting to a given endpoint. - pub async fn connect(dst: D) -> Result - where - D: std::convert::TryInto, - D::Error: Into, - { - let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; - Ok(Self::new(conn)) - } - } - impl QueryServiceClient - where - T: tonic::client::GrpcService, - T::Error: Into, - T::ResponseBody: Body + Send + 'static, - ::Error: Into + 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( - inner: T, - interceptor: F, - ) -> QueryServiceClient> - where - F: tonic::service::Interceptor, - T::ResponseBody: Default, - T: tonic::codegen::Service< - http::Request, - Response = http::Response< - >::ResponseBody, - >, - >, - >>::Error: - Into + Send + Sync, - { - QueryServiceClient::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 - } - pub async fn get_trace( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result< - tonic::Response>, - tonic::Status, - > { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/jaeger.api_v2.QueryService/GetTrace"); - self.inner - .server_streaming(request.into_request(), path, codec) - .await - } - pub async fn archive_trace( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = - http::uri::PathAndQuery::from_static("/jaeger.api_v2.QueryService/ArchiveTrace"); - self.inner.unary(request.into_request(), path, codec).await - } - pub async fn find_traces( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result< - tonic::Response>, - tonic::Status, - > { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = - http::uri::PathAndQuery::from_static("/jaeger.api_v2.QueryService/FindTraces"); - self.inner - .server_streaming(request.into_request(), path, codec) - .await - } - pub async fn get_services( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = - http::uri::PathAndQuery::from_static("/jaeger.api_v2.QueryService/GetServices"); - self.inner.unary(request.into_request(), path, codec).await - } - pub async fn get_operations( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = - http::uri::PathAndQuery::from_static("/jaeger.api_v2.QueryService/GetOperations"); - self.inner.unary(request.into_request(), path, codec).await - } - pub async fn get_dependencies( - &mut self, - request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = - http::uri::PathAndQuery::from_static("/jaeger.api_v2.QueryService/GetDependencies"); - self.inner.unary(request.into_request(), path, codec).await - } - } -} diff --git a/opentelemetry-jaeger/src/testing/mod.rs b/opentelemetry-jaeger/src/testing/mod.rs deleted file mode 100644 index 36d2c3a222..0000000000 --- a/opentelemetry-jaeger/src/testing/mod.rs +++ /dev/null @@ -1,85 +0,0 @@ -#[allow(unused, missing_docs, clippy::derive_partial_eq_without_eq)] -// tonic don't derive Eq. We shouldn't manually change it.)] -pub mod jaeger_api_v2; - -#[allow(missing_docs)] -pub mod jaeger_client { - use crate::testing::jaeger_api_v2::query_service_client::QueryServiceClient; - use crate::testing::jaeger_api_v2::{ - FindTracesRequest, GetServicesRequest, GetTraceRequest, Span as JaegerSpan, - TraceQueryParameters, - }; - use tonic::transport::Channel; - - #[derive(Debug)] - pub struct JaegerTestClient { - query_service_client: QueryServiceClient, - } - - impl JaegerTestClient { - pub fn new(jaeger_url: &'static str) -> JaegerTestClient { - let channel = Channel::from_static(jaeger_url).connect_lazy(); - - JaegerTestClient { - query_service_client: QueryServiceClient::new(channel), - } - } - - /// Check if the jaeger contains the service - pub async fn contain_service(&mut self, service_name: &String) -> bool { - self.query_service_client - .get_services(GetServicesRequest {}) - .await - .unwrap() - .get_ref() - .services - .iter() - .any(|svc_name| *svc_name == *service_name) - } - - /// Find trace by trace id. - /// Note that `trace_id` should be a u128 in hex. - pub async fn get_trace(&mut self, trace_id: String) -> Vec { - let trace_id = u128::from_str_radix(trace_id.as_ref(), 16).expect("invalid trace id"); - let mut resp = self - .query_service_client - .get_trace(GetTraceRequest { - trace_id: trace_id.to_be_bytes().into(), - }) - .await - .unwrap(); - - if let Some(spans) = resp - .get_mut() - .message() - .await - .expect("jaeger returns error") - { - spans.spans - } else { - vec![] - } - } - - /// Find traces belongs the service. - /// It assumes the service exists. - pub async fn find_traces_from_services(&mut self, service_name: &str) -> Vec { - let request = FindTracesRequest { - query: Some(TraceQueryParameters { - service_name: service_name.to_owned(), - ..Default::default() - }), - }; - self.query_service_client - .find_traces(request) - .await - .unwrap() - .get_mut() - .message() - .await - .expect("jaeger returns error") - .unwrap_or_default() - .spans - } - } -} diff --git a/opentelemetry-jaeger/tests/Dockerfile b/opentelemetry-jaeger/tests/Dockerfile deleted file mode 100644 index 0fcabd9b54..0000000000 --- a/opentelemetry-jaeger/tests/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM rust - -WORKDIR /usr/src/opentelemetry -COPY . . diff --git a/opentelemetry-jaeger/tests/docker-compose.yaml b/opentelemetry-jaeger/tests/docker-compose.yaml deleted file mode 100644 index 445ab85fb5..0000000000 --- a/opentelemetry-jaeger/tests/docker-compose.yaml +++ /dev/null @@ -1,23 +0,0 @@ -version: "3" -services: - jaeger: - image: jaegertracing/all-in-one:1 - container_name: opentelemetry-jaeger-integration-test-jaeger - ports: - - "6831:6831/udp" - - "16685:16685" - - "14268:14268" - - "16686:16686" - opentelemetry-jaeger: - build: - context: ../.. - dockerfile: ./opentelemetry-jaeger/tests/Dockerfile - container_name: opentelemetry-jaeger-integration-test-exporter - environment: - OTEL_TEST_JAEGER_AGENT_ENDPOINT: "jaeger:6831" - OTEL_TEST_JAEGER_COLLECTOR_ENDPOINT: "http://jaeger:14268/api/traces" - OTEL_TEST_JAEGER_ENDPOINT: "http://jaeger:16685" - command: [ "cargo", "test", "--package", "opentelemetry-jaeger", "--test", "integration_test", - "--features=integration_test", "tests::integration_test", "--", "--exact", "--ignored" ] - depends_on: - - jaeger diff --git a/opentelemetry-jaeger/tests/integration_test.rs b/opentelemetry-jaeger/tests/integration_test.rs deleted file mode 100644 index 275c130bdc..0000000000 --- a/opentelemetry-jaeger/tests/integration_test.rs +++ /dev/null @@ -1,243 +0,0 @@ -#[allow(deprecated)] -#[cfg(feature = "integration_test")] -mod tests { - use opentelemetry::{ - trace::{Status, TraceContextExt, Tracer}, - KeyValue, - }; - use opentelemetry_jaeger::testing::{ - jaeger_api_v2 as jaeger_api, jaeger_client::JaegerTestClient, - }; - use opentelemetry_sdk::trace::Tracer as SdkTracer; - use std::collections::HashMap; - - const SERVICE_NAME: &str = "opentelemetry_jaeger_integration_test"; - const CRATE_VERSION: &str = env!("CARGO_PKG_VERSION"); - const CRATE_NAME: &str = env!("CARGO_PKG_NAME"); - - // the sample application that will be traced. - // Expect the following span relationship: - // ┌─────────┐ - // │ Step-1 │────────────┐ - // └───┬─────┘ │ - // │ │ - // ┌───┴─────┐ ┌────┴────┐ - // │ Step-2-1│ │ Step-2-2├───────────┐ - // └─────────┘ └────┬────┘ │ - // │ │ - // ┌────┴─────┐ ┌───┴─────┐ - // │ Step-3-1 │ │ Step-3-2│ - // └──────────┘ └─────────┘ - async fn sample_application(tracer: &SdkTracer) { - { - tracer.in_span("step-1", |cx| { - tracer.in_span("step-2-1", |_cx| {}); - tracer.in_span("step-2-2", |_cx| { - tracer.in_span("step-3-1", |cx| { - let span = cx.span(); - span.set_status(Status::error("some err")) - }); - tracer.in_span("step-3-2", |cx| { - cx.span() - .set_attribute(KeyValue::new("tag-3-2-1", "tag-value-3-2-1")) - }) - }); - cx.span() - .add_event("something happened", vec![KeyValue::new("key1", "value1")]); - }); - } - } - - // This tests requires a jaeger agent running on the localhost. - // You can override the agent end point using OTEL_TEST_JAEGER_AGENT_ENDPOINT env var - // You can override the query API endpoint using OTEL_TEST_JAEGER_ENDPOINT env var - // Alternative you can run scripts/integration_tests.sh from project root path. - // - #[test] - #[ignore] // ignore this when running unit tests - #[allow(clippy::type_complexity)] - fn integration_test() { - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .expect("cannot start runtime"); - - let agent_endpoint = - option_env!("OTEL_TEST_JAEGER_AGENT_ENDPOINT").unwrap_or("localhost:6831"); - let collector_endpoint = option_env!("OTEL_TEST_JAEGER_COLLECTOR_ENDPOINT") - .unwrap_or("http://localhost:14268/api/traces"); - let query_api_endpoint = - option_env!("OTEL_TEST_JAEGER_ENDPOINT").unwrap_or("http://localhost:16685"); - - let test_cases: Vec<(&str, Box SdkTracer>)> = vec![ - ( - "agent", - Box::new(|| { - opentelemetry_jaeger::new_agent_pipeline() - .with_endpoint(agent_endpoint) - .with_service_name(format!("{}-{}", SERVICE_NAME, "agent")) - .install_batch(opentelemetry_sdk::runtime::Tokio) - .expect("cannot create tracer using default configuration") - }), - ), - ( - "collector_reqwest", - Box::new(|| { - opentelemetry_jaeger::new_collector_pipeline() - .with_endpoint(collector_endpoint) - .with_reqwest() - .with_service_name(format!("{}-{}", SERVICE_NAME, "collector_reqwest")) - .install_batch(opentelemetry_sdk::runtime::Tokio) - .expect("cannot create tracer using default configuration") - }), - ), - ( - "collector_isahc", - Box::new(|| { - opentelemetry_jaeger::new_collector_pipeline() - .with_endpoint(collector_endpoint) - .with_isahc() - .with_service_name(format!("{}-{}", SERVICE_NAME, "collector_isahc")) - .install_batch(opentelemetry_sdk::runtime::Tokio) - .expect("cannot create tracer using default configuration") - }), - ), - ( - "collector_hyper", - Box::new(|| { - opentelemetry_jaeger::new_collector_pipeline() - .with_endpoint(collector_endpoint) - .with_hyper() - .with_service_name(format!("{}-{}", SERVICE_NAME, "collector_hyper")) - .install_batch(opentelemetry_sdk::runtime::Tokio) - .expect("cannot create tracer using default configuration") - }), - ), - ]; - - for (name, build_tracer) in test_cases { - println!("Running test case: {}", name); - - runtime.block_on(async { - let tracer = build_tracer(); - sample_application(&tracer).await; - - tracer.provider().unwrap().force_flush(); - }); - - // assert the results by the jaeger query API - runtime.block_on(async { - // build client - let mut client = JaegerTestClient::new(query_api_endpoint); - let service_name = format!("{}-{}", SERVICE_NAME, name); - assert!( - client.contain_service(&service_name).await, - "jaeger cannot find service with name {}", - service_name - ); - let spans = client.find_traces_from_services(&service_name).await; - assert_eq!(spans.len(), 5); - - for span in spans.iter() { - assert_common_attributes(span, service_name.as_str(), CRATE_NAME, CRATE_VERSION) - } - - // convert to span name/operation name -> span map - let span_map: HashMap = spans - .into_iter() - .map(|spans| (spans.operation_name.clone(), spans)) - .collect(); - - let step_1 = span_map.get("step-1").expect("cannot find step-1 span"); - assert_parent(step_1, None); - assert_eq!(step_1.logs.len(), 1); - - let step_2_1 = span_map.get("step-2-1").expect("cannot find step-2-1 span"); - assert_parent(step_2_1, Some(step_1)); - - let step_2_2 = span_map.get("step-2-2").expect("cannot find step-2-2 span"); - assert_parent(step_2_2, Some(step_1)); - - let step_3_1 = span_map.get("step-3-1").expect("cannot find step-3-1 span"); - assert_parent(step_3_1, Some(step_2_2)); - assert_tags_contains(step_3_1, "otel.status_code", "ERROR"); - assert_tags_contains(step_3_1, "error", "true"); - assert_eq!(step_3_1.flags, 1); - - let step_3_2 = span_map - .get("step-3-2") - .expect("cannot find step 3-2 spans"); - assert_parent(step_3_2, Some(step_2_2)); - assert_tags_contains(step_3_2, "tag-3-2-1", "tag-value-3-2-1"); - }); - } - } - - fn assert_parent(span: &jaeger_api::Span, parent_span: Option<&jaeger_api::Span>) { - let parent = span - .references - .iter() - .filter(|span_ref| span_ref.ref_type == jaeger_api::SpanRefType::ChildOf as i32) - .collect::>(); - if let Some(parent_span) = parent_span { - assert_eq!(parent.len(), 1); - let parent = parent.first().unwrap(); - assert_eq!(parent.span_id, parent_span.span_id); - assert_eq!(parent.trace_id, parent_span.trace_id); - } else { - assert!(parent.is_empty()); - } - } - - fn assert_common_attributes( - span: &jaeger_api::Span, - service_name: T, - library_name: T, - library_version: T, - ) where - T: Into, - { - assert_eq!( - span.process.as_ref().unwrap().service_name, - service_name.into() - ); - let mut library_metadata = span - .tags - .iter() - .filter(|kvs| kvs.key == "otel.library.name" || kvs.key == "otel.library.version") - .collect::>(); - assert_eq!(library_metadata.len(), 2); - if library_metadata.first().unwrap().key != "otel.library.name" { - library_metadata.swap(0, 1) - } - assert_eq!(library_metadata.first().unwrap().v_str, library_name.into()); - assert_eq!( - library_metadata.get(1).unwrap().v_str, - library_version.into() - ); - } - - fn assert_tags_contains(span: &jaeger_api::Span, key: T, value: T) - where - T: Into, - { - let key = key.into(); - let value = value.into(); - assert!(span - .tags - .iter() - .map(|tag| { - (tag.key.clone(), { - match tag.v_type { - 0 => tag.v_str.to_string(), - 1 => tag.v_bool.to_string(), - 2 => tag.v_int64.to_string(), - 3 => tag.v_float64.to_string(), - 4 => std::str::from_utf8(&tag.v_binary).unwrap_or("").into(), - _ => "".to_string(), - } - }) - }) - .any(|(tag_key, tag_value)| tag_key == key.clone() && tag_value == value.clone())); - } -} diff --git a/opentelemetry-jaeger/trace.png b/opentelemetry-jaeger/trace.png deleted file mode 100644 index fedf78c6676489a015bfca9792c8011447da6a83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86393 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≧r4? zl;@~FF#Ed_Eb<6b*?G@eZFl??21CpGWMn*ll>#In*s{rG?6s*X-_mww+N5R2*U!Pr zC3DhegF5ss8$wmLVp7Z-UuS*7x^K=+TDW92TGC*G;g0ylQ!%tK8~Lx-3IbPvkcqp& z@pgH>Tk*v|ZGyZ8*3F`LaX37$K5nZ^&ptavF&FW*qfQO~im!4dAW>V&#UPMpA6Y%OuFl-wf>UU78<%Hg|js4%#;N9P{;*C@S?Pc+;> zwGo;lr-A^tBe1Uj!Z1yu{~1_`1Uk|WI&f0v^Y?q(s>Hw8i=HHaUBjtXLo9}Zvc#Jq z;kg*2g3~mLSnga5{O=Ajd4VbJ9G%!n#v07~4|MHY95O z$PjZof6#GnJa+_Zpb8bSw2jtBXS!7v8}bzFDt)-pbNNE@tzV99m_Hdbc_?XNJYPg& z!7qz#4aUz~Rub_bA&fSzjnU%Hv>|xolY_VbuosF#$uVzBljZ;n$^nOLsptB4>R&=6 zHQGJ@G?Z79fJSec*NtI&<=t=#-v$G{Jf(7+QfmIGB3td0DLJZzO-m%ao^$OQf=bCD zMpsSFRQ5`+eQP>FOwk+}+z@^S4lGG-lhGZ{1750?1 zXcWXyj5t&{n~~?^3ebVDKU~k0xQlxkV-|;8BG!{_|Pj%Oj-mYNyH;N}> zTPN_a1-Q{{EDo9_OIKACF*7vJ_-xl#Upml>>~?%HEWS=6aXq`GhV5y-!&43T$r0an z_?3$|Tpm9#q`$|D_lFL7d9&}C(nF^FE z)*!k7aSc4Z8h_5&==N08aEo_cwg|^0=bMp1xtn8#%EjC0?`*YTKi_YVxg=0NQ!XT^ zc4RR71I*l(Oh*9F(Jqg>vtmuCc}>@zuYq^RhZX$4&mOl(G9?YsUa$D$@SdrsUW8>F z7fzh&^7cHEvh{1$#t;vOV6~;dM3SH=tRtSeIcgZ+E`?Qhcrff8p`8LL<*SgPqNkcU zqVpu*`+5$^y*jMrB%{dVQ08LD@WW3(X42Ea)J=U&Nm?gdQU_G%xNCZTxylmeN1Sjfs8*zNdH^MVUePXCve9Y5dKxcH7hS>E+3z4VBwL(MEda9f z;7;SWm_IMA@!#+%#%8-G(9jHjDpSMUJUKU{*RP{`tRP@EZz_J6jh$D{Cg7qhwN@y; z(%SmtNd5%8+8M2wAgf%^v@atr;1e%H46k#^7dN7KH6fbBz~WbIHKlp*2qZ(nD?VT< z%oG#%wLHl4s?zhdoHkO=|8G>?0&w*U2j3p$Z70nE>sXyghF`n~x2ah;lfc`HUP*g9 zWekUqWBIva&4ru})^WDa@>f87eaDJarragAJv%~jCr%s#YGoX)Vp|_>^0EHWUU&J! zy4Jy_rcZzV4VGA0QZ+^cwE*CfJiB!wYYw1~Y?>`_WqcGi(%g6Q+@>Q0?tS}tzCu}$ zQ{L_6cR2$lrPoVZKZUsZ4_MZft~k5WGhefByL};NWd)hAPQrj^MV!21 z%pYviFNV5i6(Dh^$T8~1go%t=6ynG)Xt}@nK8ugO2~#8RPVz8o&kO%G{FwmTpaJNl z*#T1qmaofsx05oL+|>+H%5=|%v&t&2Kfx4Z(KItY-mbLUg_TF<7#N<>Oa+daMIx#) zF+rON<`?o_6xEuo23jPNU@si&A0hNLWaN{PDNQ8`alRaeDeeuUc80+Jo1DdjC1dEm zg^|luc%Gk^gxB4WffH~B0{HsUfSp(BW(!g%w@qaP?XRHdJsO-2V6qmzbCl9ZQMwLK zzN8Q#4K9DTSc=W`68$r(_NV*M3?B3wDE-DLVaGod9#)!seMGTO_e;7m<4EfGtxKgc z+^n=HJ`y?sz0vTsg5~m8IvBwPR+w?qK+O9un)t;wP0gZ^I>j;A)~(L~mP5LjfKU>D zF-u(^!q9IM^yFOf524xhFU!58$^`@(@Sy{kGwSVBP~qtEY-%((EJ`grA%$ zVMm-d_R(fn_i?bqnl!2>FjeAn_na=@i;}LU<$&>OR0P2kv||fl@Syha`gUrjyr__Op}ARB1cgn9+PMega}*WIE;C?D4|>%RU$1wWCX z8PXx;ax!k?{-5Ck6f$`>s~`JrRj6en^>>^Y>EOUNhpblE2H#%Q%ge<(;Zak*^)BQdCbc^}lE`6 zW0t3zM478tIl3T)@#mMb6VPK9Y&Xh5z0Yhm0#j1HIMkg-7aEj!6rE7?s4%C(QH5M6GGw9Hlcg&N3)!3(-LlV9DXTE(uq zu6qS8vo-<}mjkV20WLlXQX#0ps28(haqUxji~q0wr~Dl{!4j`Lw8nFLRqs9t1JYE~ z)$nf7YUQP)@+|BOL#GY)1_U9}h3hP{&kF(bifILNlXZ;t5AP z>$N<5r3YC72-OEW$JmKi!tVfVY5)>MlHFFG12OdzbiD(Ty$YL{m?(eWi9?=8k?#N& zFg3~NIVztABner#KejLa;;fteBdSv{P+Wq8fG*A#Uq1gw7!)f1;6+D+blT~(#sKg{w+$nz~35izlZAr!l1=G992K-)S_~^5?R8U97xH2Y*Oi(cd zoIYM!dU5Owb7$Pu2IAF!1*E1b-~deG=E%v)s<5g`NL>dxWB^J_TLij(>e(QYUFaYS zaD1z(qWxU9)*_qO?hv9UGCu$ZfRA8IILi7)UYoLQKw75Zu`v?5a9X(`S(rm zgPd-Dx6owjgLO%E&H}c(TIg#s!$S{%`|j$>9`GK}OuoB3JY1G|Q-=cNrwP5u z)p{uY1A6pQAZ<=!ZxQB-%#9VPbs%BHP%zwQT|$sT?HZzs%7%BaLca2@{*_`z)_;go zp_F^&uYW*}tN%hMxuCU8fAN-0_jUrJ4g3%AnF+7jfn5kZKpgMS^!x+_vViy-|D?fn zFDgd-FRroY&>h+)qy^wiJ09dkmj7=Swq-i^_ss#ye1O{Bs_c&k*dfCM0y0QN6}aho z*Z-kr%MmGtvO-`6B@vFlMLk?~2UMbfqW=S6bz3+2w+NOXFXj6aIS;q0LiE;C|1G`g zIP?Dj&hB?T6nxI|z5u{&o<%?{1s;T^GvPd5J^z6;b#AZtd0;f ziU4K&2hZ97$n>S(XY31%G=QD{ON>LE;t%`(DTdzk&;M$o3c=%k`Cy3qy8sQf*@VLX zYki)n{}Qt20h-|-3z+_g8)kqtrT>>;{Da;bJ!;R%?B)lA1H3#vpe*`&30?n~ujfbc z^0U_?6D)uQcHRDF74ku$HxNZ9|Nc@UJdVF$)WZuVzl5F!3o*0gL?SSpfSX z5WeXVg2#jLP=PxU|H&ggOZ}mj9Q@I{I~rel>Zgkn;Yo4FnIPNq2n)Igwq*bQ8dnva zWrQ01B=wOO%&=a$lDtZw^!?M2&e?vol~U(}t`{BG!G91GhrHtVT6|D-2_mvg6A}`P zfjAmj^1qEkb8^V16akb+_d#G@3jn(d4t7IDqfDTUrma0YIy#!uSWHO3VNa&#?*EGS z{JFqa01o5+sE7$d#joam;xxJN2|x^q0kiPNacL=ggfOy@T|iK9RE)eGH_j~d_qWBxCb=8p!cV+LeWRl)2Av;1B)~vnYeLN= z*I6fj{dx$&whv$3D`wvVlbF#YG8TbqrdvK)(AWU0`Mcckgr&}5W=fHle5~oIy5N& zB3oZyU+^2i^JGmQ0YhX5TwFq;5)Nboj9%L5wq9Od0G{hO*x5)5xAE0`HkFqRt*vvn z+bb)*A<8bGt8VugAP|M!bG{0A1-!f32Yeo(9Ge}gT!TSwapohEdypSrTT{aXYi=H_ zi3b7W2nVh>uYux2q6R; zBV+U?_*Iv=0ILWWckT^{d8=6UQDO=9jMZbjN!$^R)-oy^&&LR!EP~W1{U4P!PmDq^ zIEY1C7UFs|b(y(c%Rt%$F^L9}<>)aV4XDt>Q19Po?lty7{p-1j3TU%+0}sy8ZPWMaBa6$tEHa zifskS3+1ui-E4=?PnR{2bRegXdVYS41fIlVxZ>6mFvsKWBU7s@ttT&nf-H}9Ad+## zsIDuqf!V!#oUl?-IC4rVI&e&fMGq986+Paqxx$^lIEa z_$22?kg_VA23dxQ5?iCk(MP#aag|9+qdV5?sSWZprZJZ6Y6a=~wl^rM!*x ztMzVnkVVG@AsS<$1)sn_WrM=irCt)S$?HjlaTKf#@`SSR%{w(|PT_{a? zr>KoG&iJ0QL3grv9(_7)6gKbjSs}`QQZ%xkrl*K=Og8^iVsjNGm0A9ybnr2nybvW_ zma6=pSRR!gb??`zU}|u)d0j-;Dit;I;D;}S`aPk(9j=l5$SUH*#XCiRLRiFNU#yID zj{cO|cV1w>xpTlyA+)~hYYWrnW^C|a_OAKjZt%O!Zx<)Vql$sQ{5P_V8tle>ZtfcF z8qM>D39Pg8B?>LsE}?H%_A6b@v#nAcccM>L$2DCHlkWZW46{vIkj8YWSu9f`;dC9{ zryt^6mpA!cZlRJzsT-JOt!*B$=0a(Yhb$JZCKafT@R2=3(S{?Un2>(&FK*oeAWu@o zm@n;R%S830oxjDXxJ$x;c$?vZMEoPCZ)QUqn|s*N`|YC)rGI=WMs{s-SFLD`DRh5tyv8Tk&QH*RWh^8z^JxvYlotBumLLQv4^!T%M@j<6R`{hTvWF|Y^hrC?ggWQN81NzT(xE6~Pd{=sgShJ~k|SSD2}opJ|x}3cCUQfQN))x^MXq60PdI zOOuia0eL;kzS{7_B#jApzo_+Dv6ZyS@3O8w|bT*SDWA(^tYo8qtGd( zvpjY8G2yR*WLvpsE8&^@=*Xe4of zQ`meqpm7DPo})21Q%}|AO6|SapX~9Je5;)OdIU)zWqWIIN-JjTXd;oXrx2X%nkW z8$__Yct?D(q8<4vn(MXkCvun;KYV)3vDXb%nN5@b+*3@t7fX@}fq293iG~{@4q28P z=Q??gLW~hzP@@4eH>HUCV#Kv9d(lJ`%t9NIj&@hdwY>MeS0RDYNSH7i$2VBfec=nFHVzkP{aS?DVsJ`$S{dZzwjM=`U^z>?-tc#SI-nv+Ei#^dLA zw=esYE38Tt{W)kt{?tmU>PbM=mIQDsL$>g4WN;bJ+k{Qz4mTyj1JK^TL8Xo!RsH;o z1nDaeS`A6^a8xlOYa!O1je6YYX>Gls{^HnFYlU#My+_E=MxRi=C_^1dPIEsmkPeV1 zMd{&C3y@(^)`=4jLO2WHQMgU03`lg(z3QB|YNJgpyai2jFiOjk(ZS%d<@G2m>quil9yB#b zw0K6Jt~vqPxupx~cq^*%ShwhRKjB+wiI5~p_FLHvY&J2%LHOMTRK5rTrK$f|+RHBX?~yhHw)r!jTi!uiBefQq=59g-T`pWV6- z@{+<`EhBX=x8F(@QO{7RJFlSd@}y<`4gg{nP>616P<CG>FN;Kd@wG-!9|z}Gj|KMpF^Gm_fa*aq>FA? zegUG;FNrN=Xc71%zPE0k4o92r$^KSW=xPOr3rm=aPbd}eHp`zyoG~8UwKUb!)uC*$P z_~Sv1$k*T7{H`eA zD}ERHJ39lhEV>q4wM5O0fg>BV8-_1>KhjL8Y3o3!O_3v!9a{C2W8;2qbhMaNc;71R zJ%5Ms)=gX;&B>+|KIblO|7r6acmK%ZRgC3t)672$W$+nM#?BEaMEIshB_Xf{pGV%% zYa>zDrvTpfqA9Ap{4r{L^ipkv+(dP1=8yCDJin+>W26vv4U}Cdb>ez|#R{PwCSBF{ z8fgw-phOkcB(~t9k__3Vl5}}cZy=h51|P;tLSbu5$%U0zADO?7WW5Jjj$O0|W2BPo z{5}`s-r*>O?^%Z&{;W4Zc_pAF62q&+5mu=4`j!-|U$^7uM_}mtjPT=@AS&72hEdcv znGh+1)vBL}aQR2?&&2v(-2#EM-bS{!KJDUoLXU&0XLk9cR}E-xjGcD zvfj8!hceHlrjyHNBpf3((*;g8?b^xHVLqciGDsTZ`z$pxkb1^hpY)|mas+ibY1N4> zXLjLF#OGA+uQ+6zD5C;;+Xq!LM0sa+lY}?|OX2ifloG+6?_XRdYMFTO7@xJ+$0)3! zaxXtWF$kc~2r2*`ad{l7chYbwWgeJ^`|&H5-WLL9GYk=f>F=ZaFv0V41jE*ku+f%g zOS68nmyxhW64#!)y^-7ZaJ)a1WARIJK)b-}qg;J9_xCbf7ePUi2;r%|vP-xxX|U7? z?s&a6#E!+X`|U7VJ^^Go+<{Os-J}#QWa-MzxrLyeHHCwK%?d45d_IhSc*pvzK^t-& z01k~BN%1No0mY)Z>P!rz&IR#cU0bCCce#J%}4)8B}Wg+}5l$9CrzMTL-a z6vOuz6z(F9f91}imcVB&g>#%%bP`L+cL)&}m|==|P=m<3HW<0q$Cdo{ldzWre0Egcg+(IAyqGIO$^hb5o%x1QN_u<*p$%V9e=rmx; zT{5#73hlF&z~@?vVcZ#ozTU~o>b$y}qvm`vBgj8ld3fDUY|%O2aQROAj1Oo9NeC-u zC3B#RbftZQ80_QzJl&t~<9KT$zM%y{(}B2FJ53Nb_^_J`2Kk-7Q~tcuiRU_1O`c(@ zf&r}sMA`xSb|Pqn7A9aX zBCVroLMYe<-w+Wz=IZjxrv)>pGdeZrZNs!7K?Z?uQ)w#)iQgYt1uDjY*0ytAXtrta zmQ%51#at!{>%Z5A5G{STX8l~;i@=nK{vG|Z%MGy({64bLA3*0ftGtlj$&A)PJ73uVH%q)P0`sadJm;WU(1M1Zt}S5MWyG z+~)`7F;fi*crMP{X~XhX2_Jphi&z3TV}ArtBHpP(?)0gds%g8+EVt2>;pjjl8qoP} zn0}K?1(&Kz!*V%ZYNRO*SY8wjpBHl~RFH_d=dE4Er;IL#$Yiu(`|8CoS|stG3VIlKWI-)+$L1 zdC-tG$Jm*D>0OxSu?oZH&up8%cXTiSj7xU*r{J0U$0;`(#2Vv?5X5%GeE_2z(u7#L zB6)mNhWfu&em+UudZ!Gf5lhneSQ0E?d_3|}47^bumhFFt0$QQEAk{DINl;o zC{G&=c*Bx(fdIu}Fn3fN;vD{#T2V+3)GFYhI>TNFFEI2ihkspQu102{eAIopim8=T z7`jjle|}?%3~v5{fT06LdVJk$k3uztz4^o+%_`UgD_I9pS{46Bfsm_d++TUaQnUmP z+6)n5&HAK=qX`Gho;lmIx9gMDd}ZfPF&R?||7ipa$tk&Sy~4Gg;IkCMLtc6*kIhX5 zUi@<9IGvRT_9*#;40Cs5j);WE6DKlq9V6S(6qhhe8F(t|ob$e%1EBq;!}gE;jq{hL zu@sADe3LrZ;NnqW>|%R) zB1Yj*h{rbkh;arP9Yah3Q)*t5xn?3MH8Yo|g7!lvdyzn(4&>2I=N1zzygq9&CLSr- z^5h+Hvt16U`T)hF$0j4pu+b`!JPk%}TP=xXstL&)Y<36$lSWH} zu^0QA&VK-;%JXcRe9pj0tWT@$Xmdow5kCn!VrKcb5s4M#N|m6{1JOGCwywZfzD1uV zxgh8D@n+XEOOwSI5FPy|{Kp{jeg{_IwQu8?!wc25n-~HjRt+^8i}qou0SxE)AS3pR zclT4k^?1s+b6Svx`Clb7?28fWvNOQOeV=~>tRqm}<5>MSL}6|5kTN)m$T&fv{^hVK zGk3Sd3Rh%EYaGRG;z5uKnU}9U6E_|jVi_-dze2Da{(Yol&_2ZWD>F>h(zf#sVTFl= z8TK=Tcx%FAJq&juKyA2J3-T)-ZDToqq;zXpvq)ems#_GSKdTW6qI21`*o1bJ;48b} zyOd&5wtn1-TCU?*y*k;XyZg%a|26m3QCU9iy01zpAe{n=bT`recJnW-+PjLj6-E$6#_HB!44{A(X4(|^YSo^l+9CF zH!$VgFgpa1dDw}@xLP#_kRBx_PZ(>HlSQrSu@oN+(-ZlmrX*Ul!2-VDT4e(W`RX^` zW)K}Xy;?W^SeSOD@9>p$G~o^hy1CI;!jl!-2P{%L6Q*Hc;q-n9pPzMqh3ChDbvfvv zbW3)}bJ$0nY8|lnEdz3}LxE3pRy4Uo?XG!^Rju1G*JF4bk(Y(;8lKyo4_)~eV6J>I zAFDIz<3Y}Vkt)0>;|R99__=2`TdlS6+zTDQqqWMg;aJ0rSy0%IU}=X}5eGX%8ZFdB?&lbeO}U1d#_h{JS2^c6 z7*F}`JpuMuqWy4Ni01YX7+}m#h?$EUpRK+xyv$HBj?t)0QU7v#5vvX&-gryq;`&Z% zBG|wQ2H`6bowgUJ4CGUAF~hA>o3XkL@-9Ac>KGAkIE@)yc;vf1*100m8u^h5dtC{- zW_H+m-wbDA7yL$Z7ufRFQge7ko={RhTn1?XXOH=D_rNK?v$ zw=<>CziS%(wHT8RpEBvfr%cw0`N-i{Ta;_7e8k9WcSz^NQ#gvyN$K-qguhHXaj@OC z?(Fn1mgoqnGip56SabYrUiMeuA?UnHS^q{^z|>#I@)>lZp=$nS^Rzv4+t-6K*|4t?4N z==3VyjA(iBKXsK4(9CTxWIux_;=~sEm;d=q-nn0Wax z0@~t)u*5JuYT?m{mfRimFWd5rY=-vi+5!PzLp1&CBcm|ysf#(c>GC&GJn8y?&n2A| zSV1yUo};hpTPbP<^>}QT`$oU@k-S($55uUOF+)+=Wa*Bql0?gRq0*hk;zVEpjbE`h$WF9vAc>2xcw)v%d!_U7-%f=|l{o4Kg(pn1vzmK| zzx@Er4J|Kdl%Y^pX!lko=BQ;j?_4QOR94VbK}`G-%}v49`_iW2N0kNF8S3$B;Wt~&Q?1};LD)I63sO0I_YfptvW-8Aj3DZwv_`71( zy6iE!C_@L11g@?entM@Xtj2yk+zl!*O@nmurhS7X z`c1kK?nD;EiHvpmsAdQz4Y8Xi$Lw?<5f$3)byu}^-=z9<;Tzk77hsElV zs3gLolFxdII5D&a&9y()KL(3dyJa@d%9r27UpTv@^@f>83V$X-h3C1PqVf$}5}FIo z7}}oa(iY&~3j; zQY@c_Lk5k_6``Rasa3Hc67-=~gw7KMd*h98c=&*?>riu^HWRKQ?4u%4y>f?>nD`je z(X$I(xyeZn@}$gWsK7jtnQor+m1=j|#6hhM$aKXF$=*Vzv05(@qzgAr;xeMlAgAG2 zE4y-kwC#H?=3PJ&LGpBU!khW1@{ytF96F;tt>?p}Ay%t~(X8(+LARDCv}#`D!6y}w zgbx=31LKcw1Xndc}|*J0=iT zUxLq*!G{dvL$tb&j+%2ml>TNjjx1Q*ScG>ezf~2dC#t-O(QJDgVNdV?o1DscWUxX3 zx<&AOXZ^UHLr7`F(SA5x$$}CiqdmfO8S{Gab6cmx)XjO}7;*zuRj3C>di>`#2ep;d zd|MpNj@%2v^k=M8c0VhAe@k*;dXd_E!(uc;dG1okr7QJDIGh=Uo9s8A=i^=|E%gv) zz?g|~YsDP|S<42r`Ab-L%cThc&rAB#yHKrNBHL|lMVv<|qty2b>~mRn?y^hBZ<2_h zhhNWmd27m>Z}Mq|nER%Q>;2p+(EVb_{jfWPC*qqqs(TnhGn>g({_ycfFyNAXW8~U| z;K-{&!{jj)yP(m4J6Gwe_x--^+QRgP#^TvVt7SQLGdgwyNj-Oe*5bxOU7#nqQ$(Vs*HR_Nwtyew@-YDVZnu1h{=x1{U7 zm%t^XYq#jqZ4{cKouBB3<}jj~8H^dke@V+ybtHfoF0nlyc?i179ALV7>m{d{3A+G&0$n5Tx5nX<;^{Um?D_Lm(0ziF+#sO|fgmh#AV4wz)+! zJ&u-7y1%_SBU8SW!W1f$)meh|olWI=UZeLH_xLu#t_rlo@tx;IrZnx%CXmfr_Ls`O zU4q~l@z~+8(J9fN32^3uVbvL%I{PGtm~PC?FkN0V{7G@(j!KZ^?aN{p8K^c$r}2a? z;Csg6~MgbaB9}gyij9p8pbK1av7rSD1O1Gw2-GxOhyGCf zLVxEX0Ix)o$5U)R@GmMdT=0;1s~pfYv$$yaTXvESNMFR716ny@0FZI0{+=~pEkwGf z6=&WP__q0Os6rP#h6RSVHmxUK{>0kp8TQ=DvB0}-z}9s=;*@shu;%bFFZU;G*yBt;O#mWC)$ExCx$^^G9?gnd_Rx?AvmL7Su7F( z1BdpwiqRy28&Owss{V!}e~?!K{zW8j9{}DE`vcHZ#vou8gx#rc=3@Bn{O8nyn!orV zMzA_gufU>)q~y!bWrh7;0YT9RK|pJyF4mR0wSK#LEJ zYzL+J-83D z9ee)1$;yK;geRd{?;o^-PNsx594I^$V{uH>UsRJ347Y}aKH0;;S;u6m*#oV&!doAX zxD4=j!{3}9J$mSkz^d7_kRR8jyqGvv6MWb0S`7dIe6ULpRClL6Lg9f;ru9=(Pd+WG znuXvudNaX(<%Z5DPARg$3uzl>N-m96{e9iDR>F-?|7YT&7m|2y_%YmRhU-bNX}ycx zm^iggtf^YhIya2*@ox|mH@{|nA4PyR7R+H4VjkBo@`1#hG0QH^~MDN&7)P+oRE;9Ol8QG_u<3kaH9srgRQyN z#>PgFk?fP~7bpRs!@SWV<9gDa-{soce@L%gyB6!ceROoRSOWl?iVHixow|E_SMq+B zBVV+-7e^jKbQy--S&OSmA>%U2Avp$Q+D1BN{olV+Cpt^19A7&lz{SP=!2fG$2`f^L zH3w*IfL$!VF#CQRVqjtxyh@RLA>qE6T37c2FNa#fTi4G~%!IyF{%uy7X}OX4KU zXX@hOl7{w7@3w=0u&~F{x3qhqo<+sQJWjtQ()dXp*2xWgsaWmwIy%vJ26WWZ2gQOg z|A0X-Rn*k@kgzGq40U>3rVk_ouj5X=HduLZ<`1)l{OHE^jb>vO7vqRS6hh_W&JMbj(j^`Ofa*?CaX zVTY57X&_7BqmO`~?M(u9;5=PuyDu~Gz`(%$T*CWwnT*!iIc)hcleA@VaimklnXyAh zN1pc1Tb;d4hfq#|N1izqT`z*BzUXp`$F0W=zQeS%u=skDn#LxyzdrxPutx27!1+R% z{^Q4w_p$M3pAcq+#EuRQ65oftc27Y!Q8zgb^U2evWChLqHs;}lA&0^oI|eg9F)JjV z{~@QMCgEEIvIJywCvBeZ&v5&e2eQu?!t3o^PycxE4LoJ>>RJVU50H|9Rdvr#D4Y9e zTGrQ})c#amTeN1I)600$dj;`UP){JSkcO~zg?@re|DD@I(#x9Bt1b*#A!c=}>I@5* zomcc2>VItlrJuOSq}hJ@xcY7L`9u*HR?R!dBm*pE8i5`BkFv-(4^*nuE2II5p6A5q zRRpUi1lf$*E?XD1J(D^!a$`pc>B;vJoGDdn zO(iwZgJd$2UTKFLacSNmdKkBVxMNxSm|5i*&#g*a#m~BCb%W2Ts@wum2`PqHa=77g znqB9Q_cu;XPJT5-(HB3)QUzi>AOh8VGwx3y_ zA>Cy{-P2P=jo-oz$TPjn3|daASi39A%Ar=jQ&?DdBb-tZ*}dZt?V`=0H=j-INBL0C zz<__OHFk7ovDYuUbH1z3rFc4{`bxd`Az@-5-t~n0Iyz)Gzn}C+TlOX&J~TP)c`F9C zs#*<>jAS!CXIHji>aO%YESb9YfeCnfqzIprQT=o3#Pag;-}vLgOvaV4=njrT!sCmagQOF0kA|q(9`1`n*vPUqK4QzA-Dh(xqFXmYHW-m5r~)QJO4S@n$GFa z=!3-)AcruGgM;&^-e07YUSY7%dZCL!E{w9kv1Y^u z95vWdXNHA1yZRSgZaafP+T!=!iF@Kz zZ&>o1|12UbF?|M5jc`k(eqeEVIPP=+fC8|Q%ttTN-ya<}>_c*Qe5~K%O>7IGA^n^ET@Fg_%OO&O+2XU#dF@c_GiuI^pF5f56oDsmj|DZ zgupT9fH4$dzkAg83Zny~O=@<|UN41V^pwK7!Q@#&R6rOwZzopnGnhkJA9~ zZjIk4PUh7-bHeD1ROA#ZTDNBm4g`P32)h|J$3pT!BW6QLczWwx-(R?R5_kZLRXAFg zWnO-O|L^oGNDMlp2ZzJMYlQDYsKf%B7X~9|zjX`?_Aq!nd?w%=+XSEdBg)+=?Dcor z+K0z<^mp>>9Q*Wqr+|+Fd*;u-Ov$omak>RV@Y1=Ez#cUHPYC}GFd(r9ik&)>xCk~$ zE>?wC3&DsCy9LlR5AXwZMpWIop>h&`=dz5&iB3NMI!(leWB51!iQz9FZunms1(cEH z08vqQh6O|uWy7PvUvmNGX!x|gcpv^l_4g;`dMeoiV3;4XUEeP}I?|NBOwze_Yu;+81asDjM0?za9|?zN{D(`VgBE ze8g8XPwpS%We)(l?DIPRG238wpS)Ya{O9!1beai(mt{&eSiDX|;Xr6_}Lw*}`=RZ}>AHp?YgKGOj;ApmkeJX2ieJT2T6t+$5ycQAP#xVq-a&N4l*~wv*{61sLXSnJxga zQF9zMX}8!>pu$AM03_bPRD@yJ9K<3JD8N6P0Pk5VdeO2c(c0x57%yyi+(q5$M0uIT zl|uSpGJ8Pg4K4Bpjn`_iudi>e?Bqw&CLlhxWD1rn`$59%>mb|YSU6L%`+Fgr$V#)8 zU{ERjyrZeJ(_28Q3CMG3`j6Fk?C919b|;B`BK`!lzIOV6c8h+ukn_?vz{C@;=p?bz z#Rv57tn-|##?_ZHPft&010;TblM$ou=2%#|SYkIJ7}#jMd;_e{7HpZ;Ieax~(KBR# zZ65%KV*W9rCZIYqU>45{Bl0=j+~mF*Di)6?aUhi>MXK4$%G3O{*yi={R~t`dV`F0* zO%vW#)fiJcnB-W*%p*p$cymH`a>wnUO^+2||+-WeI8IG&KEMje3D>T*^sF--H#*iPHR$ZAWe$-A#Z<9ALEwR5gvA_0d zDx4pJ=Xt#LsT3nQv5<{tTj*>HIE6eEjz^P&x`e=+0rpQDy*gL1CgWB!l<4=RfPBl? z_!kNG?RDTYXQuG?2Zno2oQ75O(Oa`2T#8rK9o7NIL)?NoUg}MLTOhf!#8Y^@)p|W$ zKZbIM@*0@W(?#y!5;F&wpB(M#k+W%%5+B+TYW9lnCk7T6)#|y)K9Ho~rMdLX0Svl{+hA`#+YQ3tqS`$@vRHEctcessyM&7^ekUL|Z^bG>O~(+RH_Wi9QShX} zWut0^b4_%jk4SMfLh>|-5C+s$3L#v%g7hqW=^Jn4_ON_sRGl`bZ=0Kul&0JE2=!m~ zeZ}Lb77I4|N9I2HBhuSeF=pZYM%cV`lPL73u~CFHlp;WQLnYd4{u{SksJW&%1yL?c zp0Y+R)+1D$2!E&|tx#cU>t>Nrtw=xjuUov8Pb)e49rLscSY2nameHo_G@3t;&3**< zdi~{c0V(e!$B+Wo8;RtHpf70oK_j;V;wqr6b5*TT1o*9`cc450OkrDgdDw(;v*qr@ z0z==v-HSFPmy^=X;OzUwJW1!m>p`e^ti;6o`B&4@eweck)Iren3R4!W0YUqvoVTB1 z=>61j#)Q<4VE+w!>5gl3d7)$$XQwCHSCikG1<>5cDX<*+Da+qM<2b8nO`c^psF?3A zLV4wjwem08zz+hv9oG|h%@seK0j06D6q(B3ahj~uG{eJ?*ckFJ>d6$70Kl=tUzF=E z%ITh?Ahe{kZ=;qTv6N5n+AcG4H#A)2Ax15Sc^`0J)kRw^+;2TeqP1eqls2VSTd#)R zM>hMidGf6Ply3gA80J8K%}qzVXU(6?$o|3|SG4(oy>-{s(XF4+YW*9(>)tQT>IytH zD9@$UvHpzRed(oYHNbKkzKI&y1Y4(_n*$vj$(( z1^!lF_Fwn3$cK<}G*Xl9UaKOYMf-u(H#JpsS;1Q42~i(JZd6Yx;c<6@0K+9sy?PbA zUoCz27~?cfK2z<|7ppuP(b{i0>L_KgYH7(gtgi4pdSeU%E0xx;s|QFe*iU}GZ+UNQ zb(~9Z(tqK?%mt@xQ{1F&(?6XnQFt<~t^H@}T3uNweW#AMY^+U96Y#N^u$1rq9=V@*sDGFqlvb$*vio|aVuLa{ zBWv?U?uWtpXUDD-EWJh1wLWC@mrdpQ8yYc!qP~u97Ec4(jOb*rf;Cj!Pi{c0s1Y3< zsLATmmX*^wT9`Kf%LXs%NoJ&79$CrV9;S!$lynj1mmbOSi-~n<=F4YdnF4l|id>P> z;8z@6UAN4w7hgupN(BeS<3A32giGlsS{x$H#Ucv=%T8kW$8J)=5WOJw2&N9qdST}P zO^&!ZrYt9% ztD=+&lXnxmZ4pBJNkdXq>1MNd5Hqge&_Ly*`X7LX$1FQGA}TnjMk7^`flc7+vPur+ z?sR_}u9?5P(C+IoZ2``!3qUqCSMFk1R~XCJ0gN`%j?sthUuWWiq4$NDwnq&pBV)KW z(mj8&;NFf_864pC*nY%d82jWgh@>qo+`2R7k#q!P~c zER*rT%i8`&v!={flSSP)S&Os1>7ewjkcA{?+}W zT&?fFBUdg^IyScL-qt)gD9$Cc(eI3m{(Aa6j=Yj3{hk?nY66WVgN2{jrRF}fw#zA= za@)~n?`g^=moNI97E8*kebn3ZFv<4-nyzs?5LoTYn2(fM3c6GIp8bfex60Rg zZKo8+zSZ`4MmAhIFFg}6=#ZmZBHIF*sLGHxSpr%{DcEPR+*g9PLN2Vtjv9m6&xc^<3-oaX z@gd90Yha|$n>&f*>%B|8vt^Q%+stiGtz$-&pkAjg?07r;r<%{DH5ChW!oH>KysI)I9y5s7U#Zb=&b>5YAv+trVytkUUJ7QRD zhF-K~XGhexnci{=bYF)sU&*xTRvPVWYR1~BLxtBMyJ_pK`Hr@-TftFKYFNxJ1mDpm``ZP+ zHv!AT_7$tB~TClv|VX6Wzbx&eb^B%Agelb8p{o$Z@n8XR`<@Tg(VdINO*^5_i&Y^|(< zJ^{33{{%?gkp@QkwT;}y!)R1k$S-@4yJE$#Kp0ZzCaL-B&E(A&;UlkFE{VNUG~TW{ zs~?@iHuYdEuF5$=wB zes9JKW5JA>y`vm+6N6Lm0DO?JeF)P3m{h{$u(;6u&V%iEUb7N_&}_&ln|c<2-Yi{w z;SqB=h86u{iZ$yQwsXQG0r2c41En!v6}D7*RFKvn5nu<~!{3>+XFYD50AzS?GUz)|t)o|CzRSFu0BXG%(w>XWEk4>kr>{;X>0gxkSPvH9<(P|$aGgYWp zo=dLJ>+UTbD@!Tl($#aPU8<;NJ6N@B+qPz?my2DWMu|eR&dh@dkW=5{44&EbaPblNt+naO`rInM+1jo` zoBpeZoe3s%wo%gq=U1n(saODj%L4}hGyd`^FmkfqX1nLSZK@`|8*|27rCibsgO2vU zZ}$8<*EESZ_>vI)Nzv>>eSN*Nd+bsB+zcIFQ;s@C@0AY8mtH;^-@f5xL@skEwkvk+ zHFE1JcFoTzDt3+MV~OVn|D^f)iS^q?N!Untq73hYK<4=%E|H=3o`)dmPieBOrL0mK zHIvBNn`PReyFE|JWt1PWniGUK<#;7#Xc`FXDk^5>m29Nfj4rfzP ztoqyAepw}Nc!jQoM*fZs6t3@h{ql}=bN|UzJUpG=OQqE%G5t0#@8?S^@V;%dp#}TX zbvlAe7s~41x2j1G7+T@K29dQQRMaf_-IFRP$&;}X|KpSW`ZL41CQlvc36LpQNA-*E z4Cnpg^n_Z?XF|%DWGI|v!TrO{o5mm1Za>V`h_D&h6^c2PIZdx>@uo6*L>(-PYS%ar9v6gXj(W1K#10laSi#k73Lh zjoeG8`k!xteWv!?tXLE18KH)l&87j}p>>?k3r?1!onIy8GjAVr)|sCdr26c%bmbuhOF7h3RJLS*iUoFbmJq)XB0&7DC)-tp_$#$r61iSsfLx!E zvZ#1KiTJUsD=`J0;R0j^C|UJFRCX4@WCBx#L!{JwNC|eF;Q1?vPaqNEj|N2I3j(20 zMMFGN2fhLgr1E&3>fp(&zYl=CK&=oV^wj?GQbZOL0%=o@)-^{wNLKb;CgLSh@6HgX zKzk$p*aVV;;4mn4!^{dt*3FPhdNUNRS)O)@hhLSI5 zY?naV8V8{q2=PJClK|~^l=Vx20^{D0cFi`BV_g!A{7N+ zf(Pntc6%#`+s>~#e>tjL<2}jn4gm6_k2VlXPd?Vw@<`i9jTs{z06|&E1m$F!$fo8- zR*Mx`LiZ09rHBkDq@nD)CDLV#kyoS42tm~qRYQ}gRivdG5X}pUk8+q9SwtaFtDNAu zYRKoJs__S97X(-W(Df+6)9VmOBGQDZ1<|8O=c0cr4kyroKrHHEVvpcJU3LC)RJl^u zR;>}2a4YN~m$Tyk;Xo%y7vN5W!6m@)?+(O;yaZ(-tA9AqGo(`lA<9O!=+fsma>p5g8Pfdl=Ec|Zh0-kz;8 zaUzNM_1xc4{Ssx(^PYA#UVZ7leCiE=w|miBHPX1~PXFkwwT$LzD?Kt^ocr)iWM4i% zp@;0go5N+_>k&}`!hs6MDAz^ZwfV0nth|I#0hu1z$?eCHRtZ8yI8?8f!hmlPAEsAu01c~ ze_CiT`2Sxx&>K2rZ~PzRK;KbL0fdm@$G_PHb)w^*b$f?Q}N}Ig=4;F+!VgLXD diff --git a/scripts/lint.sh b/scripts/lint.sh index 13b728f156..2d251b75ac 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -12,7 +12,6 @@ cargo_feature() { if rustup component add clippy; then crates=( "opentelemetry" "opentelemetry-http" - "opentelemetry-jaeger" "opentelemetry-jaeger-propagator" "opentelemetry-appender-log" "opentelemetry-appender-tracing" @@ -40,16 +39,6 @@ if rustup component add clippy; then cargo_feature opentelemetry-otlp "http-proto, reqwest-rustls" cargo_feature opentelemetry-otlp "metrics" - cargo_feature opentelemetry-jaeger "isahc_collector_client" - cargo_feature opentelemetry-jaeger "reqwest_blocking_collector_client" - cargo_feature opentelemetry-jaeger "reqwest_collector_client" - cargo_feature opentelemetry-jaeger "hyper_collector_client" - cargo_feature opentelemetry-jaeger "hyper_tls_collector_client" - cargo_feature opentelemetry-jaeger "collector_client" - cargo_feature opentelemetry-jaeger "wasm_collector_client" - cargo_feature opentelemetry-jaeger "collector_client, wasm_collector_client" - cargo_feature opentelemetry-jaeger "default" - cargo_feature opentelemetry-jaeger-propagator "default" cargo_feature opentelemetry-proto "default" diff --git a/scripts/publish.sh b/scripts/publish.sh index cbe67d7630..e106fec06b 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -9,7 +9,6 @@ packages=( "opentelemetry-proto" "opentelemetry-otlp" "opentelemetry-stdout" - "opentelemetry-jaeger" "opentelemetry-zipkin" "opentelemetry-prometheus" "opentelemetry-appender-log" diff --git a/scripts/test.sh b/scripts/test.sh index 1b7437b1f6..cd02a14994 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -11,5 +11,4 @@ cargo test --manifest-path=opentelemetry/Cargo.toml --no-default-features cargo test --manifest-path=opentelemetry/Cargo.toml --all-features -- --ignored --test-threads=1 cargo test --manifest-path=opentelemetry/Cargo.toml --all-features -cargo test --manifest-path=opentelemetry-jaeger/Cargo.toml --all-features -- --test-threads=1 cargo test --manifest-path=opentelemetry-zipkin/Cargo.toml --all-features From d959e49fc4ba8e56e5cad4216e372bc96c138efa Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Fri, 17 May 2024 21:10:34 -0700 Subject: [PATCH 2/2] nuke the folder --- opentelemetry-jaeger/README.md | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 opentelemetry-jaeger/README.md diff --git a/opentelemetry-jaeger/README.md b/opentelemetry-jaeger/README.md deleted file mode 100644 index 7e2f9f68a2..0000000000 --- a/opentelemetry-jaeger/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# OpenTelemetry Jaeger (Deprecated) - -This component is deleted. The release of opentelemetry-jaeger, 0.22.0, can be -found [here](https://crates.io/crates/opentelemetry-jaeger) If you are looking -to export traces to Jaeger, use -[opentelemetry-otlp](https://crates.io/crates/opentelemetry-otlp), as Jaeger -natively supports OTLP. See [this](../examples/tracing-jaeger/README.md) for an -example. - -If you are looking for Jaeger propagator, it is available in the crate -[opentelemetry-jaeger-propagator](https://crates.io/crates/opentelemetry-jaeger-propagator).