gitlab.com/ignitionrobotics/web/ign-go@v1.0.0-rc4/logger.go (about)

     1  package ign
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"github.com/rollbar/rollbar-go"
     7  	"log"
     8  	"net/http"
     9  )
    10  
    11  // VerbosityDebug - Debug verbosity level
    12  // Output will include Critical + Error + Warning + Info + Debug
    13  const VerbosityDebug = 4
    14  
    15  // VerbosityInfo - Info verbosity level
    16  // Output will include Critical + Error + Warning + Info
    17  const VerbosityInfo = 3
    18  
    19  // VerbosityWarning - Warning verbosity level
    20  // Output will include Critical + Error + Warning
    21  const VerbosityWarning = 2
    22  
    23  // VerbosityError - Error verbosity level
    24  // Output will include Critical + Error
    25  const VerbosityError = 1
    26  
    27  // VerbosityCritical - Critical verbosity level
    28  // Output will include Critical
    29  const VerbosityCritical = 0
    30  
    31  // Logger - interface for any ign logger.
    32  type Logger interface {
    33  	// Output when verbosity => 4
    34  	Debug(interfaces ...interface{})
    35  	// Output when verbosity => 3
    36  	Info(interfaces ...interface{})
    37  	// Output when verbosity => 2
    38  	Warning(interfaces ...interface{})
    39  	// Output when verbosity => 1
    40  	Error(interfaces ...interface{})
    41  	// Output when verbosity => 0
    42  	Critical(interfaces ...interface{})
    43  	// Clone this logger and returns a copy.
    44  	Clone(reqID string) Logger
    45  }
    46  
    47  // default empty logger implementation. To be returned when there is no logger
    48  // in the context. This logger just logs to the stdout.
    49  type defaultLogImpl struct {
    50  }
    51  
    52  // Debug - debug log
    53  func (l *defaultLogImpl) Debug(interfaces ...interface{}) {
    54  	fmt.Println("[Debug][IGN EMPTY LOGGER]", interfaces)
    55  }
    56  
    57  // Info - info log
    58  func (l *defaultLogImpl) Info(interfaces ...interface{}) {
    59  	fmt.Println("[Info][IGN EMPTY LOGGER]", interfaces)
    60  }
    61  
    62  // Warning - logs a warning message.
    63  func (l *defaultLogImpl) Warning(interfaces ...interface{}) {
    64  	fmt.Println("[Warning][IGN EMPTY LOGGER]", interfaces)
    65  }
    66  
    67  // Error - logs an error message.
    68  func (l *defaultLogImpl) Error(interfaces ...interface{}) {
    69  	fmt.Println("[Error][IGN EMPTY LOGGER]", interfaces)
    70  }
    71  
    72  // Critical - logs a critical message.
    73  func (l *defaultLogImpl) Critical(interfaces ...interface{}) {
    74  	fmt.Println("[Critical][IGN EMPTY LOGGER]", interfaces)
    75  }
    76  
    77  // Clone - clone this logger
    78  func (l *defaultLogImpl) Clone(reqID string) Logger {
    79  	return l
    80  }
    81  
    82  var emptyLogger *defaultLogImpl
    83  
    84  func getEmptyLogger() Logger {
    85  	if emptyLogger == nil {
    86  		l := defaultLogImpl{}
    87  		emptyLogger = &l
    88  	}
    89  	return emptyLogger
    90  }
    91  
    92  // ignLogger - internal implementation for the ign logger interface.
    93  // The ignLogger will log to terminal and also to rollbar, if configured.
    94  // The ignLogger will prefix all logs with a configured request Id.
    95  type ignLogger struct {
    96  	reqID    string
    97  	rollbar  bool
    98  	logToStd bool
    99  	// Controls the level output
   100  	// 0 = Critical
   101  	// 1 = Critical + Error
   102  	// 2 = Critical + Error + Warning
   103  	// 3 = Critical + Error + Warning + Info
   104  	// 4 = Critical + Error + Warning + Info + Debug
   105  	verbosity int
   106  	// Controls the level to output to rollbar
   107  	RollbarVerbosity int
   108  }
   109  
   110  type ctxLoggerType int
   111  
   112  const loggerKey ctxLoggerType = iota
   113  
   114  // NewLogger - creates a new logger implementation associated to the given
   115  // request ID.
   116  func NewLogger(reqID string, std bool, verbosity int) Logger {
   117  	logger := ignLogger{reqID, true, std, verbosity, verbosity}
   118  	return &logger
   119  }
   120  
   121  // NewLoggerWithRollbarVerbosity - creates a new logger implementation associated
   122  // to the given request ID and also configures a minimum verbosity to send logs
   123  // to Rollbar.
   124  func NewLoggerWithRollbarVerbosity(reqID string, std bool, verbosity, rollbarVerbosity int) Logger {
   125  	logger := ignLogger{reqID, true, std, verbosity, rollbarVerbosity}
   126  	return &logger
   127  }
   128  
   129  // NewLoggerNoRollbar - creates a new logger implementation associated to the given
   130  // request ID, which does not log to rollbar
   131  func NewLoggerNoRollbar(reqID string, verbosity int) Logger {
   132  	logger := ignLogger{reqID, false, true, verbosity, verbosity}
   133  	return &logger
   134  }
   135  
   136  // NewContextWithLogger - configures the context with a new ign Logger,
   137  func NewContextWithLogger(ctx context.Context, logger Logger) context.Context {
   138  	return context.WithValue(ctx, loggerKey, logger)
   139  }
   140  
   141  // LoggerFromContext - gets an ign logger from the given context.
   142  func LoggerFromContext(ctx context.Context) Logger {
   143  	if ctx == nil {
   144  		return emptyLogger
   145  	}
   146  	if logger, ok := ctx.Value(loggerKey).(*ignLogger); ok {
   147  		return logger
   148  	}
   149  	return emptyLogger
   150  }
   151  
   152  // LoggerFromRequest - gets an ign logger from the given http request.
   153  func LoggerFromRequest(r *http.Request) Logger {
   154  	return LoggerFromContext(r.Context())
   155  }
   156  
   157  ////////////////////////////////////////////////////////
   158  
   159  // Clone creates a new logger based on the current logger and sets a new reqID.
   160  // This is typically used to customize a logger and still honor the original logger
   161  // configuration.
   162  func (l *ignLogger) Clone(reqID string) Logger {
   163  	logger := ignLogger{reqID, true, l.logToStd, l.verbosity, l.RollbarVerbosity}
   164  	return &logger
   165  }
   166  
   167  // Debug sends a debug message to rollbar. The valid types are:
   168  //
   169  // *http.Request
   170  // error
   171  // string
   172  // map[string]interface{}
   173  // int
   174  // ignerr.ErrMsg
   175  func (l *ignLogger) Debug(interfaces ...interface{}) {
   176  	if l.verbosity < VerbosityDebug {
   177  		return
   178  	}
   179  	logMsg, msg := processLogInterfaces(l.reqID, interfaces...)
   180  	if l.RollbarVerbosity >= VerbosityDebug && l.shouldSendToRollbar(msg) {
   181  		rollbar.Debug(msg...)
   182  	}
   183  	if l.logToStd {
   184  		log.Println(logMsg)
   185  	}
   186  }
   187  
   188  // Info sends an info message to rollbar. The valid types are:
   189  //
   190  // *http.Request
   191  // error
   192  // string
   193  // map[string]interface{}
   194  // int
   195  // ignerr.ErrMsg
   196  func (l *ignLogger) Info(interfaces ...interface{}) {
   197  	if l.verbosity < VerbosityInfo {
   198  		return
   199  	}
   200  	logMsg, msg := processLogInterfaces(l.reqID, interfaces...)
   201  	if l.RollbarVerbosity >= VerbosityInfo && l.shouldSendToRollbar(msg) {
   202  		rollbar.Info(msg...)
   203  	}
   204  	if l.logToStd {
   205  		log.Println(logMsg)
   206  	}
   207  }
   208  
   209  // Warning sends a warning message to rollbar. The valid types are:
   210  //
   211  // *http.Request
   212  // error
   213  // string
   214  // map[string]interface{}
   215  // int
   216  // ign.ErrMsg
   217  func (l *ignLogger) Warning(interfaces ...interface{}) {
   218  	if l.verbosity < VerbosityWarning {
   219  		return
   220  	}
   221  	logMsg, msg := processLogInterfaces(l.reqID, interfaces...)
   222  	if l.RollbarVerbosity >= VerbosityWarning && l.shouldSendToRollbar(msg) {
   223  		rollbar.Warning(msg...)
   224  	}
   225  	if l.logToStd {
   226  		log.Println(logMsg)
   227  	}
   228  }
   229  
   230  // Error sends an error message to rollbar. The valid types are:
   231  //
   232  // *http.Request
   233  // error
   234  // string
   235  // map[string]interface{}
   236  // int
   237  // ignerr.ErrMsg
   238  // If an error is present then a stack trace is captured. If an int is also present then we skip
   239  // that number of stack frames. If the map is present it is used as extra custom data in the
   240  // item. If a string is present without an error, then we log a message without a stack
   241  // trace. If a request is present we extract as much relevant information from it as we can.
   242  func (l *ignLogger) Error(interfaces ...interface{}) {
   243  	if l.verbosity < VerbosityError {
   244  		return
   245  	}
   246  	logMsg, msg := processLogInterfaces(l.reqID, interfaces...)
   247  	if l.RollbarVerbosity >= VerbosityError && l.shouldSendToRollbar(msg) {
   248  		rollbar.Error(msg...)
   249  	} else {
   250  	}
   251  	if l.logToStd {
   252  		log.Println(logMsg)
   253  	}
   254  }
   255  
   256  // Critical sends a critical message to rollbar. The valid types are:
   257  //
   258  // *http.Request
   259  // error
   260  // string
   261  // map[string]interface{}
   262  // int
   263  // ignerr.ErrMsg
   264  func (l *ignLogger) Critical(interfaces ...interface{}) {
   265  	logMsg, msg := processLogInterfaces(l.reqID, interfaces...)
   266  	if l.shouldSendToRollbar(msg) {
   267  		rollbar.Critical(msg...)
   268  	}
   269  
   270  	if l.logToStd {
   271  		log.Println(logMsg)
   272  	}
   273  }
   274  
   275  // shouldSendToRollbar is a helper function that validates if a processed
   276  // msg can be sent to rollbar.
   277  // The 'msg' argument is expected to be the result from a previous call to
   278  // processLogInterfaces func.
   279  func (l *ignLogger) shouldSendToRollbar(msg []interface{}) bool {
   280  	if !l.rollbar || rollbar.Token() == "" {
   281  		return false
   282  	}
   283  
   284  	// Lastly, check if the rollbar msg includes an ign.ErrMsg. If yes, then check for
   285  	// blacklisted error codes.
   286  	if len(msg) > 0 {
   287  		el := msg[len(msg)-1]
   288  		if data, ok := el.(map[string]interface{}); ok {
   289  			if statusCode, ok := data["Status Code"]; ok {
   290  				// change this when having more blacklisted codes
   291  				if statusCode == http.StatusNotFound {
   292  					return false
   293  				}
   294  			}
   295  		}
   296  	}
   297  	return true
   298  }
   299  
   300  // processLogInterfaces is a helper function for log reporting that constructs
   301  // a final message to send to rollbar
   302  func processLogInterfaces(prefix string, interfaces ...interface{}) (string, []interface{}) {
   303  	var finalMsg []interface{}
   304  	str := "[" + prefix + "]"
   305  	logMsg := "[" + prefix + "]"
   306  	var hasError bool
   307  	data := map[string]interface{}{}
   308  
   309  	// Iterate over each interface
   310  	for index, element := range interfaces {
   311  
   312  		var errMsg *ErrMsg
   313  
   314  		// The element could be a pointer to ErrMsg or an instance of ErrMsg
   315  		if e, ok := element.(*ErrMsg); ok {
   316  			errMsg = e
   317  		} else if e, ok := element.(ErrMsg); ok {
   318  			errMsg = &e
   319  		}
   320  
   321  		// Create a special log message if the type is ErrMsg
   322  		if errMsg != nil {
   323  			// Append the message to the string
   324  			str += errMsg.LogString()
   325  			logMsg += errMsg.LogString()
   326  			finalMsg = append(finalMsg, fmt.Sprintf("[ErrCode:%d] %s. Extra: %v. [Route:%s]",
   327  				errMsg.ErrCode, errMsg.Msg, errMsg.Extra, errMsg.Route))
   328  
   329  			// The map of data (Error Code, Status Code, etc) can be viewed by
   330  			// select an occurance of this log message.
   331  			data["Error Code"] = errMsg.ErrCode
   332  			data["Status Code"] = errMsg.StatusCode
   333  			data["Error ID"] = errMsg.ErrID
   334  			data["Route"] = errMsg.Route
   335  
   336  			if errMsg.RemoteAddress != "" {
   337  				data["Remote Address"] = errMsg.RemoteAddress
   338  			}
   339  
   340  			if errMsg.UserAgent != "" {
   341  				data["User-Agent"] = errMsg.UserAgent
   342  			}
   343  
   344  			// Append the base error if one exists.
   345  			if errMsg.BaseError != nil {
   346  				hasError = true
   347  				logMsg = fmt.Sprintf("%s. Base error: %+v\n", logMsg, errMsg.BaseError)
   348  				data["Trace"] = logMsg
   349  				finalMsg = append(finalMsg, errMsg.BaseError)
   350  			}
   351  
   352  		} else if e, ok := element.(string); ok {
   353  			// Concatentate strings together
   354  			str += e
   355  			logMsg += e
   356  		} else if e, ok := element.(error); ok {
   357  			// Append an error if it is present
   358  			hasError = true
   359  			// Get a full trace using %+v because we can't be sure of the correct depth
   360  			logMsg = fmt.Sprintf("%s. Base Error: %+v", logMsg, e)
   361  			data["Trace"] = logMsg
   362  			finalMsg = append(finalMsg, e)
   363  		} else {
   364  			finalMsg = append(finalMsg, interfaces[index])
   365  		}
   366  	}
   367  
   368  	// If there is error, then rollbar will override a string value with the
   369  	// error data. We can save any string value by putting the string in the
   370  	// data map.
   371  	if hasError {
   372  		data["Message"] = str
   373  	} else {
   374  		finalMsg = append(finalMsg, str)
   375  	}
   376  
   377  	data["Prefix"] = prefix
   378  	finalMsg = append(finalMsg, data)
   379  
   380  	return logMsg, finalMsg
   381  }