use std::{fmt::Write as _, time::Duration};
pub(crate) mod schema;
pub(crate) mod writer;
pub(crate) const TRACE_TARGET: &str = "arrow_tiberius";
pub(crate) const SCHEMA_PLANNING_PHASE: &str = "schema_planning";
pub(crate) const SCHEMA_PLANNING_SPAN: &str = "arrow_tiberius.schema_planning";
pub(crate) const SCHEMA_PLANNING_STARTED_EVENT: &str = "arrow_tiberius.schema_planning.started";
pub(crate) const SCHEMA_PLANNING_COMPLETED_EVENT: &str = "arrow_tiberius.schema_planning.completed";
pub(crate) const SCHEMA_PLANNING_FAILED_EVENT: &str = "arrow_tiberius.schema_planning.failed";
pub(crate) const WRITER_INITIALIZATION_PHASE: &str = "writer_initialization";
pub(crate) const TARGET_METADATA_VALIDATION_PHASE: &str = "target_metadata_validation";
pub(crate) const WRITER_INITIALIZATION_SPAN: &str = "arrow_tiberius.writer_initialization";
pub(crate) const WRITER_INITIALIZATION_STARTED_EVENT: &str =
"arrow_tiberius.writer_initialization.started";
pub(crate) const WRITER_INITIALIZATION_COMPLETED_EVENT: &str =
"arrow_tiberius.writer_initialization.completed";
pub(crate) const WRITER_INITIALIZATION_FAILED_EVENT: &str =
"arrow_tiberius.writer_initialization.failed";
pub(crate) const TARGET_METADATA_VALIDATION_STARTED_EVENT: &str =
"arrow_tiberius.target_metadata_validation.started";
pub(crate) const TARGET_METADATA_VALIDATION_COMPLETED_EVENT: &str =
"arrow_tiberius.target_metadata_validation.completed";
pub(crate) const TARGET_METADATA_VALIDATION_FAILED_EVENT: &str =
"arrow_tiberius.target_metadata_validation.failed";
pub(crate) const BATCH_WRITE_PHASE: &str = "batch_write";
#[cfg(test)]
pub(crate) const BATCH_SCHEMA_VALIDATION_PHASE: &str = "batch_schema_validation";
#[cfg(test)]
pub(crate) const VALUE_CONVERSION_PHASE: &str = "value_conversion";
pub(crate) const DIRECT_ENCODING_PHASE: &str = "direct_encoding";
pub(crate) const PACKET_WRITE_PHASE: &str = "packet_write";
pub(crate) const BATCH_WRITE_SPAN: &str = "arrow_tiberius.batch_write";
pub(crate) const BATCH_WRITE_STARTED_EVENT: &str = "arrow_tiberius.batch_write.started";
pub(crate) const BATCH_WRITE_COMPLETED_EVENT: &str = "arrow_tiberius.batch_write.completed";
pub(crate) const BATCH_WRITE_FAILED_EVENT: &str = "arrow_tiberius.batch_write.failed";
pub(crate) const DIRECT_RAW_MEASURED_EVENT: &str = "arrow_tiberius.direct_raw.measured";
pub(crate) const DIRECT_RAW_RANGES_PLANNED_EVENT: &str = "arrow_tiberius.direct_raw.ranges_planned";
pub(crate) const DIRECT_RAW_PACKET_WRITE_COMPLETED_EVENT: &str =
"arrow_tiberius.direct_raw.packet_write.completed";
pub(crate) const DIRECT_RAW_FAILED_EVENT: &str = "arrow_tiberius.direct_raw.failed";
pub(crate) const FINISH_PHASE: &str = "finish";
pub(crate) const FINALIZE_PHASE: &str = "finalize";
pub(crate) const FINISH_SPAN: &str = "arrow_tiberius.finish";
pub(crate) const FINISH_STARTED_EVENT: &str = "arrow_tiberius.finish.started";
pub(crate) const FINISH_COMPLETED_EVENT: &str = "arrow_tiberius.finish.completed";
pub(crate) const FINISH_FAILED_EVENT: &str = "arrow_tiberius.finish.failed";
pub(crate) fn duration_micros_u64(duration: Duration) -> u64 {
u64::try_from(duration.as_micros()).unwrap_or(u64::MAX)
}
pub(crate) fn usize_to_u64_saturating(value: usize) -> u64 {
u64::try_from(value).unwrap_or(u64::MAX)
}
pub(crate) fn append_debug_name<T: std::fmt::Debug>(target: &mut String, value: T) {
if !target.is_empty() {
target.push(',');
}
let _ = write!(target, "{value:?}");
}
pub(crate) fn append_text(target: &mut String, value: &str) {
if !target.is_empty() {
target.push(',');
}
target.push_str(value);
}
pub(crate) fn append_unique_text(target: &mut String, value: &str) {
if target.split(',').any(|existing| existing == value) {
return;
}
append_text(target, value);
}
#[cfg(test)]
pub(crate) const TEST_CAPTURE_SPAN: &str = "arrow_tiberius.test_capture";
#[cfg(test)]
pub(crate) const TEST_CAPTURE_EVENT: &str = "arrow_tiberius.test_capture_smoke";
#[cfg(test)]
pub(crate) fn emit_test_capture_smoke_event() {
let span = tracing::info_span!(
target: TRACE_TARGET,
TEST_CAPTURE_SPAN,
phase = "test_capture"
);
let _span_guard = span.enter();
tracing::info!(
target: TRACE_TARGET,
telemetry_event = TEST_CAPTURE_EVENT,
phase = "test_capture",
smoke_count = 1_u64,
smoke_label = "foundation",
message = TEST_CAPTURE_EVENT
);
}
#[cfg(test)]
pub(crate) mod test_support {
use std::{
collections::BTreeMap,
fmt,
sync::{Arc, Mutex},
};
use tracing::{
Event, Level, Subscriber,
field::{Field, Visit},
span::{Attributes, Id},
};
use tracing_subscriber::{
Layer, Registry,
layer::{Context, SubscriberExt},
registry::LookupSpan,
};
static CAPTURE_LOCK: Mutex<()> = Mutex::new(());
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CapturedTraceKind {
Span,
Event,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CapturedTrace {
kind: CapturedTraceKind,
name: String,
target: String,
level: Level,
span_name: Option<String>,
fields: BTreeMap<String, String>,
}
impl CapturedTrace {
pub(crate) const fn kind(&self) -> CapturedTraceKind {
self.kind
}
pub(crate) fn name(&self) -> &str {
&self.name
}
pub(crate) fn target(&self) -> &str {
&self.target
}
pub(crate) fn level(&self) -> Level {
self.level
}
pub(crate) fn span_name(&self) -> Option<&str> {
self.span_name.as_deref()
}
pub(crate) const fn fields(&self) -> &BTreeMap<String, String> {
&self.fields
}
fn contains_text(&self, text: &str) -> bool {
self.name.contains(text)
|| self.target.contains(text)
|| self
.span_name
.as_ref()
.is_some_and(|span_name| span_name.contains(text))
|| self
.fields
.iter()
.any(|(key, value)| key.contains(text) || value.contains(text))
}
}
#[derive(Debug, Clone)]
pub(crate) struct CapturedTraces {
records: Arc<Mutex<Vec<CapturedTrace>>>,
}
impl CapturedTraces {
pub(crate) fn records(&self) -> Result<Vec<CapturedTrace>, String> {
self.records
.lock()
.map_err(|_| "captured traces lock poisoned".to_owned())
.map(|records| records.clone())
}
pub(crate) fn contains_text(&self, text: &str) -> Result<bool, String> {
Ok(self
.records()?
.into_iter()
.any(|record| record.contains_text(text)))
}
pub(crate) fn assert_no_forbidden_text(&self, forbidden: &[&str]) -> Result<(), String> {
let records = self.records()?;
for text in forbidden {
if records.iter().any(|record| record.contains_text(text)) {
return Err(format!("captured trace contained forbidden text `{text}`"));
}
}
Ok(())
}
}
pub(crate) fn with_trace_capture_lock<R>(operation: impl FnOnce() -> R) -> R {
let _capture_guard = match CAPTURE_LOCK.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
tracing::callsite::rebuild_interest_cache();
let result = operation();
tracing::callsite::rebuild_interest_cache();
result
}
pub(crate) fn capture_traces<R>(operation: impl FnOnce() -> R) -> (R, CapturedTraces) {
with_trace_capture_lock(|| {
let records = Arc::new(Mutex::new(Vec::new()));
let subscriber = Registry::default().with(CaptureLayer {
records: Arc::clone(&records),
});
let result = tracing::subscriber::with_default(subscriber, || {
tracing::callsite::rebuild_interest_cache();
operation()
});
tracing::callsite::rebuild_interest_cache();
(result, CapturedTraces { records })
})
}
pub(crate) fn trace_event<'a>(
records: &'a [CapturedTrace],
telemetry_event: &str,
) -> Result<&'a CapturedTrace, String> {
records
.iter()
.find(|record| {
record.kind() == CapturedTraceKind::Event
&& record
.fields()
.get("telemetry_event")
.is_some_and(|value| value == telemetry_event)
})
.ok_or_else(|| format!("missing trace event {telemetry_event}: {records:#?}"))
}
pub(crate) fn assert_trace_field(record: &CapturedTrace, field: &str, expected: &str) {
assert_eq!(
record.fields().get(field).map(String::as_str),
Some(expected),
"trace record: {record:#?}"
);
}
struct CaptureLayer {
records: Arc<Mutex<Vec<CapturedTrace>>>,
}
impl<S> Layer<S> for CaptureLayer
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn on_new_span(&self, attrs: &Attributes<'_>, _id: &Id, _ctx: Context<'_, S>) {
let metadata = attrs.metadata();
let mut visitor = FieldVisitor::default();
attrs.record(&mut visitor);
self.push(CapturedTrace {
kind: CapturedTraceKind::Span,
name: metadata.name().to_owned(),
target: metadata.target().to_owned(),
level: *metadata.level(),
span_name: None,
fields: visitor.fields,
});
}
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
let metadata = event.metadata();
let mut visitor = FieldVisitor::default();
event.record(&mut visitor);
let span_name = ctx.event_scope(event).and_then(|scope| {
scope
.from_root()
.last()
.map(|span| span.metadata().name().to_owned())
});
self.push(CapturedTrace {
kind: CapturedTraceKind::Event,
name: metadata.name().to_owned(),
target: metadata.target().to_owned(),
level: *metadata.level(),
span_name,
fields: visitor.fields,
});
}
}
impl CaptureLayer {
fn push(&self, record: CapturedTrace) {
if let Ok(mut records) = self.records.lock() {
records.push(record);
}
}
}
#[derive(Default)]
struct FieldVisitor {
fields: BTreeMap<String, String>,
}
impl FieldVisitor {
fn insert(&mut self, field: &Field, value: impl Into<String>) {
self.fields.insert(field.name().to_owned(), value.into());
}
}
impl Visit for FieldVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.insert(field, format!("{value:?}"));
}
fn record_str(&mut self, field: &Field, value: &str) {
self.insert(field, value);
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.insert(field, value.to_string());
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.insert(field, value.to_string());
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.insert(field, value.to_string());
}
}
}
#[cfg(test)]
mod tests {
use tracing::Level;
use super::{
TEST_CAPTURE_EVENT, TEST_CAPTURE_SPAN, TRACE_TARGET, emit_test_capture_smoke_event,
test_support::{
CapturedTraceKind, assert_trace_field, capture_traces, trace_event,
with_trace_capture_lock,
},
};
#[test]
fn scoped_capture_records_event_and_fields() -> Result<(), String> {
let (_result, traces) = capture_traces(emit_test_capture_smoke_event);
let records = traces.records()?;
let has_span = records.iter().any(|record| {
record.kind() == CapturedTraceKind::Span
&& record.name() == TEST_CAPTURE_SPAN
&& record.target() == TRACE_TARGET
&& record.level() == Level::INFO
&& record
.fields()
.get("phase")
.is_some_and(|value| value == "test_capture")
});
assert!(has_span, "captured records: {records:#?}");
let event = trace_event(&records, TEST_CAPTURE_EVENT)?;
assert_eq!(event.target(), TRACE_TARGET, "trace record: {event:#?}");
assert_eq!(event.level(), Level::INFO, "trace record: {event:#?}");
assert_eq!(
event.span_name(),
Some(TEST_CAPTURE_SPAN),
"trace record: {event:#?}"
);
assert_trace_field(event, "message", TEST_CAPTURE_EVENT);
assert_trace_field(event, "smoke_count", "1");
assert_trace_field(event, "smoke_label", "foundation");
Ok(())
}
#[test]
fn smoke_event_runs_without_subscriber() {
with_trace_capture_lock(emit_test_capture_smoke_event);
}
#[test]
fn capture_helper_detects_forbidden_text() -> Result<(), String> {
let (_result, traces) = capture_traces(|| {
tracing::info!(
target: TRACE_TARGET,
safe_field = "credential-free",
secret_like = "password=secret",
"test forbidden scan"
);
});
assert!(traces.contains_text("password=secret")?);
traces.assert_no_forbidden_text(&["server=tcp:sql.example.com"])?;
assert!(
traces
.assert_no_forbidden_text(&["password=secret"])
.is_err()
);
Ok(())
}
}