diff options
Diffstat (limited to 'modules/setting/log.go')
-rw-r--r-- | modules/setting/log.go | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/modules/setting/log.go b/modules/setting/log.go new file mode 100644 index 00000000..e404074b --- /dev/null +++ b/modules/setting/log.go @@ -0,0 +1,260 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "fmt" + golog "log" + "os" + "path" + "path/filepath" + "strings" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" +) + +type LogGlobalConfig struct { + RootPath string + + Mode string + Level log.Level + StacktraceLogLevel log.Level + BufferLen int + + EnableSSHLog bool + + AccessLogTemplate string + RequestIDHeaders []string +} + +var Log LogGlobalConfig + +const accessLogTemplateDefault = `{{.Ctx.RemoteHost}} - {{.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}}"` + +func loadLogGlobalFrom(rootCfg ConfigProvider) { + sec := rootCfg.Section("log") + + Log.Level = log.LevelFromString(sec.Key("LEVEL").MustString(log.INFO.String())) + Log.StacktraceLogLevel = log.LevelFromString(sec.Key("STACKTRACE_LEVEL").MustString(log.NONE.String())) + Log.BufferLen = sec.Key("BUFFER_LEN").MustInt(10000) + Log.Mode = sec.Key("MODE").MustString("console") + + Log.RootPath = sec.Key("ROOT_PATH").MustString(path.Join(AppWorkPath, "log")) + if !filepath.IsAbs(Log.RootPath) { + Log.RootPath = filepath.Join(AppWorkPath, Log.RootPath) + } + Log.RootPath = util.FilePathJoinAbs(Log.RootPath) + + Log.EnableSSHLog = sec.Key("ENABLE_SSH_LOG").MustBool(false) + + Log.AccessLogTemplate = sec.Key("ACCESS_LOG_TEMPLATE").MustString(accessLogTemplateDefault) + Log.RequestIDHeaders = sec.Key("REQUEST_ID_HEADERS").Strings(",") +} + +func prepareLoggerConfig(rootCfg ConfigProvider) { + sec := rootCfg.Section("log") + + if !sec.HasKey("logger.default.MODE") { + sec.Key("logger.default.MODE").MustString(",") + } + + deprecatedSetting(rootCfg, "log", "ACCESS", "log", "logger.access.MODE", "1.21") + deprecatedSetting(rootCfg, "log", "ENABLE_ACCESS_LOG", "log", "logger.access.MODE", "1.21") + if val := sec.Key("ACCESS").String(); val != "" { + sec.Key("logger.access.MODE").MustString(val) + } + if sec.HasKey("ENABLE_ACCESS_LOG") && !sec.Key("ENABLE_ACCESS_LOG").MustBool() { + sec.Key("logger.access.MODE").SetValue("") + } + + deprecatedSetting(rootCfg, "log", "ROUTER", "log", "logger.router.MODE", "1.21") + deprecatedSetting(rootCfg, "log", "DISABLE_ROUTER_LOG", "log", "logger.router.MODE", "1.21") + if val := sec.Key("ROUTER").String(); val != "" { + sec.Key("logger.router.MODE").MustString(val) + } + if !sec.HasKey("logger.router.MODE") { + sec.Key("logger.router.MODE").MustString(",") // use default logger + } + if sec.HasKey("DISABLE_ROUTER_LOG") && sec.Key("DISABLE_ROUTER_LOG").MustBool() { + sec.Key("logger.router.MODE").SetValue("") + } + + deprecatedSetting(rootCfg, "log", "XORM", "log", "logger.xorm.MODE", "1.21") + deprecatedSetting(rootCfg, "log", "ENABLE_XORM_LOG", "log", "logger.xorm.MODE", "1.21") + if val := sec.Key("XORM").String(); val != "" { + sec.Key("logger.xorm.MODE").MustString(val) + } + if !sec.HasKey("logger.xorm.MODE") { + sec.Key("logger.xorm.MODE").MustString(",") // use default logger + } + if sec.HasKey("ENABLE_XORM_LOG") && !sec.Key("ENABLE_XORM_LOG").MustBool() { + sec.Key("logger.xorm.MODE").SetValue("") + } +} + +func LogPrepareFilenameForWriter(fileName, defaultFileName string) string { + if fileName == "" { + fileName = defaultFileName + } + if !filepath.IsAbs(fileName) { + fileName = filepath.Join(Log.RootPath, fileName) + } else { + fileName = filepath.Clean(fileName) + } + if err := os.MkdirAll(filepath.Dir(fileName), os.ModePerm); err != nil { + panic(fmt.Sprintf("unable to create directory for log %q: %v", fileName, err.Error())) + } + return fileName +} + +func loadLogModeByName(rootCfg ConfigProvider, loggerName, modeName string) (writerName, writerType string, writerMode log.WriterMode, err error) { + sec := rootCfg.Section("log." + modeName) + + writerMode = log.WriterMode{} + writerType = ConfigSectionKeyString(sec, "MODE") + if writerType == "" { + writerType = modeName + } + + writerName = modeName + defaultFlags := "stdflags" + defaultFilaName := "gitea.log" + if loggerName == "access" { + // "access" logger is special, by default it doesn't have output flags, so it also needs a new writer name to avoid conflicting with other writers. + // so "access" logger's writer name is usually "file.access" or "console.access" + writerName += ".access" + defaultFlags = "none" + defaultFilaName = "access.log" + } + + writerMode.Level = log.LevelFromString(ConfigInheritedKeyString(sec, "LEVEL", Log.Level.String())) + writerMode.StacktraceLevel = log.LevelFromString(ConfigInheritedKeyString(sec, "STACKTRACE_LEVEL", Log.StacktraceLogLevel.String())) + writerMode.Prefix = ConfigInheritedKeyString(sec, "PREFIX") + writerMode.Expression = ConfigInheritedKeyString(sec, "EXPRESSION") + writerMode.Flags = log.FlagsFromString(ConfigInheritedKeyString(sec, "FLAGS", defaultFlags)) + + switch writerType { + case "console": + useStderr := ConfigInheritedKey(sec, "STDERR").MustBool(false) + defaultCanColor := log.CanColorStdout + if useStderr { + defaultCanColor = log.CanColorStderr + } + writerOption := log.WriterConsoleOption{Stderr: useStderr} + writerMode.Colorize = ConfigInheritedKey(sec, "COLORIZE").MustBool(defaultCanColor) + writerMode.WriterOption = writerOption + case "file": + fileName := LogPrepareFilenameForWriter(ConfigInheritedKey(sec, "FILE_NAME").String(), defaultFilaName) + writerOption := log.WriterFileOption{} + writerOption.FileName = fileName + filenameSuffix // FIXME: the suffix doesn't seem right, see its related comments + writerOption.LogRotate = ConfigInheritedKey(sec, "LOG_ROTATE").MustBool(true) + writerOption.MaxSize = 1 << uint(ConfigInheritedKey(sec, "MAX_SIZE_SHIFT").MustInt(28)) + writerOption.DailyRotate = ConfigInheritedKey(sec, "DAILY_ROTATE").MustBool(true) + writerOption.MaxDays = ConfigInheritedKey(sec, "MAX_DAYS").MustInt(7) + writerOption.Compress = ConfigInheritedKey(sec, "COMPRESS").MustBool(true) + writerOption.CompressionLevel = ConfigInheritedKey(sec, "COMPRESSION_LEVEL").MustInt(-1) + writerMode.WriterOption = writerOption + case "conn": + writerOption := log.WriterConnOption{} + writerOption.ReconnectOnMsg = ConfigInheritedKey(sec, "RECONNECT_ON_MSG").MustBool() + writerOption.Reconnect = ConfigInheritedKey(sec, "RECONNECT").MustBool() + writerOption.Protocol = ConfigInheritedKey(sec, "PROTOCOL").In("tcp", []string{"tcp", "unix", "udp"}) + writerOption.Addr = ConfigInheritedKey(sec, "ADDR").MustString(":7020") + writerMode.WriterOption = writerOption + default: + if !log.HasEventWriter(writerType) { + return "", "", writerMode, fmt.Errorf("invalid log writer type (mode): %s, maybe it needs something like 'MODE=file' in [log.%s] section", writerType, modeName) + } + } + + return writerName, writerType, writerMode, nil +} + +var filenameSuffix = "" + +// RestartLogsWithPIDSuffix restarts the logs with a PID suffix on files +// FIXME: it seems not right, it breaks log rotating or log collectors +func RestartLogsWithPIDSuffix() { + filenameSuffix = fmt.Sprintf(".%d", os.Getpid()) + initAllLoggers() // when forking, before restarting, rename logger file and re-init all loggers +} + +func InitLoggersForTest() { + initAllLoggers() +} + +// initAllLoggers creates all the log services +func initAllLoggers() { + initManagedLoggers(log.GetManager(), CfgProvider) + + golog.SetFlags(0) + golog.SetPrefix("") + golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) +} + +func initManagedLoggers(manager *log.LoggerManager, cfg ConfigProvider) { + loadLogGlobalFrom(cfg) + prepareLoggerConfig(cfg) + + initLoggerByName(manager, cfg, log.DEFAULT) // default + initLoggerByName(manager, cfg, "access") + initLoggerByName(manager, cfg, "router") + initLoggerByName(manager, cfg, "xorm") +} + +func initLoggerByName(manager *log.LoggerManager, rootCfg ConfigProvider, loggerName string) { + sec := rootCfg.Section("log") + keyPrefix := "logger." + loggerName + + disabled := sec.HasKey(keyPrefix+".MODE") && sec.Key(keyPrefix+".MODE").String() == "" + if disabled { + return + } + + modeVal := sec.Key(keyPrefix + ".MODE").String() + if modeVal == "," { + modeVal = Log.Mode + } + + var eventWriters []log.EventWriter + modes := strings.Split(modeVal, ",") + for _, modeName := range modes { + modeName = strings.TrimSpace(modeName) + if modeName == "" { + continue + } + writerName, writerType, writerMode, err := loadLogModeByName(rootCfg, loggerName, modeName) + if err != nil { + log.FallbackErrorf("Failed to load writer mode %q for logger %s: %v", modeName, loggerName, err) + continue + } + if writerMode.BufferLen == 0 { + writerMode.BufferLen = Log.BufferLen + } + eventWriter := manager.GetSharedWriter(writerName) + if eventWriter == nil { + eventWriter, err = manager.NewSharedWriter(writerName, writerType, writerMode) + if err != nil { + log.FallbackErrorf("Failed to create event writer for logger %s: %v", loggerName, err) + continue + } + } + eventWriters = append(eventWriters, eventWriter) + } + + manager.GetLogger(loggerName).ReplaceAllWriters(eventWriters...) +} + +func InitSQLLoggersForCli(level log.Level) { + log.SetConsoleLogger("xorm", "console", level) +} + +func IsAccessLogEnabled() bool { + return log.IsLoggerEnabled("access") +} + +func IsRouteLogEnabled() bool { + return log.IsLoggerEnabled("router") +} |