diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index b88a5cc36..318aee8b3 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -6,7 +6,7 @@ set -o pipefail source .evergreen/env.sh source .evergreen/cargo-test.sh -FEATURE_FLAGS+=("tracing-unstable" "cert-key-password") +FEATURE_FLAGS+=("tracing-unstable" "cert-key-password" "opentelemetry" "error-backtrace") if [ "$OPENSSL" = true ]; then FEATURE_FLAGS+=("openssl-tls") diff --git a/Cargo.lock b/Cargo.lock index 281c3789b..0a59a3ee5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -991,7 +991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.0", ] [[package]] @@ -1972,6 +1972,8 @@ dependencies = [ "num_cpus", "openssl", "openssl-probe", + "opentelemetry", + "opentelemetry_sdk", "pbkdf2 0.11.0", "pem", "percent-encoding", @@ -2148,6 +2150,37 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.1", + "thiserror 2.0.12", + "tokio", + "tokio-stream", +] + [[package]] name = "outref" version = "0.5.2" @@ -2645,7 +2678,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.0", ] [[package]] @@ -3117,7 +3150,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.0", ] [[package]] @@ -3418,18 +3451,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typed-builder" -version = "0.20.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9d30e3a08026c78f246b173243cf07b3696d274debd26680773b6773c2afc7" +checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.20.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" +checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5bc4d79c6..faf6e1d43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,12 @@ tracing-unstable = ["dep:tracing", "dep:log", "bson3?/serde_json-1"] # compatible with the preview version. text-indexes-unstable = [] +# Enable support for opentelemetry instrumentation +opentelemetry = ["dep:opentelemetry"] + +# Capture backtraces in errors. This can be slow, memory intensive, and very verbose. +error-backtrace = [] + [dependencies] base64 = "0.22" bitflags = "2" @@ -101,6 +107,7 @@ mongodb-internal-macros = { path = "macros", version = "3.3.0" } num_cpus = { version = "1.13.1", optional = true } openssl = { version = "0.10.38", optional = true } openssl-probe = { version = "0.1.5", optional = true } +opentelemetry = { version = "0.31.0", optional = true } pem = { version = "3.0.4", optional = true } percent-encoding = "2.0.0" pkcs8 = { version = "0.10.2", features = ["encryption", "pkcs5"], optional = true } @@ -118,7 +125,7 @@ take_mut = "0.2.2" thiserror = "1.0.24" tokio-openssl = { version = "0.6.3", optional = true } tracing = { version = "0.1.36", optional = true } -typed-builder = "0.20.0" +typed-builder = "0.22.0" webpki-roots = "1" zstd = { version = "0.11.2", optional = true } macro_magic = "0.5.1" @@ -216,6 +223,7 @@ futures = "0.3" hex = "0.4" home = "0.5" lambda_runtime = "0.6.0" +opentelemetry_sdk = { version = "0.31.0", features = ["testing"] } pkcs8 = { version = "0.10.2", features = ["3des", "des-insecure", "sha1-insecure"] } pretty_assertions = "1.3.0" serde = { version = ">= 0.0.0", features = ["rc"] } diff --git a/README.md b/README.md index a3e580712..1c51b6538 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ features = ["sync"] | `azure-oidc` | Enable support for Azure OIDC environment authentication. | | `gcp-oidc` | Enable support for GCP OIDC environment authentication. | | `text-indexes-unstable` | Enables support for text indexes in explicit encryption. This feature is in preview and should be used for experimental workloads only. This feature is unstable and its security is not guaranteed until released as Generally Available (GA). The GA version of this feature may not be backwards compatible with the preview version. | +| `error-backtrace` | Capture backtraces in `Error` values. This can be slow, memory intensive, and very verbose. | ## Web Framework Examples diff --git a/src/bson_util.rs b/src/bson_util.rs index d29849352..e410595be 100644 --- a/src/bson_util.rs +++ b/src/bson_util.rs @@ -341,6 +341,38 @@ pub(crate) mod option_u64_as_i64 { } } +/// Truncates the given string at the closest UTF-8 character boundary >= the provided length. +/// If the new length is >= the current length, does nothing. +#[cfg(any(feature = "tracing-unstable", feature = "opentelemetry"))] +pub(crate) fn truncate_on_char_boundary(s: &mut String, new_len: usize) { + let original_len = s.len(); + if original_len > new_len { + // to avoid generating invalid UTF-8, find the first index >= max_length_bytes that is + // the end of a character. + // TODO: RUST-1496 we should use ceil_char_boundary here but it's currently nightly-only. + // see: https://doc.rust-lang.org/std/string/struct.String.html#method.ceil_char_boundary + let mut truncate_index = new_len; + // is_char_boundary returns true when the provided value == the length of the string, so + // if we reach the end of the string this loop will terminate. + while !s.is_char_boundary(truncate_index) { + truncate_index += 1; + } + s.truncate(truncate_index); + // due to the "rounding up" behavior we might not actually end up truncating anything. + // if we did, spec requires we add a trailing "...". + if truncate_index < original_len { + s.push_str("...") + } + } +} + +#[cfg(any(feature = "tracing-unstable", feature = "opentelemetry"))] +pub(crate) fn doc_to_json_str(doc: crate::bson::Document, max_length_bytes: usize) -> String { + let mut ext_json = Bson::Document(doc).into_relaxed_extjson().to_string(); + truncate_on_char_boundary(&mut ext_json, max_length_bytes); + ext_json +} + #[cfg(test)] mod test { use crate::bson_util::num_decimal_digits; diff --git a/src/client.rs b/src/client.rs index ff4591981..c0ef225d6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -143,6 +143,8 @@ struct ClientInner { end_sessions_token: std::sync::Mutex, #[cfg(feature = "in-use-encryption")] csfle: tokio::sync::RwLock>, + #[cfg(feature = "opentelemetry")] + tracer: opentelemetry::global::BoxedTracer, #[cfg(test)] disable_command_events: AtomicBool, } @@ -181,6 +183,9 @@ impl Client { tx: Some(cleanup_tx), }); + #[cfg(feature = "opentelemetry")] + let tracer = options.tracer(); + let inner = TrackingArc::new(ClientInner { topology: Topology::new(options.clone())?, session_pool: ServerSessionPool::new(), @@ -193,6 +198,8 @@ impl Client { end_sessions_token, #[cfg(feature = "in-use-encryption")] csfle: Default::default(), + #[cfg(feature = "opentelemetry")] + tracer, #[cfg(test)] disable_command_events: AtomicBool::new(false), }); @@ -668,6 +675,11 @@ impl Client { .await; } } + + #[cfg(feature = "opentelemetry")] + pub(crate) fn tracer(&self) -> &opentelemetry::global::BoxedTracer { + &self.inner.tracer + } } #[derive(Clone, Debug)] diff --git a/src/client/executor.rs b/src/client/executor.rs index 6b68a78e8..674eeca94 100644 --- a/src/client/executor.rs +++ b/src/client/executor.rs @@ -3,6 +3,8 @@ use crate::bson::RawDocumentBuf; use crate::bson::{doc, RawBsonRef, RawDocument, Timestamp}; #[cfg(feature = "in-use-encryption")] use futures_core::future::BoxFuture; +#[cfg(feature = "opentelemetry")] +use opentelemetry::context::FutureExt; use serde::de::DeserializeOwned; use std::sync::LazyLock; @@ -14,6 +16,8 @@ use std::{ }; use super::{options::ServerAddress, session::TransactionState, Client, ClientSession}; +#[cfg(not(feature = "opentelemetry"))] +use crate::otel::OtelFutureStub as _; use crate::{ bson::Document, change_stream::{ @@ -106,7 +110,26 @@ impl Client { op: &mut T, session: impl Into>, ) -> Result> { - // Validate inputs that can be checked before server selection and connection checkout. + let session = session.into(); + #[cfg(feature = "opentelemetry")] + let span = self.start_operation_span(op, session.as_deref()); + let inner = self.execute_operation_with_details_inner(op, session); + #[cfg(feature = "opentelemetry")] + let inner = inner.with_context(span.context.clone()); + let result = inner.await; + #[cfg(feature = "opentelemetry")] + span.record_error(&result); + + result + } + + async fn execute_operation_with_details_inner( + &self, + op: &mut T, + mut session: Option<&mut ClientSession>, + ) -> Result> { + // Validate inputs that can be checked before server selection and connection + // checkout. if self.inner.shutdown.executed.load(Ordering::SeqCst) { return Err(ErrorKind::Shutdown.into()); } @@ -122,7 +145,6 @@ impl Client { } // Validate the session and update its transaction status if needed. - let mut session = session.into(); if let Some(ref mut session) = session { if !TrackingArc::ptr_eq(&self.inner, &session.client().inner) { return Err(Error::invalid_argument( @@ -154,7 +176,13 @@ impl Client { } } - Box::pin(async { self.execute_operation_with_retry(op, session).await }).await + Box::pin(async { + self.execute_operation_with_retry(op, session) + .with_current_context() + .await + }) + .with_current_context() + .await } /// Execute the given operation, returning the cursor created by the operation. @@ -408,6 +436,7 @@ impl Client { retryability, effective_criteria, ) + .with_current_context() .await { Ok(output) => ExecutionDetails { @@ -496,9 +525,21 @@ impl Client { let should_redact = cmd.should_redact(); let cmd_name = cmd.name.clone(); let target_db = cmd.target_db.clone(); + #[cfg(feature = "opentelemetry")] + let cmd_attrs = crate::otel::CommandAttributes::new(&cmd); let mut message = Message::try_from(cmd)?; message.request_id = Some(request_id); + + #[cfg(feature = "opentelemetry")] + let span = self.start_command_span( + op, + &connection_info, + connection.stream_description()?, + &message, + cmd_attrs, + ); + #[cfg(feature = "in-use-encryption")] { let guard = self.inner.csfle.read().await; @@ -629,6 +670,8 @@ impl Client { } } }; + #[cfg(feature = "opentelemetry")] + span.record_command_result::(&result); if result .as_ref() diff --git a/src/client/options.rs b/src/client/options.rs index f299ee5d8..b06a817af 100644 --- a/src/client/options.rs +++ b/src/client/options.rs @@ -612,6 +612,10 @@ pub struct ClientOptions { /// Limit on the number of mongos connections that may be created for sharded topologies. pub srv_max_hosts: Option, + /// Configuration for opentelemetry. + #[cfg(feature = "opentelemetry")] + pub tracing: Option, + /// Information from the SRV URI that generated these client options, if applicable. #[builder(setter(skip))] #[serde(skip)] diff --git a/src/client/options/parse.rs b/src/client/options/parse.rs index 8aed36ae4..1aab30163 100644 --- a/src/client/options/parse.rs +++ b/src/client/options/parse.rs @@ -162,6 +162,8 @@ impl ClientOptions { tracing_max_document_length_bytes: None, srv_max_hosts: conn_str.srv_max_hosts, srv_service_name: conn_str.srv_service_name, + #[cfg(feature = "opentelemetry")] + tracing: None, } } } diff --git a/src/client/session.rs b/src/client/session.rs index e90aa6417..b14360a0b 100644 --- a/src/client/session.rs +++ b/src/client/session.rs @@ -118,13 +118,23 @@ pub(crate) struct Transaction { pub(crate) options: Option, pub(crate) pinned: Option, pub(crate) recovery_token: Option, + #[cfg(feature = "opentelemetry")] + pub(crate) otel_span: Option, } impl Transaction { - pub(crate) fn start(&mut self, options: Option) { + pub(crate) fn start( + &mut self, + options: Option, + #[cfg(feature = "opentelemetry")] otel_span: crate::otel::TxnSpan, + ) { self.state = TransactionState::Starting; self.options = options; self.recovery_token = None; + #[cfg(feature = "opentelemetry")] + { + self.otel_span = Some(otel_span); + } } pub(crate) fn commit(&mut self, data_committed: bool) { @@ -142,6 +152,14 @@ impl Transaction { self.options = None; self.pinned = None; self.recovery_token = None; + self.drop_span(); + } + + pub(crate) fn drop_span(&mut self) { + #[cfg(feature = "opentelemetry")] + { + self.otel_span = None; + } } #[cfg(test)] @@ -169,6 +187,8 @@ impl Transaction { options: self.options.take(), pinned: self.pinned.take(), recovery_token: self.recovery_token.take(), + #[cfg(feature = "opentelemetry")] + otel_span: self.otel_span.take(), } } } @@ -180,6 +200,8 @@ impl Default for Transaction { options: None, pinned: None, recovery_token: None, + #[cfg(feature = "opentelemetry")] + otel_span: None, } } } diff --git a/src/client/session/action.rs b/src/client/session/action.rs index 10dfbe06f..95c29d273 100644 --- a/src/client/session/action.rs +++ b/src/client/session/action.rs @@ -79,7 +79,11 @@ impl ClientSession { } self.increment_txn_number(); - self.transaction.start(options); + self.transaction.start( + options, + #[cfg(feature = "opentelemetry")] + self.client.start_transaction_span(), + ); Ok(()) } _ => Err(ErrorKind::Transaction { @@ -355,17 +359,21 @@ impl<'a> Action for CommitTransaction<'a> { .into()), TransactionState::Starting => { self.session.transaction.commit(false); + self.session.transaction.drop_span(); Ok(()) } TransactionState::InProgress => { let commit_transaction = operation::CommitTransaction::new(self.session.transaction.options.clone()); self.session.transaction.commit(true); - self.session + let out = self + .session .client .clone() - .execute_operation(commit_transaction, self.session) - .await + .execute_operation(commit_transaction, &mut *self.session) + .await; + self.session.transaction.drop_span(); + out } TransactionState::Committed { data_committed: true, @@ -406,6 +414,7 @@ impl<'a> Action for AbortTransaction<'a> { .into()), TransactionState::Starting => { self.session.transaction.abort(); + self.session.transaction.drop_span(); Ok(()) } TransactionState::InProgress => { @@ -428,6 +437,7 @@ impl<'a> Action for AbortTransaction<'a> { .clone() .execute_operation(abort_transaction, &mut *self.session) .await; + self.session.transaction.drop_span(); Ok(()) } } diff --git a/src/cmap/conn/command.rs b/src/cmap/conn/command.rs index 57fbfd974..c30030653 100644 --- a/src/cmap/conn/command.rs +++ b/src/cmap/conn/command.rs @@ -33,7 +33,7 @@ pub(crate) struct Command { #[serde(rename = "$db")] pub(crate) target_db: String, - lsid: Option, + pub(crate) lsid: Option, #[serde(rename = "$clusterTime")] cluster_time: Option, @@ -44,7 +44,7 @@ pub(crate) struct Command { #[serde(rename = "$readPreference")] read_preference: Option, - txn_number: Option, + pub(crate) txn_number: Option, start_transaction: Option, diff --git a/src/cmap/conn/pooled.rs b/src/cmap/conn/pooled.rs index 59649ba05..44c9d0836 100644 --- a/src/cmap/conn/pooled.rs +++ b/src/cmap/conn/pooled.rs @@ -20,7 +20,7 @@ use super::{ }; use crate::{ bson::oid::ObjectId, - cmap::PoolGeneration, + cmap::{PoolGeneration, StreamDescription}, error::{Error, Result}, event::cmap::{ ConnectionCheckedInEvent, @@ -276,6 +276,10 @@ impl PooledConnection { .emit_event(|| self.closed_event(reason).into()); } + pub(crate) fn stream_description(&self) -> Result<&StreamDescription> { + self.connection.stream_description() + } + /// Whether the connection supports sessions. pub(crate) fn supports_sessions(&self) -> bool { self.connection diff --git a/src/error.rs b/src/error.rs index 6601a397b..4fb3578ea 100644 --- a/src/error.rs +++ b/src/error.rs @@ -52,14 +52,14 @@ pub type Result = std::result::Result; /// cloned. #[derive(Clone, Debug, Error)] #[cfg_attr( - test, + feature = "error-backtrace", error( - "Kind: {kind}, labels: {labels:?}, source: {source:?}, backtrace: {bt}, server response: \ - {server_response:?}" + "Kind: {kind}, labels: {labels:?}, source: {source:?}, server response: \ + {server_response:?}, backtrace: {backtrace}" ) )] #[cfg_attr( - not(test), + not(feature = "error-backtrace"), error( "Kind: {kind}, labels: {labels:?}, source: {source:?}, server response: \ {server_response:?}" @@ -79,8 +79,8 @@ pub struct Error { pub(crate) server_response: Option>, - #[cfg(test)] - bt: Arc, + #[cfg(feature = "error-backtrace")] + pub(crate) backtrace: Arc, } impl Error { @@ -113,8 +113,8 @@ impl Error { wire_version: None, source: None, server_response: None, - #[cfg(test)] - bt: Arc::new(std::backtrace::Backtrace::capture()), + #[cfg(feature = "error-backtrace")] + backtrace: Arc::new(std::backtrace::Backtrace::capture()), } } @@ -782,6 +782,38 @@ impl ErrorKind { _ => None, } } + + #[cfg(feature = "opentelemetry")] + pub(crate) fn name(&self) -> &'static str { + match self { + ErrorKind::InvalidArgument { .. } => "InvalidArgument", + ErrorKind::Authentication { .. } => "Authentication", + ErrorKind::BsonDeserialization(..) => "BsonDeserialization", + ErrorKind::BsonSerialization(..) => "BsonSerialization", + #[cfg(feature = "bson-3")] + ErrorKind::Bson(..) => "Bson", + ErrorKind::InsertMany(..) => "InsertMany", + ErrorKind::BulkWrite(..) => "BulkWrite", + ErrorKind::Command(..) => "Command", + ErrorKind::DnsResolve { .. } => "DnsResolve", + ErrorKind::GridFs(..) => "GridFs", + ErrorKind::Internal { .. } => "Internal", + ErrorKind::Io(..) => "Io", + ErrorKind::ConnectionPoolCleared { .. } => "ConnectionPoolCleared", + ErrorKind::InvalidResponse { .. } => "InvalidResponse", + ErrorKind::ServerSelection { .. } => "ServerSelection", + ErrorKind::SessionsNotSupported => "SessionsNotSupported", + ErrorKind::InvalidTlsConfig { .. } => "InvalidTlsConfig", + ErrorKind::Write(..) => "Write", + ErrorKind::Transaction { .. } => "Transaction", + ErrorKind::IncompatibleServer { .. } => "IncompatibleServer", + ErrorKind::MissingResumeToken => "MissingResumeToken", + #[cfg(feature = "in-use-encryption")] + ErrorKind::Encryption(..) => "Encryption", + ErrorKind::Custom(..) => "Custom", + ErrorKind::Shutdown => "Shutdown", + } + } } /// An error that occurred due to a database command failing. diff --git a/src/lib.rs b/src/lib.rs index b6e7d0930..6e6c628aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,10 @@ mod hello; pub(crate) mod id_set; mod index; mod operation; +#[cfg(feature = "opentelemetry")] +pub mod otel; +#[cfg(not(feature = "opentelemetry"))] +mod otel_stub; pub mod results; pub(crate) mod runtime; mod sdam; @@ -69,6 +73,9 @@ pub use bson2 as bson; #[cfg(feature = "bson-3")] pub use bson3 as bson; +#[cfg(not(feature = "opentelemetry"))] +pub(crate) use otel_stub as otel; + #[cfg(feature = "in-use-encryption")] pub use crate::client::csfle::client_encryption; pub use crate::{ diff --git a/src/operation.rs b/src/operation.rs index 174c71d19..b55fa6b69 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -176,6 +176,14 @@ pub(crate) trait Operation { /// The name of the server side command associated with this operation. fn name(&self) -> &CStr; + + #[cfg(feature = "opentelemetry")] + type Otel: crate::otel::OtelWitness; + + #[cfg(feature = "opentelemetry")] + fn otel(&self) -> &impl crate::otel::OtelInfo { + ::otel(self) + } } pub(crate) type OverrideCriteriaFn = @@ -277,6 +285,9 @@ pub(crate) trait OperationWithDefaults: Send + Sync { fn name(&self) -> &CStr { Self::NAME } + + #[cfg(feature = "opentelemetry")] + type Otel: crate::otel::OtelWitness; } impl Operation for T @@ -331,6 +342,8 @@ where fn name(&self) -> &CStr { self.name() } + #[cfg(feature = "opentelemetry")] + type Otel = ::Otel; } fn should_redact_body(body: &RawDocumentBuf) -> bool { diff --git a/src/operation/abort_transaction.rs b/src/operation/abort_transaction.rs index 326a7a5e4..295966477 100644 --- a/src/operation/abort_transaction.rs +++ b/src/operation/abort_transaction.rs @@ -80,4 +80,14 @@ impl OperationWithDefaults for AbortTransaction { // The session must be "unpinned" before server selection for a retry. self.pinned = None; } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for AbortTransaction { + fn target(&self) -> crate::otel::OperationTarget<'_> { + crate::otel::OperationTarget::ADMIN + } } diff --git a/src/operation/aggregate.rs b/src/operation/aggregate.rs index 13a8932a1..e04b17e0d 100644 --- a/src/operation/aggregate.rs +++ b/src/operation/aggregate.rs @@ -153,6 +153,20 @@ impl OperationWithDefaults for Aggregate { None } } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for Aggregate { + fn output_cursor_id(output: &Self::O) -> Option { + Some(output.id()) + } + + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.target).into() + } } impl Aggregate { diff --git a/src/operation/aggregate/change_stream.rs b/src/operation/aggregate/change_stream.rs index d2b384269..f1f7abb0f 100644 --- a/src/operation/aggregate/change_stream.rs +++ b/src/operation/aggregate/change_stream.rs @@ -135,4 +135,14 @@ impl OperationWithDefaults for ChangeStreamAggregate { fn retryability(&self) -> Retryability { self.inner.retryability() } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for ChangeStreamAggregate { + fn target(&self) -> crate::otel::OperationTarget<'_> { + self.inner.target() + } } diff --git a/src/operation/bulk_write.rs b/src/operation/bulk_write.rs index a1829f1f0..60f78b77a 100644 --- a/src/operation/bulk_write.rs +++ b/src/operation/bulk_write.rs @@ -485,4 +485,14 @@ where Retryability::Write } } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for BulkWrite<'_, R> { + fn target(&self) -> crate::otel::OperationTarget<'_> { + crate::otel::OperationTarget::ADMIN + } } diff --git a/src/operation/commit_transaction.rs b/src/operation/commit_transaction.rs index a920ea68c..41ff4877e 100644 --- a/src/operation/commit_transaction.rs +++ b/src/operation/commit_transaction.rs @@ -80,4 +80,14 @@ impl OperationWithDefaults for CommitTransaction { } } } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for CommitTransaction { + fn target(&self) -> crate::otel::OperationTarget<'_> { + crate::otel::OperationTarget::ADMIN + } } diff --git a/src/operation/count.rs b/src/operation/count.rs index ab03656ee..a2baa0750 100644 --- a/src/operation/count.rs +++ b/src/operation/count.rs @@ -75,6 +75,16 @@ impl OperationWithDefaults for Count { fn retryability(&self) -> Retryability { Retryability::Read } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for Count { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } #[derive(Debug, Deserialize)] diff --git a/src/operation/count_documents.rs b/src/operation/count_documents.rs index 0fe048fb9..19f1ae513 100644 --- a/src/operation/count_documents.rs +++ b/src/operation/count_documents.rs @@ -110,6 +110,16 @@ impl OperationWithDefaults for CountDocuments { fn supports_read_concern(&self, description: &StreamDescription) -> bool { self.aggregate.supports_read_concern(description) } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for CountDocuments { + fn target(&self) -> crate::otel::OperationTarget<'_> { + self.aggregate.target() + } } #[derive(Debug, Deserialize)] diff --git a/src/operation/create.rs b/src/operation/create.rs index 40aacaf65..47b587883 100644 --- a/src/operation/create.rs +++ b/src/operation/create.rs @@ -52,4 +52,18 @@ impl OperationWithDefaults for Create { .as_ref() .and_then(|opts| opts.write_concern.as_ref()) } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for Create { + fn log_name(&self) -> &str { + "createCollection" + } + + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/create_indexes.rs b/src/operation/create_indexes.rs index 1c46a7c05..4f05e0534 100644 --- a/src/operation/create_indexes.rs +++ b/src/operation/create_indexes.rs @@ -83,4 +83,14 @@ impl OperationWithDefaults for CreateIndexes { .as_ref() .and_then(|opts| opts.write_concern.as_ref()) } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for CreateIndexes { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/delete.rs b/src/operation/delete.rs index f15bb184f..62354b782 100644 --- a/src/operation/delete.rs +++ b/src/operation/delete.rs @@ -99,4 +99,14 @@ impl OperationWithDefaults for Delete { Retryability::None } } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for Delete { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/distinct.rs b/src/operation/distinct.rs index 8a0f45307..dea795850 100644 --- a/src/operation/distinct.rs +++ b/src/operation/distinct.rs @@ -89,6 +89,16 @@ impl OperationWithDefaults for Distinct { fn supports_read_concern(&self, _description: &StreamDescription) -> bool { true } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for Distinct { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } #[derive(Debug, Deserialize)] diff --git a/src/operation/drop_collection.rs b/src/operation/drop_collection.rs index 562b086fd..5880bed3a 100644 --- a/src/operation/drop_collection.rs +++ b/src/operation/drop_collection.rs @@ -60,4 +60,19 @@ impl OperationWithDefaults for DropCollection { .as_ref() .and_then(|opts| opts.write_concern.as_ref()) } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for DropCollection { + fn log_name(&self) -> &str { + "dropCollection" + } + + #[cfg(feature = "opentelemetry")] + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/drop_database.rs b/src/operation/drop_database.rs index 1273e615f..350afdc92 100644 --- a/src/operation/drop_database.rs +++ b/src/operation/drop_database.rs @@ -52,4 +52,14 @@ impl OperationWithDefaults for DropDatabase { .as_ref() .and_then(|opts| opts.write_concern.as_ref()) } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for DropDatabase { + fn target(&self) -> crate::otel::OperationTarget<'_> { + self.target_db.as_str().into() + } } diff --git a/src/operation/drop_indexes.rs b/src/operation/drop_indexes.rs index de9a06d3f..86554a001 100644 --- a/src/operation/drop_indexes.rs +++ b/src/operation/drop_indexes.rs @@ -51,4 +51,14 @@ impl OperationWithDefaults for DropIndexes { .as_ref() .and_then(|opts| opts.write_concern.as_ref()) } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for DropIndexes { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/find.rs b/src/operation/find.rs index efd8eb32f..4277c393c 100644 --- a/src/operation/find.rs +++ b/src/operation/find.rs @@ -128,4 +128,18 @@ impl OperationWithDefaults for Find { fn retryability(&self) -> Retryability { Retryability::Read } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for Find { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } + + fn output_cursor_id(output: &Self::O) -> Option { + Some(output.id()) + } } diff --git a/src/operation/find_and_modify.rs b/src/operation/find_and_modify.rs index 208795da9..57bffae36 100644 --- a/src/operation/find_and_modify.rs +++ b/src/operation/find_and_modify.rs @@ -118,4 +118,14 @@ impl OperationWithDefaults for FindAndModify { fn retryability(&self) -> Retryability { Retryability::Write } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for FindAndModify { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/get_more.rs b/src/operation/get_more.rs index 49aa7ac0d..e79edbe54 100644 --- a/src/operation/get_more.rs +++ b/src/operation/get_more.rs @@ -105,6 +105,21 @@ impl OperationWithDefaults for GetMore<'_> { fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { self.pinned_connection } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for GetMore<'_> { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } + + #[cfg(feature = "opentelemetry")] + fn cursor_id(&self) -> Option { + Some(self.cursor_id) + } } #[derive(Debug, Deserialize)] diff --git a/src/operation/insert.rs b/src/operation/insert.rs index b79a1c42b..d80618d77 100644 --- a/src/operation/insert.rs +++ b/src/operation/insert.rs @@ -177,4 +177,14 @@ impl OperationWithDefaults for Insert<'_> { fn retryability(&self) -> Retryability { Retryability::Write } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for Insert<'_> { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/list_collections.rs b/src/operation/list_collections.rs index c4b76a3b1..4198e5ff6 100644 --- a/src/operation/list_collections.rs +++ b/src/operation/list_collections.rs @@ -81,4 +81,19 @@ impl OperationWithDefaults for ListCollections { fn retryability(&self) -> Retryability { Retryability::Read } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for ListCollections { + fn output_cursor_id(output: &Self::O) -> Option { + Some(output.id()) + } + + #[cfg(feature = "opentelemetry")] + fn target(&self) -> crate::otel::OperationTarget<'_> { + self.db.as_str().into() + } } diff --git a/src/operation/list_databases.rs b/src/operation/list_databases.rs index 9897d4047..d15117d96 100644 --- a/src/operation/list_databases.rs +++ b/src/operation/list_databases.rs @@ -57,6 +57,16 @@ impl OperationWithDefaults for ListDatabases { fn retryability(&self) -> Retryability { Retryability::Read } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for ListDatabases { + fn target(&self) -> crate::otel::OperationTarget<'_> { + crate::otel::OperationTarget::ADMIN + } } #[derive(Debug, Deserialize)] diff --git a/src/operation/list_indexes.rs b/src/operation/list_indexes.rs index 2b7c28867..b40ae1209 100644 --- a/src/operation/list_indexes.rs +++ b/src/operation/list_indexes.rs @@ -69,4 +69,18 @@ impl OperationWithDefaults for ListIndexes { fn retryability(&self) -> Retryability { Retryability::Read } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for ListIndexes { + fn output_cursor_id(output: &Self::O) -> Option { + Some(output.id()) + } + + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/raw_output.rs b/src/operation/raw_output.rs index 5bffe4f4e..d7b7f83bf 100644 --- a/src/operation/raw_output.rs +++ b/src/operation/raw_output.rs @@ -80,4 +80,26 @@ impl Operation for RawOutput { fn name(&self) -> &CStr { self.0.name() } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfo for RawOutput { + fn log_name(&self) -> &str { + self.0.otel().log_name() + } + + fn target(&self) -> crate::otel::OperationTarget<'_> { + self.0.otel().target() + } + + fn cursor_id(&self) -> Option { + self.0.otel().cursor_id() + } + + fn output_cursor_id(_output: &::O) -> Option { + None + } } diff --git a/src/operation/run_command.rs b/src/operation/run_command.rs index 06ecac047..7e11af549 100644 --- a/src/operation/run_command.rs +++ b/src/operation/run_command.rs @@ -94,4 +94,14 @@ impl OperationWithDefaults for RunCommand<'_> { fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { self.pinned_connection } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for RunCommand<'_> { + fn target(&self) -> crate::otel::OperationTarget<'_> { + self.db.as_str().into() + } } diff --git a/src/operation/run_cursor_command.rs b/src/operation/run_cursor_command.rs index b467f52ed..e326145d9 100644 --- a/src/operation/run_cursor_command.rs +++ b/src/operation/run_cursor_command.rs @@ -119,4 +119,26 @@ impl Operation for RunCursorCommand<'_> { } .boxed() } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfo for RunCursorCommand<'_> { + fn log_name(&self) -> &str { + self.run_command.otel().log_name() + } + + fn target(&self) -> crate::otel::OperationTarget<'_> { + self.run_command.otel().target() + } + + fn cursor_id(&self) -> Option { + self.run_command.otel().cursor_id() + } + + fn output_cursor_id(output: &::O) -> Option { + Some(output.id()) + } } diff --git a/src/operation/search_index.rs b/src/operation/search_index.rs index 46cc5a977..c43e1f932 100644 --- a/src/operation/search_index.rs +++ b/src/operation/search_index.rs @@ -73,6 +73,16 @@ impl OperationWithDefaults for CreateSearchIndexes { fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { false } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for CreateSearchIndexes { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } #[derive(Debug)] @@ -127,6 +137,16 @@ impl OperationWithDefaults for UpdateSearchIndex { fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { false } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for UpdateSearchIndex { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } #[derive(Debug)] @@ -179,4 +199,14 @@ impl OperationWithDefaults for DropSearchIndex { fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { false } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for DropSearchIndex { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } diff --git a/src/operation/update.rs b/src/operation/update.rs index 01341e87e..cd323ba04 100644 --- a/src/operation/update.rs +++ b/src/operation/update.rs @@ -212,6 +212,16 @@ impl OperationWithDefaults for Update { Retryability::None } } + + #[cfg(feature = "opentelemetry")] + type Otel = crate::otel::Witness; +} + +#[cfg(feature = "opentelemetry")] +impl crate::otel::OtelInfoDefaults for Update { + fn target(&self) -> crate::otel::OperationTarget<'_> { + (&self.ns).into() + } } #[derive(Deserialize)] diff --git a/src/otel.rs b/src/otel.rs new file mode 100644 index 000000000..d76d84a8e --- /dev/null +++ b/src/otel.rs @@ -0,0 +1,445 @@ +//! Support for OpenTelemetry. + +use std::sync::{Arc, LazyLock}; + +use derive_where::derive_where; + +use opentelemetry::{ + global::{BoxedTracer, ObjectSafeTracerProvider}, + trace::{SpanKind, TraceContextExt, Tracer, TracerProvider}, + Context, + KeyValue, +}; + +use crate::{ + bson::Bson, + cmap::{conn::wire::Message, Command, ConnectionInfo, StreamDescription}, + error::{ErrorKind, Result}, + operation::{aggregate::AggregateTarget, Operation}, + options::{ClientOptions, ServerAddress, DEFAULT_PORT}, + Client, + ClientSession, + Namespace, +}; + +#[cfg(test)] +pub(crate) mod testing; + +/// Configuration for OpenTelemetry. +#[derive(Clone, serde::Deserialize, typed_builder::TypedBuilder)] +#[derive_where(Debug, PartialEq)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct OpentelemetryOptions { + /// Enables or disables OpenTelemtry for this client instance. If unset, will use the value of + /// the `OTEL_RUST_INSTRUMENTATION_MONGODB_ENABLED` environment variable. + pub enabled: Option, + /// Maximum length of the `db.query.text` attribute of command spans. If unset, will use the + /// value of the `OTEL_RUST_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH` environment + /// variable. + pub query_text_max_length: Option, + /// Tracer provider to use. If unset, will use the global instance. + #[serde(skip)] + #[derive_where(skip)] + #[builder( + setter( + fn transform(provider: P) -> Option> + where + S: opentelemetry::trace::Span + Send + Sync + 'static, + T: Tracer + Send + Sync + 'static, + P: TracerProvider + Send + Sync + 'static, + { + Some(Arc::new(provider)) + }, + ) + )] + pub tracer_provider: Option>, +} + +impl ClientOptions { + pub(crate) fn tracer(&self) -> BoxedTracer { + let provider: &dyn ObjectSafeTracerProvider = match self + .tracing + .as_ref() + .and_then(|t| t.tracer_provider.as_ref()) + { + Some(provider) => &**provider, + None => &opentelemetry::global::tracer_provider(), + }; + BoxedTracer::new( + provider.boxed_tracer( + opentelemetry::InstrumentationScope::builder("mongodb") + .with_version(env!("CARGO_PKG_VERSION")) + .build(), + ), + ) + } + + fn otel_enabled(&self) -> bool { + static ENABLED_ENV: LazyLock = LazyLock::new(|| { + match std::env::var("OTEL_RUST_INSTRUMENTATION_MONGODB_ENABLED").as_deref() { + Ok("1" | "true" | "yes") => true, + _ => false, + } + }); + + self.tracing + .as_ref() + .and_then(|t| t.enabled) + .unwrap_or_else(|| *ENABLED_ENV) + } + + fn otel_query_text_max_length(&self) -> usize { + static MAX_LENGTH_ENV: LazyLock = LazyLock::new(|| { + std::env::var("OTEL_RUST_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(0) + }); + + self.tracing + .as_ref() + .and_then(|t| t.query_text_max_length) + .unwrap_or_else(|| *MAX_LENGTH_ENV) + } +} + +impl Client { + pub(crate) fn start_operation_span( + &self, + op: &impl Operation, + session: Option<&ClientSession>, + ) -> OpSpan { + let op = op.otel(); + if !self.options().otel_enabled() { + return OpSpan { + context: Context::current(), + enabled: false, + }; + } + let span_name = format!("{} {}", op.log_name(), op_target(op)); + let mut attrs = common_attrs(op); + attrs.extend([ + KeyValue::new("db.operation.name", op.log_name().to_owned()), + KeyValue::new("db.operation.summary", span_name.clone()), + ]); + let builder = self + .tracer() + .span_builder(span_name) + .with_kind(SpanKind::Client) + .with_attributes(attrs); + let context = if let Some(TxnSpan(txn_ctx)) = + session.and_then(|s| s.transaction.otel_span.as_ref()) + { + txn_ctx.with_span(builder.start_with_context(self.tracer(), txn_ctx)) + } else { + Context::current_with_span(builder.start(self.tracer())) + }; + OpSpan { + context, + enabled: true, + } + } + + pub(crate) fn start_command_span( + &self, + op: &impl Operation, + conn_info: &ConnectionInfo, + stream_desc: &StreamDescription, + message: &Message, + cmd_attrs: CommandAttributes, + ) -> CmdSpan { + let op = op.otel(); + if !self.options().otel_enabled() || cmd_attrs.should_redact { + return CmdSpan { + context: Context::current(), + enabled: false, + }; + } + let otel_driver_conn_id: i64 = conn_info.id.into(); + let mut attrs = common_attrs(op); + attrs.extend(cmd_attrs.attrs); + attrs.extend([ + KeyValue::new( + "db.query.summary", + format!("{} {}", &cmd_attrs.name, op_target(op)), + ), + KeyValue::new("db.mongodb.driver_connection_id", otel_driver_conn_id), + KeyValue::new("server.type", stream_desc.initial_server_type.to_string()), + ]); + match &conn_info.address { + ServerAddress::Tcp { host, port } => { + let otel_port: i64 = port.unwrap_or(DEFAULT_PORT).into(); + attrs.extend([ + KeyValue::new("server.port", otel_port), + KeyValue::new("server.address", host.clone()), + KeyValue::new("network.transport", "tcp"), + ]); + } + #[cfg(unix)] + ServerAddress::Unix { path } => { + attrs.extend([ + KeyValue::new("server.address", path.to_string_lossy().into_owned()), + KeyValue::new("network.transport", "unix"), + ]); + } + } + if let Some(server_id) = &conn_info.server_id { + attrs.push(KeyValue::new("db.mongodb.server_connection_id", *server_id)); + } + let text_max_len = self.options().otel_query_text_max_length(); + if text_max_len > 0 { + let mut doc = message.get_command_document(); + for key in ["lsid", "$db", "$clusterTime", "signature"] { + doc.remove(key); + } + attrs.push(KeyValue::new( + "db.query.text", + crate::bson_util::doc_to_json_str(doc, text_max_len), + )); + } + if let Some(cursor_id) = op.cursor_id() { + attrs.push(KeyValue::new("db.mongodb.cursor_id", cursor_id)); + } + let span = self + .tracer() + .span_builder(cmd_attrs.name) + .with_kind(SpanKind::Client) + .with_attributes(attrs) + .start(self.tracer()); + CmdSpan { + context: Context::current_with_span(span), + enabled: true, + } + } + + pub(crate) fn start_transaction_span(&self) -> TxnSpan { + if !self.options().otel_enabled() { + return TxnSpan(Context::current()); + } + let span = self + .tracer() + .span_builder("transaction") + .with_kind(SpanKind::Client) + .with_attributes([KeyValue::new("db.system", "mongodb")]) + .start(self.tracer()); + TxnSpan(Context::current_with_span(span)) + } +} + +pub(crate) struct OpSpan { + pub(crate) context: Context, + enabled: bool, +} + +impl OpSpan { + pub(crate) fn record_error(&self, result: &Result) { + if !self.enabled { + return; + } + record_error(&self.context, result); + } +} + +pub(crate) struct CmdSpan { + context: Context, + enabled: bool, +} + +impl CmdSpan { + pub(crate) fn record_command_result(&self, result: &Result) { + if !self.enabled { + return; + } + if let Ok(out) = result { + // tests don't match the spec here + if false { + if let Some(cursor_id) = ::output_cursor_id(out) { + let span = self.context.span(); + span.set_attribute(KeyValue::new("db.mongodb.cursor_id", cursor_id)); + } + } + } + record_error(&self.context, result); + } +} + +#[derive(Debug)] +pub(crate) struct TxnSpan(Context); + +fn record_error(context: &Context, result: &Result) { + let error = if let Err(error) = result { + error + } else { + return; + }; + let span = context.span(); + span.set_attributes([ + KeyValue::new("exception.message", error.to_string()), + KeyValue::new("exception.type", error.kind.name()), + #[cfg(feature = "error-backtrace")] + KeyValue::new("exception.stacktrace", error.backtrace.to_string()), + ]); + if let ErrorKind::Command(cmd_err) = &*error.kind { + span.set_attribute(KeyValue::new( + "db.response.status_code", + cmd_err.code.to_string(), + )); + } + span.record_error(error); + span.set_status(opentelemetry::trace::Status::Error { + description: error.to_string().into(), + }); +} + +fn op_target(op: &impl OtelInfo) -> String { + let target = op.target(); + if let Some(coll) = target.collection { + format!("{}.{}", target.database, coll) + } else { + target.database.to_owned() + } +} + +fn common_attrs(op: &impl OtelInfo) -> Vec { + let target = op.target(); + let mut attrs = vec![ + KeyValue::new("db.system", "mongodb"), + KeyValue::new("db.namespace", target.database.to_owned()), + ]; + if let Some(coll) = target.collection { + attrs.push(KeyValue::new("db.collection.name", coll.to_owned())); + } + attrs +} + +#[derive(Clone)] +pub(crate) struct CommandAttributes { + should_redact: bool, + name: String, + attrs: Vec, +} + +impl CommandAttributes { + pub(crate) fn new(cmd: &Command) -> Self { + let mut attrs = vec![KeyValue::new("db.command.name", cmd.name.clone())]; + if let Some(lsid) = &cmd.lsid { + attrs.push(KeyValue::new( + "db.mongodb.lsid", + Bson::Document(lsid.clone()) + .into_relaxed_extjson() + .to_string(), + )); + } + if let Some(txn_number) = &cmd.txn_number { + attrs.push(KeyValue::new("db.mongodb.txn_number", *txn_number)); + } + Self { + should_redact: cmd.should_redact(), + name: cmd.name.clone(), + attrs, + } + } +} + +pub(crate) trait OtelWitness { + type Op: OtelInfo; + fn otel(op: &Self::Op) -> &impl OtelInfo { + op + } + fn output_cursor_id(output: &::O) -> Option { + Self::Op::output_cursor_id(output) + } +} + +pub(crate) struct Witness { + _t: std::marker::PhantomData, +} + +impl OtelWitness for Witness { + type Op = T; +} + +pub(crate) trait OtelInfo: Operation { + fn log_name(&self) -> &str; + + fn target(&self) -> crate::otel::OperationTarget<'_>; + + fn cursor_id(&self) -> Option; + + fn output_cursor_id(output: &::O) -> Option; +} + +pub(crate) trait OtelInfoDefaults: Operation { + fn log_name(&self) -> &str { + crate::bson_compat::cstr_to_str(self.name()) + } + + fn target(&self) -> crate::otel::OperationTarget<'_>; + + fn cursor_id(&self) -> Option { + None + } + + fn output_cursor_id(_output: &::O) -> Option { + None + } +} + +impl OtelInfo for T { + fn log_name(&self) -> &str { + self.log_name() + } + + fn target(&self) -> crate::otel::OperationTarget<'_> { + self.target() + } + + fn cursor_id(&self) -> Option { + self.cursor_id() + } + + fn output_cursor_id(output: &::O) -> Option { + T::output_cursor_id(output) + } +} + +pub(crate) struct OperationTarget<'a> { + pub(crate) database: &'a str, + pub(crate) collection: Option<&'a str>, +} + +impl OperationTarget<'static> { + pub(crate) const ADMIN: Self = OperationTarget { + database: "admin", + collection: None, + }; +} + +impl<'a> From<&'a str> for OperationTarget<'a> { + fn from(value: &'a str) -> Self { + OperationTarget { + database: value, + collection: None, + } + } +} + +impl<'a> From<&'a Namespace> for OperationTarget<'a> { + fn from(value: &'a Namespace) -> Self { + OperationTarget { + database: &value.db, + collection: Some(&value.coll), + } + } +} + +impl<'a> From<&'a AggregateTarget> for OperationTarget<'a> { + fn from(value: &'a AggregateTarget) -> Self { + match value { + AggregateTarget::Database(db) => db.as_str().into(), + AggregateTarget::Collection(ns) => ns.into(), + } + } +} diff --git a/src/otel/testing.rs b/src/otel/testing.rs new file mode 100644 index 000000000..6c717b96a --- /dev/null +++ b/src/otel/testing.rs @@ -0,0 +1,219 @@ +use std::collections::HashMap; + +use opentelemetry::SpanId; +use opentelemetry_sdk::trace::{ + BatchSpanProcessor, + InMemorySpanExporter, + InMemorySpanExporterBuilder, + SdkTracerProvider, + SpanData, +}; +use serde::Deserialize; + +use crate::{ + bson::{doc, Bson, Document}, + test::spec::unified_runner::{results_match, EntityMap, TestRunner}, +}; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(crate) struct ObserveTracingMessages { + enable_command_payload: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(crate) struct ExpectedTracingMessages { + client: String, + ignore_extra_spans: Option, + spans: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct ExpectedSpan { + name: String, + attributes: Document, + #[serde(default)] + nested: Vec, +} + +#[derive(Clone, Debug)] +pub(crate) struct ClientTracing { + exporter: InMemorySpanExporter, + provider: SdkTracerProvider, +} + +impl ClientTracing { + pub(crate) fn new(observe: &ObserveTracingMessages) -> (Self, super::OpentelemetryOptions) { + let exporter = InMemorySpanExporterBuilder::new().build(); + let provider = SdkTracerProvider::builder() + .with_span_processor(BatchSpanProcessor::builder(exporter.clone()).build()) + .build(); + let mut options = super::OpentelemetryOptions::builder() + .enabled(true) + .tracer_provider(provider.clone()) + .build(); + if observe.enable_command_payload.unwrap_or(false) { + options.query_text_max_length = Some(1000); + } + (Self { exporter, provider }, options) + } +} + +impl TestRunner { + pub(crate) async fn match_spans( + &self, + expected: &ExpectedTracingMessages, + ) -> Result<(), String> { + let client_tracing = self.get_client(&expected.client).await.tracing.unwrap(); + client_tracing.provider.force_flush().unwrap(); + let mut root_spans = vec![]; + let mut nested_spans = HashMap::>::new(); + for span in client_tracing.exporter.get_finished_spans().unwrap() { + if span.parent_span_id == SpanId::INVALID { + root_spans.push(span); + } else { + nested_spans + .entry(span.parent_span_id) + .or_default() + .push(span); + } + } + let (root_spans, nested_spans) = (root_spans, nested_spans); + + let entities = self.entities.read().await; + Matcher { + nested: &nested_spans, + entities: &entities, + ignore_extra: expected.ignore_extra_spans.unwrap_or(false), + } + .match_span_slice(&root_spans, &expected.spans)?; + + Ok(()) + } +} + +struct Matcher<'a> { + nested: &'a HashMap>, + entities: &'a EntityMap, + ignore_extra: bool, +} + +impl<'a> Matcher<'a> { + fn match_span_slice( + &self, + actual: &[SpanData], + expected: &[ExpectedSpan], + ) -> Result<(), String> { + let err_suffix = || format!("actual:\n{:#?}\n\nexpected:\n{:#?}", actual, expected); + if self.ignore_extra { + if actual.len() < expected.len() { + return Err(format!( + "expected at least {} spans, got {}\n{}", + expected.len(), + actual.len(), + err_suffix(), + )); + } + let mut actual = actual; + let mut expected = expected; + while let Some((exp_span, rest)) = expected.split_first() { + expected = rest; + let act_span = loop { + let Some((span, rest)) = actual.split_first() else { + return Err(format!( + "no span found with name {:?}\n{}", + exp_span.name, + err_suffix(), + )); + }; + actual = rest; + if span.name == exp_span.name { + break span; + } + }; + self.match_span(act_span, exp_span)?; + } + } else { + if actual.len() != expected.len() { + return Err(format!( + "expected exactly {} spans, got {}\n{}", + expected.len(), + actual.len(), + err_suffix(), + )); + } + + for (act_span, exp_span) in actual.iter().zip(expected) { + self.match_span(act_span, exp_span)?; + } + } + + Ok(()) + } + + fn match_span(&self, actual: &SpanData, expected: &ExpectedSpan) -> Result<(), String> { + let err_suffix = || format!("actual:\n{:#?}\nexpected:\n{:#?}", actual, expected); + if expected.name != actual.name { + return Err(format!( + "expected name {:?}, got {:?}\n{}", + expected.name, + actual.name, + err_suffix(), + )); + } + let mut actual_attrs = doc! {}; + for kv in &actual.attributes { + let key = kv.key.as_str(); + let value = match key { + "db.mongodb.lsid" => match &kv.value { + opentelemetry::Value::String(s) => { + let doc: Bson = serde_json::from_str::(s.as_str()) + .map_err(|e| format!("serde_json error: {}", e))? + .try_into() + .map_err(|e| format!("json value error: {}", e))?; + doc + } + _ => return Err(format!("unexpected type for {:?}: {:?}", key, kv.value)), + }, + _ => value_to_bson(&kv.value)?, + }; + actual_attrs.insert(key, value); + } + for (k, expected_v) in &expected.attributes { + if let Err(e) = + results_match(actual_attrs.get(k), expected_v, false, Some(self.entities)) + { + return Err(format!("span attribute {}: {}\n{}", k, e, err_suffix())); + } + } + + let actual_nested = self + .nested + .get(&actual.span_context.span_id()) + .map(|v| v.as_slice()) + .unwrap_or(&[]); + self.match_span_slice(actual_nested, &expected.nested)?; + + Ok(()) + } +} + +fn value_to_bson(val: &opentelemetry::Value) -> Result { + use opentelemetry::{Array, Value}; + Ok(match val { + Value::Bool(b) => Bson::Boolean(*b), + Value::I64(i) => Bson::Int64(*i), + Value::F64(f) => Bson::Double(*f), + Value::String(sv) => Bson::String(sv.as_str().to_owned()), + Value::Array(array) => match array { + Array::Bool(items) => items.into(), + Array::I64(items) => items.into(), + Array::F64(items) => items.into(), + Array::String(items) => items.iter().map(|i| i.as_str()).collect::>().into(), + _ => return Err(format!("unhandled opentelemetry array {:?}", array)), + }, + _ => return Err(format!("unhandled opentelemetry value {:?}", val)), + }) +} diff --git a/src/otel_stub.rs b/src/otel_stub.rs new file mode 100644 index 000000000..2d4903c0d --- /dev/null +++ b/src/otel_stub.rs @@ -0,0 +1,7 @@ +pub(crate) trait OtelFutureStub: Sized { + fn with_current_context(self) -> Self { + self + } +} + +impl OtelFutureStub for T {} diff --git a/src/sdam/description/server.rs b/src/sdam/description/server.rs index 5159c1b28..cb654c9f8 100644 --- a/src/sdam/description/server.rs +++ b/src/sdam/description/server.rs @@ -57,6 +57,22 @@ pub enum ServerType { Unknown, } +impl std::fmt::Display for ServerType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServerType::Standalone => write!(f, "Standalone"), + ServerType::Mongos => write!(f, "Mongos"), + ServerType::RsPrimary => write!(f, "RSPrimary"), + ServerType::RsSecondary => write!(f, "RSSecondary"), + ServerType::RsArbiter => write!(f, "RSArbiter"), + ServerType::RsOther => write!(f, "RSOther"), + ServerType::RsGhost => write!(f, "RSGhost"), + ServerType::LoadBalancer => write!(f, "LoadBalancer"), + ServerType::Unknown => write!(f, "Unknown"), + } + } +} + impl ServerType { pub(crate) fn can_auth(self) -> bool { !matches!(self, ServerType::RsArbiter) diff --git a/src/test/spec.rs b/src/test/spec.rs index 071a558f0..c32d8bb61 100644 --- a/src/test/spec.rs +++ b/src/test/spec.rs @@ -12,6 +12,8 @@ mod initial_dns_seedlist_discovery; mod load_balancers; #[path = "spec/oidc.rs"] pub(crate) mod oidc_skip_ci; +#[cfg(feature = "opentelemetry")] +mod open_telemetry; mod read_write_concern; mod retryable_reads; mod retryable_writes; diff --git a/src/test/spec/json/open-telemetry/README.md b/src/test/spec/json/open-telemetry/README.md new file mode 100644 index 000000000..99bcc8d29 --- /dev/null +++ b/src/test/spec/json/open-telemetry/README.md @@ -0,0 +1,60 @@ +# OpenTelemetry Tests + +______________________________________________________________________ + +## Testing + +### Automated Tests + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +the OpenTelemetry specification. These tests utilize the +[Unified Test Format](../../unified-test-format/unified-test-format.md). + +For each test, create a MongoClient, configure it to enable tracing. + +```yaml +createEntities: + - client: + id: client0 + observeTracingMessages: + enableCommandPayload: true +``` + +These tests require the ability to collect tracing [spans](../open-telemetry.md) data in a structured form as described +in the +[Unified Test Format specification.expectTracingMessages](../../unified-test-format/unified-test-format.md#expectTracingMessages). +For example the Java driver uses [Micrometer](https://jira.mongodb.org/browse/JAVA-5732) to collect tracing spans. + +```yaml +expectTracingMessages: + client: client0 + ignoreExtraSpans: false + spans: + ... +``` + +### Prose Tests + +*Test 1: Tracing Enable/Disable via Environment Variable* + +1. Set the environment variable `OTEL_#{LANG}_INSTRUMENTATION_MONGODB_ENABLED` to `false`. +2. Create a `MongoClient` without explicitly enabling tracing. +3. Perform a database operation (e.g., `find()` on a test collection). +4. Assert that no OpenTelemetry tracing spans are emitted for the operation. +5. Set the environment variable `OTEL_#{LANG}_INSTRUMENTATION_MONGODB_ENABLED` to `true`. +6. Create a new `MongoClient` without explicitly enabling tracing. +7. Perform the same database operation. +8. Assert that OpenTelemetry tracing spans are emitted for the operation. + +*Test 2: Command Payload Emission via Environment Variable* + +1. Set the environment variable `OTEL_#{LANG}_INSTRUMENTATION_MONGODB_ENABLED` to `true`. +2. Set the environment variable `OTEL_#{LANG}_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH` to a positive integer + (e.g., 1024). +3. Create a `MongoClient` without explicitly enabling command payload emission. +4. Perform a database operation (e.g., `find()`). +5. Assert that the emitted tracing span includes the `db.query.text` attribute. +6. Unset the environment variable `OTEL_#{LANG}_INSTRUMENTATION_MONGODB_QUERY_TEXT_MAX_LENGTH`. +7. Create a new `MongoClient`. +8. Perform the same database operation. +9. Assert that the emitted tracing span does not include the `db.query.text` attribute. diff --git a/src/test/spec/json/open-telemetry/operation/aggregate.json b/src/test/spec/json/open-telemetry/operation/aggregate.json new file mode 100644 index 000000000..9c30c36f8 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/aggregate.json @@ -0,0 +1,130 @@ +{ + "description": "operation aggregate", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-aggregate" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "aggregation", + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ] + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "aggregate operation-aggregate.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-aggregate", + "db.collection.name": "test", + "db.operation.name": "aggregate", + "db.operation.summary": "aggregate operation-aggregate.test" + }, + "nested": [ + { + "name": "aggregate", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-aggregate", + "db.collection.name": "test", + "db.command.name": "aggregate", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "aggregate operation-aggregate.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "_id": 1 + } + } + ] + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/aggregate.yml b/src/test/spec/json/open-telemetry/operation/aggregate.yml new file mode 100644 index 000000000..5f78a7ed5 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/aggregate.yml @@ -0,0 +1,62 @@ +description: operation aggregate +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-aggregate + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName0 test + +tests: + - description: aggregation + operations: + - name: aggregate + object: *collection0 + arguments: + pipeline: &pipeline0 + - $match: { _id: 1 } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: aggregate operation-aggregate.test + attributes: + db.system: mongodb + db.namespace: operation-aggregate + db.collection.name: test + db.operation.name: aggregate + db.operation.summary: aggregate operation-aggregate.test + nested: + - name: aggregate + attributes: + db.system: mongodb + db.namespace: operation-aggregate + db.collection.name: *collectionName0 + db.command.name: aggregate + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + db.query.summary: aggregate operation-aggregate.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + aggregate: test + pipeline: *pipeline0 + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/atlas_search.json b/src/test/spec/json/open-telemetry/operation/atlas_search.json new file mode 100644 index 000000000..820f2f507 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/atlas_search.json @@ -0,0 +1,239 @@ +{ + "description": "operation atlas_search", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-atlas-search" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.5", + "maxServerVersion": "7.0.99", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + }, + { + "minServerVersion": "7.2.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "atlas search indexes", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "mappings": { + "dynamic": true + } + }, + "type": "search" + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + }, + { + "name": "updateSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index", + "definition": {} + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + }, + { + "name": "dropSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "createSearchIndexes operation-atlas-search.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "db.operation.name": "createSearchIndexes", + "db.operation.summary": "createSearchIndexes operation-atlas-search.test" + }, + "nested": [ + { + "name": "createSearchIndexes", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "createSearchIndexes operation-atlas-search.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "createSearchIndexes": "test", + "indexes": [ + { + "type": "search", + "definition": { + "mappings": { + "dynamic": true + } + } + } + ] + } + } + } + } + } + ] + }, + { + "name": "updateSearchIndex operation-atlas-search.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "db.operation.name": "updateSearchIndex", + "db.operation.summary": "updateSearchIndex operation-atlas-search.test" + }, + "nested": [ + { + "name": "updateSearchIndex", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "updateSearchIndex operation-atlas-search.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "updateSearchIndex": "test", + "name": "test index", + "definition": {} + } + } + } + } + } + ] + }, + { + "name": "dropSearchIndex operation-atlas-search.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "db.operation.name": "dropSearchIndex", + "db.operation.summary": "dropSearchIndex operation-atlas-search.test" + }, + "nested": [ + { + "name": "dropSearchIndex", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-atlas-search", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "dropSearchIndex operation-atlas-search.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "dropSearchIndex": "test", + "name": "test index" + } + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/atlas_search.yml b/src/test/spec/json/open-telemetry/operation/atlas_search.yml new file mode 100644 index 000000000..9800d9be3 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/atlas_search.yml @@ -0,0 +1,139 @@ +description: operation atlas_search +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-atlas-search + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: test + +runOnRequirements: + # Skip server versions without fix of SERVER-83107 to avoid error message "BSON field 'createSearchIndexes.indexes.type' is an unknown field." + # SERVER-83107 was not backported to 7.1. + - minServerVersion: "7.0.5" + maxServerVersion: "7.0.99" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + - minServerVersion: "7.2.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + + +tests: + - description: atlas search indexes + operations: + - name: createSearchIndex + object: *collection0 + arguments: + model: { definition: { mappings: { dynamic: true } } , type: 'search' } + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + + - name: updateSearchIndex + object: *collection0 + arguments: + name: 'test index' + definition: {} + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + + - name: dropSearchIndex + object: *collection0 + arguments: + name: 'test index' + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: createSearchIndexes operation-atlas-search.test + attributes: + db.system: mongodb + db.namespace: operation-atlas-search + db.collection.name: test + db.operation.name: createSearchIndexes + db.operation.summary: createSearchIndexes operation-atlas-search.test + nested: + - name: createSearchIndexes + attributes: + db.system: mongodb + db.namespace: operation-atlas-search + db.collection.name: test + server.address: { $$type: string } + server.port: { $$type: [ long, string ] } + server.type: { $$type: string } + db.query.summary: createSearchIndexes operation-atlas-search.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + createSearchIndexes: test + indexes: [ { "type": "search", "definition": { "mappings": { "dynamic": true } } } ] + + - name: updateSearchIndex operation-atlas-search.test + attributes: + db.system: mongodb + db.namespace: operation-atlas-search + db.collection.name: test + db.operation.name: updateSearchIndex + db.operation.summary: updateSearchIndex operation-atlas-search.test + nested: + - name: updateSearchIndex + attributes: + db.system: mongodb + db.namespace: operation-atlas-search + db.collection.name: test + server.address: { $$type: string } + server.port: { $$type: [ long, string ] } + server.type: { $$type: string } + db.query.summary: updateSearchIndex operation-atlas-search.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + updateSearchIndex: test + name: test index + definition: {} + + - name: dropSearchIndex operation-atlas-search.test + attributes: + db.system: mongodb + db.namespace: operation-atlas-search + db.collection.name: test + db.operation.name: dropSearchIndex + db.operation.summary: dropSearchIndex operation-atlas-search.test + nested: + - name: dropSearchIndex + attributes: + db.system: mongodb + db.namespace: operation-atlas-search + db.collection.name: test + server.address: { $$type: string } + server.port: { $$type: [ long, string ] } + server.type: { $$type: string } + db.query.summary: dropSearchIndex operation-atlas-search.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + dropSearchIndex: test + name: test index diff --git a/src/test/spec/json/open-telemetry/operation/bulk_write.json b/src/test/spec/json/open-telemetry/operation/bulk_write.json new file mode 100644 index 000000000..bf9d4d9b6 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/bulk_write.json @@ -0,0 +1,336 @@ +{ + "description": "operation bulk_write", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-bulk-write-0" + } + }, + { + "database": { + "id": "database1", + "client": "client0", + "databaseName": "operation-bulk-write-1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test0" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test1" + } + } + ], + "initialData": [ + { + "collectionName": "test0", + "databaseName": "operation-bulk-write-0", + "documents": [] + }, + { + "collectionName": "test1", + "databaseName": "operation-bulk-write-1", + "documents": [] + } + ], + "_yamlAnchors": { + "namespace0": "operation-bulk-write-0.test0", + "namespace1": "operation-bulk-write-1.test1" + }, + "tests": [ + { + "description": "bulkWrite", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "operation-bulk-write-0.test0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "operation-bulk-write-0.test0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "operation-bulk-write-1.test1", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "operation-bulk-write-1.test1", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "operation-bulk-write-0.test0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "operation-bulk-write-1.test1", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ] + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "bulkWrite admin", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "bulkWrite", + "db.operation.summary": "bulkWrite admin" + }, + "nested": [ + { + "name": "bulkWrite", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.command.name": "bulkWrite", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "bulkWrite admin", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "multi": false, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + } + }, + { + "update": 1, + "multi": true, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + } + }, + { + "update": 1, + "multi": false, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true + }, + { + "delete": 0, + "multi": false, + "filter": { + "_id": 5 + } + }, + { + "delete": 1, + "multi": true, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + ], + "nsInfo": [ + { + "ns": "operation-bulk-write-0.test0" + }, + { + "ns": "operation-bulk-write-1.test1" + } + ] + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/bulk_write.yml b/src/test/spec/json/open-telemetry/operation/bulk_write.yml new file mode 100644 index 000000000..54f3ba2b7 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/bulk_write.yml @@ -0,0 +1,199 @@ +description: operation bulk_write +schemaVersion: '1.27' +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName0 operation-bulk-write-0 + - database: + id: &database1 database1 + client: *client0 + databaseName: &databaseName1 operation-bulk-write-1 + - collection: + id: collection0 + database: *database0 + collectionName: &collectionName0 test0 + - collection: + id: collection1 + database: *database1 + collectionName: &collectionName1 test1 + +initialData: + - collectionName: *collectionName0 + databaseName: *databaseName0 + documents: [ ] + - collectionName: *collectionName1 + databaseName: *databaseName1 + documents: [ ] + +_yamlAnchors: + namespace0: &namespace0 "operation-bulk-write-0.test0" + namespace1: &namespace1 "operation-bulk-write-1.test1" + +tests: + - description: bulkWrite + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace0 + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace1 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace1 + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace0 + filter: { _id: 5 } + - deleteMany: + namespace: *namespace1 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: bulkWrite admin + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.operation.name: bulkWrite + db.operation.summary: bulkWrite admin + nested: + - name: bulkWrite + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.command.name: bulkWrite + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + server.type: { $$type: string } + db.query.summary: bulkWrite admin + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "multi": false, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + } + }, + { + "update": 1, + "multi": true, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + } + }, + { + "update": 1, + "multi": false, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true + }, + { + "delete": 0, + "multi": false, + "filter": { + "_id": 5 + } + }, + { + "delete": 1, + "multi": true, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + ] + nsInfo: [ + { + "ns": *namespace0 + }, + { + "ns": *namespace1 + } + ] + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/count.json b/src/test/spec/json/open-telemetry/operation/count.json new file mode 100644 index 000000000..6b4304dfd --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/count.json @@ -0,0 +1,123 @@ +{ + "description": "operation count", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-count" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-count", + "documents": [] + } + ], + "tests": [ + { + "description": "estimated document count", + "operations": [ + { + "object": "collection0", + "name": "estimatedDocumentCount", + "arguments": {}, + "expectResult": 0 + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "count operation-count.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-count", + "db.collection.name": "test", + "db.operation.name": "count", + "db.operation.summary": "count operation-count.test" + }, + "nested": [ + { + "name": "count", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-count", + "db.collection.name": "test", + "db.command.name": "count", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "count operation-count.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "count": "test" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/count.yml b/src/test/spec/json/open-telemetry/operation/count.yml new file mode 100644 index 000000000..3d2abcb04 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/count.yml @@ -0,0 +1,63 @@ +description: operation count +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: database0 + client: *client0 + databaseName: &database0Name operation-count + - collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection0Name test +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] +tests: + - description: estimated document count + operations: + - object: *collection0 + name: estimatedDocumentCount + arguments: { } + expectResult: 0 + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: count operation-count.test + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.operation.name: count + db.operation.summary: count operation-count.test + nested: + - name: count + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: count + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + db.query.summary: count operation-count.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + count: test + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/create_collection.json b/src/test/spec/json/open-telemetry/operation/create_collection.json new file mode 100644 index 000000000..fc296b4fd --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/create_collection.json @@ -0,0 +1,110 @@ +{ + "description": "operation create collection", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-create-collection" + } + } + ], + "tests": [ + { + "description": "create collection", + "operations": [ + { + "object": "database0", + "name": "createCollection", + "arguments": { + "collection": "newlyCreatedCollection" + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "createCollection operation-create-collection.newlyCreatedCollection", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-create-collection", + "db.collection.name": "newlyCreatedCollection", + "db.operation.name": "createCollection", + "db.operation.summary": "createCollection operation-create-collection.newlyCreatedCollection" + }, + "nested": [ + { + "name": "create", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-create-collection", + "db.collection.name": "newlyCreatedCollection", + "db.command.name": "create", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "create operation-create-collection.newlyCreatedCollection", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "create": "newlyCreatedCollection" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/create_collection.yml b/src/test/spec/json/open-telemetry/operation/create_collection.yml new file mode 100644 index 000000000..d0dee3605 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/create_collection.yml @@ -0,0 +1,55 @@ +description: operation create collection +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name operation-create-collection +tests: + - description: create collection + operations: + - object: *database0 + name: createCollection + arguments: + collection: &collectionName newlyCreatedCollection + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: createCollection operation-create-collection.newlyCreatedCollection + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collectionName + db.operation.name: createCollection + db.operation.summary: createCollection operation-create-collection.newlyCreatedCollection + nested: + - name: create + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collectionName + db.command.name: create + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + db.query.summary: create operation-create-collection.newlyCreatedCollection + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + create: newlyCreatedCollection + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/create_indexes.json b/src/test/spec/json/open-telemetry/operation/create_indexes.json new file mode 100644 index 000000000..40afac514 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/create_indexes.json @@ -0,0 +1,127 @@ +{ + "description": "operation create_indexes", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-create-indexes" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "create indexes", + "operations": [ + { + "object": "collection0", + "name": "createIndex", + "arguments": { + "keys": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "createIndexes operation-create-indexes.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-create-indexes", + "db.collection.name": "test", + "db.operation.name": "createIndexes", + "db.operation.summary": "createIndexes operation-create-indexes.test" + }, + "nested": [ + { + "name": "createIndexes", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-create-indexes", + "db.collection.name": "test", + "db.command.name": "createIndexes", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "createIndexes operation-create-indexes.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "createIndexes": "test", + "indexes": [ + { + "key": { + "x": 1 + }, + "name": "x_1" + } + ] + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/create_indexes.yml b/src/test/spec/json/open-telemetry/operation/create_indexes.yml new file mode 100644 index 000000000..01e23e242 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/create_indexes.yml @@ -0,0 +1,60 @@ +description: operation create_indexes +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name operation-create-indexes + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test +tests: + - description: create indexes + operations: + - object: *collection0 + name: createIndex + arguments: + keys: { x: 1 } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: createIndexes operation-create-indexes.test + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.operation.name: createIndexes + db.operation.summary: createIndexes operation-create-indexes.test + nested: + - name: createIndexes + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: createIndexes + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + db.query.summary: createIndexes operation-create-indexes.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + createIndexes: test + indexes: [ { key: { x: 1 }, name: "x_1" } ] + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/delete.json b/src/test/spec/json/open-telemetry/operation/delete.json new file mode 100644 index 000000000..3dec5c32a --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/delete.json @@ -0,0 +1,103 @@ +{ + "description": "operation delete", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-delete" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "delete elements", + "operations": [ + { + "object": "collection0", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "delete operation-delete.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-delete", + "db.collection.name": "test", + "db.operation.name": "delete", + "db.operation.summary": "delete operation-delete.test" + }, + "nested": [ + { + "name": "delete", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-delete", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "delete operation-delete.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "delete": "test", + "ordered": true, + "deletes": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "limit": 0 + } + ] + } + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/delete.yml b/src/test/spec/json/open-telemetry/operation/delete.yml new file mode 100644 index 000000000..eb9b86668 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/delete.yml @@ -0,0 +1,51 @@ +description: operation delete +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName0 operation-delete + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName0 test + +tests: + - description: delete elements + operations: + - object: *collection0 + name: deleteMany + arguments: + filter: { _id: { $gt: 1 } } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: delete operation-delete.test + attributes: + db.system: mongodb + db.namespace: *databaseName0 + db.collection.name: *collectionName0 + db.operation.name: delete + db.operation.summary: delete operation-delete.test + nested: + - name: delete + attributes: + db.system: mongodb + db.namespace: operation-delete + db.collection.name: test + server.address: { $$type: string } + server.port: { $$type: [ long, string ] } + db.query.summary: delete operation-delete.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + delete: test + ordered: true + deletes: [ { q: { _id: { $gt: 1 } }, limit: 0 } ] diff --git a/src/test/spec/json/open-telemetry/operation/distinct.json b/src/test/spec/json/open-telemetry/operation/distinct.json new file mode 100644 index 000000000..8478d569f --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/distinct.json @@ -0,0 +1,127 @@ +{ + "description": "operation distinct", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-distinct" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-distinct", + "documents": [] + } + ], + "tests": [ + { + "description": "distinct on a field", + "operations": [ + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": {} + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "distinct operation-distinct.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-distinct", + "db.collection.name": "test", + "db.operation.name": "distinct", + "db.operation.summary": "distinct operation-distinct.test" + }, + "nested": [ + { + "name": "distinct", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-distinct", + "db.collection.name": "test", + "db.command.name": "distinct", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "distinct operation-distinct.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "distinct": "test", + "key": "x", + "query": {} + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/distinct.yml b/src/test/spec/json/open-telemetry/operation/distinct.yml new file mode 100644 index 000000000..0b78cecca --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/distinct.yml @@ -0,0 +1,66 @@ +description: operation distinct +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: database0 + client: *client0 + databaseName: operation-distinct + - collection: + id: &collection0 collection0 + database: database0 + collectionName: test +initialData: + - collectionName: test + databaseName: operation-distinct + documents: [] +tests: + - description: distinct on a field + operations: + - object: *collection0 + name: distinct + arguments: + fieldName: x + filter: {} + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: distinct operation-distinct.test + attributes: + db.system: mongodb + db.namespace: operation-distinct + db.collection.name: test + db.operation.name: distinct + db.operation.summary: distinct operation-distinct.test + nested: + - name: distinct + attributes: + db.system: mongodb + db.namespace: operation-distinct + db.collection.name: test + db.command.name: distinct + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + db.query.summary: distinct operation-distinct.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + distinct: test + key: x + query: { } + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/drop_collection.json b/src/test/spec/json/open-telemetry/operation/drop_collection.json new file mode 100644 index 000000000..ce4ffe686 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/drop_collection.json @@ -0,0 +1,117 @@ +{ + "description": "operation drop collection", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-drop-collection" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "drop collection", + "operations": [ + { + "object": "database0", + "name": "dropCollection", + "arguments": { + "collection": "test" + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "dropCollection operation-drop-collection.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-drop-collection", + "db.collection.name": "test", + "db.operation.name": "dropCollection", + "db.operation.summary": "dropCollection operation-drop-collection.test" + }, + "nested": [ + { + "name": "drop", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-drop-collection", + "db.collection.name": "test", + "db.command.name": "drop", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "drop operation-drop-collection.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "drop": "test" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/drop_collection.yml b/src/test/spec/json/open-telemetry/operation/drop_collection.yml new file mode 100644 index 000000000..26c33715d --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/drop_collection.yml @@ -0,0 +1,60 @@ +description: operation drop collection +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-drop-collection + + - collection: + id: collection0 + database: *database0 + collectionName: &collection_name test +tests: + - description: drop collection + operations: + - object: *database0 + name: dropCollection + arguments: + collection: *collection_name + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: dropCollection operation-drop-collection.test + attributes: + db.system: mongodb + db.namespace: operation-drop-collection + db.collection.name: test + db.operation.name: dropCollection + db.operation.summary: dropCollection operation-drop-collection.test + nested: + - name: drop + attributes: + db.system: mongodb + db.namespace: operation-drop-collection + db.collection.name: test + db.command.name: drop + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + db.query.summary: drop operation-drop-collection.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + drop: test + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/drop_indexes.json b/src/test/spec/json/open-telemetry/operation/drop_indexes.json new file mode 100644 index 000000000..efc865b32 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/drop_indexes.json @@ -0,0 +1,145 @@ +{ + "description": "operation drop indexes", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-drop-indexes" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "client": { + "id": "clientWithoutTracing", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "databaseWithoutTracing", + "client": "clientWithoutTracing", + "databaseName": "operation-drop-indexes" + } + }, + { + "collection": { + "id": "collectionWithoutTracing", + "database": "databaseWithoutTracing", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "drop indexes", + "operations": [ + { + "name": "createIndex", + "object": "collectionWithoutTracing", + "arguments": { + "keys": { + "x": 1 + }, + "name": "x_1" + } + }, + { + "name": "dropIndexes", + "object": "collection0" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": true, + "spans": [ + { + "name": "dropIndexes operation-drop-indexes.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-drop-indexes", + "db.collection.name": "test", + "db.operation.name": "dropIndexes", + "db.operation.summary": "dropIndexes operation-drop-indexes.test" + }, + "nested": [ + { + "name": "dropIndexes", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-drop-indexes", + "db.collection.name": "test", + "db.command.name": "dropIndexes", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "dropIndexes operation-drop-indexes.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "dropIndexes": "test", + "index": "*" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/drop_indexes.yml b/src/test/spec/json/open-telemetry/operation/drop_indexes.yml new file mode 100644 index 000000000..519995b74 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/drop_indexes.yml @@ -0,0 +1,78 @@ +description: operation drop indexes +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-drop-indexes + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: test + - client: + id: &clientWithoutTracing clientWithoutTracing + useMultipleMongoses: false + - database: + id: &databaseWithoutTracing databaseWithoutTracing + client: *clientWithoutTracing + databaseName: operation-drop-indexes + - collection: + id: &collectionWithoutTracing collectionWithoutTracing + database: *databaseWithoutTracing + collectionName: test + +tests: + - description: drop indexes + operations: + - name: createIndex + object: *collectionWithoutTracing + arguments: + keys: + x: 1 + name: x_1 + + - name: dropIndexes + object: *collection0 + + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: true + spans: + - name: dropIndexes operation-drop-indexes.test + attributes: + db.system: mongodb + db.namespace: operation-drop-indexes + db.collection.name: test + db.operation.name: dropIndexes + db.operation.summary: dropIndexes operation-drop-indexes.test + nested: + - name: dropIndexes + attributes: + db.system: mongodb + db.namespace: operation-drop-indexes + db.collection.name: test + db.command.name: dropIndexes + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + db.query.summary: dropIndexes operation-drop-indexes.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + dropIndexes: test + index: '*' + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/find.json b/src/test/spec/json/open-telemetry/operation/find.json new file mode 100644 index 000000000..53dfcb1d2 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/find.json @@ -0,0 +1,306 @@ +{ + "description": "operation find", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-find" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-find", + "documents": [] + } + ], + "tests": [ + { + "description": "find an element", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "find operation-find.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-find.test" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "find operation-find.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + }, + { + "description": "find an element retrying failed command", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 89, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": true, + "spans": [ + { + "name": "find operation-find.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-find.test" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": "89", + "exception.message": { + "$$type": "string" + }, + "exception.type": { + "$$type": "string" + }, + "exception.stacktrace": { + "$$type": "string" + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "find operation-find.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "name": "find", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "find operation-find.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/find.yml b/src/test/spec/json/open-telemetry/operation/find.yml new file mode 100644 index 000000000..82f42b90c --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/find.yml @@ -0,0 +1,148 @@ +description: operation find +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-find + - collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection0Name test +initialData: + - collectionName: test + databaseName: operation-find + documents: [] +tests: + - description: find an element + operations: + - name: find + object: *collection0 + arguments: { filter: { x: 1 } } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: find operation-find.test + attributes: + db.system: mongodb + db.namespace: operation-find + db.collection.name: test + db.operation.name: find + db.operation.summary: find operation-find.test + nested: + - name: find + attributes: + db.system: mongodb + db.namespace: operation-find + db.collection.name: test + db.command.name: find + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + server.type: { $$type: string } + db.query.summary: find operation-find.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + find: test + filter: + x: 1 + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] + + - description: find an element retrying failed command + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ find ] + errorCode: 89 + errorLabels: [ RetryableWriteError ] + + - name: find + object: *collection0 + arguments: + filter: { x: 1 } + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: true + spans: + - name: find operation-find.test + attributes: + db.system: mongodb + db.namespace: operation-find + db.collection.name: test + db.operation.name: find + db.operation.summary: find operation-find.test + nested: + - name: find + attributes: + db.system: mongodb + db.namespace: operation-find + db.collection.name: test + db.command.name: find + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: '89' + exception.message: { $$type: string } + exception.type: { $$type: string } + exception.stacktrace: { $$type: string } + server.address: { $$type: string } + server.port: { $$type: [ long, string ] } + server.type: { $$type: string } + db.query.summary: find operation-find.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + find: test + filter: + x: 1 + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] + - name: find + attributes: + db.system: mongodb + db.namespace: operation-find + db.collection.name: test + db.command.name: find + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + server.type: { $$type: string } + db.query.summary: find operation-find.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + find: test + filter: + x: 1 + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/find_and_modify.json b/src/test/spec/json/open-telemetry/operation/find_and_modify.json new file mode 100644 index 000000000..20d595849 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/find_and_modify.json @@ -0,0 +1,138 @@ +{ + "description": "operation find_one_and_update", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-aggregate" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "findOneAndUpdate", + "operations": [ + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": [ + { + "$set": { + "x": 5 + } + } + ], + "comment": "comment" + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "findAndModify operation-aggregate.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-aggregate", + "db.collection.name": "test", + "db.operation.name": "findAndModify", + "db.operation.summary": "findAndModify operation-aggregate.test" + }, + "nested": [ + { + "name": "findAndModify", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-aggregate", + "db.collection.name": "test", + "db.command.name": "findAndModify", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "findAndModify operation-aggregate.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "findAndModify": "test", + "query": { + "_id": 1 + }, + "update": [ + { + "$set": { + "x": 5 + } + } + ], + "comment": "comment" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/find_and_modify.yml b/src/test/spec/json/open-telemetry/operation/find_and_modify.yml new file mode 100644 index 000000000..d36cd053d --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/find_and_modify.yml @@ -0,0 +1,67 @@ +description: operation find_one_and_update +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-aggregate + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: test + +tests: + - description: findOneAndUpdate + operations: + - name: findOneAndUpdate + object: *collection0 + arguments: + filter: &filter + _id: 1 + update: &update + - $set: { x: 5 } + comment: "comment" + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: findAndModify operation-aggregate.test + attributes: + db.system: mongodb + db.namespace: operation-aggregate + db.collection.name: test + db.operation.name: findAndModify + db.operation.summary: findAndModify operation-aggregate.test + nested: + - name: findAndModify + attributes: + db.system: mongodb + db.namespace: operation-aggregate + db.collection.name: test + db.command.name: findAndModify + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + db.query.summary: findAndModify operation-aggregate.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + findAndModify: test + query: *filter + update: *update + comment: "comment" + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/find_without_query_text.json b/src/test/spec/json/open-telemetry/operation/find_without_query_text.json new file mode 100644 index 000000000..df50865ce --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/find_without_query_text.json @@ -0,0 +1,113 @@ +{ + "description": "operation find without db.query.text", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": false + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-find" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-find", + "documents": [] + } + ], + "tests": [ + { + "description": "find an element", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "find operation-find.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-find.test" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "find operation-find.test", + "db.query.text": { + "$$exists": false + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/find_without_query_text.yml b/src/test/spec/json/open-telemetry/operation/find_without_query_text.yml new file mode 100644 index 000000000..26b53b979 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/find_without_query_text.yml @@ -0,0 +1,56 @@ +description: operation find without db.query.text +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: false + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-find + - collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection0Name test +initialData: + - collectionName: test + databaseName: operation-find + documents: [] +tests: + - description: find an element + operations: + - name: find + object: *collection0 + arguments: { filter: { x: 1 } } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: find operation-find.test + attributes: + db.system: mongodb + db.namespace: operation-find + db.collection.name: test + db.operation.name: find + db.operation.summary: find operation-find.test + nested: + - name: find + attributes: + db.system: mongodb + db.namespace: operation-find + db.collection.name: test + db.command.name: find + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [int, long] } + server.type: { $$type: string } + db.query.summary: find operation-find.test + db.query.text: { $$exists: false } diff --git a/src/test/spec/json/open-telemetry/operation/insert.json b/src/test/spec/json/open-telemetry/operation/insert.json new file mode 100644 index 000000000..52e659e1d --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/insert.json @@ -0,0 +1,117 @@ +{ + "description": "operation insert", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-insert" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-insert", + "documents": [] + } + ], + "tests": [ + { + "description": "insert one element", + "operations": [ + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "insert operation-insert.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-insert", + "db.collection.name": "test", + "db.operation.name": "insert", + "db.operation.summary": "insert operation-insert.test" + }, + "nested": [ + { + "name": "insert", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-insert", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "insert operation-insert.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "test", + "ordered": true, + "txnNumber": 1, + "documents": [ + { + "_id": 1 + } + ] + } + } + } + } + } + ] + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "operation-insert", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/insert.yml b/src/test/spec/json/open-telemetry/operation/insert.yml new file mode 100644 index 000000000..15019edaf --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/insert.yml @@ -0,0 +1,61 @@ +description: operation insert +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-insert + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: test +initialData: + - collectionName: test + databaseName: operation-insert + documents: [ ] +tests: + - description: insert one element + operations: + - object: *collection0 + name: insertOne + arguments: { document: { _id: 1 } } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: insert operation-insert.test + attributes: + db.system: mongodb + db.namespace: operation-insert + db.collection.name: test + db.operation.name: insert + db.operation.summary: insert operation-insert.test + nested: + - name: insert + attributes: + db.system: mongodb + db.namespace: operation-insert + server.address: { $$type: string } + server.port: { $$type: [ long, string ] } + server.type: { $$type: string } + db.query.summary: insert operation-insert.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + insert: test + ordered: true + txnNumber: 1 + documents: + - _id: 1 + + outcome: + - collectionName: test + databaseName: operation-insert + documents: + - _id: 1 diff --git a/src/test/spec/json/open-telemetry/operation/list_collections.json b/src/test/spec/json/open-telemetry/operation/list_collections.json new file mode 100644 index 000000000..3d064c3df --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/list_collections.json @@ -0,0 +1,106 @@ +{ + "description": "operation list_collections", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-list-collections" + } + } + ], + "tests": [ + { + "description": "List collections", + "operations": [ + { + "object": "database0", + "name": "listCollections" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "listCollections operation-list-collections", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-list-collections", + "db.operation.name": "listCollections", + "db.operation.summary": "listCollections operation-list-collections" + }, + "nested": [ + { + "name": "listCollections", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-list-collections", + "db.command.name": "listCollections", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "listCollections operation-list-collections", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "listCollections": 1, + "cursor": {} + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/list_collections.yml b/src/test/spec/json/open-telemetry/operation/list_collections.yml new file mode 100644 index 000000000..20c9afa5e --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/list_collections.yml @@ -0,0 +1,52 @@ +description: operation list_collections +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-list-collections +tests: + - description: List collections + operations: + - object: *database0 + name: listCollections + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: listCollections operation-list-collections + attributes: + db.system: mongodb + db.namespace: operation-list-collections + db.operation.name: listCollections + db.operation.summary: listCollections operation-list-collections + nested: + - name: listCollections + attributes: + db.system: mongodb + db.namespace: operation-list-collections + db.command.name: listCollections + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + db.query.summary: listCollections operation-list-collections + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + listCollections: 1 + cursor: {} + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/list_databases.json b/src/test/spec/json/open-telemetry/operation/list_databases.json new file mode 100644 index 000000000..f06ea2cf3 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/list_databases.json @@ -0,0 +1,101 @@ +{ + "description": "operation list_databases", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + } + ], + "tests": [ + { + "description": "list databases", + "operations": [ + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "listDatabases admin", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.operation.name": "listDatabases", + "db.operation.summary": "listDatabases admin" + }, + "nested": [ + { + "name": "listDatabases", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.command.name": "listDatabases", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "listDatabases admin", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "listDatabases": 1 + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/list_databases.yml b/src/test/spec/json/open-telemetry/operation/list_databases.yml new file mode 100644 index 000000000..77f0a5650 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/list_databases.yml @@ -0,0 +1,48 @@ +description: operation list_databases +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true +tests: + - description: list databases + operations: + - object: *client0 + name: listDatabases + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: listDatabases admin + attributes: + db.system: mongodb + db.namespace: admin + db.operation.name: listDatabases + db.operation.summary: listDatabases admin + nested: + - name: listDatabases + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.command.name: listDatabases + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + db.query.summary: listDatabases admin + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + listDatabases: 1 + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/list_indexes.json b/src/test/spec/json/open-telemetry/operation/list_indexes.json new file mode 100644 index 000000000..3e364b789 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/list_indexes.json @@ -0,0 +1,121 @@ +{ + "description": "operation list_indexes", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-list-indexes" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-list-indexes", + "documents": [] + } + ], + "tests": [ + { + "description": "List indexes", + "operations": [ + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "listIndexes operation-list-indexes.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-list-indexes", + "db.collection.name": "test", + "db.operation.name": "listIndexes", + "db.operation.summary": "listIndexes operation-list-indexes.test" + }, + "nested": [ + { + "name": "listIndexes", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-list-indexes", + "db.collection.name": "test", + "db.command.name": "listIndexes", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "listIndexes operation-list-indexes.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "listIndexes": "test" + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/list_indexes.yml b/src/test/spec/json/open-telemetry/operation/list_indexes.yml new file mode 100644 index 000000000..60f8ac23d --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/list_indexes.yml @@ -0,0 +1,61 @@ +description: operation list_indexes +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-list-indexes + - collection: + id: &collection0 collection0 + database: database0 + collectionName: test +initialData: + - collectionName: test + databaseName: operation-list-indexes + documents: [ ] +tests: + - description: List indexes + operations: + - object: *collection0 + name: listIndexes + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: listIndexes operation-list-indexes.test + attributes: + db.system: mongodb + db.namespace: operation-list-indexes + db.collection.name: test + db.operation.name: listIndexes + db.operation.summary: listIndexes operation-list-indexes.test + nested: + - name: listIndexes + attributes: + db.system: mongodb + db.namespace: operation-list-indexes + db.collection.name: test + db.command.name: listIndexes + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + db.query.summary: listIndexes operation-list-indexes.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + listIndexes: test + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/map_reduce.json b/src/test/spec/json/open-telemetry/operation/map_reduce.json new file mode 100644 index 000000000..d5ae4f677 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/map_reduce.json @@ -0,0 +1,180 @@ +{ + "description": "operation map_reduce", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-map-reduce" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-map-reduce", + "documents": [ + { + "_id": 1, + "x": 0 + }, + { + "_id": 2, + "x": 1 + }, + { + "_id": 3, + "x": 2 + } + ] + } + ], + "tests": [ + { + "description": "mapReduce", + "operations": [ + { + "object": "collection0", + "name": "mapReduce", + "arguments": { + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + }, + "expectResult": [ + { + "_id": 0, + "value": 6 + } + ] + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "mapReduce operation-map-reduce.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-map-reduce", + "db.collection.name": "test", + "db.operation.name": "mapReduce", + "db.operation.summary": "mapReduce operation-map-reduce.test" + }, + "nested": [ + { + "name": "mapReduce", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-map-reduce", + "db.collection.name": "test", + "db.command.name": "mapReduce", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "mapReduce operation-map-reduce.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "mapReduce": "test", + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/map_reduce.yml b/src/test/spec/json/open-telemetry/operation/map_reduce.yml new file mode 100644 index 000000000..d96756f9f --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/map_reduce.yml @@ -0,0 +1,99 @@ +description: operation map_reduce +schemaVersion: '1.27' +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + # serverless proxy does not support mapReduce operation + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: operation-map-reduce + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: test +initialData: + - + collectionName: test + databaseName: operation-map-reduce + documents: + - + _id: 1 + x: 0 + - + _id: 2 + x: 1 + - + _id: 3 + x: 2 +tests: + - description: mapReduce + operations: + - object: *collection0 + name: mapReduce + arguments: + map: + $code: 'function inc() { return emit(0, this.x + 1) }' + reduce: + $code: 'function sum(key, values) { return values.reduce((acc, x) => acc + x); }' + out: + inline: 1 + expectResult: + - + _id: 0 + value: 6 + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: mapReduce operation-map-reduce.test + attributes: + db.system: mongodb + db.namespace: operation-map-reduce + db.collection.name: test + db.operation.name: mapReduce + db.operation.summary: mapReduce operation-map-reduce.test + nested: + - name: mapReduce + attributes: + db.system: mongodb + db.namespace: operation-map-reduce + db.collection.name: test + db.command.name: mapReduce + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + server.type: { $$type: string } + db.query.summary: mapReduce operation-map-reduce.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + mapReduce: test + map: { $code: 'function inc() { return emit(0, this.x + 1) }' } + reduce: { $code: 'function sum(key, values) { return values.reduce((acc, x) => acc + x); }' } + out: { inline: 1 } + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/retries.json b/src/test/spec/json/open-telemetry/operation/retries.json new file mode 100644 index 000000000..97b0174fb --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/retries.json @@ -0,0 +1,212 @@ +{ + "description": "retries", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "client": { + "id": "failPointClient", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-find-retries" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "operation-find-retries", + "documents": [] + } + ], + "tests": [ + { + "description": "find an element with retries", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 89, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": true, + "spans": [ + { + "name": "find operation-find-retries.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find-retries", + "db.collection.name": "test", + "db.operation.name": "find", + "db.operation.summary": "find operation-find-retries.test" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find-retries", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": "89", + "exception.message": { + "$$exists": true + }, + "exception.type": { + "$$exists": true + }, + "exception.stacktrace": { + "$$exists": true + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "find operation-find-retries.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "name": "find", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-find-retries", + "db.collection.name": "test", + "db.command.name": "find", + "network.transport": "tcp", + "db.mongodb.cursor_id": { + "$$exists": false + }, + "db.response.status_code": { + "$$exists": false + }, + "exception.message": { + "$$exists": false + }, + "exception.type": { + "$$exists": false + }, + "exception.stacktrace": { + "$$exists": false + }, + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "int", + "long" + ] + }, + "db.query.summary": "find operation-find-retries.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "find": "test", + "filter": { + "x": 1 + } + } + } + }, + "db.mongodb.server_connection_id": { + "$$type": [ + "int", + "long" + ] + }, + "db.mongodb.driver_connection_id": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/retries.yml b/src/test/spec/json/open-telemetry/operation/retries.yml new file mode 100644 index 000000000..8d391c1b0 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/retries.yml @@ -0,0 +1,105 @@ +description: retries +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - client: + id: &failPointClient failPointClient + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name operation-find-retries + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test +initialData: + - collectionName: test + databaseName: operation-find-retries + documents: [ ] +tests: + - description: find an element with retries + operations: + - name: failPoint + object: testRunner + arguments: + client: *failPointClient + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ find ] + errorCode: 89 + errorLabels: [ RetryableWriteError ] + + - name: find + object: *collection0 + arguments: + filter: { x: 1 } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: true + spans: + - name: find operation-find-retries.test + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.operation.name: find + db.operation.summary: find operation-find-retries.test + nested: + - name: find + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: find + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: '89' + exception.message: { $$exists: true } + exception.type: { $$exists: true } + exception.stacktrace: { $$exists: true } + server.address: { $$type: string } + server.port: { $$type: [ long, string ] } + db.query.summary: find operation-find-retries.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + find: test + filter: + x: 1 + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] + - name: find + attributes: + db.system: mongodb + db.namespace: *database0Name + db.collection.name: *collection0Name + db.command.name: find + network.transport: tcp + db.mongodb.cursor_id: { $$exists: false } + db.response.status_code: { $$exists: false } + exception.message: { $$exists: false } + exception.type: { $$exists: false } + exception.stacktrace: { $$exists: false } + server.address: { $$type: string } + server.port: { $$type: [ int, long ] } + db.query.summary: find operation-find-retries.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + find: test + filter: + x: 1 + db.mongodb.server_connection_id: + $$type: [ int, long ] + db.mongodb.driver_connection_id: + $$type: [ int, long ] diff --git a/src/test/spec/json/open-telemetry/operation/update.json b/src/test/spec/json/open-telemetry/operation/update.json new file mode 100644 index 000000000..869812fb6 --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/update.json @@ -0,0 +1,108 @@ +{ + "description": "operation update", + "schemaVersion": "1.27", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "operation-update" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "update one element", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "update operation-update.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-update", + "db.collection.name": "test", + "db.operation.name": "update", + "db.operation.summary": "update operation-update.test" + }, + "nested": [ + { + "name": "update", + "attributes": { + "db.system": "mongodb", + "db.namespace": "operation-update", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "db.query.summary": "update operation-update.test", + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "update": "test", + "ordered": true, + "txnNumber": 1, + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "x": 1 + } + } + } + ] + } + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/operation/update.yml b/src/test/spec/json/open-telemetry/operation/update.yml new file mode 100644 index 000000000..1d8e38e3a --- /dev/null +++ b/src/test/spec/json/open-telemetry/operation/update.yml @@ -0,0 +1,53 @@ +description: operation update +schemaVersion: '1.27' +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName0 operation-update + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName0 test + +tests: + - description: update one element + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: update operation-update.test + attributes: + db.system: mongodb + db.namespace: *databaseName0 + db.collection.name: *collectionName0 + db.operation.name: update + db.operation.summary: update operation-update.test + nested: + - name: update + attributes: + db.system: mongodb + db.namespace: operation-update + server.address: { $$type: string } + server.port: { $$type: [ long, string ] } + db.query.summary: update operation-update.test + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + update: test + ordered: true + txnNumber: 1 + updates: [ { "q": { "_id": 1 }, "u": { "$inc": { "x": 1 } } } ] diff --git a/src/test/spec/json/open-telemetry/transaction/convenient.json b/src/test/spec/json/open-telemetry/transaction/convenient.json new file mode 100644 index 000000000..f3d8994d7 --- /dev/null +++ b/src/test/spec/json/open-telemetry/transaction/convenient.json @@ -0,0 +1,237 @@ +{ + "description": "convenient transactions", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "topologies": [ + "replicaset", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "convenient-transaction-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + }, + { + "session": { + "id": "session", + "client": "client" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "convenient-transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "withTransaction", + "operations": [ + { + "name": "withTransaction", + "object": "session", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 1 + }, + "session": "session" + } + } + ] + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "transaction", + "attributes": { + "db.system": "mongodb" + }, + "nested": [ + { + "name": "insert convenient-transaction-tests.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "convenient-transaction-tests", + "db.collection.name": "test", + "db.operation.name": "insert", + "db.operation.summary": "insert convenient-transaction-tests.test" + }, + "nested": [ + { + "name": "insert", + "attributes": { + "db.system": "mongodb", + "db.namespace": "convenient-transaction-tests", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "insert convenient-transaction-tests.test", + "db.mongodb.lsid": { + "$$sessionLsid": "session" + }, + "db.mongodb.txn_number": 1, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "test", + "ordered": true, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "documents": [ + { + "_id": 1 + } + ] + } + } + } + } + } + ] + }, + { + "name": "commitTransaction admin", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "commitTransaction" + }, + "nested": [ + { + "name": "commitTransaction", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.query.summary": "commitTransaction admin", + "db.command.name": "commitTransaction", + "db.mongodb.lsid": { + "$$sessionLsid": "session" + }, + "db.mongodb.txn_number": 1, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "commitTransaction": 1, + "txnNumber": 1, + "autocommit": false + } + } + } + } + } + ] + } + ] + }, + { + "name": "find convenient-transaction-tests.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "convenient-transaction-tests", + "db.collection.name": "test", + "db.operation.summary": "find convenient-transaction-tests.test", + "db.operation.name": "find" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system": "mongodb", + "db.namespace": "convenient-transaction-tests", + "db.collection.name": "test", + "db.command.name": "find", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "find convenient-transaction-tests.test" + } + } + ] + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "convenient-transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/transaction/convenient.yml b/src/test/spec/json/open-telemetry/transaction/convenient.yml new file mode 100644 index 000000000..a9b09cd69 --- /dev/null +++ b/src/test/spec/json/open-telemetry/transaction/convenient.yml @@ -0,0 +1,132 @@ +description: convenient transactions + +schemaVersion: "1.27" + +runOnRequirements: + - minServerVersion: "4.4" + topologies: ["replicaset", "sharded"] + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database database + client: *client + databaseName: &databaseName convenient-transaction-tests + - collection: + id: &collection collection + database: *database + collectionName: &collectionName test + - session: + id: &session session + client: *client + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - description: "withTransaction" + operations: + - name: withTransaction + object: *session + arguments: + callback: + - name: insertOne + object: *collection + arguments: + document: + _id: 1 + session: *session + - name: find + object: *collection + arguments: { filter: { x: 1 } } + + expectTracingMessages: + - client: *client + ignoreExtraSpans: false + spans: + - name: transaction + attributes: + db.system: mongodb + nested: + - name: insert convenient-transaction-tests.test + attributes: + db.system: mongodb + db.namespace: *databaseName + db.collection.name: *collectionName + db.operation.name: insert + db.operation.summary: insert convenient-transaction-tests.test + nested: + - name: insert + attributes: + db.system: mongodb + db.namespace: *databaseName + db.collection.name: *collectionName + server.address: { $$type: string } + server.port: { $$type: [ 'long', 'string' ] } + server.type: { $$type: string } + db.query.summary: insert convenient-transaction-tests.test + db.mongodb.lsid: { $$sessionLsid: *session } + db.mongodb.txn_number: 1 + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + insert: test + ordered: true + txnNumber: 1 + startTransaction: true + autocommit: false + documents: + - _id: 1 + - name: commitTransaction admin + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.operation.name: commitTransaction + nested: + - name: commitTransaction + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.query.summary: commitTransaction admin + db.command.name: commitTransaction + db.mongodb.lsid: { $$sessionLsid: *session } + db.mongodb.txn_number: 1 + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + commitTransaction: 1 + txnNumber: 1 + autocommit: false + + - name: find convenient-transaction-tests.test + attributes: + db.system: mongodb + db.namespace: *databaseName + db.collection.name: *collectionName + db.operation.summary: find convenient-transaction-tests.test + db.operation.name: find + nested: + - name: find + attributes: + db.system: mongodb + db.namespace: *databaseName + db.collection.name: *collectionName + db.command.name: find + server.address: { $$type: string } + server.port: { $$type: [ 'long', 'string' ] } + server.type: { $$type: string } + db.query.summary: find convenient-transaction-tests.test + + outcome: + - collectionName: test + databaseName: convenient-transaction-tests + documents: + - _id: 1 diff --git a/src/test/spec/json/open-telemetry/transaction/core_api.json b/src/test/spec/json/open-telemetry/transaction/core_api.json new file mode 100644 index 000000000..5491c9d66 --- /dev/null +++ b/src/test/spec/json/open-telemetry/transaction/core_api.json @@ -0,0 +1,380 @@ +{ + "description": "transaction spans", + "schemaVersion": "1.27", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeTracingMessages": { + "enableCommandPayload": true + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commit transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "x": 1 + } + } + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "transaction", + "attributes": { + "db.system": "mongodb" + }, + "nested": [ + { + "name": "insert transaction-tests.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.operation.name": "insert", + "db.operation.summary": "insert transaction-tests.test" + }, + "nested": [ + { + "name": "insert", + "attributes": { + "db.system": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "insert transaction-tests.test", + "db.mongodb.lsid": { + "$$sessionLsid": "session0" + }, + "db.mongodb.txn_number": 1, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "test", + "ordered": true, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "documents": [ + { + "_id": 1 + } + ] + } + } + } + } + } + ] + }, + { + "name": "commitTransaction admin", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "commitTransaction" + }, + "nested": [ + { + "name": "commitTransaction", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.query.summary": "commitTransaction admin", + "db.command.name": "commitTransaction", + "db.mongodb.lsid": { + "$$sessionLsid": "session0" + }, + "db.mongodb.txn_number": 1, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "commitTransaction": 1, + "txnNumber": 1, + "autocommit": false + } + } + } + } + } + ] + } + ] + }, + { + "name": "find transaction-tests.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.operation.summary": "find transaction-tests.test", + "db.operation.name": "find" + }, + "nested": [ + { + "name": "find", + "attributes": { + "db.system": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.command.name": "find", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "find transaction-tests.test" + } + } + ] + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "abort transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectTracingMessages": [ + { + "client": "client0", + "ignoreExtraSpans": false, + "spans": [ + { + "name": "transaction", + "attributes": { + "db.system": "mongodb" + }, + "nested": [ + { + "name": "insert transaction-tests.test", + "attributes": { + "db.system": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "db.operation.name": "insert", + "db.operation.summary": "insert transaction-tests.test" + }, + "nested": [ + { + "name": "insert", + "attributes": { + "db.system": "mongodb", + "db.namespace": "transaction-tests", + "db.collection.name": "test", + "server.address": { + "$$type": "string" + }, + "server.port": { + "$$type": [ + "long", + "string" + ] + }, + "server.type": { + "$$type": "string" + }, + "db.query.summary": "insert transaction-tests.test", + "db.mongodb.lsid": { + "$$sessionLsid": "session0" + }, + "db.mongodb.txn_number": 1, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "test", + "ordered": true, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "documents": [ + { + "_id": 1 + } + ] + } + } + } + } + } + ] + }, + { + "name": "abortTransaction admin", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.operation.name": "abortTransaction" + }, + "nested": [ + { + "name": "abortTransaction", + "attributes": { + "db.system": "mongodb", + "db.namespace": "admin", + "db.collection.name": { + "$$exists": false + }, + "db.query.summary": "abortTransaction admin", + "db.command.name": "abortTransaction", + "db.mongodb.lsid": { + "$$sessionLsid": "session0" + }, + "db.mongodb.txn_number": 1, + "db.query.text": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "abortTransaction": 1, + "txnNumber": 1, + "autocommit": false + } + } + } + } + } + ] + } + ] + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/open-telemetry/transaction/core_api.yml b/src/test/spec/json/open-telemetry/transaction/core_api.yml new file mode 100644 index 000000000..63ce1e93d --- /dev/null +++ b/src/test/spec/json/open-telemetry/transaction/core_api.yml @@ -0,0 +1,208 @@ +description: transaction spans +schemaVersion: '1.27' +runOnRequirements: + - minServerVersion: '4.0' + topologies: + - replicaset + - minServerVersion: '4.1.8' + topologies: + - sharded + - load-balanced +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeTracingMessages: + enableCommandPayload: true + - database: + id: &database0 database0 + client: *client0 + databaseName: transaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: test + - session: + id: &session0 session0 + client: client0 +initialData: + - collectionName: test + databaseName: transaction-tests + documents: [] +tests: + - description: commit transaction + operations: + - object: *session0 + name: startTransaction + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: + _id: 1 + - object: *session0 + name: commitTransaction + - name: find + object: *collection0 + arguments: { filter: { x: 1 } } + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: transaction + attributes: + db.system: mongodb + nested: + - name: insert transaction-tests.test + attributes: + db.system: mongodb + db.namespace: transaction-tests + db.collection.name: test + db.operation.name: insert + db.operation.summary: insert transaction-tests.test + nested: + - name: insert + attributes: + db.system: mongodb + db.namespace: transaction-tests + db.collection.name: test + server.address: { $$type: string } + server.port: { $$type: ['long', 'string'] } + server.type: { $$type: string } + db.query.summary: insert transaction-tests.test + db.mongodb.lsid: { $$sessionLsid: *session0 } + db.mongodb.txn_number: 1 + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + insert: test + ordered: true + txnNumber: 1 + startTransaction: true + autocommit: false + documents: + - _id: 1 + - name: commitTransaction admin + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.operation.name: commitTransaction + nested: + - name: commitTransaction + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.query.summary: commitTransaction admin + db.command.name: commitTransaction + db.mongodb.lsid: { $$sessionLsid: *session0 } + db.mongodb.txn_number: 1 + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + commitTransaction: 1 + txnNumber: 1 + autocommit: false + - name: find transaction-tests.test + attributes: + db.system: mongodb + db.namespace: transaction-tests + db.collection.name: test + db.operation.summary: find transaction-tests.test + db.operation.name: find + nested: + - name: find + attributes: + db.system: mongodb + db.namespace: transaction-tests + db.collection.name: test + db.command.name: find + server.address: { $$type: string } + server.port: { $$type: ['long', 'string'] } + server.type: { $$type: string } + db.query.summary: find transaction-tests.test + outcome: + - collectionName: test + databaseName: transaction-tests + documents: + - _id: 1 + + - description: abort transaction + operations: + - object: *session0 + name: startTransaction + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: + _id: 1 + - object: *session0 + name: abortTransaction + + expectTracingMessages: + - client: *client0 + ignoreExtraSpans: false + spans: + - name: transaction + attributes: + db.system: mongodb + nested: + - name: insert transaction-tests.test + attributes: + db.system: mongodb + db.namespace: transaction-tests + db.collection.name: test + db.operation.name: insert + db.operation.summary: insert transaction-tests.test + nested: + - name: insert + attributes: + db.system: mongodb + db.namespace: transaction-tests + db.collection.name: test + server.address: { $$type: string } + server.port: { $$type: ['long', 'string'] } + server.type: { $$type: string } + db.query.summary: insert transaction-tests.test + db.mongodb.lsid: { $$sessionLsid: *session0 } + db.mongodb.txn_number: 1 + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + insert: test + ordered: true + txnNumber: 1 + startTransaction: true + autocommit: false + documents: + - _id: 1 + - name: abortTransaction admin + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.operation.name: abortTransaction + nested: + - name: abortTransaction + attributes: + db.system: mongodb + db.namespace: admin + db.collection.name: { $$exists: false } + db.query.summary: abortTransaction admin + db.command.name: abortTransaction + db.mongodb.lsid: { $$sessionLsid: *session0 } + db.mongodb.txn_number: 1 + db.query.text: + $$matchAsDocument: + $$matchAsRoot: + abortTransaction: 1 + txnNumber: 1 + autocommit: false + + outcome: + - collectionName: test + databaseName: transaction-tests + documents: [] diff --git a/src/test/spec/open_telemetry.rs b/src/test/spec/open_telemetry.rs new file mode 100644 index 000000000..7d74a87bb --- /dev/null +++ b/src/test/spec/open_telemetry.rs @@ -0,0 +1,26 @@ +use crate::test::{log_uncaptured, spec::unified_runner::run_unified_tests}; + +#[tokio::test(flavor = "multi_thread")] +async fn run_unified_operation() { + // TODO: + // server.type + // output db.mongodb.cursor_id + // test parsing for db.mongodb.lsid + let mut skip = vec![ + "List collections", // expects `cursor: {}` in `db.query.text` + "update one element", // expects `txnNumber: 1` in `db.query.text` + "insert one element", // expects `txnNumber: 1` in `db.query.text` + ]; + if crate::test::server_version_lte(4, 2).await { + log_uncaptured("skipping \"findOneAndUpdate\" on server 4.2"); + skip.push("findOneAndUpdate"); // uses unsupported `comment` field + } + run_unified_tests(&["open-telemetry", "operation"]) + .skip_tests(&skip) + .await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn run_unified_transaction() { + run_unified_tests(&["open-telemetry", "transaction"]).await; +} diff --git a/src/test/spec/trace.rs b/src/test/spec/trace.rs index 0b150e7d2..056b4f266 100644 --- a/src/test/spec/trace.rs +++ b/src/test/spec/trace.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, iter, sync::Arc, time::Duration}; use crate::{ bson::{doc, Document}, + bson_util::truncate_on_char_boundary, client::options::ServerAddress, error::{ CommandError, @@ -29,7 +30,6 @@ use crate::{ SERVER_API, }, trace::{ - truncate_on_char_boundary, TracingRepresentation, COMMAND_TRACING_EVENT_TARGET, DEFAULT_MAX_DOCUMENT_LENGTH_BYTES, diff --git a/src/test/spec/unified_runner/entity.rs b/src/test/spec/unified_runner/entity.rs index 27f7255e7..a3b7cf55b 100644 --- a/src/test/spec/unified_runner/entity.rs +++ b/src/test/spec/unified_runner/entity.rs @@ -70,6 +70,8 @@ pub(crate) struct ClientEntity { observe_events: Option>, ignore_command_names: Option>, observe_sensitive_commands: bool, + #[cfg(feature = "opentelemetry")] + pub(crate) tracing: Option, } #[derive(Debug)] @@ -123,9 +125,21 @@ impl ClientEntity { observe_events: Option>, ignore_command_names: Option>, observe_sensitive_commands: bool, + #[cfg(feature = "opentelemetry")] observe_tracing_messages: Option< + &crate::otel::testing::ObserveTracingMessages, + >, ) -> Self { let events = EventBuffer::new(); events.register(&mut client_options); + #[cfg(feature = "opentelemetry")] + let tracing = match observe_tracing_messages { + Some(observe) => { + let (tracing, options) = crate::otel::testing::ClientTracing::new(observe); + client_options.tracing = Some(options); + Some(tracing) + } + None => None, + }; let client = Client::with_options(client_options).unwrap(); let topology_id = client.topology().id; Self { @@ -135,6 +149,8 @@ impl ClientEntity { observe_events, ignore_command_names, observe_sensitive_commands, + #[cfg(feature = "opentelemetry")] + tracing, } } diff --git a/src/test/spec/unified_runner/operation.rs b/src/test/spec/unified_runner/operation.rs index d6357094f..2147a0be5 100644 --- a/src/test/spec/unified_runner/operation.rs +++ b/src/test/spec/unified_runner/operation.rs @@ -52,6 +52,7 @@ use index::{ AssertIndexNotExists, CreateIndex, DropIndex, + DropIndexes, ListIndexNames, ListIndexes, }; @@ -437,6 +438,7 @@ impl<'de> Deserialize<'de> for Operation { #[cfg(feature = "in-use-encryption")] "decrypt" => deserialize_op::(definition.arguments), "dropIndex" => deserialize_op::(definition.arguments), + "dropIndexes" => deserialize_op::(definition.arguments), s => Ok(Box::new(UnimplementedOperation { _name: s.to_string(), }) as Box), diff --git a/src/test/spec/unified_runner/operation/index.rs b/src/test/spec/unified_runner/operation/index.rs index 0c6b3140e..e17051f87 100644 --- a/src/test/spec/unified_runner/operation/index.rs +++ b/src/test/spec/unified_runner/operation/index.rs @@ -194,3 +194,30 @@ impl TestOperation for DropIndex { .boxed() } } + +#[derive(Debug, Deserialize)] +pub(super) struct DropIndexes { + session: Option, + #[serde(flatten)] + options: Option, +} + +impl TestOperation for DropIndexes { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + with_opt_session!( + test_runner, + &self.session, + collection.drop_indexes().with_options(self.options.clone()) + ) + .await?; + Ok(None) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/test_file.rs b/src/test/spec/unified_runner/test_file.rs index 4a0c1400a..2c4fbc214 100644 --- a/src/test/spec/unified_runner/test_file.rs +++ b/src/test/spec/unified_runner/test_file.rs @@ -232,6 +232,8 @@ pub(crate) struct Client { pub(crate) observe_log_messages: Option>, #[cfg(feature = "in-use-encryption")] pub(crate) auto_encrypt_opts: Option, + #[cfg(feature = "opentelemetry")] + pub(crate) observe_tracing_messages: Option, } impl Client { @@ -468,6 +470,9 @@ pub(crate) struct TestCase { pub(crate) expect_events: Option>, #[cfg(feature = "tracing-unstable")] pub(crate) expect_log_messages: Option>, + #[cfg(feature = "opentelemetry")] + #[serde(default)] + pub(crate) expect_tracing_messages: Option>, #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) outcome: Option>, } diff --git a/src/test/spec/unified_runner/test_runner.rs b/src/test/spec/unified_runner/test_runner.rs index b3f089a29..e48d951d3 100644 --- a/src/test/spec/unified_runner/test_runner.rs +++ b/src/test/spec/unified_runner/test_runner.rs @@ -68,7 +68,7 @@ const SKIPPED_OPERATIONS: &[&str] = &[ ]; static MIN_SPEC_VERSION: Version = Version::new(1, 0, 0); -static MAX_SPEC_VERSION: Version = Version::new(1, 25, 0); +static MAX_SPEC_VERSION: Version = Version::new(1, 27, 0); pub(crate) type EntityMap = HashMap; @@ -362,6 +362,15 @@ impl TestRunner { } } + #[cfg(feature = "opentelemetry")] + if let Some(expected) = &test_case.expect_tracing_messages { + for exp in expected { + if let Err(e) = self.match_spans(exp).await { + panic!("[{}] {}", test_case.description, e); + } + } + } + self.fail_point_guards.write().await.clear(); if let Some(ref outcome) = test_case.outcome { @@ -527,6 +536,8 @@ impl TestRunner { observe_events, ignore_command_names, observe_sensitive_commands, + #[cfg(feature = "opentelemetry")] + client.observe_tracing_messages.as_ref(), ); #[cfg(feature = "in-use-encryption")] diff --git a/src/trace.rs b/src/trace.rs index f3841f1ea..bf806aeab 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -1,7 +1,7 @@ #[cfg(feature = "bson-3")] use crate::bson_compat::RawDocumentBufExt; use crate::{ - bson::Bson, + bson_util::{doc_to_json_str, truncate_on_char_boundary}, client::options::{ServerAddress, DEFAULT_PORT}, }; @@ -41,7 +41,7 @@ impl crate::error::Error { ); if let Some(server_response) = self.server_response() { let server_response_string = match server_response.to_document() { - Ok(document) => serialize_command_or_reply(document, max_document_length), + Ok(document) => doc_to_json_str(document, max_document_length), Err(_) => { let mut hex_string = hex::encode(server_response.as_bytes()); truncate_on_char_boundary(&mut hex_string, max_document_length); @@ -68,36 +68,6 @@ impl ServerAddress { } } -/// Truncates the given string at the closest UTF-8 character boundary >= the provided length. -/// If the new length is >= the current length, does nothing. -pub(crate) fn truncate_on_char_boundary(s: &mut String, new_len: usize) { - let original_len = s.len(); - if original_len > new_len { - // to avoid generating invalid UTF-8, find the first index >= max_length_bytes that is - // the end of a character. - // TODO: RUST-1496 we should use ceil_char_boundary here but it's currently nightly-only. - // see: https://doc.rust-lang.org/std/string/struct.String.html#method.ceil_char_boundary - let mut truncate_index = new_len; - // is_char_boundary returns true when the provided value == the length of the string, so - // if we reach the end of the string this loop will terminate. - while !s.is_char_boundary(truncate_index) { - truncate_index += 1; - } - s.truncate(truncate_index); - // due to the "rounding up" behavior we might not actually end up truncating anything. - // if we did, spec requires we add a trailing "...". - if truncate_index < original_len { - s.push_str("...") - } - } -} - -fn serialize_command_or_reply(doc: crate::bson::Document, max_length_bytes: usize) -> String { - let mut ext_json = Bson::Document(doc).into_relaxed_extjson().to_string(); - truncate_on_char_boundary(&mut ext_json, max_length_bytes); - ext_json -} - /// We don't currently use all of these levels but they are included for completeness. #[allow(dead_code)] pub(crate) enum TracingOrLogLevel { diff --git a/src/trace/command.rs b/src/trace/command.rs index 9e6bcd973..5eb044cd1 100644 --- a/src/trace/command.rs +++ b/src/trace/command.rs @@ -1,8 +1,9 @@ use crate::bson::oid::ObjectId; use crate::{ + bson_util::doc_to_json_str, event::command::CommandEvent, - trace::{serialize_command_or_reply, TracingRepresentation, COMMAND_TRACING_EVENT_TARGET}, + trace::{TracingRepresentation, COMMAND_TRACING_EVENT_TARGET}, }; use super::DEFAULT_MAX_DOCUMENT_LENGTH_BYTES; @@ -32,7 +33,7 @@ impl CommandTracingEventEmitter { tracing::debug!( target: COMMAND_TRACING_EVENT_TARGET, topologyId = self.topology_id.tracing_representation(), - command = serialize_command_or_reply(event.command, self.max_document_length_bytes), + command = doc_to_json_str(event.command, self.max_document_length_bytes), databaseName = event.db, commandName = event.command_name, requestId = event.request_id, @@ -48,7 +49,7 @@ impl CommandTracingEventEmitter { tracing::debug!( target: COMMAND_TRACING_EVENT_TARGET, topologyId = self.topology_id.tracing_representation(), - reply = serialize_command_or_reply(event.reply, self.max_document_length_bytes), + reply = doc_to_json_str(event.reply, self.max_document_length_bytes), commandName = event.command_name, requestId = event.request_id, driverConnectionId = event.connection.id, diff --git a/src/trace/topology.rs b/src/trace/topology.rs index 2ee67e111..56b3245b2 100644 --- a/src/trace/topology.rs +++ b/src/trace/topology.rs @@ -1,6 +1,7 @@ use crate::bson::oid::ObjectId; use crate::{ + bson_util::doc_to_json_str, event::sdam::{ SdamEvent, ServerClosedEvent, @@ -14,7 +15,6 @@ use crate::{ TopologyDescriptionChangedEvent, TopologyOpeningEvent, }, - trace::serialize_command_or_reply, }; use super::{ @@ -175,7 +175,7 @@ impl TopologyTracingEventEmitter { driverConnectionId = event.driver_connection_id, serverConnectionId = event.server_connection_id, awaited = event.awaited, - reply = serialize_command_or_reply(event.reply, self.max_document_length_bytes), + reply = doc_to_json_str(event.reply, self.max_document_length_bytes), durationMS = event.duration.as_millis(), "Server heartbeat succeeded" )