code.gitea.io/gitea@v1.19.3/modules/setting/log.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package setting
     5  
     6  import (
     7  	"fmt"
     8  	golog "log"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  
    15  	"code.gitea.io/gitea/modules/json"
    16  	"code.gitea.io/gitea/modules/log"
    17  	"code.gitea.io/gitea/modules/util"
    18  
    19  	ini "gopkg.in/ini.v1"
    20  )
    21  
    22  var (
    23  	filenameSuffix  = ""
    24  	descriptionLock = sync.RWMutex{}
    25  	logDescriptions = make(map[string]*LogDescription)
    26  )
    27  
    28  // Log settings
    29  var Log struct {
    30  	Level              log.Level
    31  	StacktraceLogLevel string
    32  	RootPath           string
    33  	EnableSSHLog       bool
    34  	EnableXORMLog      bool
    35  
    36  	DisableRouterLog bool
    37  
    38  	EnableAccessLog   bool
    39  	AccessLogTemplate string
    40  	BufferLength      int64
    41  }
    42  
    43  // GetLogDescriptions returns a race safe set of descriptions
    44  func GetLogDescriptions() map[string]*LogDescription {
    45  	descriptionLock.RLock()
    46  	defer descriptionLock.RUnlock()
    47  	descs := make(map[string]*LogDescription, len(logDescriptions))
    48  	for k, v := range logDescriptions {
    49  		subLogDescriptions := make([]SubLogDescription, len(v.SubLogDescriptions))
    50  		copy(subLogDescriptions, v.SubLogDescriptions)
    51  
    52  		descs[k] = &LogDescription{
    53  			Name:               v.Name,
    54  			SubLogDescriptions: subLogDescriptions,
    55  		}
    56  	}
    57  	return descs
    58  }
    59  
    60  // AddLogDescription adds a set of descriptions to the complete description
    61  func AddLogDescription(key string, description *LogDescription) {
    62  	descriptionLock.Lock()
    63  	defer descriptionLock.Unlock()
    64  	logDescriptions[key] = description
    65  }
    66  
    67  // AddSubLogDescription adds a sub log description
    68  func AddSubLogDescription(key string, subLogDescription SubLogDescription) bool {
    69  	descriptionLock.Lock()
    70  	defer descriptionLock.Unlock()
    71  	desc, ok := logDescriptions[key]
    72  	if !ok {
    73  		return false
    74  	}
    75  	for i, sub := range desc.SubLogDescriptions {
    76  		if sub.Name == subLogDescription.Name {
    77  			desc.SubLogDescriptions[i] = subLogDescription
    78  			return true
    79  		}
    80  	}
    81  	desc.SubLogDescriptions = append(desc.SubLogDescriptions, subLogDescription)
    82  	return true
    83  }
    84  
    85  // RemoveSubLogDescription removes a sub log description
    86  func RemoveSubLogDescription(key, name string) bool {
    87  	descriptionLock.Lock()
    88  	defer descriptionLock.Unlock()
    89  	desc, ok := logDescriptions[key]
    90  	if !ok {
    91  		return false
    92  	}
    93  	for i, sub := range desc.SubLogDescriptions {
    94  		if sub.Name == name {
    95  			desc.SubLogDescriptions = append(desc.SubLogDescriptions[:i], desc.SubLogDescriptions[i+1:]...)
    96  			return true
    97  		}
    98  	}
    99  	return false
   100  }
   101  
   102  type defaultLogOptions struct {
   103  	levelName      string // LogLevel
   104  	flags          string
   105  	filename       string // path.Join(LogRootPath, "gitea.log")
   106  	bufferLength   int64
   107  	disableConsole bool
   108  }
   109  
   110  func newDefaultLogOptions() defaultLogOptions {
   111  	return defaultLogOptions{
   112  		levelName:      Log.Level.String(),
   113  		flags:          "stdflags",
   114  		filename:       filepath.Join(Log.RootPath, "gitea.log"),
   115  		bufferLength:   10000,
   116  		disableConsole: false,
   117  	}
   118  }
   119  
   120  // SubLogDescription describes a sublogger
   121  type SubLogDescription struct {
   122  	Name     string
   123  	Provider string
   124  	Config   string
   125  }
   126  
   127  // LogDescription describes a named logger
   128  type LogDescription struct {
   129  	Name               string
   130  	SubLogDescriptions []SubLogDescription
   131  }
   132  
   133  func getLogLevel(section *ini.Section, key string, defaultValue log.Level) log.Level {
   134  	value := section.Key(key).MustString(defaultValue.String())
   135  	return log.FromString(value)
   136  }
   137  
   138  func getStacktraceLogLevel(section *ini.Section, key, defaultValue string) string {
   139  	value := section.Key(key).MustString(defaultValue)
   140  	return log.FromString(value).String()
   141  }
   142  
   143  func loadLogFrom(rootCfg ConfigProvider) {
   144  	sec := rootCfg.Section("log")
   145  	Log.Level = getLogLevel(sec, "LEVEL", log.INFO)
   146  	Log.StacktraceLogLevel = getStacktraceLogLevel(sec, "STACKTRACE_LEVEL", "None")
   147  	Log.RootPath = sec.Key("ROOT_PATH").MustString(path.Join(AppWorkPath, "log"))
   148  	forcePathSeparator(Log.RootPath)
   149  	Log.BufferLength = sec.Key("BUFFER_LEN").MustInt64(10000)
   150  
   151  	Log.EnableSSHLog = sec.Key("ENABLE_SSH_LOG").MustBool(false)
   152  	Log.EnableAccessLog = sec.Key("ENABLE_ACCESS_LOG").MustBool(false)
   153  	Log.AccessLogTemplate = sec.Key("ACCESS_LOG_TEMPLATE").MustString(
   154  		`{{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"`,
   155  	)
   156  	// the `MustString` updates the default value, and `log.ACCESS` is used by `generateNamedLogger("access")` later
   157  	_ = rootCfg.Section("log").Key("ACCESS").MustString("file")
   158  
   159  	sec.Key("ROUTER").MustString("console")
   160  	// Allow [log]  DISABLE_ROUTER_LOG to override [server] DISABLE_ROUTER_LOG
   161  	Log.DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool(Log.DisableRouterLog)
   162  
   163  	Log.EnableXORMLog = rootCfg.Section("log").Key("ENABLE_XORM_LOG").MustBool(true)
   164  }
   165  
   166  func generateLogConfig(sec *ini.Section, name string, defaults defaultLogOptions) (mode, jsonConfig, levelName string) {
   167  	level := getLogLevel(sec, "LEVEL", Log.Level)
   168  	levelName = level.String()
   169  	stacktraceLevelName := getStacktraceLogLevel(sec, "STACKTRACE_LEVEL", Log.StacktraceLogLevel)
   170  	stacktraceLevel := log.FromString(stacktraceLevelName)
   171  	mode = name
   172  	keys := sec.Keys()
   173  	logPath := defaults.filename
   174  	flags := log.FlagsFromString(defaults.flags)
   175  	expression := ""
   176  	prefix := ""
   177  	for _, key := range keys {
   178  		switch key.Name() {
   179  		case "MODE":
   180  			mode = key.MustString(name)
   181  		case "FILE_NAME":
   182  			logPath = key.MustString(defaults.filename)
   183  			forcePathSeparator(logPath)
   184  			if !filepath.IsAbs(logPath) {
   185  				logPath = path.Join(Log.RootPath, logPath)
   186  			}
   187  		case "FLAGS":
   188  			flags = log.FlagsFromString(key.MustString(defaults.flags))
   189  		case "EXPRESSION":
   190  			expression = key.MustString("")
   191  		case "PREFIX":
   192  			prefix = key.MustString("")
   193  		}
   194  	}
   195  
   196  	logConfig := map[string]interface{}{
   197  		"level":           level.String(),
   198  		"expression":      expression,
   199  		"prefix":          prefix,
   200  		"flags":           flags,
   201  		"stacktraceLevel": stacktraceLevel.String(),
   202  	}
   203  
   204  	// Generate log configuration.
   205  	switch mode {
   206  	case "console":
   207  		useStderr := sec.Key("STDERR").MustBool(false)
   208  		logConfig["stderr"] = useStderr
   209  		if useStderr {
   210  			logConfig["colorize"] = sec.Key("COLORIZE").MustBool(log.CanColorStderr)
   211  		} else {
   212  			logConfig["colorize"] = sec.Key("COLORIZE").MustBool(log.CanColorStdout)
   213  		}
   214  
   215  	case "file":
   216  		if err := os.MkdirAll(path.Dir(logPath), os.ModePerm); err != nil {
   217  			panic(err.Error())
   218  		}
   219  
   220  		logConfig["filename"] = logPath + filenameSuffix
   221  		logConfig["rotate"] = sec.Key("LOG_ROTATE").MustBool(true)
   222  		logConfig["maxsize"] = 1 << uint(sec.Key("MAX_SIZE_SHIFT").MustInt(28))
   223  		logConfig["daily"] = sec.Key("DAILY_ROTATE").MustBool(true)
   224  		logConfig["maxdays"] = sec.Key("MAX_DAYS").MustInt(7)
   225  		logConfig["compress"] = sec.Key("COMPRESS").MustBool(true)
   226  		logConfig["compressionLevel"] = sec.Key("COMPRESSION_LEVEL").MustInt(-1)
   227  	case "conn":
   228  		logConfig["reconnectOnMsg"] = sec.Key("RECONNECT_ON_MSG").MustBool()
   229  		logConfig["reconnect"] = sec.Key("RECONNECT").MustBool()
   230  		logConfig["net"] = sec.Key("PROTOCOL").In("tcp", []string{"tcp", "unix", "udp"})
   231  		logConfig["addr"] = sec.Key("ADDR").MustString(":7020")
   232  	case "smtp":
   233  		logConfig["username"] = sec.Key("USER").MustString("example@example.com")
   234  		logConfig["password"] = sec.Key("PASSWD").MustString("******")
   235  		logConfig["host"] = sec.Key("HOST").MustString("127.0.0.1:25")
   236  		sendTos := strings.Split(sec.Key("RECEIVERS").MustString(""), ",")
   237  		for i, address := range sendTos {
   238  			sendTos[i] = strings.TrimSpace(address)
   239  		}
   240  		logConfig["sendTos"] = sendTos
   241  		logConfig["subject"] = sec.Key("SUBJECT").MustString("Diagnostic message from Gitea")
   242  	}
   243  
   244  	logConfig["colorize"] = sec.Key("COLORIZE").MustBool(false)
   245  	byteConfig, err := json.Marshal(logConfig)
   246  	if err != nil {
   247  		log.Error("Failed to marshal log configuration: %v %v", logConfig, err)
   248  		return
   249  	}
   250  	jsonConfig = string(byteConfig)
   251  	return mode, jsonConfig, levelName
   252  }
   253  
   254  func generateNamedLogger(rootCfg ConfigProvider, key string, options defaultLogOptions) *LogDescription {
   255  	description := LogDescription{
   256  		Name: key,
   257  	}
   258  
   259  	sections := strings.Split(rootCfg.Section("log").Key(strings.ToUpper(key)).MustString(""), ",")
   260  
   261  	for i := 0; i < len(sections); i++ {
   262  		sections[i] = strings.TrimSpace(sections[i])
   263  	}
   264  
   265  	for _, name := range sections {
   266  		if len(name) == 0 || (name == "console" && options.disableConsole) {
   267  			continue
   268  		}
   269  		sec, err := rootCfg.GetSection("log." + name + "." + key)
   270  		if err != nil {
   271  			sec, _ = rootCfg.NewSection("log." + name + "." + key)
   272  		}
   273  
   274  		provider, config, levelName := generateLogConfig(sec, name, options)
   275  
   276  		if err := log.NewNamedLogger(key, options.bufferLength, name, provider, config); err != nil {
   277  			// Maybe panic here?
   278  			log.Error("Could not create new named logger: %v", err.Error())
   279  		}
   280  
   281  		description.SubLogDescriptions = append(description.SubLogDescriptions, SubLogDescription{
   282  			Name:     name,
   283  			Provider: provider,
   284  			Config:   config,
   285  		})
   286  		log.Info("%s Log: %s(%s:%s)", util.ToTitleCase(key), util.ToTitleCase(name), provider, levelName)
   287  	}
   288  
   289  	AddLogDescription(key, &description)
   290  
   291  	return &description
   292  }
   293  
   294  // initLogFrom initializes logging with settings from configuration provider
   295  func initLogFrom(rootCfg ConfigProvider) {
   296  	sec := rootCfg.Section("log")
   297  	options := newDefaultLogOptions()
   298  	options.bufferLength = Log.BufferLength
   299  
   300  	description := LogDescription{
   301  		Name: log.DEFAULT,
   302  	}
   303  
   304  	sections := strings.Split(sec.Key("MODE").MustString("console"), ",")
   305  
   306  	useConsole := false
   307  	for _, name := range sections {
   308  		name = strings.TrimSpace(name)
   309  		if name == "" {
   310  			continue
   311  		}
   312  		if name == "console" {
   313  			useConsole = true
   314  		}
   315  
   316  		sec, err := rootCfg.GetSection("log." + name + ".default")
   317  		if err != nil {
   318  			sec, err = rootCfg.GetSection("log." + name)
   319  			if err != nil {
   320  				sec, _ = rootCfg.NewSection("log." + name)
   321  			}
   322  		}
   323  
   324  		provider, config, levelName := generateLogConfig(sec, name, options)
   325  		log.NewLogger(options.bufferLength, name, provider, config)
   326  		description.SubLogDescriptions = append(description.SubLogDescriptions, SubLogDescription{
   327  			Name:     name,
   328  			Provider: provider,
   329  			Config:   config,
   330  		})
   331  		log.Info("Gitea Log Mode: %s(%s:%s)", util.ToTitleCase(name), util.ToTitleCase(provider), levelName)
   332  	}
   333  
   334  	AddLogDescription(log.DEFAULT, &description)
   335  
   336  	if !useConsole {
   337  		log.Info("According to the configuration, subsequent logs will not be printed to the console")
   338  		if err := log.DelLogger("console"); err != nil {
   339  			log.Fatal("Cannot delete console logger: %v", err)
   340  		}
   341  	}
   342  
   343  	// Finally redirect the default golog to here
   344  	golog.SetFlags(0)
   345  	golog.SetPrefix("")
   346  	golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT)))
   347  }
   348  
   349  // RestartLogsWithPIDSuffix restarts the logs with a PID suffix on files
   350  func RestartLogsWithPIDSuffix() {
   351  	filenameSuffix = fmt.Sprintf(".%d", os.Getpid())
   352  	InitLogs(false)
   353  }
   354  
   355  // InitLogs creates all the log services
   356  func InitLogs(disableConsole bool) {
   357  	initLogFrom(CfgProvider)
   358  
   359  	if !Log.DisableRouterLog {
   360  		options := newDefaultLogOptions()
   361  		options.filename = filepath.Join(Log.RootPath, "router.log")
   362  		options.flags = "date,time" // For the router we don't want any prefixed flags
   363  		options.bufferLength = Log.BufferLength
   364  		generateNamedLogger(CfgProvider, "router", options)
   365  	}
   366  
   367  	if Log.EnableAccessLog {
   368  		options := newDefaultLogOptions()
   369  		options.filename = filepath.Join(Log.RootPath, "access.log")
   370  		options.flags = "" // For the router we don't want any prefixed flags
   371  		options.bufferLength = Log.BufferLength
   372  		generateNamedLogger(CfgProvider, "access", options)
   373  	}
   374  
   375  	initSQLLogFrom(CfgProvider, disableConsole)
   376  }
   377  
   378  // InitSQLLog initializes xorm logger setting
   379  func InitSQLLog(disableConsole bool) {
   380  	initSQLLogFrom(CfgProvider, disableConsole)
   381  }
   382  
   383  func initSQLLogFrom(rootCfg ConfigProvider, disableConsole bool) {
   384  	if Log.EnableXORMLog {
   385  		options := newDefaultLogOptions()
   386  		options.filename = filepath.Join(Log.RootPath, "xorm.log")
   387  		options.bufferLength = Log.BufferLength
   388  		options.disableConsole = disableConsole
   389  
   390  		rootCfg.Section("log").Key("XORM").MustString(",")
   391  		generateNamedLogger(rootCfg, "xorm", options)
   392  	}
   393  }