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 }