Skip to main content
RunBook Academy

TerraformXVIII · Troubleshooting and RecoveryProduction Terraform

Terraform Logging and Debugging

Intermediate⏱ ~12 minbash

What you'll learn

  • Set the appropriate Terraform log verbosity for a given failure scenario
  • Use TF_LOG_PATH without losing the distinction between core and provider logs
  • Separate Terraform Core and provider-plugin evidence with TF_LOG_CORE and TF_LOG_PROVIDER
  • Recognise sensitive values in logs and apply storage, access, and retention controls
  • Capture a bounded, useful debug log without exposing credentials or flooding storage

Prerequisites

None — start here.

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

Terraform logging is the controlled release of diagnostic evidence. In Terraform 1.9.x, TF_LOG enables detailed output on stderr, while TF_LOG_CORE and TF_LOG_PROVIDER let you narrow the evidence to Terraform Core or provider plugins. TF_LOG_PATH sends enabled logs to a file, but it does not enable logging by itself. Logging is most useful when the verbosity matches the question being asked.

The levels are TRACE, DEBUG, INFO, WARN, and ERROR, in descending verbosity. The right level is the lowest one that answers the question. TRACE is not a routine production setting; it can produce a very large stream and can expose values that the normal Terraform presentation would hide.

What each control does

TF_LOG: choose the verbosity

Set TF_LOG for the current command so the setting cannot silently affect the next engineer’s shell.

READ-ONLY — the following plan inspects the working directory and does not apply infrastructure.

TF_LOG=INFO terraform plan -input=false -no-color

The useful levels are:

  • ERROR. The command failed and the final error is the main evidence. Use this for routine automation or a compact failure report.
  • WARN. Show recoverable anomalies and configuration warnings without the full provider trace.
  • INFO. Show high-level lifecycle events such as provider configuration, state operations, and resource actions.
  • DEBUG. Investigate provider calls, resource planning decisions, graph operations, and most apply failures.
  • TRACE. Use for a narrow, unexplained boundary or a suspected Terraform bug. Keep the run short and capture only the relevant command.

Terraform 1.9.x also accepts JSON for machine-oriented output. It is not a stable parsing contract across minor releases, so do not build a permanent parser around it without a compatibility plan.

TF_LOG_PATH: write the stream to one place

TF_LOG_PATH redirects enabled logging to a file. Set a restrictive umask and confirm the path is not a shared directory or a CI workspace that other jobs can read.

CONFIGURATION — writes a diagnostic file. It does not apply infrastructure, but the file may contain sensitive data.

umask 077
TF_LOG=DEBUG TF_LOG_PATH=/tmp/terraform-apply.log \
  terraform plan -input=false -no-color

A compact filtered view is useful, but filtering is a local investigation step, not the only retained evidence. Keep the original protected until the incident record is complete.

TF_LOG_CORE and TF_LOG_PROVIDER: separate the boundaries

Terraform Core builds the graph and coordinates state. Provider plugins perform provider-specific planning and API operations. The two variables accept the same level names as TF_LOG and activate only the relevant subset.

CONFIGURATION — enables bounded provider logging and writes a file. The command still plans only.

TF_LOG_CORE=INFO TF_LOG_PROVIDER=DEBUG \
  TF_LOG_PATH=/tmp/provider-only.log \
  terraform plan -input=false -no-color

Use TF_LOG_PROVIDER=DEBUG when a provider rejects a resource or when the API operation is unclear. Use TF_LOG_CORE=DEBUG when the graph, state serialisation, or Terraform orchestration is the suspected boundary. Do not assume that a provider name in a message proves a provider bug; first check the API response and the resource input.

The right verbosity by scenario

ScenarioStarting levelScopeStop condition
Routine plan or applyNo TF_LOGNormal outputOnly enable if output lacks the needed evidence
High-level operational checkINFOCore and provider lifecycleStop when the affected stage is located
Provider API or schema errorDEBUGTF_LOG_PROVIDER=DEBUG, TF_LOG_CORE=INFOOne resource or one API call is understood
Graph or state orchestration errorDEBUGTF_LOG_CORE=DEBUG, TF_LOG_PROVIDER=ERRORThe graph or state event is identified
Single unexplained boundaryTRACENarrow command and protected fileThe required evidence is captured; never run a whole estate at trace by default
Suspected Terraform bugTRACE plus provider debugSmallest reproducible configurationPreserve the version, exact command, and sanitised log

A real provider failure may produce entries like this illustrative output:

2026/08/13 02:17:44 [INFO]  provider: configuring client
2026/08/13 02:17:45 [DEBUG] provider.terraform.io/hashicorp/aws: creating resource
2026/08/13 02:17:46 [ERROR] provider.terraform.io/hashicorp/aws: operation error
2026/08/13 02:17:46 [ERROR] Retry limit reached

The sequence tells you to inspect the provider’s request, retry budget, and service status. It does not justify exposing the complete log to a public channel.

Redaction is not automatic

A value marked sensitive in HCL is a presentation control. It is deliberately suppressed in normal plan and apply output, but it is not a promise that every provider SDK, provider diagnostic, state JSON, or debug log redacts it.

output "database_password" {
  value     = var.database_password
  sensitive = true
}

terraform output will hide the value by default, but the value can still exist in state if the resource stores it and can appear in a provider request or a debug stream. Treat the variable, state, saved plan, and log as sensitive until proven otherwise.

Five logging failure modes

1. Too much logging by default

Observable symptom. The CI job is slow, the log store grows from a few megabytes to gigabytes, and provider retries dominate the output. The useful error is buried under routine lifecycle events.

Recovery. Stop the run, use INFO or WARN for the next bounded reproduction, and capture only the affected address or command. Keep trace logging as an explicit incident action with a deletion time.

2. Too little logging for a transient API failure

Observable symptom. The apply fails with a generic timeout and no provider operation, request identifier, or retry count. Re-running at the same verbosity produces no new evidence.

Recovery. Use TF_LOG_PROVIDER=DEBUG, TF_LOG_CORE=INFO, and a protected TF_LOG_PATH on one failed resource. If the failure is still opaque, add a second run with TF_LOG_PROVIDER=TRACE; do not run the whole estate at trace.

3. Log path exists but no log appears

Observable symptom. TF_LOG_PATH is set, but the file is absent or stays empty.

Recovery. Confirm that TF_LOG, TF_LOG_CORE, or TF_LOG_PROVIDER is also set, check the path and directory permissions, and inspect the process exit code. A path variable stores output; it does not select a verbosity.

4. Core and provider evidence are mixed together

Observable symptom. The log is full of graph events but has no provider operation, or it is full of SDK details but does not show the state action that led to the call.

Recovery. Split the runs. Use TF_LOG_CORE=DEBUG for the graph and TF_LOG_PROVIDER=DEBUG for provider API evidence. Correlate the two streams by command time, resource address, and provider request ID.

5. A log leaks a secret or survives too long

Observable symptom. Search or retention audit finds a token, password, private address, or customer value in a log artefact. The incident record says the value was sensitive = true in HCL.

Recovery. Revoke and rotate the credential, remove the local file, restrict the archive, and review who accessed it. Use an allow-listed log collector with encryption and bounded retention for future runs.

Security, performance, and production guidance

Trace logging increases CPU, disk, network, and log-processing work. It can also disable any practical human review of a large apply. Use INFO for operational visibility, DEBUG for a bounded provider or graph diagnosis, and TRACE only when the other levels have failed to answer the question.

Keep the Terraform version, provider lock selections, command, workspace, and resource address in the incident record. Redact at the source where possible, then apply access control and retention to the artefact. Do not put a full log in a pull request, chat transcript, or unrestricted ticket. The absence of a secret from terraform plan is not evidence that it is absent from TF_LOG=TRACE.

Verification

  • You can set TF_LOG to a level that matches the failure rather than always using trace.
  • You can explain why TF_LOG_PATH must be paired with an enabled logging variable.
  • You can isolate Core and provider-plugin evidence with TF_LOG_CORE and TF_LOG_PROVIDER.
  • You can identify the limits of sensitive = true in logs and JSON artefacts.
  • You can redact, restrict, rotate, and delete a diagnostic log when it exposes sensitive data.
  • You can cap verbosity so a diagnosis does not flood CI or fill the incident runner’s storage.

Knowledge check · 7 questions

  1. Q1. Which setting enables the most verbose Terraform Core and provider logging for a bounded run?

  2. Q2. What must be true when using `TF_LOG_PATH`?

  3. Q3. Terraform debug and trace logs can contain sensitive values even when a variable is marked `sensitive = true` in HCL.

  4. Q4. Which logging choices are appropriate for a narrow provider diagnosis? (Select all that apply.)

  5. Q5. A plan succeeds at `INFO`, but a provider rejects one resource with no request details. What is the best next logging run?

  6. Q6. Which log level is the routine starting point for a high-level operational check?

  7. Q7. Why pair `TF_LOG_CORE=INFO` with `TF_LOG_PROVIDER=DEBUG`?

Passing score: 75%. Answers are checked in this browser.