mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
3225d9ac11
Create a logger that will record any apparent crash output for later processing. If the cli command returns with a non-zero exit status, check for any recorded crashes and add those to the output.
182 lines
4.3 KiB
Go
182 lines
4.3 KiB
Go
package logging
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
)
|
|
|
|
// These are the environmental variables that determine if we log, and if
|
|
// we log whether or not the log should go to a file.
|
|
const (
|
|
envLog = "TF_LOG"
|
|
envLogFile = "TF_LOG_PATH"
|
|
|
|
// Allow logging of specific subsystems.
|
|
// We only separate core and providers for now, but this could be extended
|
|
// to other loggers, like provisioners and remote-state backends.
|
|
envLogCore = "TF_LOG_CORE"
|
|
envLogProvider = "TF_LOG_PROVIDER"
|
|
)
|
|
|
|
var (
|
|
// ValidLevels are the log level names that Terraform recognizes.
|
|
ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}
|
|
|
|
// logger is the global hclog logger
|
|
logger hclog.Logger
|
|
|
|
// logWriter is a global writer for logs, to be used with the std log package
|
|
logWriter io.Writer
|
|
|
|
// initialize our cache of panic output from providers
|
|
panics = &panicRecorder{
|
|
panics: make(map[string][]string),
|
|
maxLines: 100,
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
logger = newHCLogger("")
|
|
logWriter = logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true})
|
|
|
|
// setup the default std library logger to use our output
|
|
log.SetFlags(0)
|
|
log.SetPrefix("")
|
|
log.SetOutput(logWriter)
|
|
}
|
|
|
|
// SetupTempLog adds a new log sink which writes all logs to the given file.
|
|
func RegisterSink(f *os.File) {
|
|
l, ok := logger.(hclog.InterceptLogger)
|
|
if !ok {
|
|
panic("global logger is not an InterceptLogger")
|
|
}
|
|
|
|
if f == nil {
|
|
return
|
|
}
|
|
|
|
l.RegisterSink(hclog.NewSinkAdapter(&hclog.LoggerOptions{
|
|
Level: hclog.Trace,
|
|
Output: f,
|
|
}))
|
|
}
|
|
|
|
// LogOutput return the default global log io.Writer
|
|
func LogOutput() io.Writer {
|
|
return logWriter
|
|
}
|
|
|
|
// HCLogger returns the default global hclog logger
|
|
func HCLogger() hclog.Logger {
|
|
return logger
|
|
}
|
|
|
|
// newHCLogger returns a new hclog.Logger instance with the given name
|
|
func newHCLogger(name string) hclog.Logger {
|
|
logOutput := io.Writer(os.Stderr)
|
|
logLevel := globalLogLevel()
|
|
|
|
if logPath := os.Getenv(envLogFile); logPath != "" {
|
|
f, err := os.OpenFile(logPath, 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
|
|
}
|
|
}
|
|
|
|
return hclog.NewInterceptLogger(&hclog.LoggerOptions{
|
|
Name: name,
|
|
Level: logLevel,
|
|
Output: logOutput,
|
|
IndependentLevels: true,
|
|
})
|
|
}
|
|
|
|
// NewLogger returns a new logger based in the current global logger, with the
|
|
// given name appended.
|
|
func NewLogger(name string) hclog.Logger {
|
|
if name == "" {
|
|
panic("logger name required")
|
|
}
|
|
return &logPanicWrapper{
|
|
Logger: logger.Named(name),
|
|
}
|
|
}
|
|
|
|
// NewProviderLogger returns a logger for the provider plugin, possibly with a
|
|
// different log level from the global logger.
|
|
func NewProviderLogger(prefix string) hclog.Logger {
|
|
l := &logPanicWrapper{
|
|
Logger: logger.Named(prefix + "provider"),
|
|
}
|
|
|
|
level := providerLogLevel()
|
|
logger.Debug("created provider logger", "level", level)
|
|
|
|
l.SetLevel(level)
|
|
return l
|
|
}
|
|
|
|
// CurrentLogLevel returns the current log level string based the environment vars
|
|
func CurrentLogLevel() string {
|
|
return strings.ToUpper(globalLogLevel().String())
|
|
}
|
|
|
|
func providerLogLevel() hclog.Level {
|
|
providerEnvLevel := strings.ToUpper(os.Getenv(envLogProvider))
|
|
if providerEnvLevel == "" {
|
|
providerEnvLevel = strings.ToUpper(os.Getenv(envLog))
|
|
}
|
|
|
|
return parseLogLevel(providerEnvLevel)
|
|
}
|
|
|
|
func globalLogLevel() hclog.Level {
|
|
envLevel := strings.ToUpper(os.Getenv(envLog))
|
|
if envLevel == "" {
|
|
envLevel = strings.ToUpper(os.Getenv(envLogCore))
|
|
|
|
}
|
|
return parseLogLevel(envLevel)
|
|
}
|
|
|
|
func parseLogLevel(envLevel string) hclog.Level {
|
|
if envLevel == "" {
|
|
return hclog.Off
|
|
}
|
|
|
|
logLevel := hclog.Trace
|
|
if isValidLogLevel(envLevel) {
|
|
logLevel = hclog.LevelFromString(envLevel)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "[WARN] Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v",
|
|
envLevel, ValidLevels)
|
|
}
|
|
|
|
return logLevel
|
|
}
|
|
|
|
// IsDebugOrHigher returns whether or not the current log level is debug or trace
|
|
func IsDebugOrHigher() bool {
|
|
level := globalLogLevel()
|
|
return level == hclog.Debug || level == hclog.Trace
|
|
}
|
|
|
|
func isValidLogLevel(level string) bool {
|
|
for _, l := range ValidLevels {
|
|
if level == string(l) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|