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
flowchart TD
Fail["Unexpected CLI or apply failure"] --> Decide{"Where does it smell?"}
Decide -->|Cloud API / plugin| Prov["TF_LOG_PROVIDER=DEBUG or TRACE"]
Decide -->|Graph, backend, language| Core["TF_LOG_CORE=DEBUG or TRACE"]
Decide -->|Not sure| Both["TF_LOG=TRACE"]
Prov --> Path["TF_LOG_PATH=controlled file"]
Core --> Path
Both --> Path
Path --> Capture["Reproduce once"]
Capture --> Off["TF_LOG=off; restrict or delete the file"]
Off --> Bug["Attach TRACE to a HashiCorp/provider bug report if needed"]
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?
A provider returns HTTP 403 and you want plugin logs, not a full Terraform core TRACE. Which environment setup matches HashiCorp's split?
Why should a Terraform 1.12 pipeline not leave TF_LOG=TRACE enabled in CI that publishes job logs?