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 }