mz_compute/render/
errors.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Helpers for handling errors encountered by operators.
11
12use mz_repr::Row;
13
14use crate::render::context::ShutdownProbe;
15
16/// Used to make possibly-validating code generic: think of this as a kind of `MaybeResult`,
17/// specialized for use in compute.  Validation code will only run when the error constructor is
18/// Some.
19pub(super) trait MaybeValidatingRow<T, E> {
20    fn ok(t: T) -> Self;
21    fn into_error() -> Option<fn(E) -> Self>;
22}
23
24impl<E> MaybeValidatingRow<Row, E> for Row {
25    fn ok(t: Row) -> Self {
26        t
27    }
28
29    fn into_error() -> Option<fn(E) -> Self> {
30        None
31    }
32}
33
34impl<E> MaybeValidatingRow<(), E> for () {
35    fn ok(t: ()) -> Self {
36        t
37    }
38
39    fn into_error() -> Option<fn(E) -> Self> {
40        None
41    }
42}
43
44impl<E, R> MaybeValidatingRow<Vec<R>, E> for Vec<R> {
45    fn ok(t: Vec<R>) -> Self {
46        t
47    }
48
49    fn into_error() -> Option<fn(E) -> Self> {
50        None
51    }
52}
53
54impl<T, E> MaybeValidatingRow<T, E> for Result<T, E> {
55    fn ok(row: T) -> Self {
56        Ok(row)
57    }
58
59    fn into_error() -> Option<fn(E) -> Self> {
60        Some(Err)
61    }
62}
63
64/// Error logger to be used by rendering code.
65///
66/// Holds onto a `[ShutdownProbe`] to ensure that no false-positive errors are logged while the
67/// dataflow is in the process of shutting down.
68#[derive(Clone)]
69pub(super) struct ErrorLogger {
70    shutdown_probe: ShutdownProbe,
71    dataflow_name: String,
72}
73
74impl ErrorLogger {
75    pub fn new(shutdown_probe: ShutdownProbe, dataflow_name: String) -> Self {
76        Self {
77            shutdown_probe,
78            dataflow_name,
79        }
80    }
81
82    /// Log the given error, unless the dataflow is shutting down.
83    ///
84    /// The logging format is optimized for surfacing errors with Sentry:
85    ///  * `error` is logged at ERROR level and will appear as the error title in Sentry.
86    ///    We require it to be a static string, to ensure that Sentry always merges instances of
87    ///    the same error together.
88    ///  * `details` is logged at WARN level and will appear in the breadcrumbs.
89    ///    Put relevant dynamic information here.
90    ///
91    /// The message that's logged at WARN level has the format
92    ///   "[customer-data] {message} ({details})"
93    /// We include the [customer-data] tag out of the expectation that `details` will always
94    /// contain some sensitive customer data. We include the `message` to make it possible to match
95    /// the breadcrumbs to their associated error in Sentry.
96    ///
97    // TODO(database-issues#5362): Rethink or justify our error logging strategy.
98    pub fn log(&self, message: &'static str, details: &str) {
99        // It's important that we silence errors as soon as the local shutdown token has been
100        // dropped. Dataflow operators may start discarding results, thereby producing incorrect
101        // output, as soon as they observe that all workers have dropped their token. However, not
102        // all workers are guaranteed to make this observation at the same time. So it's possible
103        // that some workers have already started discarding results while other workers still see
104        // `shutdown_probe.in_shutdown() == false`.
105        if !self.shutdown_probe.in_local_shutdown() {
106            self.log_always(message, details);
107        }
108    }
109
110    /// Like [`Self::log`], but also logs errors when the dataflow is shutting down.
111    ///
112    /// Use this method to notify about errors that cannot be caused by dataflow shutdown.
113    pub fn log_always(&self, message: &'static str, details: &str) {
114        tracing::warn!(
115            dataflow = self.dataflow_name,
116            "[customer-data] {message} ({details})"
117        );
118        tracing::error!(message);
119    }
120
121    /// Like [`Self::log_always`], but panics in debug mode.
122    ///
123    /// Use this method to notify about errors that are certainly caused by bugs in Materialize.
124    pub fn soft_panic_or_log(&self, message: &'static str, details: &str) {
125        tracing::warn!(
126            dataflow = self.dataflow_name,
127            "[customer-data] {message} ({details})"
128        );
129        mz_ore::soft_panic_or_log!("{}", message);
130    }
131}