В терминале dbt показывает только финальные строки результата (PASS/FAIL/SKIP). Но полная картина запуска — каждое SQL-statement, каждый Jinja-warning, каждый stack trace — лежит в logs/dbt.log. Когда dbt run падает с лаконичным «Database Error» в терминале, ответ почти всегда в логе.
Где живёт лог
По умолчанию dbt пишет лог в папку logs/ в корне проекта:
my_project/
├── dbt_project.yml
├── models/
└── logs/
└── dbt.log # последний (и единственный по умолчанию) файл
Лог-файл append-only: каждый новый запуск дописывается в конец. Со временем он растёт, и удобство падает. Помогает:
# Очистить лог (можно безопасно — это просто диагностика)
> logs/dbt.log
# Удалить старые
rm logs/dbt.log
Или настроить ротацию в dbt_project.yml:
flags:
log_file_max_bytes: 10485760 # 10 MB, после ротация
log_format: text # text | json
json-формат полезен для интеграций с Datadog/Splunk/Grafana Loki — каждая строка валидный JSON.
Структура строки лога
Стандартная text-строка выглядит так:
14:23:15.123456 [info ] [MainThread]: Running with dbt=1.10.2
14:23:15.234567 [debug] [Thread-2 ]: On model.jaffle_shop.customers: /* {"app": "dbt", "dbt_version": ...} */
14:23:15.234890 [debug] [Thread-2 ]: SQL status: SUCCESS in 0.083s
Компоненты:
14:23:15.123456— точное время с микросекундами[info ]/[debug] / [warn ] / [error]— уровень[MainThread]/[Thread-2]— какой воркер- Сообщение — остальное
Уровни от тихого к шумному:
error— что-то сломалось безоговорочноwarn— потенциальная проблема, не блокирующаяinfo— стандартные шаги (как в терминале)debug— каждый SQL, каждое Jinja-развёртывание, всё
По умолчанию терминал показывает только info+. В лог пишется всё, начиная с debug.
Конфигурация логов
В dbt_project.yml или через CLI:
# Полный verbose в терминал
dbt --debug run --select customers
# Тишина — только ошибки в терминале
dbt --quiet run --select customers
# Уровень логирования в файл
dbt --log-level debug run # debug в файл (по умолчанию)
dbt --log-level info run # только info+ в файл
--debug в CLI = тот же verbose, что в dbt.log, но в терминал. Помогает когда не хочешь переключаться между окнами:
$ dbt --debug run --select customers
14:23:15 Running with dbt=1.10.2
14:23:15 Loaded profile from /Users/me/.dbt/profiles.yml
14:23:15 Building catalog
14:23:15 Connection 'master' created with target 'dev'
14:23:15 Acquiring new duckdb connection 'master'
14:23:15 Began compiling node model.jaffle_shop.customers
14:23:15 On model.jaffle_shop.customers: /* {"app": "dbt", "dbt_version": "1.10.2", ...} */
14:23:15 create or replace table "jaffle_shop"."main"."customers" as
14:23:15 (select c.customer_id, c.first_name, ...);
14:23:15 SQL status: SUCCESS 1000 in 0.083s
14:23:15 Finished compiling and running node ...
Видно каждый SQL, который dbt отправил, и его статус.
Что искать в логе
Допустим, dbt run --select customers упал. Открой logs/dbt.log, нажми Ctrl+End (или tail -n 200 logs/dbt.log в shell). Стандартный паттерн — три уровня поиска:
1. Find the error
Search по слову Error или ERROR:
14:23:17 ERROR: Database Error in model customers (models/marts/customers.sql)
14:23:17 Catalog Error: Table "stg_orders" does not exist!
14:23:17 Did you mean "main.stg_orders"?
Здесь сразу видно:
- Уровень:
Database Error(не Compilation, не Runtime) - В какой модели:
customers - Файл:
models/marts/customers.sql - Точное сообщение от warehouse:
Catalog Error: Table "stg_orders" does not exist! - Подсказка от DuckDB:
Did you mean "main.stg_orders"?
2. Find the SQL
Прокрути выше — увидишь точный SQL, который dbt пытался выполнить:
14:23:17 On model.jaffle_shop.customers:
14:23:17 create or replace table "jaffle_shop"."main"."customers" as (
14:23:17 select c.customer_id, count(o.order_id) as order_count
14:23:17 from stg_orders o -- БАГ! Прямое имя без main.
14:23:17 ...
14:23:17 );
Теперь видно: в SQL stg_orders без префикса схемы, и DuckDB не находит её в текущем search_path. Скорее всего, в исходнике вместо {{ ref('stg_orders') }} написано просто stg_orders.
3. Find the Jinja trace (если ошибка компиляции)
Если ошибка типа Compilation Error, обычно есть Python stack trace:
14:23:18 Traceback (most recent call last):
14:23:18 File ".../dbt/cli/main.py", line 234, in cli_main
14:23:18 File ".../dbt/parser/manifest.py", line 567, in parse
14:23:18 jinja2.exceptions.TemplateError: 'dict object' has no attribute 'order_id'
Это говорит, что в Jinja-условии ты пытался достучаться до атрибута, которого в словаре нет. Обычно — опечатка в имени переменной или работа с пустым результатом run_query.
Полезные shell-фильтры
Лог большой. Используй grep, awk, tail:
# Только ошибки последнего запуска
grep -E "(ERROR|FAIL)" logs/dbt.log | tail -50
# Конкретная модель
grep "customers" logs/dbt.log | tail -30
# Все SQL, отправленные в БД (verbose mode)
grep -A 5 "create or replace" logs/dbt.log
# Тайминги — что было медленным
grep "SQL status: SUCCESS" logs/dbt.log | awk '{print $NF, $(NF-1)}' | sort -rn
# Только нужный thread
grep "Thread-2" logs/dbt.log
# Последний запуск (по timestamp)
sed -n '/2026-05-19 14:23:00/,$p' logs/dbt.log
В реальной работе у тебя в .bashrc будет пара алиасов:
alias dlog-err='grep -E "(ERROR|FAIL|warn)" logs/dbt.log | tail -50'
alias dlog-last='tail -200 logs/dbt.log'
alias dlog-sql='grep -A 5 "create or replace\|insert into" logs/dbt.log | tail -60'
Типичные паттерны ошибок в логе
Паттерн 1: Connection issue
14:25:01 ERROR: Database Error in model customers
14:25:01 Could not connect to database: Permission denied
14:25:01 Connection 'master' to duckdb at path './jaffle_shop.duckdb' failed
-> Либо path неверный, либо файл заблокирован другим процессом (DuckDB single-writer). Закрой все открытые psql/DataGrip-сессии и попробуй заново.
Паттерн 2: ref() to missing model
14:26:14 Compilation Error in rpc request (from remote system)
14:26:14 Model 'stg_orders' (models/staging/stg_orders.sql) depends on a node named 'stg_jaffle__orders' which was not found
-> В customers.sql есть {{ ref('stg_jaffle__orders') }}, но в проекте файла stg_jaffle__orders.sql нет. Опечатка или забыли создать модель.
Паттерн 3: Source not declared
14:27:00 Compilation Error
14:27:00 Source 'raw_jaffle.customers' was not found
-> В SQL {{ source('raw_jaffle', 'customers') }}, но в sources.yml source raw_jaffle не объявлен. Открыть models/staging/_sources.yml и проверить.
Паттерн 4: Test failures
14:28:11 Failure in test not_null_customers_customer_id
14:28:11 Got 12 results, configured to fail if != 0
14:28:11
14:28:11 Compiled at target/compiled/jaffle_shop/tests/...sql
-> Тест провалился: 12 строк с NULL в customer_id. Открой target/compiled/.../<test>.sql, прогони руками — увидишь, какие именно строки нарушают.
Паттерн 5: Macro undefined
14:29:14 Compilation Error in model customers
14:29:14 'get_currency_symbol' is undefined
14:29:14 Line 4: amount * {{ get_currency_symbol('USD') }}
-> Либо макрос определён, но dbt deps не запускали (если из пакета), либо опечатка в имени, либо файл macros/...sql лежит вне macro-paths.
Паттерн 6: YAML parsing error
14:30:11 Compilation Error
14:30:11 mapping values are not allowed here at line 8, column 12 in models/staging/_models.yml
-> Битый YAML. Чаще всего — неправильный отступ (tabs вместо spaces) или незакрытая кавычка. Открой файл, проверь : на правильной строке.
Паттерн 7: Permission denied (warehouse)
14:31:18 Database Error
14:31:18 permission denied for table customers
-> dbt-юзер не имеет прав на CREATE/INSERT в этой схеме. Для DuckDB это редкость, для облачных warehouses — постоянная история. Решение: настроить grant’ы (см. модуль 14).
Паттерн 8: Out of memory (DuckDB)
14:32:14 Out of Memory Error: failed to allocate 2147483648 bytes, used: 8589934592 bytes
14:32:14 Memory limit reached: 8GB
-> Тяжёлая агрегация не помещается. Решения:
- Увеличить
memory_limitвprofiles.yml - Добавить
temp_directoryдля spill на диск - Упростить запрос (меньше джойнов, меньше groupBy)
--log-format json
Для облачных интеграций ставят JSON:
# dbt_project.yml
flags:
log_format: json
Тогда строки в dbt.log:
{"data": {"msg": "Running with dbt=1.10.2"}, "level": "info", "ts": "2026-05-19T14:23:15.123Z", "thread": "MainThread"}
{"data": {"msg": "SQL status: SUCCESS 1000 in 0.083s", "node_info": {"node_path": "marts/customers.sql", ...}}, "level": "debug", "ts": "...", "thread": "Thread-2"}
Каждая строка — валидный JSON. Можно агрегировать через jq:
# Сколько эррор-строк в логе?
cat logs/dbt.log | jq -r 'select(.level == "error")' | wc -l
# Какие модели самые медленные?
cat logs/dbt.log | jq -r 'select(.data.msg | startswith("SQL status: SUCCESS")) | "\(.data.node_info.node_path) \(.data.msg)"' | sort
Параметры запуска: что искать вверху лога
Каждый запуск начинается с конфигурационного блока:
14:23:15 Running with dbt=1.10.2
14:23:15 Loaded profile from /Users/me/.dbt/profiles.yml
14:23:15 target: dev
14:23:15 threads: 4
14:23:15 path: ./jaffle_shop.duckdb
Здесь видно: какой target, сколько threads, какой профиль. Если ты случайно запустил с --target prod в local-проекте, это будет видно в первых строках.
Лог — не место для секретов
В большинстве случаев dbt не пишет креды в лог — даже в debug mode. Но env_var-значения могут попасть в SQL, если ты их встраиваешь руками. Не пиши '{{ env_var("DB_PASSWORD") }}' прямо в модели — иди через secure secrets manager.
logs/ в .gitignore — стандартно. Никогда не комить логи.
Попробуй сам
-
Запусти
dbt --debug run --select customers. Сравни в терминале вывод сtail -100 logs/dbt.log. Найди SQL-statement, который dbt отправил. -
Намеренно сломай модель — добавь
SELECT * FORM customers(опечаткаFORM). Запустиdbt run --select customers. Открой лог, найдиError, найди исходный SQL. -
Запусти
grep -E "ERROR|FAIL|warn" logs/dbt.log | tail -20. Просмотри последние warning-и. -
Установи
log_format: jsonвdbt_project.yml. Запусти любую команду. Открой лог — он теперь JSON. Попробуйjq '.data.msg' logs/dbt.log | head -20. -
Найди свой самый медленный SQL:
grep "SQL status: SUCCESS" logs/dbt.log | sort -k <last>.
Чек-лист
logs/dbt.log— полный verbose с каждым SQL и каждым шагом.- Уровни: error, warn, info, debug. В лог пишется всё, в терминале — info+.
dbt --debug run— вывести verbose в терминал в дополнение к логу.- При ошибке: ищи
ERRORв логе, скролл вверх до SQL-statement, ещё выше — до Jinja-trace. - Полезные filter’ы:
grep ERROR | tail,grep "model_name",tail -200. - Типичные паттерны: connection, ref to missing, source not declared, test failure, macro undefined, YAML, permission, OOM.
log_format: jsonдля интеграций с Datadog/Splunk/Loki.logs/в.gitignore. Никогда не комить.