github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/logging/logging.go (about) 1 package logging 2 3 import ( 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. 21 const ( 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 32 var ( 33 // ValidLevels are the log level names that Durgaform 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 49 func 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. 60 func 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 77 func LogOutput() io.Writer { 78 return logWriter 79 } 80 81 // HCLogger returns the default global hclog logger 82 func HCLogger() hclog.Logger { 83 return logger 84 } 85 86 // newHCLogger returns a new hclog.Logger instance with the given name 87 func 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. 111 func 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. 122 func 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 135 func CurrentLogLevel() string { 136 ll, _ := globalLogLevel() 137 return strings.ToUpper(ll.String()) 138 } 139 140 func 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 149 func 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 161 func 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 181 func IsDebugOrHigher() bool { 182 level, _ := globalLogLevel() 183 return level == hclog.Debug || level == hclog.Trace 184 } 185 186 func 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 196 // PluginOutputMonitor creates an io.Writer that will warn about any writes in 197 // the default logger. This is used to catch unexpected output from plugins, 198 // notifying them about the problem as well as surfacing the lost data for 199 // context. 200 func PluginOutputMonitor(source string) io.Writer { 201 return pluginOutputMonitor{ 202 source: source, 203 log: logger, 204 } 205 } 206 207 // pluginOutputMonitor is an io.Writer that logs all writes as 208 // "unexpected data" with the source name. 209 type pluginOutputMonitor struct { 210 source string 211 log hclog.Logger 212 } 213 214 func (w pluginOutputMonitor) Write(d []byte) (int, error) { 215 // Limit the write size to 1024 bytes We're not expecting any data to come 216 // through this channel, so accidental writes will usually be stray fmt 217 // debugging statements and the like, but we want to provide context to the 218 // provider to indicate what the unexpected data might be. 219 n := len(d) 220 if n > 1024 { 221 d = append(d[:1024], '.', '.', '.') 222 } 223 224 w.log.Warn("unexpected data", w.source, strings.TrimSpace(string(d))) 225 return n, nil 226 }