You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

log.go 11KB

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