aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock33
-rw-r--r--Cargo.toml2
-rw-r--r--crates/arti-relay/Cargo.toml22
-rw-r--r--crates/arti-relay/src/config.rs12
-rw-r--r--crates/arti/Cargo.toml4
-rw-r--r--crates/arti/src/cfg.rs3
-rw-r--r--crates/arti/src/logging.rs137
-rw-r--r--crates/otlp-file-exporter/Cargo.toml24
-rw-r--r--crates/otlp-file-exporter/README.md12
-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.toml32
-rw-r--r--crates/tor-config-shared/README.md12
-rw-r--r--crates/tor-config-shared/src/lib.rs52
-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.rs95
-rw-r--r--crates/tor-config-shared/src/opentelemetry_stub.rs15
-rw-r--r--crates/tor-config/src/lib.rs2
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;