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  }