github.com/atc0005/elbow@v0.8.8/internal/logging/logging.go (about) 1 // Copyright 2020 Adam Chalkley 2 // 3 // https://github.com/atc0005/elbow 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // https://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 // Package logging is intended mostly as a set of helper functions around 18 // configuring and using a common logger to provide structured, leveled 19 // logging. 20 package logging 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "runtime" 27 "strings" 28 29 "github.com/sirupsen/logrus" 30 ) 31 32 // Buffer is a package global instance of LogBuffer intended to ease log 33 // message collection for later emission when all required logger settings 34 // have been applied. 35 // 36 // FIXME: Moved here from Config package What is the best approach to handling 37 // this instead of using a package global? 38 var Buffer LogBuffer 39 40 // LogRecord holds logrus.Field values along with additional metadata that can be 41 // used later to complete the log message submission process. 42 type LogRecord struct { 43 Level logrus.Level 44 Message string 45 Fields logrus.Fields 46 } 47 48 // LogBuffer represents a slice of LogRecord objects 49 type LogBuffer []LogRecord 50 51 // Add passed LogRecord type to slice of LogRecord objects 52 func (lb *LogBuffer) Add(r LogRecord) { 53 *lb = append(*lb, r) 54 } 55 56 // Flush LogRecord entries after applying user-provided logging settings 57 func (lb *LogBuffer) Flush(logger *logrus.Logger) error { 58 59 // Check for nil *logrus.Logger before attempting to use it. 60 if logger == nil { 61 return fmt.Errorf("nil logger received by LogBuffer.Flush()") 62 } 63 64 for _, entry := range *lb { 65 66 switch { 67 68 case entry.Level == logrus.PanicLevel: 69 logger.WithFields(entry.Fields).Panic(entry.Message) 70 71 case entry.Level == logrus.FatalLevel: 72 logger.WithFields(entry.Fields).Fatal(entry.Message) 73 74 case entry.Level == logrus.ErrorLevel: 75 logger.WithFields(entry.Fields).Error(entry.Message) 76 77 case entry.Level == logrus.WarnLevel: 78 logger.WithFields(entry.Fields).Warn(entry.Message) 79 80 case entry.Level == logrus.InfoLevel: 81 logger.WithFields(entry.Fields).Info(entry.Message) 82 83 case entry.Level == logrus.DebugLevel: 84 logger.WithFields(entry.Fields).Debug(entry.Message) 85 86 case entry.Level == logrus.TraceLevel: 87 logger.WithFields(entry.Fields).Trace(entry.Message) 88 89 default: 90 return fmt.Errorf("unhandled codepath; invalid option provided for entry.Level: %v", entry.Level) 91 92 } 93 94 } 95 96 // Empty slice now that we're done processing all items 97 // https://yourbasic.org/golang/clear-slice/ 98 *lb = nil 99 100 // indicate no errors were encountered 101 return nil 102 } 103 104 // SetLoggerFormatter sets a user-specified logging format for the provided 105 // logger object. 106 func SetLoggerFormatter(logger *logrus.Logger, format string) error { 107 switch format { 108 case LogFormatText: 109 logger.SetFormatter(&logrus.TextFormatter{}) 110 case LogFormatJSON: 111 // Log as JSON instead of the default ASCII formatter. 112 logger.SetFormatter(&logrus.JSONFormatter{}) 113 default: 114 return fmt.Errorf("invalid option provided: %v", format) 115 } 116 117 return nil 118 } 119 120 // SetLoggerConsoleOutput configures the chosen console output to one of 121 // stdout or stderr. 122 func SetLoggerConsoleOutput(logger *logrus.Logger, consoleOutput string) error { 123 124 switch consoleOutput { 125 case ConsoleOutputStdout: 126 logger.SetOutput(os.Stdout) 127 case ConsoleOutputStderr: 128 logger.SetOutput(os.Stderr) 129 default: 130 return fmt.Errorf("invalid option provided: %v", consoleOutput) 131 } 132 133 return nil 134 135 } 136 137 // SetLoggerLogFile configures a log file as the destination for all log 138 // messages. NOTE: If successfully set, console output is muted. 139 func SetLoggerLogFile(logger *logrus.Logger, logFilePath string) (*os.File, error) { 140 141 var file *os.File 142 var err error 143 144 if strings.TrimSpace(logFilePath) != "" { 145 // If this is set, do not log to console unless writing to log file fails 146 // FIXME: How do we defer the file close without killing the file handle? 147 // https://github.com/sirupsen/logrus/blob/de736cf91b921d56253b4010270681d33fdf7cb5/logger.go#L332 148 file, err = os.OpenFile( 149 filepath.Clean(logFilePath), 150 os.O_CREATE|os.O_WRONLY|os.O_APPEND, 151 0600, 152 ) 153 if err != nil { 154 return nil, fmt.Errorf("failed to log to %s, will leave configuration as is", 155 logFilePath) 156 } 157 // The `file` handle is what we'll use to close the file handle from main() 158 // https://kgrz.io/reading-files-in-go-an-overview.html 159 logger.SetOutput(file) 160 } 161 162 return file, nil 163 } 164 165 // SetLoggerLevel applies the requested logger level to filter out messages 166 // with a lower level than the one configured. 167 func SetLoggerLevel(logger *logrus.Logger, logLevel string) error { 168 169 // https://godoc.org/github.com/sirupsen/logrus#Level 170 // https://golang.org/pkg/log/syslog/#Priority 171 // https://en.wikipedia.org/wiki/Syslog#Severity_level 172 switch logLevel { 173 case LogLevelEmergency, LogLevelPanic: 174 logger.SetLevel(logrus.PanicLevel) 175 case LogLevelAlert, LogLevelCritical, LogLevelFatal: 176 logger.SetLevel(logrus.FatalLevel) 177 case LogLevelError: 178 logger.SetLevel(logrus.ErrorLevel) 179 case LogLevelWarn, LogLevelNotice: 180 logger.SetLevel(logrus.WarnLevel) 181 case LogLevelInfo: 182 logger.SetLevel(logrus.InfoLevel) 183 case LogLevelDebug: 184 logger.SetLevel(logrus.DebugLevel) 185 case LogLevelTrace: 186 logger.SetLevel(logrus.TraceLevel) 187 default: 188 return fmt.Errorf("invalid option provided: %v", logLevel) 189 } 190 191 // signal that a case was triggered as expected 192 return nil 193 194 } 195 196 // GetLineNumber is a wrapper around runtime.Caller to return only the current 197 // line number from the point this function was called. 198 // TODO: Find a better location for this utility function 199 func GetLineNumber() int { 200 // TODO: How else to retrieve only the one value that I need? See GH-237. 201 _, _, line, _ := runtime.Caller(1) // nolint:dogsled 202 return line 203 }