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 std::hash::Hash;
13
14use differential_dataflow::ExchangeData;
15use differential_dataflow::containers::Columnation;
16use mz_repr::Row;
17
18use crate::render::context::ShutdownToken;
19
20/// Used to make possibly-validating code generic: think of this as a kind of `MaybeResult`,
21/// specialized for use in compute.  Validation code will only run when the error constructor is
22/// Some.
23pub(super) trait MaybeValidatingRow<T, E>: ExchangeData + Columnation + Hash {
24    fn ok(t: T) -> Self;
25    fn into_error() -> Option<fn(E) -> Self>;
26}
27
28impl<E> MaybeValidatingRow<Row, E> for Row {
29    fn ok(t: Row) -> Self {
30        t
31    }
32
33    fn into_error() -> Option<fn(E) -> Self> {
34        None
35    }
36}
37
38impl<E> MaybeValidatingRow<(), E> for () {
39    fn ok(t: ()) -> Self {
40        t
41    }
42
43    fn into_error() -> Option<fn(E) -> Self> {
44        None
45    }
46}
47
48impl<E, R> MaybeValidatingRow<Vec<R>, E> for Vec<R>
49where
50    R: ExchangeData + Columnation + Hash,
51{
52    fn ok(t: Vec<R>) -> Self {
53        t
54    }
55
56    fn into_error() -> Option<fn(E) -> Self> {
57        None
58    }
59}
60
61impl<T, E> MaybeValidatingRow<T, E> for Result<T, E>
62where
63    T: ExchangeData + Columnation + Hash,
64    E: ExchangeData + Columnation + Hash,
65{
66    fn ok(row: T) -> Self {
67        Ok(row)
68    }
69
70    fn into_error() -> Option<fn(E) -> Self> {
71        Some(Err)
72    }
73}
74
75/// Error logger to be used by rendering code.
76///
77/// Holds onto a token to ensure that no false-positive errors are logged while the dataflow is in
78/// the process of shutting down.
79#[derive(Clone)]
80pub(super) struct ErrorLogger {
81    token: ShutdownToken,
82    dataflow_name: String,
83}
84
85impl ErrorLogger {
86    pub fn new(token: ShutdownToken, dataflow_name: String) -> Self {
87        Self {
88            token,
89            dataflow_name,
90        }
91    }
92
93    /// Log the given error, unless the dataflow is shutting down.
94    ///
95    /// The logging format is optimized for surfacing errors with Sentry:
96    ///  * `error` is logged at ERROR level and will appear as the error title in Sentry.
97    ///    We require it to be a static string, to ensure that Sentry always merges instances of
98    ///    the same error together.
99    ///  * `details` is logged at WARN level and will appear in the breadcrumbs.
100    ///    Put relevant dynamic information here.
101    ///
102    /// The message that's logged at WARN level has the format
103    ///   "[customer-data] {message} ({details})"
104    /// We include the [customer-data] tag out of the expectation that `details` will always
105    /// contain some sensitive customer data. We include the `message` to make it possible to match
106    /// the breadcrumbs to their associated error in Sentry.
107    ///
108    // TODO(database-issues#5362): Rethink or justify our error logging strategy.
109    pub fn log(&self, message: &'static str, details: &str) {
110        if !self.token.in_shutdown() {
111            self.log_always(message, details);
112        }
113    }
114
115    /// Like [`Self::log`], but also logs errors when the dataflow is shutting down.
116    ///
117    /// Use this method to notify about errors that cannot be caused by dataflow shutdown.
118    pub fn log_always(&self, message: &'static str, details: &str) {
119        tracing::warn!(
120            dataflow = self.dataflow_name,
121            "[customer-data] {message} ({details})"
122        );
123        tracing::error!(message);
124    }
125
126    /// Like [`Self::log_always`], but panics in debug mode.
127    ///
128    /// Use this method to notify about errors that are certainly caused by bugs in Materialize.
129    pub fn soft_panic_or_log(&self, message: &'static str, details: &str) {
130        tracing::warn!(
131            dataflow = self.dataflow_name,
132            "[customer-data] {message} ({details})"
133        );
134        mz_ore::soft_panic_or_log!("{}", message);
135    }
136}