diff --git a/core/dbt/context/base.py b/core/dbt/context/base.py index bf334a7d11f..f99849e1a68 100644 --- a/core/dbt/context/base.py +++ b/core/dbt/context/base.py @@ -11,6 +11,7 @@ from dbt.exceptions import ( CompilationException, MacroReturn, + RuntimeException, raise_compiler_error, raise_parsing_error, disallow_secret_env_var, @@ -543,10 +544,11 @@ def zip_strict(*args: Iterable[Any]) -> Iterable[Any]: @contextmember @staticmethod - def log(msg: str, info: bool = False) -> str: + def log(msg: Any, info: bool = False) -> str: """Logs a line to either the log file or stdout. - :param msg: The message to log + :param msg: The message to log. If not a string, then translated into + a string via a call to the python builtin str() function. :param info: If `False`, write to the log file. If `True`, write to both the log file and stdout. @@ -556,6 +558,10 @@ def log(msg: str, info: bool = False) -> str: {{ log("Running some_macro: " ~ arg1 ~ ", " ~ arg2) }} {% endmacro %}" """ + try: + msg = str(msg) + except Exception as e: + raise RuntimeException("log failed to stringify the given object") from e if info: fire_event(MacroEventInfo(msg=msg)) else: diff --git a/tests/functional/context_methods/test_cli_var_override.py b/tests/functional/context_methods/test_cli_var_override.py index 22b26697bbf..73d29af72b8 100644 --- a/tests/functional/context_methods/test_cli_var_override.py +++ b/tests/functional/context_methods/test_cli_var_override.py @@ -43,7 +43,7 @@ def test__override_vars_global(self, project): # This one switches to setting a var in 'test' -class TestCLIVarOverridePorject: +class TestCLIVarOverrideProject: @pytest.fixture(scope="class") def models(self): return { diff --git a/tests/functional/context_methods/test_log_arbitrary_types.py b/tests/functional/context_methods/test_log_arbitrary_types.py new file mode 100644 index 00000000000..58ed1689752 --- /dev/null +++ b/tests/functional/context_methods/test_log_arbitrary_types.py @@ -0,0 +1,55 @@ +import pytest +import os + +from dbt.tests.util import run_dbt, get_manifest, run_dbt_and_capture + +str_case_sql = """ + {{ log('str987654321') }} + select NULL +""" + +int_case_sql = """ + {{ log(987654321) }} + select NULL +""" + +float_case_sql = """ + {{ log(9876.54321) }} + select NULL +""" + +list_case_sql = """ + {{ log([9, 8, 7, 6, 5, 4, 3, 2, 1]) }} + select NULL +""" + +dict_case_sql = """ + {{ log({9: 8}) }} + select NULL +""" + + +class TestLogArbitraryTypes: + @pytest.fixture(scope="class") + def models(self): + return { + "str_case.sql": str_case_sql, + "int_case.sql": int_case_sql, + "float_case.sql": float_case_sql, + "list_case.sql": list_case_sql, + "dict_case.sql": dict_case_sql, + } + + def test_log(self, project): + # Induce dbt to try scrubbing logs, which fails if not given strings + os.environ["DBT_ENV_SECRET_WHATEVER"] = "1234" + os.environ["DBT_DEBUG"] = "True" + _, log_output = run_dbt_and_capture(["run"]) + + # These aren't theoretically sound ways to verify the output + # but they should be sufficient for this minor test. + assert " str987654321\n" in log_output + assert " 987654321\n" in log_output + assert " 9876.54321\n" in log_output + assert " [9, 8, 7, 6, 5, 4, 3, 2, 1]\n" in log_output + assert " {9: 8}\n" in log_output