Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion rust/otap-dataflow/crates/telemetry/src/internal_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@

#[doc(hidden)]
pub mod _private {
pub use tracing::{debug, error, info, warn};
pub use tracing::callsite::{Callsite, DefaultCallsite};
pub use tracing::field::ValueSet;
pub use tracing::metadata::Kind;
pub use tracing::{Event, Level};
pub use tracing::{callsite2, debug, error, info, valueset, warn};
}

/// Macro for logging informational messages.
Expand All @@ -29,6 +33,8 @@ pub mod _private {
/// ```
// TODO: Remove `name` attribute duplication in logging macros below once `tracing::Fmt` supports displaying `name`.
// See issue: https://github.com/tokio-rs/tracing/issues/2774
///
/// TODO: Update to use valueset! for full `tracing` syntax, see raw_error!
#[macro_export]
macro_rules! otel_info {
($name:expr $(,)?) => {
Expand Down Expand Up @@ -80,6 +86,8 @@ macro_rules! otel_warn {
/// use otap_df_telemetry::otel_debug;
/// otel_debug!("processing.batch", batch_size = 100);
/// ```
///
/// TODO: Update to use valueset! for full `tracing` syntax, see raw_error!
#[macro_export]
macro_rules! otel_debug {
($name:expr $(,)?) => {
Expand All @@ -102,6 +110,8 @@ macro_rules! otel_debug {
/// use otap_df_telemetry::otel_error;
/// otel_error!("export.failure", error_code = 500);
/// ```
///
/// TODO: Update to use valueset! for full `tracing` syntax, see raw_error!
#[macro_export]
macro_rules! otel_error {
($name:expr $(,)?) => {
Expand All @@ -118,3 +128,46 @@ macro_rules! otel_error {
)
};
}

/// Log an error message directly to stderr, bypassing the tracing dispatcher.
///
/// Note! the way this is written, it supports the full `tracing` syntax for
/// debug and display formatting of field values, following tracing::valueset!
/// where ? signifies debug and % signifies display.
///
/// ```ignore
/// use otap_df_telemetry::raw_error;
/// raw_error!("logging.write.failed", error = ?err, thing = %display);
/// ```
#[macro_export]
macro_rules! raw_error {
($name:expr $(, $($fields:tt)*)?) => {{
use $crate::self_tracing::{ConsoleWriter, RawLoggingLayer};
use $crate::_private::Callsite;

static __CALLSITE: $crate::_private::DefaultCallsite = $crate::_private::callsite2! {
name: $name,
kind: $crate::_private::Kind::EVENT,
target: module_path!(),
level: $crate::_private::Level::ERROR,
fields: $($($fields)*)?
};

let meta = __CALLSITE.metadata();
let layer = RawLoggingLayer::new(ConsoleWriter::no_color());

// Use closure to extend valueset lifetime (same pattern as tracing::event!)
(|valueset: $crate::_private::ValueSet<'_>| {
let event = $crate::_private::Event::new(meta, &valueset);
layer.dispatch_event(&event);
})($crate::_private::valueset!(meta.fields(), $($($fields)*)?));
}};
}

mod tests {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mod tests should be placed under #[cfg(test)].

#[test]
fn test_raw_error() {
let err = crate::error::Error::ConfigurationError("bad config".into());
raw_error!("raw error message", error = ?err);
}
}
35 changes: 22 additions & 13 deletions rust/otap-dataflow/crates/telemetry/src/self_tracing/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ impl RawLoggingLayer {
pub fn new(writer: ConsoleWriter) -> Self {
Self { writer }
}

/// Process a tracing Event directly, bypassing the dispatcher.
pub fn dispatch_event(&self, event: &Event<'_>) {
// TODO: there are allocations implied in LogRecord::new that we
// would prefer to avoid; it will be an extensive change in the
// ProtoBuffer impl to stack-allocate this as a temporary.
let record = LogRecord::new(event);
let callsite = SavedCallsite::new(event.metadata());
self.writer.print_log_record(&record, &callsite);
}
}

/// Type alias for a cursor over a byte buffer.
Expand Down Expand Up @@ -119,13 +129,20 @@ impl ConsoleWriter {
/// Output format: `2026-01-06T10:30:45.123Z INFO target::name (file.rs:42): body [attr=value, ...]`
pub fn format_log_record(&self, record: &LogRecord, callsite: &SavedCallsite) -> String {
let mut buf = [0u8; LOG_BUFFER_SIZE];
let len = self.write_log_record(&mut buf, record, callsite);
let len = self.encode_log_record(&mut buf, record, callsite);
// The buffer contains valid UTF-8 since we only write ASCII and valid UTF-8 strings
String::from_utf8_lossy(&buf[..len]).into_owned()
}

/// Write a LogRecord to a byte buffer. Returns the number of bytes written.
pub fn write_log_record(
/// Print a LogRecord directly to stdout or stderr (based on level).
pub fn print_log_record(&self, record: &LogRecord, callsite: &SavedCallsite) {
let mut buf = [0u8; LOG_BUFFER_SIZE];
let len = self.encode_log_record(&mut buf, record, callsite);
self.write_line(callsite.level(), &buf[..len]);
}

/// Encode a LogRecord to a byte buffer. Returns the number of bytes written.
fn encode_log_record(
&self,
buf: &mut [u8],
record: &LogRecord,
Expand Down Expand Up @@ -323,15 +340,7 @@ where
// Allocates a buffer on the stack, formats the event to a LogRecord
// with partial OTLP bytes.
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
// TODO: there are allocations implied here that we would prefer
// to avoid, it will be an extensive change in the ProtoBuffer to
// stack-allocate this temporary.
let record = LogRecord::new(event);
let callsite = SavedCallsite::new(event.metadata());

let mut buf = [0u8; LOG_BUFFER_SIZE];
let len = self.writer.write_log_record(&mut buf, &record, &callsite);
self.writer.write_line(callsite.level(), &buf[..len]);
self.dispatch_event(event);
}

// Note! This tracing layer does not implement Span-related features
Expand Down Expand Up @@ -595,7 +604,7 @@ mod tests {

let mut buf = [0u8; LOG_BUFFER_SIZE];
let writer = ConsoleWriter::no_color();
let len = writer.write_log_record(&mut buf, &record, &test_callsite());
let len = writer.encode_log_record(&mut buf, &record, &test_callsite());

// Fills exactly to capacity due to overflow.
// Note! we could append a ... or some other indicator.
Expand Down
Loading