separate core and provider loggers

Now that hclog can independently set levels on related loggers, we can
separate the log levels for different subsystems in terraform.

This adds the new environment variables, `TF_LOG_CORE` and
`TF_LOG_PROVIDER`, which each take the same set of log level arguments,
and only applies to logs from that subsystem. This means that setting
`TF_LOG_CORE=level` will not show logs from providers, and
`TF_LOG_PROVIDER=level` will not show logs from core. The behavior of
`TF_LOG` alone does not change.

While it is not necessarily needed since the default is to disable logs,
there is also a new level argument of `off`, which reflects the
associated level in hclog.
This commit is contained in:
James Bardin 2020-10-22 15:32:13 -04:00
parent b4f9c54c4d
commit f8893785f0
5 changed files with 72 additions and 33 deletions

View File

@ -335,7 +335,7 @@ func providerFactory(meta *providercache.CachedProvider) providers.Factory {
config := &plugin.ClientConfig{ config := &plugin.ClientConfig{
HandshakeConfig: tfplugin.Handshake, HandshakeConfig: tfplugin.Handshake,
Logger: logging.NewProviderLogger(), Logger: logging.NewProviderLogger(""),
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
Managed: true, Managed: true,
Cmd: exec.Command(execFile), Cmd: exec.Command(execFile),
@ -383,7 +383,7 @@ func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.Reattach
config := &plugin.ClientConfig{ config := &plugin.ClientConfig{
HandshakeConfig: tfplugin.Handshake, HandshakeConfig: tfplugin.Handshake,
Logger: logging.NewHCLogger("unmanaged-plugin"), Logger: logging.NewProviderLogger("unmanaged."),
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
Managed: false, Managed: false,
Reattach: reattach, Reattach: reattach,

View File

@ -169,7 +169,7 @@ func internalPluginClient(kind, name string) (*plugin.Client, error) {
VersionedPlugins: tfplugin.VersionedPlugins, VersionedPlugins: tfplugin.VersionedPlugins,
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
AutoMTLS: enableProviderAutoMTLS, AutoMTLS: enableProviderAutoMTLS,
Logger: logging.NewProviderLogger(), Logger: logging.NewLogger(kind),
} }
return plugin.NewClient(cfg), nil return plugin.NewClient(cfg), nil
@ -182,7 +182,7 @@ func provisionerFactory(meta discovery.PluginMeta) terraform.ProvisionerFactory
HandshakeConfig: tfplugin.Handshake, HandshakeConfig: tfplugin.Handshake,
VersionedPlugins: tfplugin.VersionedPlugins, VersionedPlugins: tfplugin.VersionedPlugins,
Managed: true, Managed: true,
Logger: logging.NewHCLogger("provisioner"), Logger: logging.NewLogger("provisioner"),
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
AutoMTLS: enableProviderAutoMTLS, AutoMTLS: enableProviderAutoMTLS,
} }

View File

@ -3,7 +3,6 @@ package logging
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"strings" "strings"
@ -15,12 +14,18 @@ import (
// These are the environmental variables that determine if we log, and if // These are the environmental variables that determine if we log, and if
// we log whether or not the log should go to a file. // we log whether or not the log should go to a file.
const ( const (
EnvLog = "TF_LOG" // Set to True envLog = "TF_LOG"
EnvLogFile = "TF_LOG_PATH" // Set to a file 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"
) )
// ValidLevels are the log level names that Terraform recognizes. // ValidLevels are the log level names that Terraform recognizes.
var ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"} var ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}
// logger is the global hclog logger // logger is the global hclog logger
var logger hclog.Logger var logger hclog.Logger
@ -29,7 +34,7 @@ var logger hclog.Logger
var logWriter io.Writer var logWriter io.Writer
func init() { func init() {
logger = NewHCLogger("") logger = newHCLogger("")
logWriter = logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true}) logWriter = logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true})
// setup the default std library logger to use our output // setup the default std library logger to use our output
@ -65,15 +70,12 @@ func HCLogger() hclog.Logger {
return logger return logger
} }
// NewHCLogger returns a new hclog.Logger instance with the given name // newHCLogger returns a new hclog.Logger instance with the given name
func NewHCLogger(name string) hclog.Logger { func newHCLogger(name string) hclog.Logger {
logOutput := io.Writer(os.Stderr) logOutput := io.Writer(os.Stderr)
logLevel := CurrentLogLevel() logLevel := globalLogLevel()
if logLevel == "" {
logOutput = ioutil.Discard
}
if logPath := os.Getenv(EnvLogFile); logPath != "" { if logPath := os.Getenv(envLogFile); logPath != "" {
f, err := os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) f, err := os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error opening log file: %v\n", err) fmt.Fprintf(os.Stderr, "Error opening log file: %v\n", err)
@ -83,29 +85,68 @@ func NewHCLogger(name string) hclog.Logger {
} }
return hclog.NewInterceptLogger(&hclog.LoggerOptions{ return hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: name, Name: name,
Level: hclog.LevelFromString(logLevel), Level: logLevel,
Output: logOutput, Output: logOutput,
IndependentLevels: true,
}) })
} }
func NewProviderLogger() hclog.Logger { // NewLogger returns a new logger based in the current global logger, with the
return logger.Named("plugin") // given name appended.
func NewLogger(name string) hclog.Logger {
if name == "" {
panic("logger name required")
}
return 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 := 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 // CurrentLogLevel returns the current log level string based the environment vars
func CurrentLogLevel() string { func CurrentLogLevel() string {
envLevel := strings.ToUpper(os.Getenv(EnvLog)) return strings.ToUpper(globalLogLevel().String())
if envLevel == "" { }
return ""
func providerLogLevel() hclog.Level {
providerEnvLevel := strings.ToUpper(os.Getenv(envLogProvider))
if providerEnvLevel == "" {
providerEnvLevel = strings.ToUpper(os.Getenv(envLog))
} }
logLevel := "TRACE" 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) { if isValidLogLevel(envLevel) {
logLevel = envLevel logLevel = hclog.LevelFromString(envLevel)
} else { } else {
logger.Warn(fmt.Sprintf("Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v", fmt.Fprintf(os.Stderr, "[WARN] Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v",
envLevel, ValidLevels)) envLevel, ValidLevels)
} }
return logLevel return logLevel
@ -113,8 +154,8 @@ func CurrentLogLevel() string {
// IsDebugOrHigher returns whether or not the current log level is debug or trace // IsDebugOrHigher returns whether or not the current log level is debug or trace
func IsDebugOrHigher() bool { func IsDebugOrHigher() bool {
level := string(CurrentLogLevel()) level := globalLogLevel()
return level == "DEBUG" || level == "TRACE" return level == hclog.Debug || level == hclog.Trace
} }
func isValidLogLevel(level string) bool { func isValidLogLevel(level string) bool {

View File

@ -5,7 +5,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"strings"
"github.com/mitchellh/panicwrap" "github.com/mitchellh/panicwrap"
) )
@ -67,6 +66,6 @@ func PanicHandler(tmpLogPath string) panicwrap.HandlerFunc {
// Tell the user a crash occurred in some helpful way that // Tell the user a crash occurred in some helpful way that
// they'll hopefully notice. // they'll hopefully notice.
fmt.Printf("\n\n") fmt.Printf("\n\n")
fmt.Printf(strings.TrimSpace(panicOutput), f.Name()) fmt.Printf(panicOutput, f.Name())
} }
} }

View File

@ -95,7 +95,6 @@ func wrappedMain() int {
if tmpLogPath != "" { if tmpLogPath != "" {
f, err := os.OpenFile(tmpLogPath, os.O_RDWR|os.O_APPEND, 0666) f, err := os.OpenFile(tmpLogPath, os.O_RDWR|os.O_APPEND, 0666)
if err == nil { if err == nil {
defer os.Remove(f.Name())
defer f.Close() defer f.Close()
log.Printf("[DEBUG] Adding temp file log sink: %s", f.Name()) log.Printf("[DEBUG] Adding temp file log sink: %s", f.Name())