12.3 Verbose Logging and Diagnostics
Key Takeaways
TF_LOG levels, from most to least verbose, are TRACE, DEBUG, INFO, WARN, and ERROR
TF_LOG_PATH appends logs to a file, but TF_LOG must still be set or nothing is written
TF_LOG_CORE and TF_LOG_PROVIDER take the same levels and split Terraform core logs from provider plugin logs
Turn logging up to diagnose provider bugs, unexpected API failures, and plugin crashes
Never leave TRACE enabled in CI that captures logs: traces can contain credentials and other secrets
12.3 Verbose Logging and Diagnostics
Quick Answer: Set
TF_LOGtoTRACE,DEBUG,INFO,WARN, orERROR(most verbose to least). Logs go to stderr.TF_LOG_PATHappends the same stream to a file — butTF_LOGmust still be set or nothing is logged.TF_LOG_COREandTF_LOG_PROVIDERsplit Terraform-core logs from provider-plugin logs. Use this for provider bugs and API failures. Never leaveTRACEon in CI that prints logs: traces can contain credentials.
Objective 7c on Terraform Associate (004) is: describe when and how to use verbose logging on Terraform 1.12. This is an operations question, not an HCL question. Official pages: Enable Terraform logs and CLI environment variables.
Why this objective appears on 004
When apply dies with a vague provider error, the next step is not terraform destroy. It is a controlled debug log. 004 checks that you know the level names, that TF_LOG_PATH alone is not enough, that you can isolate core versus provider, and that you treat TRACE as sensitive.
TF_LOG levels
Enabling TF_LOG makes detailed logs appear on stderr. HashiCorp documents these levels, in decreasing verbosity:
| Level | Relative volume | What you are usually looking for |
|---|---|---|
TRACE | Highest — every internal step | Graph walks, plugin protocol, full request/response detail |
DEBUG | High | Provider setup, retries, detailed operation progress |
INFO | Moderate | Notable non-error events |
WARN | Low | Recoverable problems |
ERROR | Lowest | Failures only |
export TF_LOG=TRACE # or DEBUG, INFO, WARN, ERROR
terraform plan
unset TF_LOG # or: export TF_LOG=off
The environment-variables page shows export TF_LOG=trace (case is conventionally upper-case in examples; the documented disable is off or unset). Levels are the exam fact. Setting TF_LOG to JSON is a special value: logs at TRACE or higher, formatted as JSON. HashiCorp warns that this JSON encoding is not a stable interface — it may change without notice and exists to support forthcoming tooling. Do not treat TF_LOG=JSON as the same thing as terraform show -json.
None of these variables is required for normal use. They change verbosity for debugging. They do not change how plan/apply compute actions, and they do not replace terraform validate.
TF_LOG_PATH does not enable logging by itself
export TF_LOG=DEBUG
export TF_LOG_PATH=./terraform.log
terraform apply
TF_LOG_PATH forces the log to be appended to that file whenever logging is enabled. Official wording, repeated on both the debugging page and the environment-variables page: even when TF_LOG_PATH is set, TF_LOG must be set for any logging to be enabled. A CI job that only exports a path and then wonders why the file is empty has not turned logging on.
Use a path you control (a job artifact directory, not a world-readable shared volume). The file can grow quickly at TRACE. Delete or restrict it after you capture the failure.
Split core versus provider: TF_LOG_CORE and TF_LOG_PROVIDER
Terraform CLI ("core") and provider plugins are separate processes. A 403 from AWS and a panic inside Terraform's graph walker look similar at ERROR on the console. HashiCorp lets you enable subsets:
| Variable | What it turns on | Same level names as TF_LOG? |
|---|---|---|
TF_LOG | Combined detailed logging (the usual switch) | Yes (TRACE … ERROR, plus JSON) |
TF_LOG_CORE | Terraform itself — graph, backend, state, language | Yes |
TF_LOG_PROVIDER | Provider plugin logs (the binary talking to the cloud API) | Yes |
TF_LOG_PATH | Destination file (append); not a level | Requires TF_LOG set |
When the symptom is "the provider returned InvalidParameterValue / 403 / 429," start with TF_LOG_PROVIDER=DEBUG or TRACE so you are not drowned in core graph TRACE. When the symptom is a crash or odd behavior inside Terraform before the provider is called (backend init, state lock, malformed config evaluation), use TF_LOG_CORE. You can set both to different levels. That split is the 7c skill: isolate who is noisy.
# Provider plugin only — typical cloud API failure
export TF_LOG_PROVIDER=TRACE
export TF_LOG_PATH=./provider-trace.log
# Official persist note: TF_LOG must also be set for file logging to start.
# Keep TF_LOG at a quiet level so CORE stays out of a provider-only capture.
export TF_LOG=ERROR
# After the capture
export TF_LOG=off
unset TF_LOG_PROVIDER
When to turn it on
HashiCorp's debugging page exists so you can debug unexpected behavior and, if you file a Terraform bug, include a detailed log (they suggest a gist). Exam-level situations:
- A provider operation fails with a raw API error and the human message is not enough.
- A plugin exits or hangs during refresh/apply.
- You need to see whether Terraform even reached the provider (core) or the provider reached the API (provider).
- Support or a GitHub issue asks for a TRACE.
Situations that are not 7c:
- "Make plan faster" — logging makes it slower and noisier.
- "Hide secrets from state" — that is
sensitive, ephemeral values, and a secrets manager (4h), notTF_LOG=ERROR. - "Pretty-print HCL" —
terraform fmt. - "See what would change" —
terraform plan/terraform show.
Leave logging at default (unset) for everyday laptop and production apply. Turn it up for a single reproduction, capture the file, then turn it off.
Never leave TRACE on in CI with secrets
TRACE (and often DEBUG) can include:
- provider credentials and session tokens,
- request headers and bodies,
- resource attributes that are secrets,
- backend credentials used to reach state.
If a pipeline exports TF_LOG=TRACE and the job log is world-readable (or shipped to a log vendor with broad access), you have published credentials. That is why 7c is also a safety question.
Practical rules for 004:
- Do not set
TF_LOG=TRACEas a standing variable in the CI image. - If you must capture a failing job, write to
TF_LOG_PATHon a restricted artifact, redact before sharing, and rotate any credentials that appeared. TF_LOG_PATHwithout access control is still a secret file.TF_LOG=JSONis still TRACE-level detail in another wrapper — same secret risk.- Unset the variables or set
TF_LOG=offwhen the debug run ends.
TF_IN_AUTOMATION only changes cosmetic "what to run next" hints. It does not enable logging and it does not redact TRACE. Do not confuse the two variables.
Scenario: the 403 that was not a VPC bug
Priya's apply fails: the AWS provider reports AccessDenied on CreateRoute. A teammate wants to apply -replace the route table. Priya instead sets TF_LOG_PROVIDER=DEBUG and TF_LOG_PATH=./aws.log with TF_LOG enabled, reproduces once, and turns logging off. The provider log shows the request used the wrong account alias, not a bad CIDR. She fixes the aliased provider credentials. She never commits aws.log. She never leaves TF_LOG=TRACE in the GitHub Actions env: block, because that workflow prints logs to a public fork pull request.
If the log had shown a Terraform core panic before any AWS HTTP, she would have captured TF_LOG_CORE=TRACE and opened a Terraform issue with that gist — the debugging page's intended use.
004 traps for objective 7c
- Levels are
TRACE,DEBUG,INFO,WARN,ERROR— notVERBOSE/SILENT. TF_LOG_PATHwithoutTF_LOGwrites nothing.TF_LOG_COREversusTF_LOG_PROVIDERisolates Terraform versus the plugin.TF_LOG=JSONis unstable TRACE-shaped JSON, notterraform show -json.- TRACE belongs in a controlled file for a single reproduction, not in standing CI.
- Logging does not apply infrastructure and does not format configuration.
In official Terraform 1.12 docs, what are the TF_LOG levels in order of decreasing verbosity?
VERBOSE, DEFAULT, QUIET
TRACE, DEBUG, INFO, WARN, ERROR
TRACE, INFO, SILENT
DEBUG, PLAN, APPLY, ERROR
A provider returns HTTP 403 and you want plugin logs, not a full Terraform core TRACE. Which environment setup matches HashiCorp's split?
Set only TF_LOG=off and TF_LOG_PATH=trace so a file appears automatically
Set TF_LOG_CORE=TRACE and leave provider logging off to see the AWS HTTP bodies
Set TF_LOG_PROVIDER to DEBUG or TRACE so provider plugin logs are enabled separately from core
Set TF_PLUGIN_CACHE_DIR=DEBUG to raise provider verbosity
Why should a Terraform 1.12 pipeline not leave TF_LOG=TRACE enabled in CI that publishes job logs?
TRACE disables state locking, so two jobs can apply at once
TRACE upgrades providers past the lock file on every init
TRACE is ignored unless TF_IN_AUTOMATION is also set
TRACE logs can include credentials and other secrets; write them only to a controlled TF_LOG_PATH if needed, then turn logging off
Sections you finish are checked off in the contents.