1package logging 2 3import ( 4 "fmt" 5 "io" 6 "log" 7 "os" 8 "strings" 9 "syscall" 10 11 // go.etcd.io/etcd imports capnslog, which calls log.SetOutput in its 12 // init() function, so importing it here means that our log.SetOutput 13 // wins. this is fixed in coreos v3.5, which is not released yet. See 14 // https://github.com/etcd-io/etcd/issues/12498 for more information. 15 _ "github.com/coreos/pkg/capnslog" 16 "github.com/hashicorp/go-hclog" 17) 18 19// These are the environmental variables that determine if we log, and if 20// we log whether or not the log should go to a file. 21const ( 22 envLog = "TF_LOG" 23 envLogFile = "TF_LOG_PATH" 24 25 // Allow logging of specific subsystems. 26 // We only separate core and providers for now, but this could be extended 27 // to other loggers, like provisioners and remote-state backends. 28 envLogCore = "TF_LOG_CORE" 29 envLogProvider = "TF_LOG_PROVIDER" 30) 31 32var ( 33 // ValidLevels are the log level names that Terraform recognizes. 34 ValidLevels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"} 35 36 // logger is the global hclog logger 37 logger hclog.Logger 38 39 // logWriter is a global writer for logs, to be used with the std log package 40 logWriter io.Writer 41 42 // initialize our cache of panic output from providers 43 panics = &panicRecorder{ 44 panics: make(map[string][]string), 45 maxLines: 100, 46 } 47) 48 49func init() { 50 logger = newHCLogger("") 51 logWriter = logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true}) 52 53 // set up the default std library logger to use our output 54 log.SetFlags(0) 55 log.SetPrefix("") 56 log.SetOutput(logWriter) 57} 58 59// SetupTempLog adds a new log sink which writes all logs to the given file. 60func RegisterSink(f *os.File) { 61 l, ok := logger.(hclog.InterceptLogger) 62 if !ok { 63 panic("global logger is not an InterceptLogger") 64 } 65 66 if f == nil { 67 return 68 } 69 70 l.RegisterSink(hclog.NewSinkAdapter(&hclog.LoggerOptions{ 71 Level: hclog.Trace, 72 Output: f, 73 })) 74} 75 76// LogOutput return the default global log io.Writer 77func LogOutput() io.Writer { 78 return logWriter 79} 80 81// HCLogger returns the default global hclog logger 82func HCLogger() hclog.Logger { 83 return logger 84} 85 86// newHCLogger returns a new hclog.Logger instance with the given name 87func newHCLogger(name string) hclog.Logger { 88 logOutput := io.Writer(os.Stderr) 89 logLevel, json := globalLogLevel() 90 91 if logPath := os.Getenv(envLogFile); logPath != "" { 92 f, err := os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) 93 if err != nil { 94 fmt.Fprintf(os.Stderr, "Error opening log file: %v\n", err) 95 } else { 96 logOutput = f 97 } 98 } 99 100 return hclog.NewInterceptLogger(&hclog.LoggerOptions{ 101 Name: name, 102 Level: logLevel, 103 Output: logOutput, 104 IndependentLevels: true, 105 JSONFormat: json, 106 }) 107} 108 109// NewLogger returns a new logger based in the current global logger, with the 110// given name appended. 111func NewLogger(name string) hclog.Logger { 112 if name == "" { 113 panic("logger name required") 114 } 115 return &logPanicWrapper{ 116 Logger: logger.Named(name), 117 } 118} 119 120// NewProviderLogger returns a logger for the provider plugin, possibly with a 121// different log level from the global logger. 122func NewProviderLogger(prefix string) hclog.Logger { 123 l := &logPanicWrapper{ 124 Logger: logger.Named(prefix + "provider"), 125 } 126 127 level := providerLogLevel() 128 logger.Debug("created provider logger", "level", level) 129 130 l.SetLevel(level) 131 return l 132} 133 134// CurrentLogLevel returns the current log level string based the environment vars 135func CurrentLogLevel() string { 136 ll, _ := globalLogLevel() 137 return strings.ToUpper(ll.String()) 138} 139 140func providerLogLevel() hclog.Level { 141 providerEnvLevel := strings.ToUpper(os.Getenv(envLogProvider)) 142 if providerEnvLevel == "" { 143 providerEnvLevel = strings.ToUpper(os.Getenv(envLog)) 144 } 145 146 return parseLogLevel(providerEnvLevel) 147} 148 149func globalLogLevel() (hclog.Level, bool) { 150 var json bool 151 envLevel := strings.ToUpper(os.Getenv(envLog)) 152 if envLevel == "" { 153 envLevel = strings.ToUpper(os.Getenv(envLogCore)) 154 } 155 if envLevel == "JSON" { 156 json = true 157 } 158 return parseLogLevel(envLevel), json 159} 160 161func parseLogLevel(envLevel string) hclog.Level { 162 if envLevel == "" { 163 return hclog.Off 164 } 165 if envLevel == "JSON" { 166 envLevel = "TRACE" 167 } 168 169 logLevel := hclog.Trace 170 if isValidLogLevel(envLevel) { 171 logLevel = hclog.LevelFromString(envLevel) 172 } else { 173 fmt.Fprintf(os.Stderr, "[WARN] Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v", 174 envLevel, ValidLevels) 175 } 176 177 return logLevel 178} 179 180// IsDebugOrHigher returns whether or not the current log level is debug or trace 181func IsDebugOrHigher() bool { 182 level, _ := globalLogLevel() 183 return level == hclog.Debug || level == hclog.Trace 184} 185 186func isValidLogLevel(level string) bool { 187 for _, l := range ValidLevels { 188 if level == string(l) { 189 return true 190 } 191 } 192 193 return false 194} 195