airbyte_cdk.utils.traced_exception

  1#
  2# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
  3#
  4import time
  5import traceback
  6from typing import Any, Optional
  7
  8import orjson
  9
 10from airbyte_cdk.models import (
 11    AirbyteConnectionStatus,
 12    AirbyteErrorTraceMessage,
 13    AirbyteMessage,
 14    AirbyteMessageSerializer,
 15    AirbyteTraceMessage,
 16    FailureType,
 17    Status,
 18    StreamDescriptor,
 19    TraceType,
 20)
 21from airbyte_cdk.models import Type as MessageType
 22from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
 23
 24
 25class AirbyteTracedException(Exception):
 26    """
 27    An exception that should be emitted as an AirbyteTraceMessage
 28    """
 29
 30    def __init__(
 31        self,
 32        internal_message: Optional[str] = None,
 33        message: Optional[str] = None,
 34        failure_type: FailureType = FailureType.system_error,
 35        exception: Optional[BaseException] = None,
 36        stream_descriptor: Optional[StreamDescriptor] = None,
 37    ):
 38        """
 39        :param internal_message: the internal error that caused the failure
 40        :param message: a user-friendly message that indicates the cause of the error
 41        :param failure_type: the type of error
 42        :param exception: the exception that caused the error, from which the stack trace should be retrieved
 43        :param stream_descriptor: describe the stream from which the exception comes from
 44        """
 45        self.internal_message = internal_message
 46        self.message = message
 47        self.failure_type = failure_type
 48        self._exception = exception
 49        self._stream_descriptor = stream_descriptor
 50        super().__init__(internal_message)
 51
 52    def __str__(self) -> str:
 53        """Return the user-facing message, falling back to internal_message."""
 54        if self.message is not None:
 55            return self.message
 56        if self.internal_message is not None:
 57            return self.internal_message
 58        return ""
 59
 60    def as_airbyte_message(
 61        self, stream_descriptor: Optional[StreamDescriptor] = None
 62    ) -> AirbyteMessage:
 63        """
 64        Builds an AirbyteTraceMessage from the exception
 65
 66        :param stream_descriptor is deprecated, please use the stream_description in `__init__ or `from_exception`. If many
 67          stream_descriptors are defined, the one from `as_airbyte_message` will be discarded.
 68        """
 69        now_millis = time.time_ns() // 1_000_000
 70
 71        trace_exc = self._exception or self
 72        stack_trace_str = "".join(traceback.TracebackException.from_exception(trace_exc).format())
 73
 74        trace_message = AirbyteTraceMessage(
 75            type=TraceType.ERROR,
 76            emitted_at=now_millis,
 77            error=AirbyteErrorTraceMessage(
 78                message=self.message
 79                or "Something went wrong in the connector. See the logs for more details.",
 80                internal_message=self.internal_message,
 81                failure_type=self.failure_type,
 82                stack_trace=stack_trace_str,
 83                stream_descriptor=self._stream_descriptor
 84                if self._stream_descriptor is not None
 85                else stream_descriptor,
 86            ),
 87        )
 88
 89        return AirbyteMessage(type=MessageType.TRACE, trace=trace_message)
 90
 91    def as_connection_status_message(self) -> Optional[AirbyteMessage]:
 92        """Build a failed CONNECTION_STATUS from a config error, with secrets masked.
 93
 94        The message can quote config values, as a config validation error does, so it goes
 95        through the same secret filter as logs and traces.
 96        """
 97        if self.failure_type == FailureType.config_error:
 98            return AirbyteMessage(
 99                type=MessageType.CONNECTION_STATUS,
100                connectionStatus=AirbyteConnectionStatus(
101                    status=Status.FAILED,
102                    message=filter_secrets(self.message) if self.message else self.message,
103                ),
104            )
105        return None
106
107    def emit_message(self) -> None:
108        """
109        Prints the exception as an AirbyteTraceMessage.
110        Note that this will be called automatically on uncaught exceptions when using the airbyte_cdk entrypoint.
111        """
112        message = orjson.dumps(AirbyteMessageSerializer.dump(self.as_airbyte_message())).decode()
113        filtered_message = filter_secrets(message)
114        print(filtered_message)
115
116    @classmethod
117    def from_exception(
118        cls,
119        exc: BaseException,
120        stream_descriptor: Optional[StreamDescriptor] = None,
121        *args: Any,
122        **kwargs: Any,
123    ) -> "AirbyteTracedException":
124        """
125        Helper to create an AirbyteTracedException from an existing exception
126        :param exc: the exception that caused the error
127        :param stream_descriptor: describe the stream from which the exception comes from
128        """
129        if isinstance(exc, AirbyteTracedException):
130            internal_message = exc.internal_message
131            # Preserve the original user-facing message if the caller didn't provide one
132            if "message" not in kwargs:
133                kwargs["message"] = exc.message
134        else:
135            internal_message = str(exc)
136        return cls(
137            internal_message=internal_message,
138            exception=exc,
139            stream_descriptor=stream_descriptor,
140            *args,
141            **kwargs,
142        )  # type: ignore  # ignoring because of args and kwargs
143
144    def as_sanitized_airbyte_message(
145        self, stream_descriptor: Optional[StreamDescriptor] = None
146    ) -> AirbyteMessage:
147        """
148        Builds an AirbyteTraceMessage from the exception and sanitizes any secrets from the message body
149
150        :param stream_descriptor is deprecated, please use the stream_description in `__init__ or `from_exception`. If many
151          stream_descriptors are defined, the one from `as_sanitized_airbyte_message` will be discarded.
152        """
153        error_message = self.as_airbyte_message(stream_descriptor=stream_descriptor)
154        if error_message.trace.error.message:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
155            error_message.trace.error.message = filter_secrets(  # type: ignore[union-attr]
156                error_message.trace.error.message,  # type: ignore[union-attr]
157            )
158        if error_message.trace.error.internal_message:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
159            error_message.trace.error.internal_message = filter_secrets(  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
160                error_message.trace.error.internal_message  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
161            )
162        if error_message.trace.error.stack_trace:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
163            error_message.trace.error.stack_trace = filter_secrets(  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
164                error_message.trace.error.stack_trace  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
165            )
166        return error_message
class AirbyteTracedException(builtins.Exception):
 26class AirbyteTracedException(Exception):
 27    """
 28    An exception that should be emitted as an AirbyteTraceMessage
 29    """
 30
 31    def __init__(
 32        self,
 33        internal_message: Optional[str] = None,
 34        message: Optional[str] = None,
 35        failure_type: FailureType = FailureType.system_error,
 36        exception: Optional[BaseException] = None,
 37        stream_descriptor: Optional[StreamDescriptor] = None,
 38    ):
 39        """
 40        :param internal_message: the internal error that caused the failure
 41        :param message: a user-friendly message that indicates the cause of the error
 42        :param failure_type: the type of error
 43        :param exception: the exception that caused the error, from which the stack trace should be retrieved
 44        :param stream_descriptor: describe the stream from which the exception comes from
 45        """
 46        self.internal_message = internal_message
 47        self.message = message
 48        self.failure_type = failure_type
 49        self._exception = exception
 50        self._stream_descriptor = stream_descriptor
 51        super().__init__(internal_message)
 52
 53    def __str__(self) -> str:
 54        """Return the user-facing message, falling back to internal_message."""
 55        if self.message is not None:
 56            return self.message
 57        if self.internal_message is not None:
 58            return self.internal_message
 59        return ""
 60
 61    def as_airbyte_message(
 62        self, stream_descriptor: Optional[StreamDescriptor] = None
 63    ) -> AirbyteMessage:
 64        """
 65        Builds an AirbyteTraceMessage from the exception
 66
 67        :param stream_descriptor is deprecated, please use the stream_description in `__init__ or `from_exception`. If many
 68          stream_descriptors are defined, the one from `as_airbyte_message` will be discarded.
 69        """
 70        now_millis = time.time_ns() // 1_000_000
 71
 72        trace_exc = self._exception or self
 73        stack_trace_str = "".join(traceback.TracebackException.from_exception(trace_exc).format())
 74
 75        trace_message = AirbyteTraceMessage(
 76            type=TraceType.ERROR,
 77            emitted_at=now_millis,
 78            error=AirbyteErrorTraceMessage(
 79                message=self.message
 80                or "Something went wrong in the connector. See the logs for more details.",
 81                internal_message=self.internal_message,
 82                failure_type=self.failure_type,
 83                stack_trace=stack_trace_str,
 84                stream_descriptor=self._stream_descriptor
 85                if self._stream_descriptor is not None
 86                else stream_descriptor,
 87            ),
 88        )
 89
 90        return AirbyteMessage(type=MessageType.TRACE, trace=trace_message)
 91
 92    def as_connection_status_message(self) -> Optional[AirbyteMessage]:
 93        """Build a failed CONNECTION_STATUS from a config error, with secrets masked.
 94
 95        The message can quote config values, as a config validation error does, so it goes
 96        through the same secret filter as logs and traces.
 97        """
 98        if self.failure_type == FailureType.config_error:
 99            return AirbyteMessage(
100                type=MessageType.CONNECTION_STATUS,
101                connectionStatus=AirbyteConnectionStatus(
102                    status=Status.FAILED,
103                    message=filter_secrets(self.message) if self.message else self.message,
104                ),
105            )
106        return None
107
108    def emit_message(self) -> None:
109        """
110        Prints the exception as an AirbyteTraceMessage.
111        Note that this will be called automatically on uncaught exceptions when using the airbyte_cdk entrypoint.
112        """
113        message = orjson.dumps(AirbyteMessageSerializer.dump(self.as_airbyte_message())).decode()
114        filtered_message = filter_secrets(message)
115        print(filtered_message)
116
117    @classmethod
118    def from_exception(
119        cls,
120        exc: BaseException,
121        stream_descriptor: Optional[StreamDescriptor] = None,
122        *args: Any,
123        **kwargs: Any,
124    ) -> "AirbyteTracedException":
125        """
126        Helper to create an AirbyteTracedException from an existing exception
127        :param exc: the exception that caused the error
128        :param stream_descriptor: describe the stream from which the exception comes from
129        """
130        if isinstance(exc, AirbyteTracedException):
131            internal_message = exc.internal_message
132            # Preserve the original user-facing message if the caller didn't provide one
133            if "message" not in kwargs:
134                kwargs["message"] = exc.message
135        else:
136            internal_message = str(exc)
137        return cls(
138            internal_message=internal_message,
139            exception=exc,
140            stream_descriptor=stream_descriptor,
141            *args,
142            **kwargs,
143        )  # type: ignore  # ignoring because of args and kwargs
144
145    def as_sanitized_airbyte_message(
146        self, stream_descriptor: Optional[StreamDescriptor] = None
147    ) -> AirbyteMessage:
148        """
149        Builds an AirbyteTraceMessage from the exception and sanitizes any secrets from the message body
150
151        :param stream_descriptor is deprecated, please use the stream_description in `__init__ or `from_exception`. If many
152          stream_descriptors are defined, the one from `as_sanitized_airbyte_message` will be discarded.
153        """
154        error_message = self.as_airbyte_message(stream_descriptor=stream_descriptor)
155        if error_message.trace.error.message:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
156            error_message.trace.error.message = filter_secrets(  # type: ignore[union-attr]
157                error_message.trace.error.message,  # type: ignore[union-attr]
158            )
159        if error_message.trace.error.internal_message:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
160            error_message.trace.error.internal_message = filter_secrets(  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
161                error_message.trace.error.internal_message  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
162            )
163        if error_message.trace.error.stack_trace:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
164            error_message.trace.error.stack_trace = filter_secrets(  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
165                error_message.trace.error.stack_trace  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
166            )
167        return error_message

An exception that should be emitted as an AirbyteTraceMessage

AirbyteTracedException( internal_message: Optional[str] = None, message: Optional[str] = None, failure_type: airbyte_protocol_dataclasses.models.airbyte_protocol.FailureType = <FailureType.system_error: 'system_error'>, exception: Optional[BaseException] = None, stream_descriptor: Optional[airbyte_protocol_dataclasses.models.airbyte_protocol.StreamDescriptor] = None)
31    def __init__(
32        self,
33        internal_message: Optional[str] = None,
34        message: Optional[str] = None,
35        failure_type: FailureType = FailureType.system_error,
36        exception: Optional[BaseException] = None,
37        stream_descriptor: Optional[StreamDescriptor] = None,
38    ):
39        """
40        :param internal_message: the internal error that caused the failure
41        :param message: a user-friendly message that indicates the cause of the error
42        :param failure_type: the type of error
43        :param exception: the exception that caused the error, from which the stack trace should be retrieved
44        :param stream_descriptor: describe the stream from which the exception comes from
45        """
46        self.internal_message = internal_message
47        self.message = message
48        self.failure_type = failure_type
49        self._exception = exception
50        self._stream_descriptor = stream_descriptor
51        super().__init__(internal_message)
Parameters
  • internal_message: the internal error that caused the failure
  • message: a user-friendly message that indicates the cause of the error
  • failure_type: the type of error
  • exception: the exception that caused the error, from which the stack trace should be retrieved
  • stream_descriptor: describe the stream from which the exception comes from
internal_message
message
failure_type
def as_airbyte_message( self, stream_descriptor: Optional[airbyte_protocol_dataclasses.models.airbyte_protocol.StreamDescriptor] = None) -> airbyte_cdk.AirbyteMessage:
61    def as_airbyte_message(
62        self, stream_descriptor: Optional[StreamDescriptor] = None
63    ) -> AirbyteMessage:
64        """
65        Builds an AirbyteTraceMessage from the exception
66
67        :param stream_descriptor is deprecated, please use the stream_description in `__init__ or `from_exception`. If many
68          stream_descriptors are defined, the one from `as_airbyte_message` will be discarded.
69        """
70        now_millis = time.time_ns() // 1_000_000
71
72        trace_exc = self._exception or self
73        stack_trace_str = "".join(traceback.TracebackException.from_exception(trace_exc).format())
74
75        trace_message = AirbyteTraceMessage(
76            type=TraceType.ERROR,
77            emitted_at=now_millis,
78            error=AirbyteErrorTraceMessage(
79                message=self.message
80                or "Something went wrong in the connector. See the logs for more details.",
81                internal_message=self.internal_message,
82                failure_type=self.failure_type,
83                stack_trace=stack_trace_str,
84                stream_descriptor=self._stream_descriptor
85                if self._stream_descriptor is not None
86                else stream_descriptor,
87            ),
88        )
89
90        return AirbyteMessage(type=MessageType.TRACE, trace=trace_message)

Builds an AirbyteTraceMessage from the exception

:param stream_descriptor is deprecated, please use the stream_description in __init__ orfrom_exception. If many stream_descriptors are defined, the one fromas_airbyte_message` will be discarded.

def as_connection_status_message(self) -> Optional[airbyte_cdk.AirbyteMessage]:
 92    def as_connection_status_message(self) -> Optional[AirbyteMessage]:
 93        """Build a failed CONNECTION_STATUS from a config error, with secrets masked.
 94
 95        The message can quote config values, as a config validation error does, so it goes
 96        through the same secret filter as logs and traces.
 97        """
 98        if self.failure_type == FailureType.config_error:
 99            return AirbyteMessage(
100                type=MessageType.CONNECTION_STATUS,
101                connectionStatus=AirbyteConnectionStatus(
102                    status=Status.FAILED,
103                    message=filter_secrets(self.message) if self.message else self.message,
104                ),
105            )
106        return None

Build a failed CONNECTION_STATUS from a config error, with secrets masked.

The message can quote config values, as a config validation error does, so it goes through the same secret filter as logs and traces.

def emit_message(self) -> None:
108    def emit_message(self) -> None:
109        """
110        Prints the exception as an AirbyteTraceMessage.
111        Note that this will be called automatically on uncaught exceptions when using the airbyte_cdk entrypoint.
112        """
113        message = orjson.dumps(AirbyteMessageSerializer.dump(self.as_airbyte_message())).decode()
114        filtered_message = filter_secrets(message)
115        print(filtered_message)

Prints the exception as an AirbyteTraceMessage. Note that this will be called automatically on uncaught exceptions when using the airbyte_cdk entrypoint.

@classmethod
def from_exception( cls, exc: BaseException, stream_descriptor: Optional[airbyte_protocol_dataclasses.models.airbyte_protocol.StreamDescriptor] = None, *args: Any, **kwargs: Any) -> AirbyteTracedException:
117    @classmethod
118    def from_exception(
119        cls,
120        exc: BaseException,
121        stream_descriptor: Optional[StreamDescriptor] = None,
122        *args: Any,
123        **kwargs: Any,
124    ) -> "AirbyteTracedException":
125        """
126        Helper to create an AirbyteTracedException from an existing exception
127        :param exc: the exception that caused the error
128        :param stream_descriptor: describe the stream from which the exception comes from
129        """
130        if isinstance(exc, AirbyteTracedException):
131            internal_message = exc.internal_message
132            # Preserve the original user-facing message if the caller didn't provide one
133            if "message" not in kwargs:
134                kwargs["message"] = exc.message
135        else:
136            internal_message = str(exc)
137        return cls(
138            internal_message=internal_message,
139            exception=exc,
140            stream_descriptor=stream_descriptor,
141            *args,
142            **kwargs,
143        )  # type: ignore  # ignoring because of args and kwargs

Helper to create an AirbyteTracedException from an existing exception

Parameters
  • exc: the exception that caused the error
  • stream_descriptor: describe the stream from which the exception comes from
def as_sanitized_airbyte_message( self, stream_descriptor: Optional[airbyte_protocol_dataclasses.models.airbyte_protocol.StreamDescriptor] = None) -> airbyte_cdk.AirbyteMessage:
145    def as_sanitized_airbyte_message(
146        self, stream_descriptor: Optional[StreamDescriptor] = None
147    ) -> AirbyteMessage:
148        """
149        Builds an AirbyteTraceMessage from the exception and sanitizes any secrets from the message body
150
151        :param stream_descriptor is deprecated, please use the stream_description in `__init__ or `from_exception`. If many
152          stream_descriptors are defined, the one from `as_sanitized_airbyte_message` will be discarded.
153        """
154        error_message = self.as_airbyte_message(stream_descriptor=stream_descriptor)
155        if error_message.trace.error.message:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
156            error_message.trace.error.message = filter_secrets(  # type: ignore[union-attr]
157                error_message.trace.error.message,  # type: ignore[union-attr]
158            )
159        if error_message.trace.error.internal_message:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
160            error_message.trace.error.internal_message = filter_secrets(  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
161                error_message.trace.error.internal_message  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
162            )
163        if error_message.trace.error.stack_trace:  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
164            error_message.trace.error.stack_trace = filter_secrets(  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
165                error_message.trace.error.stack_trace  # type: ignore[union-attr] # AirbyteMessage with MessageType.TRACE has AirbyteTraceMessage
166            )
167        return error_message

Builds an AirbyteTraceMessage from the exception and sanitizes any secrets from the message body

:param stream_descriptor is deprecated, please use the stream_description in __init__ orfrom_exception. If many stream_descriptors are defined, the one fromas_sanitized_airbyte_message` will be discarded.