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  }