log.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Copyright 2020 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package conf
  5. import (
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "github.com/pkg/errors"
  10. "gopkg.in/ini.v1"
  11. log "unknwon.dev/clog/v2"
  12. )
  13. type loggerConf struct {
  14. Buffer int64
  15. Config any
  16. }
  17. type logConf struct {
  18. RootPath string
  19. Modes []string
  20. Configs []*loggerConf
  21. }
  22. // Log settings
  23. var Log *logConf
  24. // initLogConf returns parsed logging configuration from given INI file. When the
  25. // argument "hookMode" is true, it only initializes the root path for log files.
  26. // NOTE: Because we always create a console logger as the primary logger at init time,
  27. // we need to remove it in case the user doesn't configure to use it after the logging
  28. // service is initialized.
  29. func initLogConf(cfg *ini.File, hookMode bool) (_ *logConf, hasConsole bool, _ error) {
  30. rootPath := cfg.Section("log").Key("ROOT_PATH").MustString(filepath.Join(WorkDir(), "log"))
  31. if hookMode {
  32. return &logConf{
  33. RootPath: ensureAbs(rootPath),
  34. }, false, nil
  35. }
  36. modes := strings.Split(cfg.Section("log").Key("MODE").MustString("console"), ",")
  37. lc := &logConf{
  38. RootPath: ensureAbs(rootPath),
  39. Modes: make([]string, 0, len(modes)),
  40. Configs: make([]*loggerConf, 0, len(modes)),
  41. }
  42. // Iterate over [log.*] sections to initialize individual logger.
  43. levelMappings := map[string]log.Level{
  44. "trace": log.LevelTrace,
  45. "info": log.LevelInfo,
  46. "warn": log.LevelWarn,
  47. "error": log.LevelError,
  48. "fatal": log.LevelFatal,
  49. }
  50. for i := range modes {
  51. modes[i] = strings.ToLower(strings.TrimSpace(modes[i]))
  52. secName := "log." + modes[i]
  53. sec, err := cfg.GetSection(secName)
  54. if err != nil {
  55. return nil, hasConsole, errors.Errorf("missing configuration section [%s] for %q logger", secName, modes[i])
  56. }
  57. level := levelMappings[strings.ToLower(sec.Key("LEVEL").MustString("trace"))]
  58. buffer := sec.Key("BUFFER_LEN").MustInt64(100)
  59. var c *loggerConf
  60. switch modes[i] {
  61. case log.DefaultConsoleName:
  62. hasConsole = true
  63. c = &loggerConf{
  64. Buffer: buffer,
  65. Config: log.ConsoleConfig{
  66. Level: level,
  67. },
  68. }
  69. case log.DefaultFileName:
  70. logPath := filepath.Join(lc.RootPath, "gogs.log")
  71. c = &loggerConf{
  72. Buffer: buffer,
  73. Config: log.FileConfig{
  74. Level: level,
  75. Filename: logPath,
  76. FileRotationConfig: log.FileRotationConfig{
  77. Rotate: sec.Key("LOG_ROTATE").MustBool(true),
  78. Daily: sec.Key("DAILY_ROTATE").MustBool(true),
  79. MaxSize: 1 << uint(sec.Key("MAX_SIZE_SHIFT").MustInt(28)),
  80. MaxLines: sec.Key("MAX_LINES").MustInt64(1000000),
  81. MaxDays: sec.Key("MAX_DAYS").MustInt64(7),
  82. },
  83. },
  84. }
  85. case log.DefaultSlackName:
  86. c = &loggerConf{
  87. Buffer: buffer,
  88. Config: log.SlackConfig{
  89. Level: level,
  90. URL: sec.Key("URL").String(),
  91. },
  92. }
  93. case log.DefaultDiscordName:
  94. c = &loggerConf{
  95. Buffer: buffer,
  96. Config: log.DiscordConfig{
  97. Level: level,
  98. URL: sec.Key("URL").String(),
  99. Username: sec.Key("USERNAME").String(),
  100. },
  101. }
  102. default:
  103. continue
  104. }
  105. lc.Modes = append(lc.Modes, modes[i])
  106. lc.Configs = append(lc.Configs, c)
  107. }
  108. return lc, hasConsole, nil
  109. }
  110. // InitLogging initializes the logging service of the application. When the
  111. // "hookMode" is true, it only initializes the root path for log files without
  112. // creating any logger. It will also not remove the primary logger in "hookMode"
  113. // and is up to the caller to decide when to remove it.
  114. func InitLogging(hookMode bool) {
  115. logConf, hasConsole, err := initLogConf(File, hookMode)
  116. if err != nil {
  117. log.Fatal("Failed to init logging configuration: %v", err)
  118. }
  119. defer func() {
  120. Log = logConf
  121. }()
  122. if hookMode {
  123. return
  124. }
  125. err = os.MkdirAll(logConf.RootPath, os.ModePerm)
  126. if err != nil {
  127. log.Fatal("Failed to create log directory: %v", err)
  128. }
  129. for i, mode := range logConf.Modes {
  130. c := logConf.Configs[i]
  131. var err error
  132. var level log.Level
  133. switch mode {
  134. case log.DefaultConsoleName:
  135. level = c.Config.(log.ConsoleConfig).Level
  136. err = log.NewConsole(c.Buffer, c.Config)
  137. case log.DefaultFileName:
  138. level = c.Config.(log.FileConfig).Level
  139. err = log.NewFile(c.Buffer, c.Config)
  140. case log.DefaultSlackName:
  141. level = c.Config.(log.SlackConfig).Level
  142. err = log.NewSlack(c.Buffer, c.Config)
  143. case log.DefaultDiscordName:
  144. level = c.Config.(log.DiscordConfig).Level
  145. err = log.NewDiscord(c.Buffer, c.Config)
  146. default:
  147. panic("unreachable")
  148. }
  149. if err != nil {
  150. log.Fatal("Failed to init %s logger: %v", mode, err)
  151. return
  152. }
  153. log.Trace("Log mode: %s (%s)", strings.Title(mode), strings.Title(strings.ToLower(level.String())))
  154. }
  155. // ⚠️ WARNING: It is only safe to remove the primary logger until
  156. // there are other loggers that are initialized. Otherwise, the
  157. // application will print errors to nowhere.
  158. if !hasConsole {
  159. log.Remove(log.DefaultConsoleName)
  160. }
  161. }