github.com/dschalla/mattermost-server@v4.8.1-rc1+incompatible/utils/logger/logger.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  // this is a new logger interface for mattermost
     5  
     6  package logger
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"path/filepath"
    13  	"runtime"
    14  
    15  	l4g "github.com/alecthomas/log4go"
    16  
    17  	"strings"
    18  
    19  	"github.com/mattermost/mattermost-server/model"
    20  	"github.com/mattermost/mattermost-server/utils"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  // this pattern allows us to "mock" the underlying l4g code when unit testing
    25  var logger l4g.Logger
    26  var debugLog = l4g.Debug
    27  var infoLog = l4g.Info
    28  var errorLog = l4g.Error
    29  
    30  // assumes that ../config.go::configureLog has already been called, and has in turn called l4g.close() to clean up
    31  // any old filters that we might have previously created
    32  func initL4g(logSettings model.LogSettings) {
    33  	// TODO: add support for newConfig.LogSettings.EnableConsole. Right now, ../config.go sets it up in its configureLog
    34  	// method. If we also set it up here, messages will be written to the console twice. Eventually, when all instances
    35  	// of l4g have been replaced by this logger, we can move that code to here
    36  	if logSettings.EnableFile {
    37  		level := l4g.DEBUG
    38  		if logSettings.FileLevel == "INFO" {
    39  			level = l4g.INFO
    40  		} else if logSettings.FileLevel == "WARN" {
    41  			level = l4g.WARNING
    42  		} else if logSettings.FileLevel == "ERROR" {
    43  			level = l4g.ERROR
    44  		}
    45  
    46  		// create a logger that writes JSON objects to a file, and override our log methods to use it
    47  		if logger != nil {
    48  			logger.Close()
    49  		}
    50  		logger = NewJSONFileLogger(level, utils.GetLogFileLocation(logSettings.FileLocation)+".jsonl")
    51  		debugLog = logger.Debug
    52  		infoLog = logger.Info
    53  		errorLog = logger.Error
    54  	}
    55  }
    56  
    57  // contextKey lets us add contextual information to log messages
    58  type contextKey string
    59  
    60  func (c contextKey) String() string {
    61  	return string(c)
    62  }
    63  
    64  const contextKeyUserID contextKey = contextKey("user_id")
    65  const contextKeyRequestID contextKey = contextKey("request_id")
    66  
    67  // any contextKeys added to this array will be serialized in every log message
    68  var contextKeys = [2]contextKey{contextKeyUserID, contextKeyRequestID}
    69  
    70  // WithUserId adds a user id to the specified context. If the returned Context is subsequently passed to a logging
    71  // method, the user id will automatically be included in the logged message
    72  func WithUserId(ctx context.Context, userID string) context.Context {
    73  	return context.WithValue(ctx, contextKeyUserID, userID)
    74  }
    75  
    76  // WithRequestId adds a request id to the specified context. If the returned Context is subsequently passed to a logging
    77  // method, the request id will automatically be included in the logged message
    78  func WithRequestId(ctx context.Context, requestID string) context.Context {
    79  	return context.WithValue(ctx, contextKeyRequestID, requestID)
    80  }
    81  
    82  // extracts known contextKey values from the specified Context and assembles them into the returned map
    83  func serializeContext(ctx context.Context) map[string]string {
    84  	serialized := make(map[string]string)
    85  	for _, key := range contextKeys {
    86  		value, ok := ctx.Value(key).(string)
    87  		if ok {
    88  			serialized[string(key)] = value
    89  		}
    90  	}
    91  	return serialized
    92  }
    93  
    94  // Returns the path to the next file up the callstack that has a different name than this file
    95  // in other words, finds the path to the file that is doing the logging.
    96  // Removes machine-specific prefix, so returned path starts with /mattermost-server.
    97  // Looks a maximum of 10 frames up the call stack to find a file that has a different name than this one.
    98  func getCallerFilename() (string, error) {
    99  	_, currentFilename, _, ok := runtime.Caller(0)
   100  	if !ok {
   101  		return "", errors.New("Failed to traverse stack frame")
   102  	}
   103  
   104  	platformDirectory := currentFilename
   105  	for filepath.Base(platformDirectory) != "platform" {
   106  		platformDirectory = filepath.Dir(platformDirectory)
   107  		if platformDirectory == "." || platformDirectory == string(filepath.Separator) {
   108  			break
   109  		}
   110  	}
   111  
   112  	for i := 1; i < 10; i++ {
   113  		_, parentFilename, _, ok := runtime.Caller(i)
   114  		if !ok {
   115  			return "", errors.New("Failed to traverse stack frame")
   116  		} else if parentFilename != currentFilename && strings.Contains(parentFilename, platformDirectory) {
   117  			// trim parentFilename such that we return the path to parentFilename, relative to platformDirectory
   118  			return parentFilename[strings.LastIndex(parentFilename, platformDirectory)+len(platformDirectory)+1:], nil
   119  		}
   120  	}
   121  	return "", errors.New("Failed to traverse stack frame")
   122  }
   123  
   124  // creates a JSON representation of a log message
   125  func serializeLogMessage(ctx context.Context, message string) string {
   126  	callerFilename, err := getCallerFilename()
   127  	if err != nil {
   128  		callerFilename = "Unknown"
   129  	}
   130  
   131  	bytes, err := json.Marshal(&struct {
   132  		Context map[string]string `json:"context"`
   133  		File    string            `json:"file"`
   134  		Message string            `json:"message"`
   135  	}{
   136  		serializeContext(ctx),
   137  		callerFilename,
   138  		message,
   139  	})
   140  	if err != nil {
   141  		errorLog("Failed to serialize log message %v", message)
   142  	}
   143  	return string(bytes)
   144  }
   145  
   146  func formatMessage(args ...interface{}) string {
   147  	msg, ok := args[0].(string)
   148  	if !ok {
   149  		panic("Second argument is not of type string")
   150  	}
   151  	if len(args) > 1 {
   152  		variables := args[1:]
   153  		msg = fmt.Sprintf(msg, variables...)
   154  	}
   155  	return msg
   156  }
   157  
   158  // Debugc logs a debugLog level message, including context information that is stored in the first parameter.
   159  // If two parameters are supplied, the second must be a message string, and will be logged directly.
   160  // If more than two parameters are supplied, the second parameter must be a format string, and the remaining parameters
   161  // must be the variables to substitute into the format string, following the convention of the fmt.Sprintf(...) function.
   162  func Debugc(ctx context.Context, args ...interface{}) {
   163  	debugLog(func() string {
   164  		msg := formatMessage(args...)
   165  		return serializeLogMessage(ctx, msg)
   166  	})
   167  }
   168  
   169  // Debugf logs a debugLog level message.
   170  // If one parameter is supplied, it must be a message string, and will be logged directly.
   171  // If two or more parameters are specified, the first parameter must be a format string, and the remaining parameters
   172  // must be the variables to substitute into the format string, following the convention of the fmt.Sprintf(...) function.
   173  func Debugf(args ...interface{}) {
   174  	debugLog(func() string {
   175  		msg := formatMessage(args...)
   176  		return serializeLogMessage(context.Background(), msg)
   177  	})
   178  }
   179  
   180  // Infoc logs an infoLog level message, including context information that is stored in the first parameter.
   181  // If two parameters are supplied, the second must be a message string, and will be logged directly.
   182  // If more than two parameters are supplied, the second parameter must be a format string, and the remaining parameters
   183  // must be the variables to substitute into the format string, following the convention of the fmt.Sprintf(...) function.
   184  func Infoc(ctx context.Context, args ...interface{}) {
   185  	infoLog(func() string {
   186  		msg := formatMessage(args...)
   187  		return serializeLogMessage(ctx, msg)
   188  	})
   189  }
   190  
   191  // Infof logs an infoLog level message.
   192  // If one parameter is supplied, it must be a message string, and will be logged directly.
   193  // If two or more parameters are specified, the first parameter must be a format string, and the remaining parameters
   194  // must be the variables to substitute into the format string, following the convention of the fmt.Sprintf(...) function.
   195  func Infof(args ...interface{}) {
   196  	infoLog(func() string {
   197  		msg := formatMessage(args...)
   198  		return serializeLogMessage(context.Background(), msg)
   199  	})
   200  }
   201  
   202  // Errorc logs an error level message, including context information that is stored in the first parameter.
   203  // If two parameters are supplied, the second must be a message string, and will be logged directly.
   204  // If more than two parameters are supplied, the second parameter must be a format string, and the remaining parameters
   205  // must be the variables to substitute into the format string, following the convention of the fmt.Sprintf(...) function.
   206  func Errorc(ctx context.Context, args ...interface{}) {
   207  	errorLog(func() string {
   208  		msg := formatMessage(args...)
   209  		return serializeLogMessage(ctx, msg)
   210  	})
   211  }
   212  
   213  // Errorf logs an error level message.
   214  // If one parameter is supplied, it must be a message string, and will be logged directly.
   215  // If two or more parameters are specified, the first parameter must be a format string, and the remaining parameters
   216  // must be the variables to substitute into the format string, following the convention of the fmt.Sprintf(...) function.
   217  func Errorf(args ...interface{}) {
   218  	errorLog(func() string {
   219  		msg := formatMessage(args...)
   220  		return serializeLogMessage(context.Background(), msg)
   221  	})
   222  }