Writing Log Output
Use the tflog
package to write logs for your provider. SDKs like the terraform-plugin-framework
, terraform-plugin-go
, and terraform-plugin-sdk/v2
set up logging for you, so you only need to write the logs themselves. You can write log output at varying verbosity levels, add variables to logs, and create subsystems to group logs that relate to distinct sections of code (e.g., the API client).
Warning: Do not use fmt.Println
and similar methods to log. They will silently fail, making the output unavailable.
Structured Logging
The tflog
package uses structured logging, based on go-hclog
. Rather than writing logs as sentences with embedded variables and values, tflog
takes a sentence describing the logging event and a set of variables to log. When variables are separate from the log description, you can use them to programmatically parse, filter, and search log output. This separation also allows other parts of the system to associate variables with downstream log output.
Log Levels
You must choose a verbosity level for each line of log output. This lets consumers specify a type of log output to write from your provider. For example, you can use environment variables to set your provider to write only logs of type Warn
during a Terraform run.
Error
The least verbose output that typically describes an unexpected condition prior to halting execution. It often provides more information about a user-facing error.
tflog.Error(ctx, "Unrecognized API response body")
Warn
Output that describes an unexpected condition, but not one that should halt execution. It often includes deprecations or environment issues.
tflog.Warn(ctx, "Retrying due to API server-side error")
Info
Output that describes a certain logic condition or event. It often includes details about the environment your provider is running in or how it has been configured to run.
tflog.Info(ctx, "Using API token for authentication")
Debug
Verbose output that typically describes important operational details like milestones in logic. It often describes behaviors that may confusing even though they are correct.
tflog.Debug(ctx, "Two identical diagnostics in the response, deduplicating down to one")
Trace
The most verbose output that describes the lowest level operational details, such as intra-function steps or raw data.
tflog.Trace(ctx, "Creating the widget")
Variables
Use variables to attach information to specific logs or to an entire logger, which adds that information to all subsequent logs. You can also combine both methods to simplify your logging code.
Single Log Variables
To specify filterable variables in the log output, add a map of additional fields after the log message.
The following example adds both a URL and a method for an API request.
tflog.Trace(ctx, "executing API request", map[string]interface{}{
"url": "https://www.example.com/my/endpoint",
"method": "POST",
})
You can also use other standard Go types for values.
tflog.Trace(ctx, "executing API request", map[string]interface{}{
"url": "https://www.example.com/my/endpoint",
"method": "POST",
"size": 200,
"authenticated": true,
"headers": map[string][]string{"content-type": []string{"application/json"}},
})
Multiple Log Variables
Loggers are transported using a context.Context
type, so injecting a variable into a logger returns a new context.Context
containing the modified logger. Subsequent calls to tflog
with that logger will implicitly include the variable.
Use tflog.With()
to attach variables to a logger.
// overwrite logger to include new `url` variable
ctx = tflog.With(ctx, "url", "https://www.example.com/my/endpoint")
// will include the `url` variable
tflog.Debug(ctx, "Calling API")
You can also conditionally attach the variables by creating a new context and injecting it into the logger.
// create new logger that includes `url` variable
apiContext := tflog.With(ctx, "url", "https://www.example.com/my/endpoint")
// will not include the `url` variable
tflog.Debug(ctx, "Calling database")
// will include the `url` variable
tflog.Debug(apiContext, "Calling API")
Subsystems
You can create a subsystem to manage loggers for sections of code that are large, complex, or have distinct functionality. You can then configure environment variables that allow each subsystem to be included or excluded from log output. For example, you may want to create a subsystem for logs that relate to the API client so that you can turn them off when when debugging an unrelated issue.
Create Subsystems
To create a new subsystem, pass context and the subsystem name to the NewSubsystem()
method.
// my-subsystem is the name of the logging subsystem
// It will be available to subsequent calls via the tflog.Subsystem* functions.
ctx = tflog.NewSubsystem(ctx, "my-subsystem")
Optionally, specify a log level for the subsystem.
ctx = tflog.NewSubsystem(ctx, "my-subsystem", hclog.Debug)
You can also create an environment variable to control the logging level instead of hardcoding it into the subsystem.
// read the level from TF_LOG_PROVIDER_MYPROVIDER_CLIENT
ctx = tflog.NewSubsystem(ctx, "my-subsystem",
tflog.WithLevelFromEnv("TF_LOG_PROVIDER_MYPROVIDER_CLIENT"))
Use Subsystems
Logging or adding variables to subsystem loggers requires separate function calls for each log level:
tflog.SubsystemError()
: Equivalent totflog.Error()
, but using a subsystem logger.tflog.SubsystemWarn()
: Equivalent totflog.Warn()
, but using a subsystem logger.tflog.SubsystemInfo()
: Equivalent totflog.Info()
, but using a subsystem logger.tflog.SubsystemDebug()
: Equivalent totflog.Debug()
, but using a subsystem logger.tflog.SubsystemTrace()
: Equivalent totflog.Trace()
, but using a subsystem logger.tflog.SubsystemWith()
: Equivalent totflog.With()
, but using a subsystem logger.
For example, use tflog.SubsystemDebug()
to write a debug level log with a specific subsystem.
tflog.SubsystemDebug(ctx, "my-subsystem", "writing to a subsystem", map[string]interface{}{
"meaning_of_life": 42,
})
Use tflog.SubsystemWith()
to attach variables to a specific subsystem.
// overwrite logger to include new `url` variable
ctx = tflog.SubsystemWith(ctx, "my-subsystem", "url", "https://www.example.com/my/endpoint")
// will include the `url` variable
tflog.SubsystemDebug(ctx, "my-subsystem", "Calling API")
You can also conditionally attach variables by creating a new context and injecting it into the logger.
// create new logger that includes `url` variable
apiContext := tflog.SubsystemWith(ctx, "my-subsystem", "url", "https://www.example.com/my/endpoint")
// will not include the `url` variable
tflog.SubsystemDebug(ctx, "my-subsystem", "Calling database")
// will include the `url` variable
tflog.SubsystemDebug(apiContext, "my-subsystem", "Calling API")