github.com/getgauge/gauge@v1.6.9/logger/logger.go (about) 1 // Copyright 2019 ThoughtWorks, Inc. 2 3 /*---------------------------------------------------------------- 4 * Copyright (c) ThoughtWorks, Inc. 5 * Licensed under the Apache License, Version 2.0 6 * See LICENSE in the project root for license information. 7 *----------------------------------------------------------------*/ 8 9 package logger 10 11 import ( 12 "encoding/json" 13 "fmt" 14 "io" 15 "os" 16 "path/filepath" 17 "runtime" 18 "strings" 19 "sync" 20 21 "github.com/getgauge/common" 22 "github.com/getgauge/gauge/config" 23 "github.com/getgauge/gauge/plugin/pluginInfo" 24 "github.com/getgauge/gauge/version" 25 "github.com/natefinch/lumberjack" 26 logging "github.com/op/go-logging" 27 ) 28 29 const ( 30 gaugeModuleID = "Gauge" 31 logsDirectory = "logs_directory" 32 logs = "logs" 33 gaugeLogFileName = "gauge.log" 34 apiLogFileName = "api.log" 35 lspLogFileName = "lsp.log" 36 // CLI indicates gauge is used as a CLI. 37 CLI = iota 38 // API indicates gauge is in daemon mode. Used in IDEs. 39 API 40 // LSP indicates that gauge is acting as an LSP server. 41 LSP 42 ) 43 44 var level logging.Level 45 var initialized bool 46 var loggersMap logCache 47 var fatalErrors []string 48 var fileLogFormat = logging.MustStringFormatter("%{time:02-01-2006 15:04:05.000} [%{module}] [%{level}] %{message}") 49 var fileLoggerLeveled logging.LeveledBackend 50 51 // ActiveLogFile log file represents the file which will be used for the backend logging 52 var ActiveLogFile string 53 var machineReadable bool 54 var isLSP bool 55 56 type logCache struct { 57 mutex sync.RWMutex 58 loggers map[string]*logging.Logger 59 } 60 61 // getLogger gets logger for given modules. It creates a new logger for the module if not exists 62 func (l *logCache) getLogger(module string) *logging.Logger { 63 if !initialized { 64 return nil 65 } 66 l.mutex.RLock() 67 defer l.mutex.RUnlock() 68 if module == "" { 69 return l.loggers[gaugeModuleID] 70 } 71 if _, ok := l.loggers[module]; !ok { 72 l.mutex.RUnlock() 73 l.addLogger(module) 74 l.mutex.RLock() 75 } 76 return l.loggers[module] 77 } 78 79 func (l *logCache) addLogger(module string) { 80 logger := logging.MustGetLogger(module) 81 logger.SetBackend(fileLoggerLeveled) 82 l.mutex.Lock() 83 defer l.mutex.Unlock() 84 l.loggers[module] = logger 85 } 86 87 // Initialize logger with given level 88 func Initialize(mr bool, logLevel string, c int) { 89 machineReadable = mr 90 level = loggingLevel(logLevel) 91 switch c { 92 case CLI: 93 ActiveLogFile = getLogFile(gaugeLogFileName) 94 case API: 95 ActiveLogFile = getLogFile(apiLogFileName) 96 case LSP: 97 isLSP = true 98 ActiveLogFile = getLogFile(lspLogFileName) 99 } 100 initFileLoggerBackend() 101 loggersMap = logCache{loggers: make(map[string]*logging.Logger)} 102 loggersMap.addLogger(gaugeModuleID) 103 initialized = true 104 } 105 106 func logInfo(logger *logging.Logger, stdout bool, msg string) { 107 if level >= logging.INFO { 108 write(stdout, msg, os.Stdout) 109 } 110 if !initialized { 111 return 112 } 113 logger.Infof(msg) 114 } 115 116 func logError(logger *logging.Logger, stdout bool, msg string) { 117 if level >= logging.ERROR { 118 write(stdout, msg, os.Stdout) 119 } 120 if !initialized { 121 fmt.Fprint(os.Stderr, msg) 122 return 123 } 124 logger.Errorf(msg) 125 } 126 127 func logWarning(logger *logging.Logger, stdout bool, msg string) { 128 if level >= logging.WARNING { 129 write(stdout, msg, os.Stdout) 130 } 131 if !initialized { 132 return 133 } 134 logger.Warningf(msg) 135 } 136 137 func logDebug(logger *logging.Logger, stdout bool, msg string) { 138 if level >= logging.DEBUG { 139 write(stdout, msg, os.Stdout) 140 } 141 if !initialized { 142 return 143 } 144 logger.Debugf(msg) 145 } 146 147 func logCritical(logger *logging.Logger, msg string) { 148 if !initialized { 149 fmt.Fprint(os.Stderr, msg) 150 return 151 } 152 logger.Criticalf(msg) 153 154 } 155 156 func write(stdout bool, msg string, writer io.Writer) { 157 if !isLSP && stdout { 158 if machineReadable { 159 machineReadableLog(msg) 160 } else { 161 fmt.Fprintln(writer, msg) 162 } 163 } 164 } 165 166 // OutMessage contains information for output log 167 type OutMessage struct { 168 MessageType string `json:"type"` 169 Message string `json:"message"` 170 } 171 172 // ToJSON converts OutMessage into JSON 173 func (out *OutMessage) ToJSON() (string, error) { 174 jsonMsg, err := json.Marshal(out) 175 if err != nil { 176 return "", err 177 } 178 return string(jsonMsg), nil 179 } 180 181 func machineReadableLog(msg string) { 182 strs := strings.Split(msg, "\n") 183 for _, m := range strs { 184 outMessage := &OutMessage{MessageType: "out", Message: m} 185 m, _ = outMessage.ToJSON() 186 fmt.Println(m) 187 } 188 } 189 190 func initFileLoggerBackend() { 191 var backend = createFileLogger(ActiveLogFile, 10) 192 fileFormatter := logging.NewBackendFormatter(backend, fileLogFormat) 193 fileLoggerLeveled = logging.AddModuleLevel(fileFormatter) 194 fileLoggerLeveled.SetLevel(logging.DEBUG, "") 195 } 196 197 var createFileLogger = func(name string, size int) logging.Backend { 198 return logging.NewLogBackend(&lumberjack.Logger{ 199 Filename: name, 200 MaxSize: size, // megabytes 201 MaxBackups: 3, 202 MaxAge: 28, //days 203 }, "", 0) 204 } 205 206 func addLogsDirPath(logFileName string) string { 207 customLogsDir := os.Getenv(logsDirectory) 208 if customLogsDir == "" { 209 return filepath.Join(logs, logFileName) 210 } 211 return filepath.Join(customLogsDir, logFileName) 212 } 213 214 func getLogFile(logFileName string) string { 215 logDirPath := addLogsDirPath(logFileName) 216 if filepath.IsAbs(logDirPath) { 217 return logDirPath 218 } 219 if config.ProjectRoot != "" { 220 return filepath.Join(config.ProjectRoot, logDirPath) 221 } 222 gaugeHome, err := common.GetGaugeHomeDirectory() 223 if err != nil { 224 return logDirPath 225 } 226 return filepath.Join(gaugeHome, logDirPath) 227 } 228 229 func loggingLevel(logLevel string) logging.Level { 230 if logLevel != "" { 231 switch strings.ToLower(logLevel) { 232 case "debug": 233 return logging.DEBUG 234 case "info": 235 return logging.INFO 236 case "warning": 237 return logging.WARNING 238 case "error": 239 return logging.ERROR 240 } 241 } 242 return logging.INFO 243 } 244 245 func getFatalErrorMsg() string { 246 env := []string{runtime.GOOS, version.FullVersion()} 247 if version.CommitHash != "" { 248 env = append(env, version.CommitHash) 249 } 250 envText := strings.Join(env, ", ") 251 252 return fmt.Sprintf(`Error ---------------------------------- 253 254 %s 255 256 Get Support ---------------------------- 257 Docs: https://docs.gauge.org 258 Bugs: https://github.com/getgauge/gauge/issues 259 Chat: https://github.com/getgauge/gauge/discussions 260 261 Your Environment Information ----------- 262 %s 263 %s`, strings.Join(fatalErrors, "\n\n"), 264 envText, 265 getPluginVersions()) 266 } 267 268 func addFatalError(module, msg string) { 269 msg = strings.TrimSpace(msg) 270 fatalErrors = append(fatalErrors, fmt.Sprintf("[%s]\n%s", module, msg)) 271 } 272 273 func getPluginVersions() string { 274 pis, err := pluginInfo.GetAllInstalledPluginsWithVersion() 275 if err != nil { 276 return "Could not retrieve plugin information." 277 } 278 pluginVersions := make([]string, 0) 279 for _, pi := range pis { 280 pluginVersions = append(pluginVersions, fmt.Sprintf(`%s (%s)`, pi.Name, pi.Version)) 281 } 282 return strings.Join(pluginVersions, ", ") 283 }