gitee.com/mirrors/gauge@v1.0.6/logger/logger.go (about)

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