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}