<@ULVA73B9P> can you search for threads about dyna...
# ask-marvin
p
@Marvin can you search for threads about dynamically configuring logging levels? Specifically around suppressing info messages like
Finished in state Completed()
m
thought for 111.0 seconds
I searched the Prefect community Slack index for “Finished in state Completed()” and related keywords around dynamic logging level control. I didn’t find a thread specifically about suppressing that exact info message, but here are the most relevant recent threads on logging configuration, verbosity, and reducing noise: - Integrating Non-Prefect Logs into Prefect UI — discusses how Prefect hooks into Python logging, handler setup, and level control when forwarding logs. - Comprehensive Guide to Resolve Prefect 3.x Duplicate Logging Issue — deep dive into
logging.yaml
configuration, preventing duplicate handlers, and setting appropriate levels. - Investigation into Duplicate Logs in Prefect 3.x UI — explores common causes of duplicate/noisy logs and configuration patterns to mitigate them. - Logging and Materialization in Prefect 3 — covers logging best practices alongside materialization; touches on controlling verbosity. Would you like me to: - Broaden the search to GitHub Discussions/Issues as well? - Look for examples of per-run/per-flow dynamic level changes (e.g., setting
PREFECT_LOGGING_LEVEL
vs. programmatically adjusting loggers/filters at runtime)? - Or are you targeting suppression specifically in worker stdout vs. hiding these entries from the Prefect UI? If you share which environment you’re on (assuming Prefect 3.x) and whether you want a global or per-run solution, I can dig up or draft a concrete snippet to suppress that “Finished in state Completed()” line.
p
@Marvin shoot. Unfortunately I don't think any of those are quite what I'm looking for (or I'm not understanding). I'm using the
PrefectDbtRunner
and I have one task that essentially executes
dbt compile
because subsequent tasks parse the manifest. During the
dbt compile
task -- each model generates its own task (I believe) so the logging surfaces a
Finished in state Completed()
message for each model. Is there a way to suppress those messages for just that
dbt compile
task and its "sub-tasks". Apologies if the terminology for "sub-task" is incorrect
m
thought for 273.4 seconds
Thanks, that context helps a lot. Short answer: those “Finished in state Completed()” lines come from the Prefect engine logger (
prefect.engine
) for every task run. PrefectDbtRunner turns each dbt node (model/seed/etc.) into its own Prefect task during compile, so you’ll see one of those messages per node. If you want to suppress them only during your dbt compile step (and keep normal logging elsewhere), wrap the compile call and temporarily quiet the
prefect.engine
logger. Here are a few approaches in Prefect 3.x: Option A: Hide only INFO “Finished in state …” lines but still show errors - Temporarily set
prefect.engine
to ERROR around your compile call.
Copy code
import logging
from contextlib import contextmanager
from prefect import flow, task
from prefect_dbt import PrefectDbtRunner

@contextmanager
def temporarily_set_logger_level(name, level):
    logger = logging.getLogger(name)
    old = logger.level
    logger.setLevel(level)
    try:
        yield
    finally:
        logger.setLevel(old)

@task
def dbt_compile_quiet():
    with temporarily_set_logger_level("prefect.engine", logging.ERROR):
        PrefectDbtRunner().invoke(["compile"])

@flow
def run():
    dbt_compile_quiet()
    # other steps run with normal logging
Option B: Filter just the “Finished in state …” records (keeps other engine logs)
Copy code
import logging
from prefect import flow, task
from prefect_dbt import PrefectDbtRunner

class SuppressEngineFinishedFilter(logging.Filter):
    def filter(self, record):
        # Drop only the engine’s “Finished in state …” lines
        return not (record.name == "prefect.engine" and "Finished in state" in record.getMessage())

@task
def dbt_compile_quiet():
    engine_logger = logging.getLogger("prefect.engine")
    f = SuppressEngineFinishedFilter()
    engine_logger.addFilter(f)
    try:
        PrefectDbtRunner().invoke(["compile"])
    finally:
        engine_logger.removeFilter(f)

@flow
def run():
    dbt_compile_quiet()
Option C: Mute the engine logger entirely during compile - Uses Prefect’s built-in context helper to disable a logger by name.
Copy code
from prefect import flow, task
from prefect.logging.loggers import disable_logger
from prefect_dbt import PrefectDbtRunner

@task
def dbt_compile_silent():
    with disable_logger("prefect.engine"):
        PrefectDbtRunner().invoke(["compile"])

@flow
def run():
    dbt_compile_silent()
Notes and tradeoffs - These changes are process-wide while active. Because PrefectDbtRunner runs node tasks in threads within the same process, the suppression will apply to those per-model tasks created during the compile call. If you have other, unrelated tasks running concurrently in the same process at the same time, they’ll be affected during the window too. - If you also want to quiet dbt’s own event spam, set PrefectDbtRunner’s dbt log level (this does not affect the engine “Finished in state …” lines, which the options above handle):
Copy code
from dbt_common.events.base_types import EventLevel
from prefect_dbt import PrefectDbtRunner, PrefectDbtSettings

PrefectDbtRunner(
    settings=PrefectDbtSettings(log_level=EventLevel.WARN)
).invoke(["compile"])
If you tell me whether you want to keep ERROR logs for those dbt node tasks or hide everything, I can tailor one of the snippets above precisely to your setup.
p
@Marvin just an fyi. I tried option C and got a compilation error.
Copy code
2026-03-09T15:46:02.106457855Z Exception in thread dbt-callback-processor:
2026-03-09T15:46:02.106854599Z Traceback (most recent call last):
2026-03-09T15:46:02.106862698Z   File "/usr/local/lib/python3.12/threading.py", line 1075, in _bootstrap_inner
2026-03-09T15:46:02.127945236Z     self.run()
2026-03-09T15:46:02.128470306Z   File "/usr/local/lib/python3.12/threading.py", line 1012, in run
2026-03-09T15:46:02.128595710Z     self._target(*self._args, **self._kwargs)
2026-03-09T15:46:02.128600587Z   File "/usr/local/lib/python3.12/site-packages/prefect_dbt/core/runner.py", line 518, in _callback_worker
2026-03-09T15:46:02.128995849Z     callback_func(event_msg)
2026-03-09T15:46:02.129004627Z   File "/usr/local/lib/python3.12/site-packages/prefect_dbt/core/runner.py", line 660, in _process_node_started_sync
2026-03-09T15:46:02.129006810Z     manifest_node, prefect_config = self._get_manifest_node_and_config(node_id)
2026-03-09T15:46:02.129801256Z                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-03-09T15:46:02.129810344Z   File "/usr/local/lib/python3.12/site-packages/prefect_dbt/core/runner.py", line 367, in _get_manifest_node_and_config
2026-03-09T15:46:02.130226824Z     manifest_node = self.manifest.nodes.get(node_id)
2026-03-09T15:46:02.130233258Z                     ^^^^^^^^^^^^^
2026-03-09T15:46:02.130234925Z   File "/usr/local/lib/python3.12/site-packages/prefect_dbt/core/runner.py", line 180, in manifest
2026-03-09T15:46:02.130236577Z     self._set_manifest_from_project_dir()
2026-03-09T15:46:02.130237643Z   File "/usr/local/lib/python3.12/site-packages/prefect_dbt/core/runner.py", line 214, in _set_manifest_from_project_dir
2026-03-09T15:46:02.130238828Z     self._manifest = Manifest.from_dict(json.load(f))  # type: ignore[reportUnknownMemberType]
2026-03-09T15:46:02.130239786Z                                         ^^^^^^^^^^^^
2026-03-09T15:46:02.130240667Z   File "/usr/local/lib/python3.12/json/__init__.py", line 293, in load
2026-03-09T15:46:02.137608887Z     return loads(fp.read(),
2026-03-09T15:46:02.138047571Z            ^^^^^^^^^^^^^^^^
2026-03-09T15:46:02.138055031Z   File "/usr/local/lib/python3.12/json/__init__.py", line 346, in loads
2026-03-09T15:46:02.138350063Z     return _default_decoder.decode(s)
2026-03-09T15:46:02.138366742Z            ^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-03-09T15:46:02.138369030Z   File "/usr/local/lib/python3.12/json/decoder.py", line 338, in decode
2026-03-09T15:46:02.139097535Z     obj, end = self.raw_decode(s, idx=_w(s, 0).end())
2026-03-09T15:46:02.139474823Z                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-03-09T15:46:02.139496599Z   File "/usr/local/lib/python3.12/json/decoder.py", line 354, in raw_decode
2026-03-09T15:46:02.139499112Z     obj, end = self.scan_once(s, idx)
2026-03-09T15:46:02.139500174Z                ^^^^^^^^^^^^^^^^^^^^^^
2026-03-09T15:46:02.139501065Z json.decoder.JSONDecodeError: Expecting ',' delimiter: line 1 column 298984 (char 298983)
m
thought for 86.2 seconds
Thanks for the heads-up — and sorry you hit that. What you’re seeing isn’t actually from logging; it’s a JSONDecodeError in the dbt callback thread while it’s trying to read the manifest during compile. In PrefectDbtRunner, the callback processor handles NodeStart events and reads target/manifest.json to resolve node info. During compile, that file can be in-flight and temporarily invalid, which can trigger exactly this stack trace. The timing change from muting logs likely just made the race more likely, but the root cause is manifest read/write overlap during compile. Safer ways to quiet those “Finished in state Completed()” messages for just compile: - Keep errors, hide the “Finished…” INFO lines (recommended) - Temporarily bump only the engine logger during compile:
Copy code
import logging
  from contextlib import contextmanager
  from prefect import flow, task
  from prefect_dbt import PrefectDbtRunner

  @contextmanager
  def set_logger_level(name, level):
    logger = logging.getLogger(name)
    old = logger.level
    logger.setLevel(level)
    try:
        yield
    finally:
        logger.setLevel(old)

  @task
  def dbt_compile_quiet():
    with set_logger_level("prefect.engine", logging.ERROR):
        PrefectDbtRunner().invoke(["compile"])

  @flow
  def run():
    dbt_compile_quiet()
    # ... other steps ...
- Filter just the “Finished in state …” records (keeps other engine logs):
Copy code
import logging
  from prefect import flow, task
  from prefect_dbt import PrefectDbtRunner

  class SuppressEngineFinishedFilter(logging.Filter):
      def filter(self, record):
          return not (record.name == "prefect.engine" and "Finished in state" in record.getMessage())

  @task
  def dbt_compile_quiet():
      engine = logging.getLogger("prefect.engine")
      f = SuppressEngineFinishedFilter()
      engine.addFilter(f)
      try:
          PrefectDbtRunner().invoke(["compile"])
      finally:
          engine.removeFilter(f)

  @flow
  def run():
      dbt_compile_quiet()
If you also want to avoid per-model tasks entirely during compile (which also eliminates those engine lines and avoids the manifest-read race): - Run compile with callbacks disabled for that step:
Copy code
from prefect import flow, task
  from prefect_dbt import PrefectDbtRunner

  @task
  def dbt_compile_no_callbacks():
      # No per-node tasks; no engine "Finished..." lines; avoids manifest read during compile
      PrefectDbtRunner(_disable_callbacks=True).invoke(["compile"])

  @flow
  def run():
      dbt_compile_no_callbacks()
      # Then run normal dbt steps (with callbacks) that need the manifest
      PrefectDbtRunner().invoke(["build"])
- Or, call the dbt CLI directly for compile, then use PrefectDbtRunner afterward:
Copy code
import subprocess
  from prefect import flow, task
  from prefect_dbt import PrefectDbtRunner

  @task
  def cli_compile(project_dir=".", profiles_dir=None):
      cmd = ["dbt", "compile", "--project-dir", project_dir]
      if profiles_dir:
          cmd += ["--profiles-dir", profiles_dir]
      # --quiet reduces dbt stdout chatter
      cmd += ["--quiet"]
      subprocess.run(cmd, check=True)

  @flow
  def run():
      cli_compile()
      # Now the manifest is fully written; safe to parse/use in subsequent tasks
      PrefectDbtRunner().invoke(["build"])
A couple follow-ups to help us confirm whether there’s a separate issue to file: - Can you reproduce that JSONDecodeError when you run compile without
disable_logger
(i.e., with Option A or B, or with callbacks disabled)? - Which versions are you on (Prefect, prefect-dbt, dbt-core, and adapter)? If it repros without logger suppression, I can dig into issues/discussions or open a bug with those details. If you prefer one of these approaches, I can tailor it to your exact runner settings (profiles_dir, project_dir, partial parse settings, etc.).
p
@Marvin I cannot reproduce the error when I remove
disable_logger
. I'm running dbt-core and not using prefect cloud. I am on older versions: • prefect==3.4.* • prefect-dbt==0.7.* I'll do some work to upgrade these libraries. Thanks for the support!
m
thought for 35.3 seconds
Appreciate the update — and thanks for the versions. I saved a few notes so we can pick up smoothly next time. Given your findings: - Since the JSONDecodeError disappears without
disable_logger("prefect.engine")
, I’d stick with either: - Temporarily raising
prefect.engine
to ERROR during compile (keeps failures visible), or - Adding a focused filter that drops only “Finished in state …” lines - Both avoid touching the callback thread timing and should be stable on your versions. Upgrading Prefect and prefect-dbt is a good move. Recent 3.x releases include improvements in logging and the dbt integration. After you upgrade, if you still want a tighter solution just for compile, we can: - Use
_disable_callbacks=True
for the compile step only (no per-model tasks/logs), then run your downstream parsing/tasks as usual - Or route compile via dbt CLI with
--quiet
, then use PrefectDbtRunner for build/test If you hit anything during the upgrade or want a snippet tailored to your project_dir/profiles_dir, just ping me.
n
hey @Preston Sharpe - did you get what you needed from marvin here?
p
I believe so. On me to bump some versions, do some testing, and then see if Marvin's suggestions work
n
🎉 cool, just checking in
❤️ 1