Skip to content

Commit 4fa2a26

Browse files
QMalcolmemmyoop
authored andcommitted
CT-2264, CT-2259, CT-1783: Improved event serialization failure handling (#7249)
* Add tests for logging jinja2.Undefined objects [CT-2259](#7108) identifies an issue wherein dbt-core 1.0-1.3 raise errors if a jinja2.Undefined object is attempted to be logged. This generally happened in the form of `{{ log(undefined_variable, info=True) }}`. This commit adding this test exists for two reasons 1. Ensure we don't have a regression in this going forward 2. Exist as a commit to be used for backport fixes for dbt-core 1.0-1.3 * Add tests for checking `DBT_ENV_SECRET_`s don't break logging [CT-1783](#6568) describes a bug in dbt-core 1.0-1.3 wherein when a `DBT_ENV_SECRET_` all `{{ log("logging stuff", info=True) }}` invocations break. This commit adds a test for this for two reasons: 1. Ensure we don't regress to this behavior going forward 2. Act as a base commit for making the backport fixes to dbt-core 1.0-1.3 * Add tests ensuring failed event serialization is handled correctly [CT-2264](#7113) states that failed serialization should result in an exception handling path which will fire another event instead of raising an exception. This is hard to test perfectly because the exception handling path for serialization depending on whether pytest is present. If pytest isn't present, a new event documentation the failed serialization is fired. If pytest is present, the failed serialization gets raised as an exception. Thus this added test ensures that the expected exception is raised and assumes that the correct event will be fired normally. * Log warning when event serialization fails in `msg_to_dict` This commit updates the `msg_to_dict` exception handling path to fire a warning level event instead of raising an exception. Truthfully, we're not sure if this exception handling path is even possible to hit. That's because we recently switched from betterproto to google's protobuf. However, exception path is the subject of [CT-2264](#7113). Though we don't think it's actually possible to hit it anymore, we still want to handle the case if it is. * Update serialization failure note to be a warn level event in `BaseEvent` [CT-2264](#7113) wants logging messages about event serialization failure to be `WARNING` level events. This does that. * Add changie info for changes * Add test to check exception handling of `msg_to_dict`
1 parent fa826e0 commit 4fa2a26

File tree

6 files changed

+70
-5
lines changed

6 files changed

+70
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: Fixes
2+
body: Improved failed event serialization handling and associated tests
3+
time: 2023-03-31T09:54:28.701963-07:00
4+
custom:
5+
Author: QMalcolm
6+
Issue: 7113 7108 6568

core/dbt/events/base_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def __init__(self, *args, **kwargs):
7979
if "pytest" in sys.modules:
8080
raise Exception(error_msg)
8181
else:
82-
fire_event(Note(msg=error_msg))
82+
fire_event(Note(msg=error_msg), level=EventLevel.WARN)
8383
self.pb_msg = msg_cls()
8484

8585
def __setattr__(self, key, value):

core/dbt/events/functions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from dbt.events.base_types import BaseEvent, EventLevel, EventMsg
33
from dbt.events.eventmgr import EventManager, LoggerConfig, LineFormat, NoFilter
44
from dbt.events.helpers import env_secrets, scrub_secrets
5-
from dbt.events.types import Formatting
5+
from dbt.events.types import Formatting, Note
66
from dbt.flags import get_flags, ENABLE_LEGACY_LOGGER
77
from dbt.logger import GLOBAL_LOGGER, make_log_dir_if_missing
88
from functools import partial
@@ -219,7 +219,9 @@ def msg_to_dict(msg: EventMsg) -> dict:
219219
)
220220
except Exception as exc:
221221
event_type = type(msg).__name__
222-
raise Exception(f"type {event_type} is not serializable. {str(exc)}")
222+
fire_event(
223+
Note(msg=f"type {event_type} is not serializable. {str(exc)}"), level=EventLevel.WARN
224+
)
223225
# We don't want an empty NodeInfo in output
224226
if (
225227
"data" in msg_dict

tests/unit/test_base_context.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
3+
from dbt.context.base import BaseContext
4+
from jinja2.runtime import Undefined
5+
6+
7+
class TestBaseContext:
8+
def test_log_jinja_undefined(self):
9+
# regression test for CT-2259
10+
try:
11+
os.environ["DBT_ENV_SECRET_LOG_TEST"] = "cats_are_cool"
12+
BaseContext.log(msg=Undefined(), info=True)
13+
except Exception as e:
14+
assert False, f"Logging an jinja2.Undefined object raises an exception: {e}"
15+
16+
def test_log_with_dbt_env_secret(self):
17+
# regression test for CT-1783
18+
try:
19+
os.environ["DBT_ENV_SECRET_LOG_TEST"] = "cats_are_cool"
20+
BaseContext.log({"fact1": "I like cats"}, info=True)
21+
except Exception as e:
22+
assert False, f"Logging while a `DBT_ENV_SECRET` was set raised an exception: {e}"

tests/unit/test_events.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pytest
12
import re
23
from typing import TypeVar
34

@@ -443,3 +444,21 @@ def test_date_serialization():
443444
ti_dict = ti.to_dict()
444445
assert ti_dict["started_at"].endswith("Z")
445446
assert ti_dict["completed_at"].endswith("Z")
447+
448+
449+
def test_bad_serialization():
450+
"""Tests that bad serialization enters the proper exception handling
451+
452+
When pytest is in use the exception handling of `BaseEvent` raises an
453+
exception. When pytest isn't present, it fires a Note event. Thus to test
454+
that bad serializations are properly handled, the best we can do is test
455+
that the exception handling path is used.
456+
"""
457+
458+
with pytest.raises(Exception) as excinfo:
459+
types.Note(param_event_doesnt_have="This should break")
460+
461+
assert (
462+
str(excinfo.value)
463+
== "[Note]: Unable to parse dict {'param_event_doesnt_have': 'This should break'}"
464+
)

tests/unit/test_functions.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import pytest
33

44
import dbt.flags as flags
5-
from dbt.events.functions import warn_or_error
6-
from dbt.events.types import NoNodesForSelectionCriteria
5+
from dbt.events.functions import msg_to_dict, warn_or_error
6+
from dbt.events.types import InfoLevel, NoNodesForSelectionCriteria
77
from dbt.exceptions import EventCompilationError
88

99

@@ -43,3 +43,19 @@ def test_warn_or_error_warn_error(warn_error, expect_compilation_exception):
4343
warn_or_error(NoNodesForSelectionCriteria())
4444
else:
4545
warn_or_error(NoNodesForSelectionCriteria())
46+
47+
48+
def test_msg_to_dict_handles_exceptions_gracefully():
49+
class BadEvent(InfoLevel):
50+
"""A spoof Note event which breaks dictification"""
51+
52+
def __init__(self):
53+
self.__class__.__name__ = "Note"
54+
55+
event = BadEvent()
56+
try:
57+
msg_to_dict(event)
58+
except Exception as exc:
59+
assert (
60+
False
61+
), f"We expect `msg_to_dict` to gracefully handle exceptions, but it raised {exc}"

0 commit comments

Comments
 (0)