Skip to content

Commit cebbc3f

Browse files
fix(topology): preserve component failure details (#26549)
Co-authored-by: Thomas <thomas.schneider@datadoghq.com>
1 parent e473eef commit cebbc3f

70 files changed

Lines changed: 749 additions & 230 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Vector now reports the underlying error when a source or sink task fails at runtime; previously, it only said `the task completed with an error`. That includes failures to bind a listening socket (for example `Address already in use (os error 48)`), broker subscribe and consume errors, checkpoint file errors, sink runtime errors, and sources that stop because their downstream channel closed. Vector now includes the panic payload in its logs.
2+
3+
authors: gwenaskell

‎lib/vector-core/src/sink.rs‎

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{fmt, iter::IntoIterator, pin::Pin};
1+
use std::{fmt, iter::IntoIterator, ops::Deref, pin::Pin};
22

33
use futures::{
44
Sink, SinkExt, Stream, StreamExt, stream,
@@ -7,20 +7,71 @@ use futures::{
77

88
use crate::event::{Event, EventArray, EventContainer, into_event_stream};
99

10+
/// Error returned by a running sink.
11+
#[repr(transparent)]
12+
#[derive(Debug)]
13+
pub struct SinkError(vector_common::Error);
14+
15+
impl SinkError {
16+
pub fn new(error: impl Into<vector_common::Error>) -> Self {
17+
Self(error.into())
18+
}
19+
20+
pub fn into_inner(self) -> vector_common::Error {
21+
self.0
22+
}
23+
}
24+
25+
impl Default for SinkError {
26+
fn default() -> Self {
27+
Self("the sink task completed with an error".into())
28+
}
29+
}
30+
31+
impl From<vector_common::Error> for SinkError {
32+
fn from(error: vector_common::Error) -> Self {
33+
Self(error)
34+
}
35+
}
36+
37+
impl fmt::Display for SinkError {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
self.0.fmt(f)
40+
}
41+
}
42+
43+
impl std::error::Error for SinkError {
44+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
45+
Some(self.0.as_ref())
46+
}
47+
}
48+
49+
impl Deref for SinkError {
50+
type Target = vector_common::Error;
51+
52+
fn deref(&self) -> &Self::Target {
53+
&self.0
54+
}
55+
}
56+
1057
pub enum VectorSink {
1158
Sink(Box<dyn Sink<EventArray, Error = ()> + Send + Unpin>),
12-
Stream(Box<dyn StreamSink<EventArray> + Send>),
59+
Stream(Box<dyn StreamSink<EventArray, SinkError> + Send>),
1360
}
1461

1562
impl VectorSink {
1663
/// Run the `VectorSink`
1764
///
1865
/// # Errors
1966
///
20-
/// It is unclear under what conditions this function will error.
21-
pub async fn run(self, input: impl Stream<Item = EventArray> + Send) -> Result<(), ()> {
67+
/// Returns an error if the sink reports a failure.
68+
pub async fn run(self, input: impl Stream<Item = EventArray> + Send) -> Result<(), SinkError> {
2269
match self {
23-
Self::Sink(sink) => input.map(Ok).forward(sink).await,
70+
Self::Sink(sink) => input
71+
.map(Ok)
72+
.forward(sink)
73+
.await
74+
.map_err(|()| SinkError::default()),
2475
Self::Stream(s) => s.run(Box::pin(input)).await,
2576
}
2677
}
@@ -30,7 +81,7 @@ impl VectorSink {
3081
/// # Errors
3182
///
3283
/// See `VectorSink::run` for errors.
33-
pub async fn run_events<I>(self, input: I) -> Result<(), ()>
84+
pub async fn run_events<I>(self, input: I) -> Result<(), SinkError>
3485
where
3586
I: IntoIterator<Item = Event> + Send,
3687
I::IntoIter: Send,
@@ -55,7 +106,7 @@ impl VectorSink {
55106
/// # Panics
56107
///
57108
/// This function will panic if the self instance is not `VectorSink::Stream`.
58-
pub fn into_stream(self) -> Box<dyn StreamSink<EventArray> + Send> {
109+
pub fn into_stream(self) -> Box<dyn StreamSink<EventArray, SinkError> + Send> {
59110
match self {
60111
Self::Stream(stream) => stream,
61112
_ => panic!("Failed type coercion, {self:?} is not a Stream"),
@@ -75,6 +126,20 @@ impl VectorSink {
75126

76127
/// Converts an event stream into a `VectorSink`
77128
pub fn from_event_streamsink(sink: impl StreamSink<Event> + Send + 'static) -> Self {
129+
Self::from_streamsink(EventStream {
130+
sink: Box::new(sink),
131+
})
132+
}
133+
134+
/// Converts a stream sink with opaque runtime errors into a `VectorSink`.
135+
pub fn from_streamsink(sink: impl StreamSink<EventArray> + Send + 'static) -> Self {
136+
Self::Stream(Box::new(OpaqueStream(Box::new(sink))))
137+
}
138+
139+
/// Converts a sink that retains its runtime errors into a `VectorSink`.
140+
pub fn from_detailed_event_streamsink(
141+
sink: impl StreamSink<Event, SinkError> + Send + 'static,
142+
) -> Self {
78143
let sink = Box::new(sink);
79144
VectorSink::Stream(Box::new(EventStream { sink }))
80145
}
@@ -89,8 +154,23 @@ impl fmt::Debug for VectorSink {
89154
// === StreamSink ===
90155

91156
#[async_trait::async_trait]
92-
pub trait StreamSink<T> {
93-
async fn run(self: Box<Self>, input: stream::BoxStream<'_, T>) -> Result<(), ()>;
157+
pub trait StreamSink<T, E = ()> {
158+
async fn run(self: Box<Self>, input: stream::BoxStream<'_, T>) -> Result<(), E>;
159+
}
160+
161+
struct OpaqueStream<T>(Box<T>);
162+
163+
#[async_trait::async_trait]
164+
impl<T> StreamSink<EventArray, SinkError> for OpaqueStream<T>
165+
where
166+
T: StreamSink<EventArray> + Send,
167+
{
168+
async fn run(
169+
self: Box<Self>,
170+
input: stream::BoxStream<'_, EventArray>,
171+
) -> Result<(), SinkError> {
172+
self.0.run(input).await.map_err(|()| SinkError::default())
173+
}
94174
}
95175

96176
/// Wrapper for sinks implementing `Sink<Event>` to implement
@@ -175,8 +255,12 @@ struct EventStream<T> {
175255
}
176256

177257
#[async_trait::async_trait]
178-
impl<T: StreamSink<Event> + Send> StreamSink<EventArray> for EventStream<T> {
179-
async fn run(self: Box<Self>, input: stream::BoxStream<'_, EventArray>) -> Result<(), ()> {
258+
impl<T, E> StreamSink<EventArray, E> for EventStream<T>
259+
where
260+
T: StreamSink<Event, E> + Send,
261+
E: Send,
262+
{
263+
async fn run(self: Box<Self>, input: stream::BoxStream<'_, EventArray>) -> Result<(), E> {
180264
let input = Box::pin(input.flat_map(into_event_stream));
181265
self.sink.run(input).await
182266
}

‎lib/vector-core/src/source.rs‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1-
use futures::future::BoxFuture;
1+
use std::future::Future;
22

3-
pub type Source = BoxFuture<'static, Result<(), ()>>;
3+
use futures::{TryFutureExt, future::BoxFuture};
4+
5+
/// Error returned by a running source.
6+
pub type SourceError = vector_common::Error;
7+
8+
/// Future that runs a source until shutdown or failure.
9+
pub type Source = BoxFuture<'static, Result<(), SourceError>>;
10+
11+
fn default_source_error() -> SourceError {
12+
"the source task completed with an error".into()
13+
}
14+
15+
/// Adapt a source that only reports whether it failed.
16+
pub fn opaque_source<F>(future: F) -> Source
17+
where
18+
F: Future<Output = Result<(), ()>> + Send + 'static,
19+
{
20+
Box::pin(future.map_err(|()| default_source_error()))
21+
}
22+
23+
/// Adapt a source that returns an error with a cause.
24+
pub fn source_with_error<F, E>(future: F) -> Source
25+
where
26+
F: Future<Output = Result<(), E>> + Send + 'static,
27+
E: Into<SourceError>,
28+
{
29+
Box::pin(future.err_into())
30+
}

‎lib/vector-core/src/source_sender/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ mod tests;
1414

1515
pub use builder::Builder;
1616
pub use errors::SendError;
17+
/// Result of sending a source's events downstream.
18+
pub type SendResult = Result<(), SendError>;
1719
use output::{Output, OutputMetrics};
1820
pub use sender::{SourceSender, SourceSenderItem};
1921

‎src/config/unit_test/unit_test_components.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl SourceConfig for UnitTestSourceConfig {
4141
Ok(Box::pin(async move {
4242
let mut out = cx.out;
4343
let _shutdown = cx.shutdown;
44-
out.send_batch(events).await.map_err(|_| ())?;
44+
out.send_batch(events).await?;
4545
Ok(())
4646
}))
4747
}
@@ -98,7 +98,7 @@ impl SourceConfig for UnitTestStreamSourceConfig {
9898
Ok(Box::pin(async move {
9999
let mut out = cx.out;
100100
let _shutdown = cx.shutdown;
101-
out.send_event_stream(stream).await.map_err(|_| ())?;
101+
out.send_event_stream(stream).await?;
102102
Ok(())
103103
}))
104104
}

‎src/enrichment_tables/memory/config.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ impl SourceConfig for MemoryConfig {
375375

376376
let log_namespace = cx.log_namespace(self.log_namespace);
377377

378-
Ok(Box::pin(
378+
Ok(crate::sources::opaque_source(
379379
memory.as_source(cx.shutdown, cx.out, log_namespace).run(),
380380
))
381381
}

‎src/internal_events/amqp.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ pub mod source {
2727
}
2828

2929
#[derive(Debug, NamedInternalEvent)]
30-
pub struct AmqpEventError {
31-
pub error: lapin::Error,
30+
pub struct AmqpEventError<'a> {
31+
pub error: &'a lapin::Error,
3232
}
3333

34-
impl InternalEvent for AmqpEventError {
34+
impl InternalEvent for AmqpEventError<'_> {
3535
fn emit(self) {
3636
error!(message = "Failed to read message.",
3737
error = ?self.error,

‎src/internal_events/journald.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,12 @@ impl InternalEvent for JournaldCheckpointSetError {
9898
}
9999

100100
#[derive(Debug, NamedInternalEvent)]
101-
pub struct JournaldCheckpointFileOpenError {
102-
pub error: std::io::Error,
101+
pub struct JournaldCheckpointFileOpenError<'a> {
102+
pub error: &'a std::io::Error,
103103
pub path: String,
104104
}
105105

106-
impl InternalEvent for JournaldCheckpointFileOpenError {
106+
impl InternalEvent for JournaldCheckpointFileOpenError<'_> {
107107
fn emit(self) {
108108
error!(
109109
message = "Unable to open checkpoint file.",

‎src/sinks/blackhole/config.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ impl ValidatedSink for BlackholeConfig {
8181
let sink = BlackholeSink::new(self.clone());
8282
let healthcheck = future::ok(()).boxed();
8383

84-
Ok((VectorSink::Stream(Box::new(sink)), healthcheck))
84+
Ok((VectorSink::from_streamsink(sink), healthcheck))
8585
}
8686
}
8787

‎src/sinks/blackhole/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ mod tests {
3030
acknowledgements: AcknowledgementsConfig::default(),
3131
};
3232
let sink = BlackholeSink::new(config);
33-
let sink = VectorSink::Stream(Box::new(sink));
33+
let sink = VectorSink::from_streamsink(sink);
3434

3535
let (_input_lines, events) = random_events_with_stream(100, 10, None);
3636
run_and_assert_sink_compliance(sink, events, &SINK_TAGS).await;

0 commit comments

Comments
 (0)