153 lines
4.7 KiB
Go
153 lines
4.7 KiB
Go
|
package tfsdklog
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"syscall"
|
||
|
|
||
|
"github.com/hashicorp/go-hclog"
|
||
|
"github.com/hashicorp/terraform-plugin-log/internal/logging"
|
||
|
testing "github.com/mitchellh/go-testing-interface"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// envLog is the environment variable that users can set to control the
|
||
|
// least-verbose level of logs that will be output during testing. If
|
||
|
// this environment variable is set, it will default to off. This is
|
||
|
// just the default; specific loggers and sub-loggers can set a lower
|
||
|
// or higher verbosity level without a problem right now. In theory,
|
||
|
// they should not be able to.
|
||
|
//
|
||
|
// Valid values are TRACE, DEBUG, INFO, WARN, ERROR, and OFF. A special
|
||
|
// pseudo-value, JSON, will set the value to TRACE and output the
|
||
|
// results in their JSON format.
|
||
|
envLog = "TF_LOG"
|
||
|
|
||
|
// envLogFile is the environment variable that controls where log
|
||
|
// output is written during tests. By default, logs will be written to
|
||
|
// standard error. Setting this environment variable to another file
|
||
|
// path will write logs there instead during tests.
|
||
|
envLogFile = "TF_LOG_PATH"
|
||
|
|
||
|
// envAccLogFile is the environment variable that controls where log
|
||
|
// output from the provider under test and the Terraform binary (and
|
||
|
// other providers) will be written during tests. Setting this
|
||
|
// environment variable to a file will combine all log output in that
|
||
|
// file. If both this environment variable and TF_LOG_PATH are set,
|
||
|
// this environment variable will take precedence.
|
||
|
envAccLogFile = "TF_ACC_LOG_PATH"
|
||
|
|
||
|
// envLogPathMask is the environment variable that controls per-test
|
||
|
// logging output. It should be set to a fmt-compatible string, where a
|
||
|
// single %s will be replaced with the test name, and the log output
|
||
|
// for that test (and only that test) will be written to that file.
|
||
|
// Setting this environment variable will override TF_LOG_PATH.
|
||
|
// Only the logs for the provider under test are included.
|
||
|
envLogPathMask = "TF_LOG_PATH_MASK"
|
||
|
)
|
||
|
|
||
|
// ValidLevels are the string representations of levels that can be set for
|
||
|
// loggers.
|
||
|
var ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}
|
||
|
|
||
|
// Only show invalid log level message once across any number of level lookups.
|
||
|
var invalidLogLevelMessage sync.Once
|
||
|
|
||
|
// RegisterTestSink sets up a logging sink, for use with test frameworks and
|
||
|
// other cases where plugin logs don't get routed through Terraform. This
|
||
|
// applies the same filtering and file output behaviors that Terraform does.
|
||
|
//
|
||
|
// RegisterTestSink should only ever be called by test frameworks, providers
|
||
|
// should never call it.
|
||
|
//
|
||
|
// RegisterTestSink must be called prior to any loggers being setup or
|
||
|
// instantiated.
|
||
|
func RegisterTestSink(ctx context.Context, t testing.T) context.Context {
|
||
|
logger, loggerOptions := newSink(t)
|
||
|
|
||
|
ctx = logging.SetSink(ctx, logger)
|
||
|
ctx = logging.SetSinkOptions(ctx, loggerOptions)
|
||
|
|
||
|
return ctx
|
||
|
}
|
||
|
|
||
|
func newSink(t testing.T) (hclog.Logger, *hclog.LoggerOptions) {
|
||
|
logOutput := io.Writer(os.Stderr)
|
||
|
var json bool
|
||
|
var logLevel hclog.Level
|
||
|
var logFile string
|
||
|
|
||
|
envLevel := strings.ToUpper(os.Getenv(envLog))
|
||
|
|
||
|
// if TF_LOG_PATH is set, output logs there
|
||
|
if logPath := os.Getenv(envLogFile); logPath != "" {
|
||
|
logFile = logPath
|
||
|
}
|
||
|
|
||
|
// if TF_ACC_LOG_PATH is set, output logs there instead
|
||
|
if logPath := os.Getenv(envAccLogFile); logPath != "" {
|
||
|
logFile = logPath
|
||
|
// helper/resource makes this default to TRACE, so we should,
|
||
|
// too
|
||
|
envLevel = "TRACE"
|
||
|
}
|
||
|
|
||
|
// if TF_LOG_PATH_MASK is set, use a test-name specific logging file,
|
||
|
// instead
|
||
|
if logPathMask := os.Getenv(envLogPathMask); logPathMask != "" {
|
||
|
testName := strings.Replace(t.Name(), "/", "__", -1)
|
||
|
logFile = fmt.Sprintf(logPathMask, testName)
|
||
|
}
|
||
|
|
||
|
if logFile != "" {
|
||
|
f, err := os.OpenFile(logFile, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666)
|
||
|
if err != nil {
|
||
|
fmt.Fprintf(os.Stderr, "Error opening log file: %v\n", err)
|
||
|
} else {
|
||
|
logOutput = f
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if TF_LOG is set, set the level
|
||
|
if envLevel == "" {
|
||
|
logLevel = hclog.Off
|
||
|
} else if envLevel == "JSON" {
|
||
|
logLevel = hclog.Trace
|
||
|
json = true
|
||
|
} else if isValidLogLevel(envLevel) {
|
||
|
logLevel = hclog.LevelFromString(envLevel)
|
||
|
} else {
|
||
|
invalidLogLevelMessage.Do(func() {
|
||
|
fmt.Fprintf(
|
||
|
os.Stderr,
|
||
|
"[WARN] Invalid log level: %q. Defaulting to level: OFF. Valid levels are: %+v\n",
|
||
|
envLevel,
|
||
|
ValidLevels,
|
||
|
)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
loggerOptions := &hclog.LoggerOptions{
|
||
|
Level: logLevel,
|
||
|
Output: logOutput,
|
||
|
IndependentLevels: true,
|
||
|
JSONFormat: json,
|
||
|
}
|
||
|
|
||
|
return hclog.New(loggerOptions), loggerOptions
|
||
|
}
|
||
|
|
||
|
func isValidLogLevel(level string) bool {
|
||
|
for _, validLevel := range ValidLevels {
|
||
|
if level == validLevel {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|