diff options
| -rw-r--r-- | Cargo.lock | 33 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | crates/arti-relay/Cargo.toml | 22 | ||||
| -rw-r--r-- | crates/arti-relay/src/config.rs | 12 | ||||
| -rw-r--r-- | crates/arti/Cargo.toml | 4 | ||||
| -rw-r--r-- | crates/arti/src/cfg.rs | 3 | ||||
| -rw-r--r-- | crates/arti/src/logging.rs | 137 | ||||
| -rw-r--r-- | crates/otlp-file-exporter/Cargo.toml | 24 | ||||
| -rw-r--r-- | crates/otlp-file-exporter/README.md | 12 | ||||
| -rw-r--r-- | crates/otlp-file-exporter/src/lib.rs (renamed from crates/arti/src/logging/otlp_file_exporter.rs) | 51 | ||||
| -rw-r--r-- | crates/tor-config-shared/Cargo.toml | 32 | ||||
| -rw-r--r-- | crates/tor-config-shared/README.md | 12 | ||||
| -rw-r--r-- | crates/tor-config-shared/src/lib.rs | 52 | ||||
| -rw-r--r-- | crates/tor-config-shared/src/metrics.rs (renamed from crates/tor-config/src/metrics.rs) | 4 | ||||
| -rw-r--r-- | crates/tor-config-shared/src/opentelemetry.rs | 95 | ||||
| -rw-r--r-- | crates/tor-config-shared/src/opentelemetry_stub.rs | 15 | ||||
| -rw-r--r-- | crates/tor-config/src/lib.rs | 2 |
17 files changed, 377 insertions, 135 deletions
diff --git a/Cargo.lock b/Cargo.lock index da6ff146b..cfd816cec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,6 +218,7 @@ dependencies = [ "opentelemetry-otlp", "opentelemetry-proto", "opentelemetry_sdk", + "otlp-file-exporter", "paste", "pin-project", "postage", @@ -241,6 +242,7 @@ dependencies = [ "tor-cell", "tor-config", "tor-config-path", + "tor-config-shared", "tor-error", "tor-general-addr", "tor-hsclient", @@ -389,6 +391,12 @@ dependencies = [ "futures", "libc", "metrics-exporter-prometheus", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry-proto", + "opentelemetry_sdk", + "otlp-file-exporter", "rand 0.10.2", "rustls", "safelog", @@ -405,6 +413,7 @@ dependencies = [ "tor-circmgr", "tor-config", "tor-config-path", + "tor-config-shared", "tor-dirclient", "tor-dircommon", "tor-dirmgr", @@ -427,6 +436,7 @@ dependencies = [ "tor-rtmock", "tor-units", "tracing", + "tracing-opentelemetry", "tracing-subscriber", "void", "web-time-compat", @@ -4436,6 +4446,16 @@ dependencies = [ ] [[package]] +name = "otlp-file-exporter" +version = "0.1.0" +dependencies = [ + "futures", + "opentelemetry-proto", + "opentelemetry_sdk", + "serde_json", +] + +[[package]] name = "p256" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -7116,6 +7136,19 @@ dependencies = [ ] [[package]] +name = "tor-config-shared" +version = "0.45.0" +dependencies = [ + "amplify", + "derive-deftly", + "opentelemetry-otlp", + "opentelemetry_sdk", + "serde", + "tor-config", + "tor-config-path", +] + +[[package]] name = "tor-consdiff" version = "0.45.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index 78b13b290..ddc1f114d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ # https://blog.iany.me/2020/10/gotchas-to-publish-rust-crates-in-a-workspace/#cyclic-dependencies members = [ "crates/oneshot-fused-workaround", + "crates/otlp-file-exporter", "crates/web-time-compat", "crates/slotmap-careful", "crates/test-temp-dir", @@ -35,6 +36,7 @@ members = [ "crates/tor-async-utils", "crates/tor-config", "crates/tor-config-path", + "crates/tor-config-shared", "crates/tor-rpc-connect", "crates/tor-log-ratelim", "crates/tor-rpcbase", diff --git a/crates/arti-relay/Cargo.toml b/crates/arti-relay/Cargo.toml index ccbcd505f..630fa351f 100644 --- a/crates/arti-relay/Cargo.toml +++ b/crates/arti-relay/Cargo.toml @@ -51,9 +51,19 @@ full = [ # This feature flag enables experimental features that are not supported. Turning it on may # void your API. -experimental = ["metrics"] +experimental = ["metrics", "opentelemetry"] metrics = ["tor-chanmgr/metrics", "dep:metrics-exporter-prometheus", "__is_experimental"] +opentelemetry = [ + "dep:tracing-opentelemetry", + "dep:opentelemetry", + "dep:otlp-file-exporter", + "dep:opentelemetry_sdk", + "dep:opentelemetry-otlp", + "dep:opentelemetry-appender-tracing", + "dep:opentelemetry-proto", + "__is_experimental", +] __is_experimental = [] [dependencies] @@ -71,6 +81,13 @@ libc = "0.2" metrics-exporter-prometheus = { version = "0.18.0", optional = true, default-features = false, features = [ "http-listener", ] } +opentelemetry = { version = "0.32.0", optional = true } +opentelemetry-appender-tracing = { version = "0.32.0", optional = true } +opentelemetry-otlp = { version = "0.32.0", optional = true } +opentelemetry-proto = { version = "0.32.0", optional = true } +# TODO: Figure out why this feature is needed, and how it interacts with non-tokio runtimes. +opentelemetry_sdk = { version = "0.32.1", features = ["rt-tokio"], optional = true } +otlp-file-exporter = { path = "../otlp-file-exporter", version = "0.1.0", optional = true } rand = "0.10.1" rustls = { version = "0.23.5", default-features = false, features = ["logging", "aws_lc_rs"] } safelog = { path = "../safelog", version = "0.9.0" } @@ -87,6 +104,7 @@ tor-chanmgr = { path = "../tor-chanmgr", version = "0.45.0", features = ["relay" tor-circmgr = { version = "0.45.0", path = "../tor-circmgr", features = ["flowctl-cc"] } tor-config = { path = "../tor-config", version = "0.45.0" } tor-config-path = { path = "../tor-config-path", version = "0.45.0" } +tor-config-shared = { path = "../tor-config-shared", version = "0.45.0" } tor-dirclient = { version = "0.45.0", path = "../tor-dirclient", features = ["relay"] } tor-dircommon = { version = "0.45.0", path = "../tor-dircommon" } tor-dirmgr = { path = "../tor-dirmgr", version = "0.45.0" } @@ -108,6 +126,7 @@ tor-relay-crypto = { path = "../tor-relay-crypto", version = "0.45.0" } tor-rtcompat = { path = "../tor-rtcompat", version = "0.45.0", features = ["rustls", "tokio", "tls-server"] } tor-units = { path = "../tor-units", version = "0.45.0" } tracing = "0.1.36" +tracing-opentelemetry = { version = "0.33.0", optional = true } tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } void = "1" web-time-compat = { path = "../web-time-compat", version = "0.2.0" } @@ -115,4 +134,3 @@ web-time-compat = { path = "../web-time-compat", version = "0.2.0" } [dev-dependencies] tor-keymgr = { path = "../tor-keymgr", version = "0.45.0", features = ["ephemeral-keystore", "testing"] } tor-rtmock = { path = "../tor-rtmock", version = "0.45.0" } - diff --git a/crates/arti-relay/src/config.rs b/crates/arti-relay/src/config.rs index ee0fe5312..2259a9234 100644 --- a/crates/arti-relay/src/config.rs +++ b/crates/arti-relay/src/config.rs @@ -20,10 +20,10 @@ use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder}; use tor_circmgr::{CircuitTiming, PathConfig, PreemptiveCircuitConfig}; use tor_config::derive::prelude::*; use tor_config::{ - ConfigBuildError, ExplicitOrAuto, MetricsConfig, MetricsConfigBuilder, - extend_builder::extend_with_replace, mistrust::BuilderExt, + ConfigBuildError, ExplicitOrAuto, extend_builder::extend_with_replace, mistrust::BuilderExt, }; use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver}; +use tor_config_shared::metrics::{MetricsConfig, MetricsConfigBuilder}; use tor_dircommon::config::{NetworkConfig, NetworkConfigBuilder}; use tor_dircommon::fallback::FallbackList; use tor_guardmgr::bridge::BridgeConfig; @@ -297,6 +297,14 @@ pub(crate) struct LoggingConfig { /// Do not turn this on in production unless you have a good log rotation mechanism. #[deftly(tor_config(default))] pub(crate) log_sensitive_information: bool, + + /// Configuration for logging spans with OpenTelemetry. + #[deftly(tor_config( + sub_builder, + cfg = r#" feature = "opentelemetry" "#, + cfg_desc = "with opentelemetry support" + ))] + opentelemetry: tor_config_shared::opentelemetry::OpentelemetryConfig, } impl LoggingConfigBuilder { diff --git a/crates/arti/Cargo.toml b/crates/arti/Cargo.toml index cce45ad87..ad1f49aad 100644 --- a/crates/arti/Cargo.toml +++ b/crates/arti/Cargo.toml @@ -125,10 +125,12 @@ syslog = ["syslog-tracing"] opentelemetry = [ "tracing-opentelemetry", "dep:opentelemetry", + "dep:otlp-file-exporter", "opentelemetry_sdk", "opentelemetry-otlp", "opentelemetry-appender-tracing", "opentelemetry-proto", + "tor-config-shared/opentelemetry", "__is_experimental", ] @@ -200,6 +202,7 @@ opentelemetry-appender-tracing = { version = "0.32.0", optional = true } opentelemetry-otlp = { version = "0.32.0", optional = true } opentelemetry-proto = { version = "0.32.0", optional = true } opentelemetry_sdk = { version = "0.32.1", features = ["rt-tokio"], optional = true } # TODO: what's up with this feature +otlp-file-exporter = { path = "../otlp-file-exporter", version = "0.1.0", optional = true } paste = "1.0.3" pin-project = "1" postage = { version = "0.5.0", default-features = false, features = ["futures-traits"] } @@ -221,6 +224,7 @@ tor-basic-utils = { path = "../tor-basic-utils", version = "0.45.0" } tor-cell = { path = "../tor-cell", version = "0.45.0", optional = true } tor-config = { path = "../tor-config", version = "0.45.0" } tor-config-path = { path = "../tor-config-path", version = "0.45.0" } +tor-config-shared = { path = "../tor-config-shared", version = "0.45.0" } tor-error = { path = "../tor-error", version = "0.45.0", default-features = false, features = ["tracing"] } tor-general-addr = { path = "../tor-general-addr", version = "0.45.0" } tor-hsclient = { path = "../tor-hsclient", version = "0.45.0", optional = true } diff --git a/crates/arti/src/cfg.rs b/crates/arti/src/cfg.rs index 181ab295d..50081c877 100644 --- a/crates/arti/src/cfg.rs +++ b/crates/arti/src/cfg.rs @@ -21,7 +21,8 @@ use arti_client::TorClientConfig; #[cfg(feature = "onion-service-service")] use tor_config::define_list_builder_accessors; use tor_config::derive::prelude::*; -pub(crate) use tor_config::{ConfigBuildError, Listen, MetricsConfig, MetricsConfigBuilder}; +pub(crate) use tor_config::{ConfigBuildError, Listen}; +pub(crate) use tor_config_shared::metrics::{MetricsConfig, MetricsConfigBuilder}; use crate::{LoggingConfig, LoggingConfigBuilder}; diff --git a/crates/arti/src/logging.rs b/crates/arti/src/logging.rs index fde918b77..ad9347a0a 100644 --- a/crates/arti/src/logging.rs +++ b/crates/arti/src/logging.rs @@ -7,9 +7,9 @@ use serde::{Deserialize, Serialize}; use std::io::IsTerminal as _; use std::path::Path; use std::str::FromStr; -use std::time::Duration; use tor_basic_utils::PathExt as _; use tor_config::ConfigBuildError; +use tor_config_shared::opentelemetry::{OpentelemetryConfig, OpentelemetryConfigBuilder}; use tor_config::derive::prelude::*; use tor_config_path::{CfgPath, CfgPathResolver}; use tor_error::warn_report; @@ -20,8 +20,6 @@ use tracing_subscriber::prelude::*; use tracing_subscriber::{Layer, filter::Targets, fmt, registry}; mod fields; -#[cfg(feature = "opentelemetry")] -mod otlp_file_exporter; mod time; /// Structure to hold our logging configuration options @@ -149,109 +147,6 @@ pub(crate) enum LogRotation { Never, } -/// Configuration for exporting spans with OpenTelemetry. -#[derive(Debug, Deftly, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[derive_deftly(TorConfig)] -#[cfg_attr(feature = "experimental-api", visibility::make(pub))] -#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))] -pub(crate) struct OpentelemetryConfig { - /// Write spans to a file in OTLP JSON format. - #[deftly(tor_config(default))] - file: Option<OpentelemetryFileExporterConfig>, - /// Export spans via HTTP. - #[deftly(tor_config(default))] - http: Option<OpentelemetryHttpExporterConfig>, -} - -/// Configuration for the OpenTelemetry HTTP exporter. -#[derive(Debug, Deftly, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[derive_deftly(TorConfig)] -#[deftly(tor_config(no_default_trait))] -#[cfg_attr(feature = "experimental-api", visibility::make(pub))] -#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))] -pub(crate) struct OpentelemetryHttpExporterConfig { - /// HTTP(S) endpoint to send spans to. - /// - /// For Jaeger, this should be something like: `http://localhost:4318/v1/traces` - #[deftly(tor_config(no_default))] - endpoint: String, - /// Configuration for how to batch exports. - #[deftly(tor_config(sub_builder))] - batch: OpentelemetryBatchConfig, - /// Timeout for sending data. - /// - /// If this is set to [`None`], it will be left at the OpenTelemetry default, which is - /// currently 10 seconds unless overridden with a environment variable. - // - // NOTE: there is no way to actually override this with None, so we have to say - // "no magic" to tell dd(TorConfig) not to worry about that. - #[deftly(tor_config(no_magic, default))] - timeout: Option<Duration>, - // TODO: Once opentelemetry-otlp supports more than one protocol over HTTP, add a config option - // to choose protocol here. -} - -/// Configuration for the OpenTelemetry HTTP exporter. -#[derive(Debug, Deftly, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[derive_deftly(TorConfig)] -#[deftly(tor_config(no_default_trait))] -#[cfg_attr(feature = "experimental-api", visibility::make(pub))] -#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))] -pub(crate) struct OpentelemetryFileExporterConfig { - /// The path to write the JSON file to. - #[deftly(tor_config(no_default))] - path: CfgPath, - /// Configuration for how to batch writes. - #[deftly(tor_config(sub_builder))] - batch: OpentelemetryBatchConfig, -} - -/// Configuration for the Opentelemetry batch exporting. -/// -/// This is a copy of [`opentelemetry_sdk::trace::BatchConfig`]. -#[derive(Debug, Deftly, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[derive_deftly(TorConfig)] -#[cfg_attr(feature = "experimental-api", visibility::make(pub))] -#[cfg_attr(feature = "experimental-api", deftly(tor_config(vis = "pub")))] -pub(crate) struct OpentelemetryBatchConfig { - /// Maximum queue size. See [`opentelemetry_sdk::trace::BatchConfig::max_queue_size`]. - #[deftly(tor_config(default))] - max_queue_size: Option<usize>, - /// Maximum export batch size. See [`opentelemetry_sdk::trace::BatchConfig::max_export_batch_size`]. - #[deftly(tor_config(default))] - max_export_batch_size: Option<usize>, - /// Scheduled delay. See [`opentelemetry_sdk::trace::BatchConfig::scheduled_delay`]. - #[deftly(tor_config(no_magic, default))] - scheduled_delay: Option<Duration>, -} - -#[cfg(feature = "opentelemetry")] -impl From<OpentelemetryBatchConfig> for opentelemetry_sdk::trace::BatchConfig { - fn from(config: OpentelemetryBatchConfig) -> opentelemetry_sdk::trace::BatchConfig { - let batch_config = opentelemetry_sdk::trace::BatchConfigBuilder::default(); - - let batch_config = if let Some(max_queue_size) = config.max_queue_size { - batch_config.with_max_queue_size(max_queue_size) - } else { - batch_config - }; - - let batch_config = if let Some(max_export_batch_size) = config.max_export_batch_size { - batch_config.with_max_export_batch_size(max_export_batch_size) - } else { - batch_config - }; - - let batch_config = if let Some(scheduled_delay) = config.scheduled_delay { - batch_config.with_scheduled_delay(scheduled_delay) - } else { - batch_config - }; - - batch_config.build() - } -} - /// Configuration for logging to the tokio console. #[derive(Debug, Deftly, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive_deftly(TorConfig)] @@ -372,7 +267,7 @@ where use opentelemetry::trace::TracerProvider; use opentelemetry_otlp::WithExportConfig; - if config.opentelemetry.file.is_some() && config.opentelemetry.http.is_some() { + if config.opentelemetry.file().is_some() && config.opentelemetry.http().is_some() { return Err(ConfigBuildError::Invalid { field: "logging.opentelemetry".into(), problem: "Only one OpenTelemetry exporter can be enabled at once.".into(), @@ -384,21 +279,21 @@ where .with_service_name("arti") .build(); - let span_processor = if let Some(otel_file_config) = &config.opentelemetry.file { + let span_processor = if let Some(otel_file_config) = &config.opentelemetry.file() { let file = std::fs::File::options() .create(true) .append(true) - .open(otel_file_config.path.path(path_resolver)?)?; + .open(otel_file_config.path().path(path_resolver)?)?; let exporter = otlp_file_exporter::FileExporter::new(file, resource.clone()); opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter) - .with_batch_config(otel_file_config.batch.into()) + .with_batch_config(otel_file_config.batch().clone().into()) .build() - } else if let Some(otel_http_config) = &config.opentelemetry.http { - if otel_http_config.endpoint.starts_with("http://") - && !(otel_http_config.endpoint.starts_with("http://localhost") - || otel_http_config.endpoint.starts_with("http://127.0.0.1")) + } else if let Some(otel_http_config) = &config.opentelemetry.http() { + if otel_http_config.endpoint().starts_with("http://") + && !(otel_http_config.endpoint().starts_with("http://localhost") + || otel_http_config.endpoint().starts_with("http://127.0.0.1")) { return Err(ConfigBuildError::Invalid { field: "logging.opentelemetry.http.endpoint".into(), @@ -408,18 +303,12 @@ where } let exporter = opentelemetry_otlp::SpanExporter::builder() .with_http() - .with_endpoint(otel_http_config.endpoint.clone()); - - let exporter = if let Some(timeout) = otel_http_config.timeout { - exporter.with_timeout(timeout) - } else { - exporter - }; - - let exporter = exporter.build()?; + .with_endpoint(otel_http_config.endpoint().clone()) + .with_timeout(*otel_http_config.timeout()) + .build()?; opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter) - .with_batch_config(otel_http_config.batch.into()) + .with_batch_config(otel_http_config.batch().clone().into()) .build() } else { return Ok(None); diff --git a/crates/otlp-file-exporter/Cargo.toml b/crates/otlp-file-exporter/Cargo.toml new file mode 100644 index 000000000..14ef4a46e --- /dev/null +++ b/crates/otlp-file-exporter/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "otlp-file-exporter" +version = "0.1.0" +authors = ["The Tor Project, Inc.", "Wesley Aptekar-Cassels <[email protected]>"] +edition = "2024" +rust-version = "1.91" +license = "MIT OR Apache-2.0" +homepage = "https://gitlab.torproject.org/tpo/core/arti/-/wikis/home" +description = "OpenTelemetry SpanExporter for writing to an OTLP file." +keywords = ["opentelemetry", "otlp", "tor", "arti"] +categories = ["development-tools::profiling", "development-tools::debugging"] +repository = "https://gitlab.torproject.org/tpo/core/arti.git/" + +[dependencies] +futures = "0.3.14" +opentelemetry-proto = "0.32.0" +opentelemetry_sdk = "0.32.1" +serde_json = "1.0.50" + +[features] +full = [] + +[package.metadata.docs.rs] +all-features = true diff --git a/crates/otlp-file-exporter/README.md b/crates/otlp-file-exporter/README.md new file mode 100644 index 000000000..7e31609e8 --- /dev/null +++ b/crates/otlp-file-exporter/README.md @@ -0,0 +1,12 @@ +# otlp-file-exporter + +This crate implements an OpenTelemetry exporter for writing [OTLP] JSON files to disk. + +This at some point will be [implemented upstream], +at which point this crate will be deprecated, +but for now it's here. + +License: MIT OR Apache-2.0 + +[OTLP]: https://opentelemetry.io/docs/specs/otel/protocol/file-exporter/ +[implemented upstream]: https://github.com/open-telemetry/opentelemetry-rust/issues/2602 diff --git a/crates/arti/src/logging/otlp_file_exporter.rs b/crates/otlp-file-exporter/src/lib.rs index 19943b1e4..7ed386be9 100644 --- a/crates/arti/src/logging/otlp_file_exporter.rs +++ b/crates/otlp-file-exporter/src/lib.rs @@ -1,3 +1,50 @@ +// @@ begin lint list maintained by maint/add_warning @@ +#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable) +#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly) +#![warn(missing_docs)] +#![warn(noop_method_call)] +#![warn(unreachable_pub)] +#![warn(clippy::all)] +#![deny(clippy::await_holding_lock)] +#![deny(clippy::cargo_common_metadata)] +#![deny(clippy::cast_lossless)] +#![deny(clippy::checked_conversions)] +#![allow(clippy::cognitive_complexity)] // See arti#2556 +#![deny(clippy::debug_assert_with_mut_call)] +#![deny(clippy::exhaustive_enums)] +#![deny(clippy::exhaustive_structs)] +#![deny(clippy::expl_impl_clone_on_copy)] +#![deny(clippy::fallible_impl_from)] +#![deny(clippy::implicit_clone)] +#![deny(clippy::large_stack_arrays)] +#![warn(clippy::manual_ok_or)] +#![deny(clippy::missing_docs_in_private_items)] +#![warn(clippy::needless_borrow)] +#![warn(clippy::needless_pass_by_value)] +#![warn(clippy::option_option)] +#![deny(clippy::print_stderr)] +#![deny(clippy::print_stdout)] +#![warn(clippy::rc_buffer)] +#![deny(clippy::ref_option_ref)] +#![warn(clippy::semicolon_if_nothing_returned)] +#![warn(clippy::trait_duplication_in_bounds)] +#![deny(clippy::unchecked_time_subtraction)] +#![deny(clippy::unnecessary_wraps)] +#![warn(clippy::unseparated_literal_suffix)] +#![deny(clippy::unwrap_used)] +#![deny(clippy::mod_module_files)] +#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness +#![allow(clippy::uninlined_format_args)] +#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945 +#![allow(clippy::result_large_err)] // temporary workaround for arti#587 +#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best +#![allow(clippy::needless_lifetimes)] // See arti#1765 +#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060 +#![allow(clippy::collapsible_if)] // See arti#2342 +#![deny(clippy::unused_async)] +#![deny(clippy::string_slice)] // See arti#2571 +//! <!-- @@ end lint list maintained by maint/add_warning @@ --> + //! Tracing exporter to write spans to a file in the OTLP JSON format. // TODO: If https://github.com/open-telemetry/opentelemetry-rust/issues/2602 gets fixed, we can @@ -18,7 +65,7 @@ use std::{ /// Tracing exporter to write OTLP JSON to a file (or anything else that implements [`LineWriter`]. #[derive(Debug)] -pub(crate) struct FileExporter<W: Write + Send + Debug> { +pub struct FileExporter<W: Write + Send + Debug> { /// The [`LineWriter`] to write to. writer: Arc<Mutex<LineWriter<W>>>, /// The [`Resource`] to associate spans with. @@ -27,7 +74,7 @@ pub(crate) struct FileExporter<W: Write + Send + Debug> { impl<W: Write + Send + Debug> FileExporter<W> { /// Create a new [`FileExporter`] - pub(crate) fn new(writer: W, resource: Resource) -> Self { + pub fn new(writer: W, resource: Resource) -> Self { Self { writer: Arc::new(Mutex::new(LineWriter::new(writer))), resource, diff --git a/crates/tor-config-shared/Cargo.toml b/crates/tor-config-shared/Cargo.toml new file mode 100644 index 000000000..88cff4b64 --- /dev/null +++ b/crates/tor-config-shared/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "tor-config-shared" +version = "0.45.0" +authors = ["The Tor Project, Inc.", "Wesley Aptekar-Cassels <[email protected]>"] +edition = "2024" +rust-version = "1.91" +license = "MIT OR Apache-2.0" +homepage = "https://gitlab.torproject.org/tpo/core/arti/-/wikis/home" +description = "Configuration information that is shared between arti, arti-relay, and other Arti projects." +keywords = ["tor", "arti"] +categories = ["config"] +repository = "https://gitlab.torproject.org/tpo/core/arti.git/" + +[features] +default = [] +experimental = ["opentelemetry"] +opentelemetry = ["dep:opentelemetry_sdk", "dep:opentelemetry-otlp", "__is_experimental"] + +__is_experimental = [] + +[dependencies] +amplify = { version = "4", default-features = false, features = ["derive"] } +derive-deftly = { version = "~1.11.3", features = ["full", "beta"] } +opentelemetry-otlp = { version = "0.32.0", optional = true } +# TODO: Figure out why this feature is needed, and how it interacts with non-tokio runtimes. +opentelemetry_sdk = { version = "0.32.1", features = ["rt-tokio"], optional = true } +serde = { version = "1.0.103", features = ["derive"] } +tor-config = { path = "../tor-config", version = "0.45.0" } +tor-config-path = { path = "../tor-config-path", version = "0.45.0" } + +[package.metadata.docs.rs] +all-features = true diff --git a/crates/tor-config-shared/README.md b/crates/tor-config-shared/README.md new file mode 100644 index 000000000..127c8681a --- /dev/null +++ b/crates/tor-config-shared/README.md @@ -0,0 +1,12 @@ +# tor-config-shared + +Shared configuration between `arti` client and `arti` relay. + +## Overview + +This crate is part of +[Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to +implement [Tor](https://www.torproject.org/) in Rust. + +--- +License: MIT OR Apache-2.0 diff --git a/crates/tor-config-shared/src/lib.rs b/crates/tor-config-shared/src/lib.rs new file mode 100644 index 000000000..1bdbac837 --- /dev/null +++ b/crates/tor-config-shared/src/lib.rs @@ -0,0 +1,52 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +// @@ begin lint list maintained by maint/add_warning @@ +#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable) +#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly) +#![warn(missing_docs)] +#![warn(noop_method_call)] +#![warn(unreachable_pub)] +#![warn(clippy::all)] +#![deny(clippy::await_holding_lock)] +#![deny(clippy::cargo_common_metadata)] +#![deny(clippy::cast_lossless)] +#![deny(clippy::checked_conversions)] +#![allow(clippy::cognitive_complexity)] // See arti#2556 +#![deny(clippy::debug_assert_with_mut_call)] +#![deny(clippy::exhaustive_enums)] +#![deny(clippy::exhaustive_structs)] +#![deny(clippy::expl_impl_clone_on_copy)] +#![deny(clippy::fallible_impl_from)] +#![deny(clippy::implicit_clone)] +#![deny(clippy::large_stack_arrays)] +#![warn(clippy::manual_ok_or)] +#![deny(clippy::missing_docs_in_private_items)] +#![warn(clippy::needless_borrow)] +#![warn(clippy::needless_pass_by_value)] +#![warn(clippy::option_option)] +#![deny(clippy::print_stderr)] +#![deny(clippy::print_stdout)] +#![warn(clippy::rc_buffer)] +#![deny(clippy::ref_option_ref)] +#![warn(clippy::semicolon_if_nothing_returned)] +#![warn(clippy::trait_duplication_in_bounds)] +#![deny(clippy::unchecked_time_subtraction)] +#![deny(clippy::unnecessary_wraps)] +#![warn(clippy::unseparated_literal_suffix)] +#![deny(clippy::unwrap_used)] +#![deny(clippy::mod_module_files)] +#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness +#![allow(clippy::uninlined_format_args)] +#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945 +#![allow(clippy::result_large_err)] // temporary workaround for arti#587 +#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best +#![allow(clippy::needless_lifetimes)] // See arti#1765 +#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060 +#![allow(clippy::collapsible_if)] // See arti#2342 +#![deny(clippy::unused_async)] +#![deny(clippy::string_slice)] // See arti#2571 +//! <!-- @@ end lint list maintained by maint/add_warning @@ --> + +pub mod metrics; +#[cfg_attr(not(feature = "opentelemetry"), path = "opentelemetry_stub.rs")] +pub mod opentelemetry; diff --git a/crates/tor-config/src/metrics.rs b/crates/tor-config-shared/src/metrics.rs index 654061818..f8d05c7ab 100644 --- a/crates/tor-config/src/metrics.rs +++ b/crates/tor-config-shared/src/metrics.rs @@ -2,8 +2,8 @@ use derive_deftly::Deftly; -use crate::Listen; -use crate::derive::prelude::*; +use tor_config::Listen; +use tor_config::derive::prelude::*; /// Configuration for exporting metrics (eg, perf data) #[derive(Debug, Clone, Deftly, Eq, PartialEq)] diff --git a/crates/tor-config-shared/src/opentelemetry.rs b/crates/tor-config-shared/src/opentelemetry.rs new file mode 100644 index 000000000..c3b2a9992 --- /dev/null +++ b/crates/tor-config-shared/src/opentelemetry.rs @@ -0,0 +1,95 @@ +//! Configuration for OpenTelemetry exporter + +use amplify::Getters; +use derive_deftly::Deftly; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tor_config::derive::prelude::*; +use tor_config_path::CfgPath; + +/// Configuration for exporting spans with OpenTelemetry. +#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)] +#[derive_deftly(TorConfig)] +pub struct OpentelemetryConfig { + /// Write spans to a file in OTLP JSON format. + #[deftly(tor_config(default))] + file: Option<OpentelemetryFileExporterConfig>, + /// Export spans via HTTP. + #[deftly(tor_config(default))] + http: Option<OpentelemetryHttpExporterConfig>, +} + +/// Configuration for the OpenTelemetry HTTP exporter. +#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)] +#[derive_deftly(TorConfig)] +#[deftly(tor_config(no_default_trait))] +pub struct OpentelemetryHttpExporterConfig { + /// HTTP(S) endpoint to send spans to. + /// + /// For Jaeger, this should be something like: `http://localhost:4318/v1/traces` + #[deftly(tor_config(no_default))] + endpoint: String, + /// Configuration for how to batch exports. + #[deftly(tor_config(sub_builder))] + batch: OpentelemetryBatchConfig, + // TODO: A different approach to this may be better, as getting the default in this way + // prevents the use of environment variables to override this, and also is inconsistent with + // other aspects of configuration. + /// Timeout for sending data. + #[deftly(tor_config(default = "opentelemetry_otlp::OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT"))] + timeout: Duration, + // TODO: Once opentelemetry-otlp supports more than one protocol over HTTP, add a config option + // to choose protocol here. +} + +/// Configuration for the OpenTelemetry File exporter. +#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)] +#[derive_deftly(TorConfig)] +#[deftly(tor_config(no_default_trait))] +pub struct OpentelemetryFileExporterConfig { + /// The path to write the JSON file to. + #[deftly(tor_config(no_default))] + path: CfgPath, + /// Configuration for how to batch writes. + #[deftly(tor_config(sub_builder))] + batch: OpentelemetryBatchConfig, +} + +/// Configuration for the Opentelemetry batch exporting. +/// +/// This is a copy of [`opentelemetry_sdk::trace::BatchConfig`]. +#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize, Getters)] +#[derive_deftly(TorConfig)] +pub struct OpentelemetryBatchConfig { + /// Maximum queue size. See [`opentelemetry_sdk::trace::BatchConfig::max_queue_size`]. + #[deftly(tor_config(default))] + max_queue_size: Option<usize>, + /// Maximum export batch size. See [`opentelemetry_sdk::trace::BatchConfig::max_export_batch_size`]. + #[deftly(tor_config(default))] + max_export_batch_size: Option<usize>, + /// Scheduled delay. See [`opentelemetry_sdk::trace::BatchConfig::scheduled_delay`]. + #[deftly(tor_config(default = "Duration::from_secs(5)"))] + scheduled_delay: Duration, +} + +impl From<OpentelemetryBatchConfig> for opentelemetry_sdk::trace::BatchConfig { + fn from(config: OpentelemetryBatchConfig) -> opentelemetry_sdk::trace::BatchConfig { + let batch_config = opentelemetry_sdk::trace::BatchConfigBuilder::default(); + + let batch_config = if let Some(max_queue_size) = config.max_queue_size { + batch_config.with_max_queue_size(max_queue_size) + } else { + batch_config + }; + + let batch_config = if let Some(max_export_batch_size) = config.max_export_batch_size { + batch_config.with_max_export_batch_size(max_export_batch_size) + } else { + batch_config + }; + + let batch_config = batch_config.with_scheduled_delay(config.scheduled_delay); + + batch_config.build() + } +} diff --git a/crates/tor-config-shared/src/opentelemetry_stub.rs b/crates/tor-config-shared/src/opentelemetry_stub.rs new file mode 100644 index 000000000..d8e86aa4b --- /dev/null +++ b/crates/tor-config-shared/src/opentelemetry_stub.rs @@ -0,0 +1,15 @@ +//! Stub for configuration for OpenTelemetry exporter + +use derive_deftly::Deftly; +use serde::{Deserialize, Serialize}; +use tor_config::derive::prelude::*; + +#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize)] +#[derive_deftly(TorConfig)] +/// Stub configuration for exporting spans with OpenTelemetry. +pub struct OpentelemetryConfig; + +#[derive(Debug, Clone, Deftly, Eq, PartialEq, Serialize, Deserialize)] +#[derive_deftly(TorConfig)] +/// Stub configuration for the OpenTelemetry HTTP exporter. +pub struct OpentelemetryFileExporterConfig; diff --git a/crates/tor-config/src/lib.rs b/crates/tor-config/src/lib.rs index da3c4a6fe..b86020ee2 100644 --- a/crates/tor-config/src/lib.rs +++ b/crates/tor-config/src/lib.rs @@ -58,7 +58,6 @@ pub mod list_builder; mod listen; pub mod load; pub mod map_builder; -pub mod metrics; mod misc; pub mod mistrust; mod mut_cfg; @@ -84,7 +83,6 @@ pub use flatten::{Flatten, Flattenable}; pub use list_builder::{MultilineListBuilder, MultilineListBuilderError}; pub use listen::*; pub use load::{resolve, resolve_ignore_warnings, resolve_return_results}; -pub use metrics::*; pub use misc::*; pub use mut_cfg::MutCfg; use serde::de::DeserializeOwned; |
