loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rewrite logger system (#24726)

## ⚠️ Breaking

The `log.<mode>.<logger>` style config has been dropped. If you used it,
please check the new config manual & app.example.ini to make your
instance output logs as expected.

Although many legacy options still work, it's encouraged to upgrade to
the new options.

The SMTP logger is deleted because SMTP is not suitable to collect logs.

If you have manually configured Gitea log options, please confirm the
logger system works as expected after upgrading.

## Description

Close #12082 and maybe more log-related issues, resolve some related
FIXMEs in old code (which seems unfixable before)

Just like rewriting queue #24505 : make code maintainable, clear legacy
bugs, and add the ability to support more writers (eg: JSON, structured
log)

There is a new document (with examples): `logging-config.en-us.md`

This PR is safer than the queue rewriting, because it's just for
logging, it won't break other logic.

## The old problems

The logging system is quite old and difficult to maintain:
* Unclear concepts: Logger, NamedLogger, MultiChannelledLogger,
SubLogger, EventLogger, WriterLogger etc
* Some code is diffuclt to konw whether it is right:
`log.DelNamedLogger("console")` vs `log.DelNamedLogger(log.DEFAULT)` vs
`log.DelLogger("console")`
* The old system heavily depends on ini config system, it's difficult to
create new logger for different purpose, and it's very fragile.
* The "color" trick is difficult to use and read, many colors are
unnecessary, and in the future structured log could help
* It's difficult to add other log formats, eg: JSON format
* The log outputer doesn't have full control of its goroutine, it's
difficult to make outputer have advanced behaviors
* The logs could be lost in some cases: eg: no Fatal error when using
CLI.
* Config options are passed by JSON, which is quite fragile.
* INI package makes the KEY in `[log]` section visible in `[log.sub1]`
and `[log.sub1.subA]`, this behavior is quite fragile and would cause
more unclear problems, and there is no strong requirement to support
`log.<mode>.<logger>` syntax.


## The new design

See `logger.go` for documents.


## Screenshot

<details>


![image](https://github.com/go-gitea/gitea/assets/2114189/4462d713-ba39-41f5-bb08-de912e67e1ff)


![image](https://github.com/go-gitea/gitea/assets/2114189/b188035e-f691-428b-8b2d-ff7b2199b2f9)


![image](https://github.com/go-gitea/gitea/assets/2114189/132e9745-1c3b-4e00-9e0d-15eaea495dee)

</details>

## TODO

* [x] add some new tests
* [x] fix some tests
* [x] test some sub-commands (manually ....)

---------

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: Giteabot <teabot@gitea.io>

authored by

wxiaoguang
Jason Song
delvh
Giteabot
and committed by
GitHub
46476607 65dff8e3

+3822 -5353
+16 -1
cmd/cmd.go
··· 9 9 "context" 10 10 "errors" 11 11 "fmt" 12 + "io" 12 13 "os" 13 14 "os/signal" 14 15 "strings" ··· 59 60 func initDB(ctx context.Context) error { 60 61 setting.Init(&setting.Options{}) 61 62 setting.LoadDBSetting() 62 - setting.InitSQLLog(false) 63 + setting.InitSQLLoggersForCli(log.INFO) 63 64 64 65 if setting.Database.Type == "" { 65 66 log.Fatal(`Database settings are missing from the configuration file: %q. ··· 93 94 94 95 return ctx, cancel 95 96 } 97 + 98 + func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) { 99 + if out != os.Stdout && out != os.Stderr { 100 + panic("setupConsoleLogger can only be used with os.Stdout or os.Stderr") 101 + } 102 + 103 + writeMode := log.WriterMode{ 104 + Level: level, 105 + Colorize: colorize, 106 + WriterOption: log.WriterConsoleOption{Stderr: out == os.Stderr}, 107 + } 108 + writer := log.NewEventWriterConsole("console-default", writeMode) 109 + log.GetManager().GetLogger(log.DEFAULT).RemoveAllWriters().AddWriters(writer) 110 + }
+31 -59
cmd/doctor.go
··· 4 4 package cmd 5 5 6 6 import ( 7 - "errors" 8 7 "fmt" 9 8 golog "log" 10 9 "os" 10 + "path/filepath" 11 11 "strings" 12 12 "text/tabwriter" 13 13 ··· 82 82 } 83 83 84 84 func runRecreateTable(ctx *cli.Context) error { 85 + stdCtx, cancel := installSignals() 86 + defer cancel() 87 + 85 88 // Redirect the default golog to here 86 89 golog.SetFlags(0) 87 90 golog.SetPrefix("") 88 - golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT))) 91 + golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) 89 92 93 + debug := ctx.Bool("debug") 90 94 setting.Init(&setting.Options{}) 91 95 setting.LoadDBSetting() 92 96 93 - setting.Log.EnableXORMLog = ctx.Bool("debug") 94 - setting.Database.LogSQL = ctx.Bool("debug") 95 - // FIXME: don't use CfgProvider directly 96 - setting.CfgProvider.Section("log").Key("XORM").SetValue(",") 97 - 98 - setting.InitSQLLog(!ctx.Bool("debug")) 99 - stdCtx, cancel := installSignals() 100 - defer cancel() 97 + if debug { 98 + setting.InitSQLLoggersForCli(log.DEBUG) 99 + } else { 100 + setting.InitSQLLoggersForCli(log.INFO) 101 + } 101 102 103 + setting.Database.LogSQL = debug 102 104 if err := db.InitEngine(stdCtx); err != nil { 103 105 fmt.Println(err) 104 106 fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") ··· 125 127 }) 126 128 } 127 129 128 - func setDoctorLogger(ctx *cli.Context) { 130 + func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { 131 + // Silence the default loggers 132 + setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) 133 + 129 134 logFile := ctx.String("log-file") 130 135 if !ctx.IsSet("log-file") { 131 136 logFile = "doctor.log" 132 137 } 133 - colorize := log.CanColorStdout 134 - if ctx.IsSet("color") { 135 - colorize = ctx.Bool("color") 136 - } 137 138 138 139 if len(logFile) == 0 { 139 - log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize)) 140 + // if no doctor log-file is set, do not show any log from default logger 140 141 return 141 142 } 142 143 143 - defer func() { 144 - recovered := recover() 145 - if recovered == nil { 146 - return 147 - } 148 - 149 - err, ok := recovered.(error) 150 - if !ok { 151 - panic(recovered) 152 - } 153 - if errors.Is(err, os.ErrPermission) { 154 - fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file due to permissions error: %s\n %v\n", logFile, err) 155 - } else { 156 - fmt.Fprintf(os.Stderr, "ERROR: Unable to write logs to provided file: %s\n %v\n", logFile, err) 157 - } 158 - fmt.Fprintf(os.Stderr, "WARN: Logging will be disabled\n Use `--log-file` to configure log file location\n") 159 - log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"NONE","stacktracelevel":"NONE","colorize":%t}`, colorize)) 160 - }() 161 - 162 144 if logFile == "-" { 163 - log.NewLogger(1000, "doctor", "console", fmt.Sprintf(`{"level":"trace","stacktracelevel":"NONE","colorize":%t}`, colorize)) 145 + setupConsoleLogger(log.TRACE, colorize, os.Stdout) 164 146 } else { 165 - log.NewLogger(1000, "doctor", "file", fmt.Sprintf(`{"filename":%q,"level":"trace","stacktracelevel":"NONE"}`, logFile)) 147 + logFile, _ = filepath.Abs(logFile) 148 + writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}} 149 + writer, err := log.NewEventWriter("console-to-file", "file", writeMode) 150 + if err != nil { 151 + log.FallbackErrorf("unable to create file log writer: %v", err) 152 + return 153 + } 154 + log.GetManager().GetLogger(log.DEFAULT).RemoveAllWriters().AddWriters(writer) 166 155 } 167 156 } 168 157 ··· 170 159 stdCtx, cancel := installSignals() 171 160 defer cancel() 172 161 173 - // Silence the default loggers 174 - log.DelNamedLogger("console") 175 - log.DelNamedLogger(log.DEFAULT) 176 - 177 - // Now setup our own 178 - setDoctorLogger(ctx) 179 - 180 162 colorize := log.CanColorStdout 181 163 if ctx.IsSet("color") { 182 164 colorize = ctx.Bool("color") 183 165 } 184 166 185 - // Finally redirect the default golog to here 167 + setupDoctorDefaultLogger(ctx, colorize) 168 + 169 + // Finally redirect the default golang's log to here 186 170 golog.SetFlags(0) 187 171 golog.SetPrefix("") 188 - golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT))) 172 + golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) 189 173 190 174 if ctx.IsSet("list") { 191 175 w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) ··· 233 217 } 234 218 } 235 219 236 - // Now we can set up our own logger to return information about what the doctor is doing 237 - if err := log.NewNamedLogger("doctorouter", 238 - 0, 239 - "console", 240 - "console", 241 - fmt.Sprintf(`{"level":"INFO","stacktracelevel":"NONE","colorize":%t,"flags":-1}`, colorize)); err != nil { 242 - fmt.Println(err) 243 - return err 244 - } 245 - 246 - logger := log.GetLogger("doctorouter") 247 - defer logger.Close() 248 - return doctor.RunChecks(stdCtx, logger, ctx.Bool("fix"), checks) 220 + return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks) 249 221 }
+1 -4
cmd/dump.go
··· 172 172 outType := ctx.String("type") 173 173 if fileName == "-" { 174 174 file = os.Stdout 175 - err := log.DelLogger("console") 176 - if err != nil { 177 - fatal("Deleting default logger failed. Can not write to stdout: %v", err) 178 - } 175 + setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) 179 176 } else { 180 177 for _, suffix := range outputTypeEnum.Enum { 181 178 if strings.HasSuffix(fileName, "."+suffix) {
+1 -7
cmd/embedded.go
··· 97 97 } 98 98 99 99 func initEmbeddedExtractor(c *cli.Context) error { 100 - // FIXME: there is a bug, if the user runs `gitea embedded` with a different user or root, 101 - // The setting.Init (loadRunModeFrom) will fail and do log.Fatal 102 - // But the console logger has been deleted, so nothing is printed, the user sees nothing and Gitea just exits. 103 - 104 - // Silence the console logger 105 - log.DelNamedLogger("console") 106 - log.DelNamedLogger(log.DEFAULT) 100 + setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) 107 101 108 102 // Read configuration file 109 103 setting.Init(&setting.Options{
+21 -99
cmd/manager_logging.go
··· 16 16 var ( 17 17 defaultLoggingFlags = []cli.Flag{ 18 18 cli.StringFlag{ 19 - Name: "group, g", 20 - Usage: "Group to add logger to - will default to \"default\"", 19 + Name: "logger", 20 + Usage: `Logger name - will default to "default"`, 21 21 }, cli.StringFlag{ 22 - Name: "name, n", 23 - Usage: "Name of the new logger - will default to mode", 22 + Name: "writer", 23 + Usage: "Name of the log writer - will default to mode", 24 24 }, cli.StringFlag{ 25 - Name: "level, l", 25 + Name: "level", 26 26 Usage: "Logging level for the new logger", 27 27 }, cli.StringFlag{ 28 28 Name: "stacktrace-level, L", ··· 83 83 cli.BoolFlag{ 84 84 Name: "debug", 85 85 }, cli.StringFlag{ 86 - Name: "group, g", 87 - Usage: "Group to add logger to - will default to \"default\"", 86 + Name: "logger", 87 + Usage: `Logger name - will default to "default"`, 88 88 }, 89 89 }, 90 90 Action: runRemoveLogger, ··· 93 93 Usage: "Add a logger", 94 94 Subcommands: []cli.Command{ 95 95 { 96 - Name: "console", 97 - Usage: "Add a console logger", 98 - Flags: append(defaultLoggingFlags, 99 - cli.BoolFlag{ 100 - Name: "stderr", 101 - Usage: "Output console logs to stderr - only relevant for console", 102 - }), 103 - Action: runAddConsoleLogger, 104 - }, { 105 96 Name: "file", 106 97 Usage: "Add a file logger", 107 98 Flags: append(defaultLoggingFlags, []cli.Flag{ ··· 148 139 }, 149 140 }...), 150 141 Action: runAddConnLogger, 151 - }, { 152 - Name: "smtp", 153 - Usage: "Add an SMTP logger", 154 - Flags: append(defaultLoggingFlags, []cli.Flag{ 155 - cli.StringFlag{ 156 - Name: "username, u", 157 - Usage: "Mail server username", 158 - }, cli.StringFlag{ 159 - Name: "password, P", 160 - Usage: "Mail server password", 161 - }, cli.StringFlag{ 162 - Name: "host, H", 163 - Usage: "Mail server host (defaults to: 127.0.0.1:25)", 164 - }, cli.StringSliceFlag{ 165 - Name: "send-to, s", 166 - Usage: "Email address(es) to send to", 167 - }, cli.StringFlag{ 168 - Name: "subject, S", 169 - Usage: "Subject header of sent emails", 170 - }, 171 - }...), 172 - Action: runAddSMTPLogger, 173 142 }, 174 143 }, 175 144 }, { ··· 194 163 defer cancel() 195 164 196 165 setup(ctx, c.Bool("debug")) 197 - group := c.String("group") 198 - if len(group) == 0 { 199 - group = log.DEFAULT 166 + logger := c.String("logger") 167 + if len(logger) == 0 { 168 + logger = log.DEFAULT 200 169 } 201 - name := c.Args().First() 170 + writer := c.Args().First() 202 171 203 - extra := private.RemoveLogger(ctx, group, name) 172 + extra := private.RemoveLogger(ctx, logger, writer) 204 173 return handleCliResponseExtra(extra) 205 174 } 206 175 207 - func runAddSMTPLogger(c *cli.Context) error { 208 - ctx, cancel := installSignals() 209 - defer cancel() 210 - 211 - setup(ctx, c.Bool("debug")) 212 - vals := map[string]interface{}{} 213 - mode := "smtp" 214 - if c.IsSet("host") { 215 - vals["host"] = c.String("host") 216 - } else { 217 - vals["host"] = "127.0.0.1:25" 218 - } 219 - 220 - if c.IsSet("username") { 221 - vals["username"] = c.String("username") 222 - } 223 - if c.IsSet("password") { 224 - vals["password"] = c.String("password") 225 - } 226 - 227 - if !c.IsSet("send-to") { 228 - return fmt.Errorf("Some recipients must be provided") 229 - } 230 - vals["sendTos"] = c.StringSlice("send-to") 231 - 232 - if c.IsSet("subject") { 233 - vals["subject"] = c.String("subject") 234 - } else { 235 - vals["subject"] = "Diagnostic message from Gitea" 236 - } 237 - 238 - return commonAddLogger(c, mode, vals) 239 - } 240 - 241 176 func runAddConnLogger(c *cli.Context) error { 242 177 ctx, cancel := installSignals() 243 178 defer cancel() ··· 301 236 return commonAddLogger(c, mode, vals) 302 237 } 303 238 304 - func runAddConsoleLogger(c *cli.Context) error { 305 - ctx, cancel := installSignals() 306 - defer cancel() 307 - 308 - setup(ctx, c.Bool("debug")) 309 - vals := map[string]interface{}{} 310 - mode := "console" 311 - if c.IsSet("stderr") && c.Bool("stderr") { 312 - vals["stderr"] = c.Bool("stderr") 313 - } 314 - return commonAddLogger(c, mode, vals) 315 - } 316 - 317 239 func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error { 318 240 if len(c.String("level")) > 0 { 319 - vals["level"] = log.FromString(c.String("level")).String() 241 + vals["level"] = log.LevelFromString(c.String("level")).String() 320 242 } 321 243 if len(c.String("stacktrace-level")) > 0 { 322 - vals["stacktraceLevel"] = log.FromString(c.String("stacktrace-level")).String() 244 + vals["stacktraceLevel"] = log.LevelFromString(c.String("stacktrace-level")).String() 323 245 } 324 246 if len(c.String("expression")) > 0 { 325 247 vals["expression"] = c.String("expression") ··· 333 255 if c.IsSet("color") { 334 256 vals["colorize"] = c.Bool("color") 335 257 } 336 - group := "default" 337 - if c.IsSet("group") { 338 - group = c.String("group") 258 + logger := log.DEFAULT 259 + if c.IsSet("logger") { 260 + logger = c.String("logger") 339 261 } 340 - name := mode 341 - if c.IsSet("name") { 342 - name = c.String("name") 262 + writer := mode 263 + if c.IsSet("writer") { 264 + writer = c.String("writer") 343 265 } 344 266 ctx, cancel := installSignals() 345 267 defer cancel() 346 268 347 - extra := private.AddLogger(ctx, group, name, mode, vals) 269 + extra := private.AddLogger(ctx, logger, writer, mode, vals) 348 270 return handleCliResponseExtra(extra) 349 271 } 350 272
+2 -3
cmd/serv.go
··· 56 56 } 57 57 58 58 func setup(ctx context.Context, debug bool) { 59 - _ = log.DelLogger("console") 60 59 if debug { 61 - _ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`) 60 + setupConsoleLogger(log.TRACE, false, os.Stderr) 62 61 } else { 63 - _ = log.NewLogger(1000, "console", "console", `{"level":"fatal","stacktracelevel":"NONE","stderr":true}`) 62 + setupConsoleLogger(log.FATAL, false, os.Stderr) 64 63 } 65 64 setting.Init(&setting.Options{}) 66 65 if debug {
+4 -6
cmd/web.go
··· 103 103 104 104 func runWeb(ctx *cli.Context) error { 105 105 if ctx.Bool("verbose") { 106 - _ = log.DelLogger("console") 107 - log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "trace", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout)) 106 + setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout) 108 107 } else if ctx.Bool("quiet") { 109 - _ = log.DelLogger("console") 110 - log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "fatal", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout)) 108 + setupConsoleLogger(log.FATAL, log.CanColorStdout, os.Stdout) 111 109 } 112 110 defer func() { 113 111 if panicked := recover(); panicked != nil { ··· 156 154 case <-graceful.GetManager().IsShutdown(): 157 155 <-graceful.GetManager().Done() 158 156 log.Info("PID: %d Gitea Web Finished", os.Getpid()) 159 - log.Close() 157 + log.GetManager().Close() 160 158 return err 161 159 default: 162 160 } ··· 199 197 err := listen(c, true) 200 198 <-graceful.GetManager().Done() 201 199 log.Info("PID: %d Gitea Web Finished", os.Getpid()) 202 - log.Close() 200 + log.GetManager().Close() 203 201 return err 204 202 } 205 203
+25 -67
custom/conf/app.example.ini
··· 230 230 ;; 231 231 ;; Disable CDN even in "prod" mode 232 232 ;OFFLINE_MODE = false 233 - ;DISABLE_ROUTER_LOG = false 234 233 ;; 235 234 ;; TLS Settings: Either ACME or manual 236 235 ;; (Other common TLS configuration are found before) ··· 387 386 ;ITERATE_BUFFER_SIZE = 50 388 387 ;; 389 388 ;; Show the database generated SQL 390 - LOG_SQL = false ; if unset defaults to true 389 + ;LOG_SQL = false 391 390 ;; 392 391 ;; Maximum number of DB Connect retries 393 392 ;DB_RETRIES = 10 ··· 550 549 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 551 550 ;; Main Logger 552 551 ;; 553 - ;; Either "console", "file", "conn", "smtp" or "database", default is "console" 552 + ;; Either "console", "file" or "conn", default is "console" 554 553 ;; Use comma to separate multiple modes, e.g. "console, file" 555 554 MODE = console 556 555 ;; 557 - ;; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical" or "None", default is "Info" 556 + ;; Either "Trace", "Debug", "Info", "Warn", "Error" or "None", default is "Info" 558 557 LEVEL = Info 559 558 ;; 560 - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 561 - ;; Router Logger 559 + ;; Print Stacktrace with logs (rarely helpful, do not set) Either "Trace", "Debug", "Info", "Warn", "Error", default is "None" 560 + ;STACKTRACE_LEVEL = None 562 561 ;; 563 - ;; Switch off the router log 564 - ;DISABLE_ROUTER_LOG=false 562 + ;; Buffer length of the channel, keep it as it is if you don't know what it is. 563 + ;BUFFER_LEN = 10000 565 564 ;; 566 - ;; Set the log "modes" for the router log (if file is set the log file will default to router.log) 567 - ROUTER = console 568 - ;; 569 - ;; The router will log different things at different levels. 565 + ;; Sub logger modes, a single comma means use default MODE above, empty means disable it 566 + ;logger.access.MODE= 567 + ;logger.router.MODE=, 568 + ;logger.xorm.MODE=, 570 569 ;; 571 - ;; * started messages will be logged at TRACE level 572 - ;; * polling/completed routers will be logged at INFO 573 - ;; * slow routers will be logged at WARN 574 - ;; * failed routers will be logged at WARN 570 + ;; Collect SSH logs (Creates log from ssh git request) 575 571 ;; 576 - ;; The routing level will default to that of the system but individual router level can be set in 577 - ;; [log.<mode>.router] LEVEL 572 + ;ENABLE_SSH_LOG = false 578 573 ;; 579 574 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 575 + ;; 576 + ;; Access Logger (Creates log in NCSA common log format) 580 577 ;; 581 578 ;; Print request id which parsed from request headers in access log, when access log is enabled. 582 579 ;; * E.g: ··· 587 584 ;; If you configure more than one in the .ini file, it will match in the order of configuration, 588 585 ;; and the first match will be finally printed in the log. 589 586 ;; * E.g: 590 - ;; * In reuqest Header: X-Trace-ID: trace-id-1q2w3e4r 587 + ;; * In request Header: X-Trace-ID: trace-id-1q2w3e4r 591 588 ;; * Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID, X-Trace-ID, X-Req-ID 592 589 ;; * Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "trace-id-1q2w3e4r" 593 590 ;; 594 - ;; REQUEST_ID_HEADERS = 595 - 596 - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 597 - ;; 598 - ;; Access Logger (Creates log in NCSA common log format) 599 - ;; 600 - ;ENABLE_ACCESS_LOG = false 601 - ;; 602 - ;; Set the log "modes" for the access log (if file is set the log file will default to access.log) 603 - ;ACCESS = file 591 + ;REQUEST_ID_HEADERS = 604 592 ;; 605 593 ;; Sets the template used to create the access log. 606 594 ;ACCESS_LOG_TEMPLATE = {{.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}}" 607 - ;; 608 - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 609 - ;; 610 - ;; SSH log (Creates log from ssh git request) 611 - ;; 612 - ;ENABLE_SSH_LOG = false 613 - ;; 614 - ;; Other Settings 615 - ;; 616 - ;; Print Stacktraces with logs. (Rarely helpful.) Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "None" 617 - ;STACKTRACE_LEVEL = None 618 - ;; 619 - ;; Buffer length of the channel, keep it as it is if you don't know what it is. 620 - ;BUFFER_LEN = 10000 621 595 622 596 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 623 - ;; Creating specific log configuration 624 597 ;; 625 - ;; You can set specific configuration for individual modes and subloggers 598 + ;; Log modes (aka log writers) 626 599 ;; 627 - ;; Configuration available to all log modes/subloggers 600 + ;[log.%(WriterMode)] 601 + ;MODE=console/file/conn/... 628 602 ;LEVEL= 629 603 ;FLAGS = stdflags 630 604 ;EXPRESSION = 631 605 ;PREFIX = 632 606 ;COLORIZE = false 633 607 ;; 634 - ;; For "console" mode only 608 + ;[log.console] 635 609 ;STDERR = false 636 610 ;; 637 - ;; For "file" mode only 638 - ;LEVEL = 639 - ;; Set the file_name for the logger. If this is a relative path this 640 - ;; will be relative to ROOT_PATH 611 + ;[log.file] 612 + ;; Set the file_name for the logger. If this is a relative path this will be relative to ROOT_PATH 641 613 ;FILE_NAME = 642 614 ;; This enables automated log rotate(switch of following options), default is true 643 615 ;LOG_ROTATE = true ··· 651 623 ;COMPRESS = true 652 624 ;; compression level see godoc for compress/gzip 653 625 ;COMPRESSION_LEVEL = -1 654 - ; 655 - ;; For "conn" mode only 656 - ;LEVEL = 626 + ;; 627 + ;[log.conn] 657 628 ;; Reconnect host for every single message, default is false 658 629 ;RECONNECT_ON_MSG = false 659 630 ;; Try to reconnect when connection is lost, default is false ··· 662 633 ;PROTOCOL = tcp 663 634 ;; Host address 664 635 ;ADDR = 665 - ; 666 - ;; For "smtp" mode only 667 - ;LEVEL = 668 - ;; Name displayed in mail title, default is "Diagnostic message from server" 669 - ;SUBJECT = Diagnostic message from server 670 - ;; Mail server 671 - ;HOST = 672 - ;; Mailer user name and password 673 - ;USER = 674 - ;; Use PASSWD = `your password` for quoting if you use special characters in the password. 675 - ;PASSWD = 676 - ;; Receivers, can be one or more, e.g. 1@example.com,2@example.com 677 - ;RECEIVERS = 678 636 679 637 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 680 638 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+13 -27
docs/content/doc/administration/config-cheat-sheet.en-us.md
··· 835 835 ## Log (`log`) 836 836 837 837 - `ROOT_PATH`: **\<empty\>**: Root path for log files. 838 - - `MODE`: **console**: Logging mode. For multiple modes, use a comma to separate values. You can configure each mode in per mode log subsections `\[log.modename\]`. By default the file mode will log to `$ROOT_PATH/gitea.log`. 838 + - `MODE`: **console**: Logging mode. For multiple modes, use a comma to separate values. You can configure each mode in per mode log subsections `\[log.writer-mode-name\]`. 839 839 - `LEVEL`: **Info**: General log level. \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\] 840 - - `STACKTRACE_LEVEL`: **None**: Default log level at which to log create stack traces. \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\] 840 + - `STACKTRACE_LEVEL`: **None**: Default log level at which to log create stack traces (rarely useful, do not set it). \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\] 841 841 - `ENABLE_SSH_LOG`: **false**: save ssh log to log file 842 - - `ENABLE_XORM_LOG`: **true**: Set whether to perform XORM logging. Please note SQL statement logging can be disabled by setting `LOG_SQL` to false in the `[database]` section. 843 - 844 - ### Router Log (`log`) 845 - 846 - - `DISABLE_ROUTER_LOG`: **false**: Mute printing of the router log. 847 - - `ROUTER`: **console**: The mode or name of the log the router should log to. (If you set this to `,` it will log to default Gitea logger.) 848 - NB: You must have `DISABLE_ROUTER_LOG` set to `false` for this option to take effect. Configure each mode in per mode log subsections `\[log.modename.router\]`. 842 + - `logger.access.MODE`: **\<empty\>**: The "access" logger 843 + - `logger.router.MODE`: **,**: The "router" logger, a single comma means it will use the default MODE above 844 + - `logger.xorm.MODE`: **,**: The "xorm" logger 849 845 850 846 ### Access Log (`log`) 851 847 852 - - `ENABLE_ACCESS_LOG`: **false**: Creates an access.log in NCSA common log format, or as per the following template 853 - - `ACCESS`: **file**: Logging mode for the access logger, use a comma to separate values. Configure each mode in per mode log subsections `\[log.modename.access\]`. By default the file mode will log to `$ROOT_PATH/access.log`. (If you set this to `,` it will log to the default Gitea logger.) 854 848 - `ACCESS_LOG_TEMPLATE`: **`{{.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}}"`**: Sets the template used to create the access log. 855 849 - The following variables are available: 856 850 - `Ctx`: the `context.Context` of the request. ··· 858 852 - `Start`: the start time of the request. 859 853 - `ResponseWriter`: the responseWriter from the request. 860 854 - `RequestID`: the value matching REQUEST_ID_HEADERS(default: `-`, if not matched). 861 - - You must be very careful to ensure that this template does not throw errors or panics as this template runs outside of the panic/recovery script. 855 + - You must be very careful to ensure that this template does not throw errors or panics as this template runs outside the panic/recovery script. 862 856 - `REQUEST_ID_HEADERS`: **\<empty\>**: You can configure multiple values that are splited by comma here. It will match in the order of configuration, and the first match will be finally printed in the access log. 863 857 - e.g. 864 858 - In the Request Header: X-Request-ID: **test-id-123** 865 859 - Configuration in app.ini: REQUEST_ID_HEADERS = X-Request-ID 866 860 - Print in log: 127.0.0.1:58384 - - [14/Feb/2023:16:33:51 +0800] "**test-id-123**" ... 867 861 868 - ### Log subsections (`log.name`, `log.name.*`) 862 + ### Log subsections (`log.<writer-mode-name>`) 869 863 870 - - `LEVEL`: **log.LEVEL**: Sets the log-level of this sublogger. Defaults to the `LEVEL` set in the global `[log]` section. 864 + - `MODE`: **name**: Sets the mode of this log writer - Defaults to the provided subsection name. This allows you to have two different file loggers at different levels. 865 + - `LEVEL`: **log.LEVEL**: Sets the log-level of this writer. Defaults to the `LEVEL` set in the global `[log]` section. 871 866 - `STACKTRACE_LEVEL`: **log.STACKTRACE_LEVEL**: Sets the log level at which to log stack traces. 872 - - `MODE`: **name**: Sets the mode of this sublogger - Defaults to the provided subsection name. This allows you to have two different file loggers at different levels. 873 867 - `EXPRESSION`: **""**: A regular expression to match either the function name, file or message. Defaults to empty. Only log messages that match the expression will be saved in the logger. 874 868 - `FLAGS`: **stdflags**: A comma separated string representing the log flags. Defaults to `stdflags` which represents the prefix: `2009/01/23 01:23:23 ...a/b/c/d.go:23:runtime.Caller() [I]: message`. `none` means don't prefix log lines. See `modules/log/flags.go` for more information. 875 869 - `PREFIX`: **""**: An additional prefix for every log line in this logger. Defaults to empty. 876 870 - `COLORIZE`: **false**: Whether to colorize the log lines 877 871 878 - ### Console log mode (`log.console`, `log.console.*`, or `MODE=console`) 872 + ### Console log mode (`log.console`, or `MODE=console`) 879 873 880 874 - For the console logger `COLORIZE` will default to `true` if not on windows or the terminal is determined to be able to color. 881 875 - `STDERR`: **false**: Use Stderr instead of Stdout. 882 876 883 - ### File log mode (`log.file`, `log.file.*` or `MODE=file`) 877 + ### File log mode (`log.file`, or `MODE=file`) 884 878 885 - - `FILE_NAME`: Set the file name for this logger. Defaults as described above. If relative will be relative to the `ROOT_PATH` 879 + - `FILE_NAME`: Set the file name for this logger. Defaults to `gitea.log` (exception: access log defaults to `access.log`). If relative will be relative to the `ROOT_PATH` 886 880 - `LOG_ROTATE`: **true**: Rotate the log files. 887 881 - `MAX_SIZE_SHIFT`: **28**: Maximum size shift of a single file, 28 represents 256Mb. 888 882 - `DAILY_ROTATE`: **true**: Rotate logs daily. ··· 890 884 - `COMPRESS`: **true**: Compress old log files by default with gzip 891 885 - `COMPRESSION_LEVEL`: **-1**: Compression level 892 886 893 - ### Conn log mode (`log.conn`, `log.conn.*` or `MODE=conn`) 887 + ### Conn log mode (`log.conn`, or `MODE=conn`) 894 888 895 889 - `RECONNECT_ON_MSG`: **false**: Reconnect host for every single message. 896 890 - `RECONNECT`: **false**: Try to reconnect when connection is lost. 897 891 - `PROTOCOL`: **tcp**: Set the protocol, either "tcp", "unix" or "udp". 898 892 - `ADDR`: **:7020**: Sets the address to connect to. 899 - 900 - ### SMTP log mode (`log.smtp`, `log.smtp.*` or `MODE=smtp`) 901 - 902 - - `USER`: User email address to send from. 903 - - `PASSWD`: Password for the smtp server. 904 - - `HOST`: **127.0.0.1:25**: The SMTP host to connect to. 905 - - `RECEIVERS`: Email addresses to send to. 906 - - `SUBJECT`: **Diagnostic message from Gitea** 907 893 908 894 ## Cron (`cron`) 909 895
+295
docs/content/doc/administration/logging-config.en-us.md
··· 1 + --- 2 + date: "2019-04-02T17:06:00+01:00" 3 + title: "Logging Configuration" 4 + slug: "logging-config" 5 + weight: 40 6 + toc: false 7 + draft: false 8 + aliases: 9 + - /en-us/logging-configuration 10 + menu: 11 + sidebar: 12 + parent: "administration" 13 + name: "Logging Configuration" 14 + weight: 40 15 + identifier: "logging-config" 16 + --- 17 + 18 + # Logging Configuration 19 + 20 + The logging configuration of Gitea mainly consists of 3 types of components: 21 + 22 + - The `[log]` section for general configuration 23 + - `[log.<mode-name>]` sections for the configuration of different log writers to output logs, aka: "writer mode", the mode name is also used as "writer name". 24 + - The `[log]` section can also contain sub-logger configurations following the key schema `logger.<logger-name>.<CONFIG-KEY>` 25 + 26 + There is a fully functional log output by default, so it is not necessary to define one. 27 + 28 + **Table of Contents** 29 + 30 + {{< toc >}} 31 + 32 + ## Collecting Logs for Help 33 + 34 + To collect logs for help and issue report, see [Support Options]({{< relref "doc/help/support.en-us.md" >}}). 35 + 36 + ## The `[log]` section 37 + 38 + Configuration of logging facilities in Gitea happen in the `[log]` section and its subsections. 39 + 40 + In the top level `[log]` section the following configurations can be placed: 41 + 42 + - `ROOT_PATH`: (Default: **%(GITEA_WORK_DIR)/log**): Base path for log files 43 + - `MODE`: (Default: **console**) List of log outputs to use for the Default logger. 44 + - `LEVEL`: (Default: **Info**) Least severe log events to persist, case-insensitive. Possible values are: `Trace`, `Debug`, `Info`, `Warn`, `Error`, `Fatal`. 45 + - `STACKTRACE_LEVEL`: (Default: **None**) For this and more severe events the stacktrace will be printed upon getting logged. 46 + 47 + And it can contain the following sub-loggers: 48 + 49 + - `logger.router.MODE`: (Default: **,**): List of log outputs to use for the Router logger. 50 + - `logger.access.MODE`: (Default: **\<empty\>**) List of log outputs to use for the Access logger. By default, the access logger is disabled. 51 + - `logger.xorm.MODE`: (Default: **,**) List of log outputs to use for the XORM logger. 52 + 53 + Setting a comma (`,`) to sub-logger's mode means making it use the default global `MODE`. 54 + 55 + ## Quick samples 56 + 57 + ### Default (empty) Configuration 58 + 59 + The empty configuration is equivalent to default: 60 + 61 + ```ini 62 + [log] 63 + ROOT_PATH = %(GITEA_WORK_DIR)/log 64 + MODE = console 65 + LEVEL = Info 66 + STACKTRACE_LEVEL = None 67 + logger.router.MODE = , 68 + logger.xorm.MODE = , 69 + logger.access.MODE = 70 + 71 + ; this is the config options of "console" mode (used by MODE=console above) 72 + [log.console] 73 + MODE = console 74 + FLAGS = stdflags 75 + PREFIX = 76 + COLORIZE = true 77 + ``` 78 + 79 + This is equivalent to sending all logs to the console, with default Golang log being sent to the console log too. 80 + 81 + This is only a sample, and it is the default, do not need to write it into your configuration file. 82 + 83 + ### Disable Router logs and record some access logs to file 84 + 85 + The Router logger is disabled, the access logs (>=Warn) goes into `access.log`: 86 + 87 + ```ini 88 + [log] 89 + logger.router.MODE = 90 + logger.access.MODE = access-file 91 + 92 + [log.access-file] 93 + MODE = file 94 + LEVEL = Warn 95 + FILE_NAME = access.log 96 + ``` 97 + 98 + ### Set different log levels for different modes 99 + 100 + Default logs (>=Warn) goes into `gitea.log`, while Error logs goes into `file-error.log`: 101 + 102 + ```ini 103 + [log] 104 + LEVEL = Warn 105 + MODE = file, file-error 106 + 107 + ; by default, the "file" mode will record logs to %(log.ROOT_PATH)/gitea.log, so we don't need to set it 108 + ; [log.file] 109 + 110 + [log.file-error] 111 + LEVEL = Error 112 + FILE_NAME = file-error.log 113 + ``` 114 + 115 + ## Log outputs (mode and writer) 116 + 117 + Gitea provides the following log output writers: 118 + 119 + - `console` - Log to `stdout` (or `stderr` if it is set in the config) 120 + - `file` - Log to a file 121 + - `conn` - Log to a socket (network or unix) 122 + 123 + ### Common configuration 124 + 125 + Certain configuration is common to all modes of log output: 126 + 127 + - `MODE` is the mode of the log output writer. It will default to the mode name in the ini section. Thus `[log.console]` will default to `MODE = console`. 128 + - `LEVEL` is the lowest level that this output will log. 129 + - `STACKTRACE_LEVEL` is the lowest level that this output will print a stacktrace. 130 + - `COLORIZE` will default to `true` for `console` as described, otherwise it will default to `false`. 131 + 132 + #### `EXPRESSION` 133 + 134 + `EXPRESSION` represents a regular expression that log events must match to be logged by the output writer. 135 + Either the log message, (with colors removed), must match or the `longfilename:linenumber:functionname` must match. 136 + NB: the whole message or string doesn't need to completely match. 137 + 138 + Please note this expression will be run in the writer's goroutine but not the logging event goroutine. 139 + 140 + #### `FLAGS` 141 + 142 + `FLAGS` represents the preceding logging context information that is 143 + printed before each message. It is a comma-separated string set. The order of values does not matter. 144 + 145 + It defaults to `stdflags` (= `date,time,medfile,shortfuncname,levelinitial`) 146 + 147 + Possible values are: 148 + 149 + - `none` or `,` - No flags. 150 + - `date` - the date in the local time zone: `2009/01/23`. 151 + - `time` - the time in the local time zone: `01:23:23`. 152 + - `microseconds` - microsecond resolution: `01:23:23.123123`. Assumes time. 153 + - `longfile` - full file name and line number: `/a/b/c/d.go:23`. 154 + - `shortfile` - final file name element and line number: `d.go:23`. 155 + - `funcname` - function name of the caller: `runtime.Caller()`. 156 + - `shortfuncname` - last part of the function name. Overrides `funcname`. 157 + - `utc` - if date or time is set, use UTC rather than the local time zone. 158 + - `levelinitial` - initial character of the provided level in brackets eg. `[I]` for info. 159 + - `level` - level in brackets `[INFO]`. 160 + - `gopid` - the Goroutine-PID of the context. 161 + - `medfile` - last 20 characters of the filename - equivalent to `shortfile,longfile`. 162 + - `stdflags` - equivalent to `date,time,medfile,shortfuncname,levelinitial`. 163 + 164 + ### Console mode 165 + 166 + In this mode the logger will forward log messages to the stdout and 167 + stderr streams attached to the Gitea process. 168 + 169 + For loggers in console mode, `COLORIZE` will default to `true` if not 170 + on windows, or the Windows terminal can be set into ANSI mode or is a 171 + cygwin or Msys pipe. 172 + 173 + Settings: 174 + 175 + - `STDERR`: **false**: Whether the logger should print to `stderr` instead of `stdout`. 176 + 177 + ### File mode 178 + 179 + In this mode the logger will save log messages to a file. 180 + 181 + Settings: 182 + 183 + - `FILE_NAME`: The file to write the log events to, relative to `ROOT_PATH`, Default to `%(ROOT_PATH)/gitea.log`. Exception: access log will default to `%(ROOT_PATH)/access.log`. 184 + - `MAX_SIZE_SHIFT`: **28**: Maximum size shift of a single file. 28 represents 256Mb. For details see below. 185 + - `LOG_ROTATE` **true**: Whether to rotate the log files. TODO: if false, will it delete instead on daily rotate, or do nothing?. 186 + - `DAILY_ROTATE`: **true**: Whether to rotate logs daily. 187 + - `MAX_DAYS`: **7**: Delete rotated log files after this number of days. 188 + - `COMPRESS`: **true**: Whether to compress old log files by default with gzip. 189 + - `COMPRESSION_LEVEL`: **-1**: Compression level. For details see below. 190 + 191 + `MAX_SIZE_SHIFT` defines the maximum size of a file by left shifting 1 the given number of times (`1 << x`). 192 + The exact behavior at the time of v1.17.3 can be seen [here](https://github.com/go-gitea/gitea/blob/v1.17.3/modules/setting/log.go#L185). 193 + 194 + The useful values of `COMPRESSION_LEVEL` are from 1 to (and including) 9, where higher numbers mean better compression. 195 + Beware that better compression might come with higher resource usage. 196 + Must be preceded with a `-` sign. 197 + 198 + ### Conn mode 199 + 200 + In this mode the logger will send log messages over a network socket. 201 + 202 + Settings: 203 + 204 + - `ADDR`: **:7020**: Sets the address to connect to. 205 + - `PROTOCOL`: **tcp**: Set the protocol, either "tcp", "unix" or "udp". 206 + - `RECONNECT`: **false**: Try to reconnect when connection is lost. 207 + - `RECONNECT_ON_MSG`: **false**: Reconnect host for every single message. 208 + 209 + ### The "Router" logger 210 + 211 + The Router logger logs the following message types when Gitea's route handlers work: 212 + 213 + - `started` messages will be logged at TRACE level 214 + - `polling`/`completed` routers will be logged at INFO. Exception: "/assets" static resource requests are also logged at TRACE. 215 + - `slow` routers will be logged at WARN 216 + - `failed` routers will be logged at WARN 217 + 218 + ### The "XORM" logger 219 + 220 + To make XORM outputs SQL logs, the `LOG_SQL` in `[database]` section should also be set to `true`. 221 + 222 + ### The "Access" logger 223 + 224 + The Access logger is a new logger since Gitea 1.9. It provides a NCSA 225 + Common Log compliant log format. It's highly configurable but caution 226 + should be taken when changing its template. The main benefit of this 227 + logger is that Gitea can now log accesses in a standard log format so 228 + standard tools may be used. 229 + 230 + You can enable this logger using `logger.access.MODE = ...`. 231 + 232 + If desired the format of the Access logger can be changed by changing 233 + the value of the `ACCESS_LOG_TEMPLATE`. 234 + 235 + Please note, the access logger will log at `INFO` level, setting the 236 + `LEVEL` of this logger to `WARN` or above will result in no access logs. 237 + 238 + #### The ACCESS_LOG_TEMPLATE 239 + 240 + This value represents a go template. Its default value is 241 + 242 + ```tmpl 243 + {{.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}}"` 244 + ``` 245 + 246 + The template is passed following options: 247 + 248 + - `Ctx` is the `context.Context` 249 + - `Identity` is the `SignedUserName` or `"-"` if the user is not logged in 250 + - `Start` is the start time of the request 251 + - `ResponseWriter` is the `http.ResponseWriter` 252 + 253 + Caution must be taken when changing this template as it runs outside of 254 + the standard panic recovery trap. The template should also be as simple 255 + as it runs for every request. 256 + 257 + ## Releasing-and-Reopening, Pausing and Resuming logging 258 + 259 + If you are running on Unix you may wish to release-and-reopen logs in order to use `logrotate` or other tools. 260 + It is possible force Gitea to release and reopen it's logging files and connections by sending `SIGUSR1` to the 261 + running process, or running `gitea manager logging release-and-reopen`. 262 + 263 + Alternatively, you may wish to pause and resume logging - this can be accomplished through the use of the 264 + `gitea manager logging pause` and `gitea manager logging resume` commands. Please note that whilst logging 265 + is paused log events below INFO level will not be stored and only a limited number of events will be stored. 266 + Logging may block, albeit temporarily, slowing Gitea considerably whilst paused - therefore it is 267 + recommended that pausing only done for a very short period of time. 268 + 269 + ## Adding and removing logging whilst Gitea is running 270 + 271 + It is possible to add and remove logging whilst Gitea is running using the `gitea manager logging add` and `remove` subcommands. 272 + This functionality can only adjust running log systems and cannot be used to start the access or router loggers if they 273 + were not already initialized. If you wish to start these systems you are advised to adjust the app.ini and (gracefully) restart 274 + the Gitea service. 275 + 276 + The main intention of these commands is to easily add a temporary logger to investigate problems on running systems where a restart 277 + may cause the issue to disappear. 278 + 279 + ## Using `logrotate` instead of built-in log rotation 280 + 281 + Gitea includes built-in log rotation, which should be enough for most deployments. However, if you instead want to use the `logrotate` utility: 282 + 283 + - Disable built-in log rotation by setting `LOG_ROTATE` to `false` in your `app.ini`. 284 + - Install `logrotate`. 285 + - Configure `logrotate` to match your deployment requirements, see `man 8 logrotate` for configuration syntax details. 286 + In the `postrotate/endscript` block send Gitea a `USR1` signal via `kill -USR1` or `kill -10` to the `gitea` process itself, 287 + or run `gitea manager logging release-and-reopen` (with the appropriate environment). 288 + Ensure that your configurations apply to all files emitted by Gitea loggers as described in the above sections. 289 + - Always do `logrotate /etc/logrotate.conf --debug` to test your configurations. 290 + - If you are using docker and are running from outside the container you can use 291 + `docker exec -u $OS_USER $CONTAINER_NAME sh -c 'gitea manager logging release-and-reopen'` 292 + or `docker exec $CONTAINER_NAME sh -c '/bin/s6-svc -1 /etc/s6/gitea/'` or send `USR1` directly to the Gitea process itself. 293 + 294 + The next `logrotate` jobs will include your configurations, so no restart is needed. 295 + You can also immediately reload `logrotate` with `logrotate /etc/logrotate.conf --force`.
-524
docs/content/doc/administration/logging-documentation.en-us.md
··· 1 - --- 2 - date: "2019-04-02T17:06:00+01:00" 3 - title: "Logging Configuration" 4 - slug: "logging-configuration" 5 - weight: 40 6 - toc: false 7 - draft: false 8 - aliases: 9 - - /en-us/logging-configuration 10 - menu: 11 - sidebar: 12 - parent: "administration" 13 - name: "Logging Configuration" 14 - weight: 40 15 - identifier: "logging-configuration" 16 - --- 17 - 18 - # Logging Configuration 19 - 20 - The logging configuration of Gitea mainly consists of 3 types of components: 21 - 22 - - The `[log]` section for general configuration 23 - - `[log.<sublogger>]` sections for the configuration of different log outputs 24 - - `[log.<sublogger>.<group>]` sections for output specific configuration of a log group 25 - 26 - As mentioned below, there is a fully functional log output by default, so it is not necessary to define one. 27 - 28 - **Table of Contents** 29 - 30 - {{< toc >}} 31 - 32 - ## Collecting Logs for Help 33 - 34 - To collect logs for help and issue report, see [Support Options]({{< relref "doc/help/support.en-us.md" >}}). 35 - 36 - ## The `[log]` section 37 - 38 - Configuration of logging facilities in Gitea happen in the `[log]` section and it's subsections. 39 - 40 - In the top level `[log]` section the following configurations can be placed: 41 - 42 - - `ROOT_PATH`: (Default: **%(GITEA_WORK_DIR)/log**): Base path for log files 43 - - `MODE`: (Default: **console**) List of log outputs to use for the Default logger. 44 - - `ROUTER`: (Default: **console**): List of log outputs to use for the Router logger. 45 - - `ACCESS`: List of log outputs to use for the Access logger. 46 - - `XORM`: (Default: **,**) List of log outputs to use for the XORM logger. 47 - - `ENABLE_ACCESS_LOG`: (Default: **false**): whether the Access logger is allowed to emit logs 48 - - `ENABLE_XORM_LOG`: (Default: **true**): whether the XORM logger is allowed to emit logs 49 - 50 - For details on the loggers check the "Log Groups" section. 51 - Important: log outputs won't be used if you don't enable them for the desired loggers in the corresponding list value. 52 - 53 - Lists are specified as comma separated values. This format also works in subsection. 54 - 55 - This section may be used for defining default values for subsections. 56 - Examples: 57 - 58 - - `LEVEL`: (Default: **Info**) Least severe log events to persist. Case insensitive. The full list of levels as of v1.17.3 can be read [here](https://github.com/go-gitea/gitea/blob/v1.17.3/custom/conf/app.example.ini#L507). 59 - - `STACKTRACE_LEVEL`: (Default: **None**) For this and more severe events the stacktrace will be printed upon getting logged. 60 - 61 - Some values are not inherited by subsections. For details see the "Non-inherited default values" section. 62 - 63 - ## Log outputs 64 - 65 - Log outputs are the targets to which log messages will be sent. 66 - The content and the format of the log messages to be saved can be configured in these. 67 - 68 - Log outputs are also called subloggers. 69 - 70 - Gitea provides 4 possible log outputs: 71 - 72 - - `console` - Log to `os.Stdout` or `os.Stderr` 73 - - `file` - Log to a file 74 - - `conn` - Log to a socket (network or unix) 75 - - `smtp` - Log via email 76 - 77 - By default, Gitea has a `console` output configured, which is used by the loggers as seen in the section "The log section" above. 78 - 79 - ### Common configuration 80 - 81 - Certain configuration is common to all modes of log output: 82 - 83 - - `MODE` is the mode of the log output. It will default to the sublogger 84 - name, thus `[log.console.router]` will default to `MODE = console`. 85 - For mode specific confgurations read further. 86 - - `LEVEL` is the lowest level that this output will log. This value 87 - is inherited from `[log]` and in the case of the non-default loggers 88 - from `[log.sublogger]`. 89 - - `STACKTRACE_LEVEL` is the lowest level that this output will print 90 - a stacktrace. This value is inherited. 91 - - `COLORIZE` will default to `true` for `console` as 92 - described, otherwise it will default to `false`. 93 - 94 - ### Non-inherited default values 95 - 96 - There are several values which are not inherited as described above but 97 - rather default to those specific to type of logger, these are: 98 - `EXPRESSION`, `FLAGS`, `PREFIX` and `FILE_NAME`. 99 - 100 - #### `EXPRESSION` 101 - 102 - `EXPRESSION` represents a regular expression that log events must match to be logged by the sublogger. Either the log message, (with colors removed), must match or the `longfilename:linenumber:functionname` must match. NB: the whole message or string doesn't need to completely match. 103 - 104 - Please note this expression will be run in the sublogger's goroutine 105 - not the logging event subroutine. Therefore it can be complicated. 106 - 107 - #### `FLAGS` 108 - 109 - `FLAGS` represents the preceding logging context information that is 110 - printed before each message. It is a comma-separated string set. The order of values does not matter. 111 - 112 - Possible values are: 113 - 114 - - `none` or `,` - No flags. 115 - - `date` - the date in the local time zone: `2009/01/23`. 116 - - `time` - the time in the local time zone: `01:23:23`. 117 - - `microseconds` - microsecond resolution: `01:23:23.123123`. Assumes 118 - time. 119 - - `longfile` - full file name and line number: `/a/b/c/d.go:23`. 120 - - `shortfile` - final file name element and line number: `d.go:23`. 121 - - `funcname` - function name of the caller: `runtime.Caller()`. 122 - - `shortfuncname` - last part of the function name. Overrides 123 - `funcname`. 124 - - `utc` - if date or time is set, use UTC rather than the local time 125 - zone. 126 - - `levelinitial` - Initial character of the provided level in brackets eg. `[I]` for info. 127 - - `level` - Provided level in brackets `[INFO]` 128 - - `medfile` - Last 20 characters of the filename - equivalent to 129 - `shortfile,longfile`. 130 - - `stdflags` - Equivalent to `date,time,medfile,shortfuncname,levelinitial` 131 - 132 - ### Console mode 133 - 134 - In this mode the logger will forward log messages to the stdout and 135 - stderr streams attached to the Gitea process. 136 - 137 - For loggers in console mode, `COLORIZE` will default to `true` if not 138 - on windows, or the windows terminal can be set into ANSI mode or is a 139 - cygwin or Msys pipe. 140 - 141 - Settings: 142 - 143 - - `STDERR`: **false**: Whether the logger should print to `stderr` instead of `stdout`. 144 - 145 - ### File mode 146 - 147 - In this mode the logger will save log messages to a file. 148 - 149 - Settings: 150 - 151 - - `FILE_NAME`: The file to write the log events to. For details see below. 152 - - `MAX_SIZE_SHIFT`: **28**: Maximum size shift of a single file. 28 represents 256Mb. For details see below. 153 - - `LOG_ROTATE` **true**: Whether to rotate the log files. TODO: if false, will it delete instead on daily rotate, or do nothing?. 154 - - `DAILY_ROTATE`: **true**: Whether to rotate logs daily. 155 - - `MAX_DAYS`: **7**: Delete rotated log files after this number of days. 156 - - `COMPRESS`: **true**: Whether to compress old log files by default with gzip. 157 - - `COMPRESSION_LEVEL`: **-1**: Compression level. For details see below. 158 - 159 - The default value of `FILE_NAME` depends on the respective logger facility. 160 - If unset, their own default will be used. 161 - If set it will be relative to the provided `ROOT_PATH` in the master `[log]` section. 162 - 163 - `MAX_SIZE_SHIFT` defines the maximum size of a file by left shifting 1 the given number of times (`1 << x`). 164 - The exact behavior at the time of v1.17.3 can be seen [here](https://github.com/go-gitea/gitea/blob/v1.17.3/modules/setting/log.go#L185). 165 - 166 - The useful values of `COMPRESSION_LEVEL` are from 1 to (and including) 9, where higher numbers mean better compression. 167 - Beware that better compression might come with higher resource usage. 168 - Must be preceded with a `-` sign. 169 - 170 - ### Conn mode 171 - 172 - In this mode the logger will send log messages over a network socket. 173 - 174 - Settings: 175 - 176 - - `ADDR`: **:7020**: Sets the address to connect to. 177 - - `PROTOCOL`: **tcp**: Set the protocol, either "tcp", "unix" or "udp". 178 - - `RECONNECT`: **false**: Try to reconnect when connection is lost. 179 - - `RECONNECT_ON_MSG`: **false**: Reconnect host for every single message. 180 - 181 - ### SMTP mode 182 - 183 - In this mode the logger will send log messages in email. 184 - 185 - It is not recommended to use this logger to send general logging 186 - messages. However, you could perhaps set this logger to work on `FATAL` messages only. 187 - 188 - Settings: 189 - 190 - - `HOST`: **127.0.0.1:25**: The SMTP host to connect to. 191 - - `USER`: User email address to send from. 192 - - `PASSWD`: Password for the smtp server. 193 - - `RECEIVERS`: Email addresses to send to. 194 - - `SUBJECT`: **Diagnostic message from Gitea**. The content of the email's subject field. 195 - 196 - ## Log Groups 197 - 198 - The fundamental thing to be aware of in Gitea is that there are several 199 - log groups: 200 - 201 - - The "Default" logger 202 - - The Router logger 203 - - The Access logger 204 - - The XORM logger 205 - 206 - There is also the go log logger. 207 - 208 - ### The go log logger 209 - 210 - Go provides its own extremely basic logger in the `log` package, 211 - however, this is not sufficient for our purposes as it does not provide 212 - a way of logging at multiple levels, nor does it provide a good way of 213 - controlling where these logs are logged except through setting of a 214 - writer. 215 - 216 - We have therefore redirected this logger to our Default logger, and we 217 - will log anything that is logged using the go logger at the INFO level. 218 - 219 - ### The "Default" logger 220 - 221 - Calls to `log.Info`, `log.Debug`, `log.Error` etc. from the `code.gitea.io/gitea/modules/log` package will log to this logger. 222 - 223 - You can configure the outputs of this logger by setting the `MODE` 224 - value in the `[log]` section of the configuration. 225 - 226 - Each output sublogger is configured in a separate `[log.sublogger.default]` 227 - which inherits from the sublogger `[log.sublogger]` section and from the 228 - generic `[log]` section, but there are certain default values. These will 229 - not be inherited from the `[log]` section: 230 - 231 - - `FLAGS` is `stdflags` (Equal to 232 - `date,time,medfile,shortfuncname,levelinitial`) 233 - - `FILE_NAME` will default to `%(ROOT_PATH)/gitea.log` 234 - - `EXPRESSION` will default to `""` 235 - - `PREFIX` will default to `""` 236 - 237 - The provider type of the sublogger can be set using the `MODE` value in 238 - its subsection, but will default to the name. This allows you to have 239 - multiple subloggers that will log to files. 240 - 241 - ### The "Router" logger 242 - 243 - The Router logger has been substantially changed in v1.17. If you are using the router logger for fail2ban or other monitoring 244 - you will need to update this configuration. 245 - 246 - You can disable Router log by setting `DISABLE_ROUTER_LOG` or by setting all of its sublogger configurations to `none`. 247 - 248 - You can configure the outputs of this 249 - router log by setting the `ROUTER` value in the `[log]` section of the 250 - configuration. `ROUTER` will default to `console` if unset and will default to same level as main logger. 251 - 252 - The Router logger logs the following: 253 - 254 - - `started` messages will be logged at TRACE level 255 - - `polling`/`completed` routers will be logged at INFO 256 - - `slow` routers will be logged at WARN 257 - - `failed` routers will be logged at WARN 258 - 259 - The logging level for the router will default to that of the main configuration. Set `[log.<mode>.router]` `LEVEL` to change this. 260 - 261 - Each output sublogger for this logger is configured in 262 - `[log.sublogger.router]` sections. There are certain default values 263 - which will not be inherited from the `[log]` or relevant 264 - `[log.sublogger]` sections: 265 - 266 - - `FILE_NAME` will default to `%(ROOT_PATH)/router.log` 267 - - `FLAGS` defaults to `date,time` 268 - - `EXPRESSION` will default to `""` 269 - - `PREFIX` will default to `""` 270 - 271 - NB: You can redirect the router logger to send its events to the Gitea 272 - log using the value: `ROUTER = ,` 273 - 274 - ### The "Access" logger 275 - 276 - The Access logger is a new logger for version 1.9. It provides a NCSA 277 - Common Log compliant log format. It's highly configurable but caution 278 - should be taken when changing its template. The main benefit of this 279 - logger is that Gitea can now log accesses in a standard log format so 280 - standard tools may be used. 281 - 282 - You can enable this logger using `ENABLE_ACCESS_LOG`. Its outputs are 283 - configured by setting the `ACCESS` value in the `[log]` section of the 284 - configuration. `ACCESS` defaults to `file` if unset. 285 - 286 - Each output sublogger for this logger is configured in 287 - `[log.sublogger.access]` sections. There are certain default values 288 - which will not be inherited from the `[log]` or relevant 289 - `[log.sublogger]` sections: 290 - 291 - - `FILE_NAME` will default to `%(ROOT_PATH)/access.log` 292 - - `FLAGS` defaults to `` or None 293 - - `EXPRESSION` will default to `""` 294 - - `PREFIX` will default to `""` 295 - 296 - If desired the format of the Access logger can be changed by changing 297 - the value of the `ACCESS_LOG_TEMPLATE`. 298 - 299 - Please note, the access logger will log at `INFO` level, setting the 300 - `LEVEL` of this logger to `WARN` or above will result in no access logs. 301 - 302 - NB: You can redirect the access logger to send its events to the Gitea 303 - log using the value: `ACCESS = ,` 304 - 305 - #### The ACCESS_LOG_TEMPLATE 306 - 307 - This value represent a go template. It's default value is: 308 - 309 - `{{.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}}"` 310 - 311 - The template is passed following options: 312 - 313 - - `Ctx` is the `context.Context` 314 - - `Identity` is the `SignedUserName` or `"-"` if the user is not logged 315 - in 316 - - `Start` is the start time of the request 317 - - `ResponseWriter` is the `http.ResponseWriter` 318 - 319 - Caution must be taken when changing this template as it runs outside of 320 - the standard panic recovery trap. The template should also be as simple 321 - as it runs for every request. 322 - 323 - ### The "XORM" logger 324 - 325 - The XORM logger is a long-standing logger that exists to collect XORM 326 - log events. It is enabled by default but can be switched off by setting 327 - `ENABLE_XORM_LOG` to `false` in the `[log]` section. Its outputs are 328 - configured by setting the `XORM` value in the `[log]` section of the 329 - configuration. `XORM` defaults to `,` if unset, meaning it is redirected 330 - to the main Gitea log. 331 - 332 - XORM will log SQL events by default. This can be changed by setting 333 - the `LOG_SQL` value to `false` in the `[database]` section. 334 - 335 - Each output sublogger for this logger is configured in 336 - `[log.sublogger.xorm]` sections. There are certain default values 337 - which will not be inherited from the `[log]` or relevant 338 - `[log.sublogger]` sections: 339 - 340 - - `FILE_NAME` will default to `%(ROOT_PATH)/xorm.log` 341 - - `FLAGS` defaults to `date,time` 342 - - `EXPRESSION` will default to `""` 343 - - `PREFIX` will default to `""` 344 - 345 - ## Debugging problems 346 - 347 - When submitting logs in Gitea issues it is often helpful to submit 348 - merged logs obtained by either by redirecting the console log to a file or 349 - copying and pasting it. To that end it is recommended to set your logging to: 350 - 351 - ```ini 352 - [database] 353 - LOG_SQL = false ; SQL logs are rarely helpful unless we specifically ask for them 354 - 355 - ... 356 - 357 - [log] 358 - MODE = console 359 - LEVEL = debug ; please set the level to debug when we are debugging a problem 360 - ROUTER = console 361 - COLORIZE = false ; this can be true if you can strip out the ansi coloring 362 - ENABLE_SSH_LOG = true ; shows logs related to git over SSH. 363 - ``` 364 - 365 - Sometimes it will be helpful get some specific `TRACE` level logging restricted 366 - to messages that match a specific `EXPRESSION`. Adjusting the `MODE` in the 367 - `[log]` section to `MODE = console,traceconsole` to add a new logger output 368 - `traceconsole` and then adding its corresponding section would be helpful: 369 - 370 - ```ini 371 - [log.traceconsole] ; traceconsole here is just a name 372 - MODE = console ; this is the output that the traceconsole writes to 373 - LEVEL = trace 374 - EXPRESSION = ; putting a string here will restrict this logger to logging only those messages that match this expression 375 - ``` 376 - 377 - (It's worth noting that log messages that match the expression at or above debug 378 - level will get logged twice so don't worry about that.) 379 - 380 - `STACKTRACE_LEVEL` should generally be left unconfigured (and hence kept at 381 - `none`). There are only very specific occasions when it useful. 382 - 383 - ## Empty Configuration 384 - 385 - The empty configuration is equivalent to: 386 - 387 - ```ini 388 - [log] 389 - ROOT_PATH = %(GITEA_WORK_DIR)/log 390 - MODE = console 391 - LEVEL = Info 392 - STACKTRACE_LEVEL = None 393 - ENABLE_ACCESS_LOG = false 394 - ENABLE_XORM_LOG = true 395 - XORM = , 396 - 397 - [log.console] 398 - MODE = console 399 - LEVEL = %(LEVEL) 400 - STACKTRACE_LEVEL = %(STACKTRACE_LEVEL) 401 - FLAGS = stdflags 402 - PREFIX = 403 - COLORIZE = true # Or false if your windows terminal cannot color 404 - ``` 405 - 406 - This is equivalent to sending all logs to the console, with default go log being sent to the console log too. 407 - 408 - ## Releasing-and-Reopening, Pausing and Resuming logging 409 - 410 - If you are running on Unix you may wish to release-and-reopen logs in order to use `logrotate` or other tools. 411 - It is possible force Gitea to release and reopen it's logging files and connections by sending `SIGUSR1` to the 412 - running process, or running `gitea manager logging release-and-reopen`. 413 - 414 - Alternatively, you may wish to pause and resume logging - this can be accomplished through the use of the 415 - `gitea manager logging pause` and `gitea manager logging resume` commands. Please note that whilst logging 416 - is paused log events below INFO level will not be stored and only a limited number of events will be stored. 417 - Logging may block, albeit temporarily, slowing Gitea considerably whilst paused - therefore it is 418 - recommended that pausing only done for a very short period of time. 419 - 420 - ## Adding and removing logging whilst Gitea is running 421 - 422 - It is possible to add and remove logging whilst Gitea is running using the `gitea manager logging add` and `remove` subcommands. 423 - This functionality can only adjust running log systems and cannot be used to start the access or router loggers if they 424 - were not already initialized. If you wish to start these systems you are advised to adjust the app.ini and (gracefully) restart 425 - the Gitea service. 426 - 427 - The main intention of these commands is to easily add a temporary logger to investigate problems on running systems where a restart 428 - may cause the issue to disappear. 429 - 430 - ## Log colorization 431 - 432 - Logs to the console will be colorized by default when not running on 433 - Windows. Terminal sniffing will occur on Windows and if it is 434 - determined that we are running on a terminal capable of color we will 435 - colorize. 436 - 437 - Further, on \*nix it is becoming common to have file logs that are 438 - colored by default. Therefore file logs will be colorised by default 439 - when not running on Windows. 440 - 441 - You can switch on or off colorization by using the `COLORIZE` value. 442 - 443 - From a development point of view. If you write 444 - `log.Info("A %s string", "formatted")` the `formatted` part of the log 445 - message will be Bolded on colorized logs. 446 - 447 - You can change this by either rendering the formatted string yourself. 448 - Or you can wrap the value in a `log.ColoredValue` struct. 449 - 450 - The `log.ColoredValue` struct contains a pointer to value, a pointer to 451 - string of bytes which should represent a color and second set of reset 452 - bytes. Pointers were chosen to prevent copying of large numbers of 453 - values. There are several helper methods: 454 - 455 - - `log.NewColoredValue` takes a value and 0 or more color attributes 456 - that represent the color. If 0 are provided it will default to a cached 457 - bold. Note, it is recommended that color bytes constructed from 458 - attributes should be cached if this is a commonly used log message. 459 - - `log.NewColoredValuePointer` takes a pointer to a value, and 460 - 0 or more color attributes that represent the color. 461 - - `log.NewColoredValueBytes` takes a value and a pointer to an array 462 - of bytes representing the color. 463 - 464 - These functions will not double wrap a `log.ColoredValue`. They will 465 - also set the `resetBytes` to the cached `resetBytes`. 466 - 467 - The `colorBytes` and `resetBytes` are not exported to prevent 468 - accidental overwriting of internal values. 469 - 470 - ## ColorFormat & ColorFormatted 471 - 472 - Structs may implement the `log.ColorFormatted` interface by implementing the `ColorFormat(fmt.State)` function. 473 - 474 - If a `log.ColorFormatted` struct is logged with `%-v` format, its `ColorFormat` will be used instead of the usual `%v`. The full `fmt.State` will be passed to allow implementers to look at additional flags. 475 - 476 - In order to help implementers provide `ColorFormat` methods. There is a 477 - `log.ColorFprintf(...)` function in the log module that will wrap values in `log.ColoredValue` and recognise `%-v`. 478 - 479 - In general it is recommended not to make the results of this function too verbose to help increase its versatility. Usually this should simply be an `ID`:`Name`. If you wish to make a more verbose result, it is recommended to use `%-+v` as your marker. 480 - 481 - ## Log Spoofing protection 482 - 483 - In order to protect the logs from being spoofed with cleverly 484 - constructed messages. Newlines are now prefixed with a tab and control 485 - characters except those used in an ANSI CSI are escaped with a 486 - preceding `\` and their octal value. 487 - 488 - ## Creating a new named logger group 489 - 490 - Should a developer wish to create a new named logger, `NEWONE`. It is 491 - recommended to add an `ENABLE_NEWONE_LOG` value to the `[log]` 492 - section, and to add a new `NEWONE` value for the modes. 493 - 494 - A function like `func newNewOneLogService()` is recommended to manage 495 - construction of the named logger. e.g. 496 - 497 - ```go 498 - func newNewoneLogService() { 499 - EnableNewoneLog = Cfg.Section("log").Key("ENABLE_NEWONE_LOG").MustBool(false) 500 - Cfg.Section("log").Key("NEWONE").MustString("file") // or console? or "," if you want to send this to default logger by default 501 - if EnableNewoneLog { 502 - options := newDefaultLogOptions() 503 - options.filename = filepath.Join(LogRootPath, "newone.log") 504 - options.flags = "stdflags" 505 - options.bufferLength = Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000) 506 - generateNamedLogger("newone", options) 507 - } 508 - } 509 - ``` 510 - 511 - You should then add `newOneLogService` to `NewServices()` in 512 - `modules/setting/setting.go` 513 - 514 - ## Using `logrotate` instead of built-in log rotation 515 - 516 - Gitea includes built-in log rotation, which should be enough for most deployments. However, if you instead want to use the `logrotate` utility: 517 - 518 - - Disable built-in log rotation by setting `LOG_ROTATE` to `false` in your `app.ini`. 519 - - Install `logrotate`. 520 - - Configure `logrotate` to match your deployment requirements, see `man 8 logrotate` for configuration syntax details. In the `postrotate/endscript` block send Gitea a `USR1` signal via `kill -USR1` or `kill -10` to the `gitea` process itself, or run `gitea manager logging release-and-reopen` (with the appropriate environment). Ensure that your configurations apply to all files emitted by Gitea loggers as described in the above sections. 521 - - Always do `logrotate /etc/logrotate.conf --debug` to test your configurations. 522 - - If you are using docker and are running from outside of the container you can use `docker exec -u $OS_USER $CONTAINER_NAME sh -c 'gitea manager logging release-and-reopen'` or `docker exec $CONTAINER_NAME sh -c '/bin/s6-svc -1 /etc/s6/gitea/'` or send `USR1` directly to the Gitea process itself. 523 - 524 - The next `logrotate` jobs will include your configurations, so no restart is needed. You can also immediately reload `logrotate` with `logrotate /etc/logrotate.conf --force`.
+2
main.go
··· 120 120 if err != nil { 121 121 log.Fatal("Failed to run app with %s: %v", os.Args, err) 122 122 } 123 + 124 + log.GetManager().Close() 123 125 } 124 126 125 127 func setFlagsAndBeforeOnSubcommands(command *cli.Command, defaultFlags []cli.Flag, before cli.BeforeFunc) {
+23 -29
models/db/log.go
··· 14 14 15 15 // XORMLogBridge a logger bridge from Logger to xorm 16 16 type XORMLogBridge struct { 17 - showSQLint *int32 18 - logger log.Logger 17 + showSQL atomic.Bool 18 + logger log.Logger 19 19 } 20 20 21 21 // NewXORMLogger inits a log bridge for xorm 22 22 func NewXORMLogger(showSQL bool) xormlog.Logger { 23 - showSQLint := int32(0) 24 - if showSQL { 25 - showSQLint = 1 26 - } 27 - return &XORMLogBridge{ 28 - showSQLint: &showSQLint, 29 - logger: log.GetLogger("xorm"), 30 - } 23 + l := &XORMLogBridge{logger: log.GetLogger("xorm")} 24 + l.showSQL.Store(showSQL) 25 + return l 31 26 } 32 27 33 28 const stackLevel = 8 34 29 35 30 // Log a message with defined skip and at logging level 36 - func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...interface{}) error { 37 - return l.logger.Log(skip+1, level, format, v...) 31 + func (l *XORMLogBridge) Log(skip int, level log.Level, format string, v ...interface{}) { 32 + l.logger.Log(skip+1, level, format, v...) 38 33 } 39 34 40 35 // Debug show debug log 41 36 func (l *XORMLogBridge) Debug(v ...interface{}) { 42 - _ = l.Log(stackLevel, log.DEBUG, fmt.Sprint(v...)) 37 + l.Log(stackLevel, log.DEBUG, "%s", fmt.Sprint(v...)) 43 38 } 44 39 45 40 // Debugf show debug log 46 41 func (l *XORMLogBridge) Debugf(format string, v ...interface{}) { 47 - _ = l.Log(stackLevel, log.DEBUG, format, v...) 42 + l.Log(stackLevel, log.DEBUG, format, v...) 48 43 } 49 44 50 45 // Error show error log 51 46 func (l *XORMLogBridge) Error(v ...interface{}) { 52 - _ = l.Log(stackLevel, log.ERROR, fmt.Sprint(v...)) 47 + l.Log(stackLevel, log.ERROR, "%s", fmt.Sprint(v...)) 53 48 } 54 49 55 50 // Errorf show error log 56 51 func (l *XORMLogBridge) Errorf(format string, v ...interface{}) { 57 - _ = l.Log(stackLevel, log.ERROR, format, v...) 52 + l.Log(stackLevel, log.ERROR, format, v...) 58 53 } 59 54 60 55 // Info show information level log 61 56 func (l *XORMLogBridge) Info(v ...interface{}) { 62 - _ = l.Log(stackLevel, log.INFO, fmt.Sprint(v...)) 57 + l.Log(stackLevel, log.INFO, "%s", fmt.Sprint(v...)) 63 58 } 64 59 65 60 // Infof show information level log 66 61 func (l *XORMLogBridge) Infof(format string, v ...interface{}) { 67 - _ = l.Log(stackLevel, log.INFO, format, v...) 62 + l.Log(stackLevel, log.INFO, format, v...) 68 63 } 69 64 70 65 // Warn show warning log 71 66 func (l *XORMLogBridge) Warn(v ...interface{}) { 72 - _ = l.Log(stackLevel, log.WARN, fmt.Sprint(v...)) 67 + l.Log(stackLevel, log.WARN, "%s", fmt.Sprint(v...)) 73 68 } 74 69 75 70 // Warnf show warnning log 76 71 func (l *XORMLogBridge) Warnf(format string, v ...interface{}) { 77 - _ = l.Log(stackLevel, log.WARN, format, v...) 72 + l.Log(stackLevel, log.WARN, format, v...) 78 73 } 79 74 80 75 // Level get logger level ··· 86 81 return xormlog.LOG_INFO 87 82 case log.WARN: 88 83 return xormlog.LOG_WARNING 89 - case log.ERROR, log.CRITICAL: 84 + case log.ERROR: 90 85 return xormlog.LOG_ERR 86 + case log.NONE: 87 + return xormlog.LOG_OFF 91 88 } 92 - return xormlog.LOG_OFF 89 + return xormlog.LOG_UNKNOWN 93 90 } 94 91 95 92 // SetLevel set the logger level ··· 98 95 99 96 // ShowSQL set if record SQL 100 97 func (l *XORMLogBridge) ShowSQL(show ...bool) { 101 - showSQL := int32(1) 102 - if len(show) > 0 && !show[0] { 103 - showSQL = 0 98 + if len(show) == 0 { 99 + show = []bool{true} 104 100 } 105 - atomic.StoreInt32(l.showSQLint, showSQL) 101 + l.showSQL.Store(show[0]) 106 102 } 107 103 108 104 // IsShowSQL if record SQL 109 105 func (l *XORMLogBridge) IsShowSQL() bool { 110 - showSQL := atomic.LoadInt32(l.showSQLint) 111 - 112 - return showSQL == 1 106 + return l.showSQL.Load() 113 107 }
+11 -24
models/issues/pull.go
··· 224 224 return err 225 225 } 226 226 227 - // ColorFormat writes a colored string to identify this struct 228 - func (pr *PullRequest) ColorFormat(s fmt.State) { 227 + func (pr *PullRequest) String() string { 229 228 if pr == nil { 230 - log.ColorFprintf(s, "PR[%d]%s#%d[%s...%s:%s]", 231 - log.NewColoredIDValue(0), 232 - log.NewColoredValue("<nil>/<nil>"), 233 - log.NewColoredIDValue(0), 234 - log.NewColoredValue("<nil>"), 235 - log.NewColoredValue("<nil>/<nil>"), 236 - log.NewColoredValue("<nil>"), 237 - ) 238 - return 229 + return "<PullRequest nil>" 239 230 } 240 231 241 - log.ColorFprintf(s, "PR[%d]", log.NewColoredIDValue(pr.ID)) 232 + s := new(strings.Builder) 233 + fmt.Fprintf(s, "<PullRequest [%d]", pr.ID) 242 234 if pr.BaseRepo != nil { 243 - log.ColorFprintf(s, "%s#%d[%s...", log.NewColoredValue(pr.BaseRepo.FullName()), 244 - log.NewColoredIDValue(pr.Index), log.NewColoredValue(pr.BaseBranch)) 235 + fmt.Fprintf(s, "%s#%d[%s...", pr.BaseRepo.FullName(), pr.Index, pr.BaseBranch) 245 236 } else { 246 - log.ColorFprintf(s, "Repo[%d]#%d[%s...", log.NewColoredIDValue(pr.BaseRepoID), 247 - log.NewColoredIDValue(pr.Index), log.NewColoredValue(pr.BaseBranch)) 237 + fmt.Fprintf(s, "Repo[%d]#%d[%s...", pr.BaseRepoID, pr.Index, pr.BaseBranch) 248 238 } 249 239 if pr.HeadRepoID == pr.BaseRepoID { 250 - log.ColorFprintf(s, "%s]", log.NewColoredValue(pr.HeadBranch)) 240 + fmt.Fprintf(s, "%s]", pr.HeadBranch) 251 241 } else if pr.HeadRepo != nil { 252 - log.ColorFprintf(s, "%s:%s]", log.NewColoredValue(pr.HeadRepo.FullName()), log.NewColoredValue(pr.HeadBranch)) 242 + fmt.Fprintf(s, "%s:%s]", pr.HeadRepo.FullName(), pr.HeadBranch) 253 243 } else { 254 - log.ColorFprintf(s, "Repo[%d]:%s]", log.NewColoredIDValue(pr.HeadRepoID), log.NewColoredValue(pr.HeadBranch)) 244 + fmt.Fprintf(s, "Repo[%d]:%s]", pr.HeadRepoID, pr.HeadBranch) 255 245 } 256 - } 257 - 258 - // String represents the pr as a simple string 259 - func (pr *PullRequest) String() string { 260 - return log.ColorFormatAsString(pr) 246 + s.WriteByte('>') 247 + return s.String() 261 248 } 262 249 263 250 // MustHeadUserName returns the HeadRepo's username if failed return blank
+4 -2
models/migrations/base/tests.go
··· 24 24 "xorm.io/xorm" 25 25 ) 26 26 27 + // FIXME: this file shouldn't be in a normal package, it should only be compiled for tests 28 + 27 29 // PrepareTestEnv prepares the test environment and reset the database. The skip parameter should usually be 0. 28 30 // Provide models to be sync'd with the database - in particular any models you expect fixtures to be loaded from. 29 31 // ··· 110 112 } 111 113 112 114 func MainTest(m *testing.M) { 113 - log.Register("test", testlogger.NewTestLogger) 115 + log.RegisterEventWriter("test", testlogger.NewTestLoggerWriter) 114 116 115 117 giteaRoot := base.SetupGiteaRoot() 116 118 if giteaRoot == "" { ··· 154 156 os.Exit(1) 155 157 } 156 158 setting.LoadDBSetting() 157 - setting.InitLogs(true) 159 + setting.InitLoggersForTest() 158 160 159 161 exitStatus := m.Run() 160 162
+3 -13
models/organization/team.go
··· 94 94 db.RegisterModel(new(TeamInvite)) 95 95 } 96 96 97 - // ColorFormat provides a basic color format for a Team 98 - func (t *Team) ColorFormat(s fmt.State) { 97 + func (t *Team) LogString() string { 99 98 if t == nil { 100 - log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v", 101 - log.NewColoredIDValue(0), 102 - "<nil>", 103 - log.NewColoredIDValue(0), 104 - 0) 105 - return 99 + return "<Team nil>" 106 100 } 107 - log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v", 108 - log.NewColoredIDValue(t.ID), 109 - t.Name, 110 - log.NewColoredIDValue(t.OrgID), 111 - t.AccessMode) 101 + return fmt.Sprintf("<Team %d:%s OrgID=%d AccessMode=%s>", t.ID, t.Name, t.OrgID, t.AccessMode.LogString()) 112 102 } 113 103 114 104 // LoadUnits load a list of available units for a team
+18 -35
models/perm/access/repo_permission.go
··· 102 102 return p.CanWrite(unit.TypeIssues) 103 103 } 104 104 105 - // ColorFormat writes a colored string for these Permissions 106 - func (p *Permission) ColorFormat(s fmt.State) { 107 - noColor := log.ColorBytes(log.Reset) 105 + func (p *Permission) LogString() string { 106 + format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ " 107 + args := []any{p.AccessMode.String(), len(p.Units), len(p.UnitsMode)} 108 108 109 - format := "perm_model.AccessMode: %-v, %d Units, %d UnitsMode(s): [ " 110 - args := []interface{}{ 111 - p.AccessMode, 112 - log.NewColoredValueBytes(len(p.Units), &noColor), 113 - log.NewColoredValueBytes(len(p.UnitsMode), &noColor), 114 - } 115 - if s.Flag('+') { 116 - for i, unit := range p.Units { 117 - config := "" 118 - if unit.Config != nil { 119 - configBytes, err := unit.Config.ToDB() 120 - config = string(configBytes) 121 - if err != nil { 122 - config = err.Error() 123 - } 109 + for i, unit := range p.Units { 110 + config := "" 111 + if unit.Config != nil { 112 + configBytes, err := unit.Config.ToDB() 113 + config = string(configBytes) 114 + if err != nil { 115 + config = err.Error() 124 116 } 125 - format += "\nUnits[%d]: ID: %d RepoID: %d Type: %-v Config: %s" 126 - args = append(args, 127 - log.NewColoredValueBytes(i, &noColor), 128 - log.NewColoredIDValue(unit.ID), 129 - log.NewColoredIDValue(unit.RepoID), 130 - unit.Type, 131 - config) 132 117 } 133 - for key, value := range p.UnitsMode { 134 - format += "\nUnitMode[%-v]: %-v" 135 - args = append(args, 136 - key, 137 - value) 138 - } 139 - } else { 140 - format += "..." 118 + format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s" 119 + args = append(args, i, unit.ID, unit.RepoID, unit.Type.LogString(), config) 120 + } 121 + for key, value := range p.UnitsMode { 122 + format += "\nUnitMode[%-v]: %-v" 123 + args = append(args, key.LogString(), value.LogString()) 141 124 } 142 - format += " ]" 143 - log.ColorFprintf(s, format, args...) 125 + format += " ]>" 126 + return fmt.Sprintf(format, args...) 144 127 } 145 128 146 129 // GetUserRepoPermission returns the user permissions to the repository
+2 -7
models/perm/access_mode.go
··· 5 5 6 6 import ( 7 7 "fmt" 8 - 9 - "code.gitea.io/gitea/modules/log" 10 8 ) 11 9 12 10 // AccessMode specifies the users access mode ··· 40 38 } 41 39 } 42 40 43 - // ColorFormat provides a ColorFormatted version of this AccessMode 44 - func (mode AccessMode) ColorFormat(s fmt.State) { 45 - log.ColorFprintf(s, "%d:%s", 46 - log.NewColoredIDValue(mode), 47 - mode) 41 + func (mode AccessMode) LogString() string { 42 + return fmt.Sprintf("<AccessMode:%d:%s>", mode, mode.String()) 48 43 } 49 44 50 45 // ParseAccessMode returns corresponding access mode to given permission string.
+3 -11
models/repo/repo.go
··· 196 196 return u.String() 197 197 } 198 198 199 - // ColorFormat returns a colored string to represent this repo 200 - func (repo *Repository) ColorFormat(s fmt.State) { 199 + func (repo *Repository) LogString() string { 201 200 if repo == nil { 202 - log.ColorFprintf(s, "%d:%s/%s", 203 - log.NewColoredIDValue(0), 204 - "<nil>", 205 - "<nil>") 206 - return 201 + return "<Repository nil>" 207 202 } 208 - log.ColorFprintf(s, "%d:%s/%s", 209 - log.NewColoredIDValue(repo.ID), 210 - repo.OwnerName, 211 - repo.Name) 203 + return fmt.Sprintf("<Repository %d:%s/%s>", repo.ID, repo.OwnerName, repo.Name) 212 204 } 213 205 214 206 // IsBeingMigrated indicates that repository is being migrated
+2 -5
models/unit/unit.go
··· 62 62 return fmt.Sprintf("Unknown Type %d", u) 63 63 } 64 64 65 - // ColorFormat provides a ColorFormatted version of this Type 66 - func (u Type) ColorFormat(s fmt.State) { 67 - log.ColorFprintf(s, "%d:%s", 68 - log.NewColoredIDValue(u), 69 - u) 65 + func (u Type) LogString() string { 66 + return fmt.Sprintf("<UnitType:%d:%s>", u, u.String()) 70 67 } 71 68 72 69 var (
+3 -9
models/user/user.go
··· 151 151 All bool 152 152 } 153 153 154 - // ColorFormat writes a colored string to identify this struct 155 - func (u *User) ColorFormat(s fmt.State) { 154 + func (u *User) LogString() string { 156 155 if u == nil { 157 - log.ColorFprintf(s, "%d:%s", 158 - log.NewColoredIDValue(0), 159 - log.NewColoredValue("<nil>")) 160 - return 156 + return "<User nil>" 161 157 } 162 - log.ColorFprintf(s, "%d:%s", 163 - log.NewColoredIDValue(u.ID), 164 - log.NewColoredValue(u.Name)) 158 + return fmt.Sprintf("<User %d:%s>", u.ID, u.Name) 165 159 } 166 160 167 161 // BeforeUpdate is invoked from XORM before updating this object.
+1 -4
modules/context/access_log.go
··· 95 95 log.Error("Could not set up chi access logger: %v", err.Error()) 96 96 } 97 97 98 - err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "") 99 - if err != nil { 100 - log.Error("Could not set up chi access logger: %v", err.Error()) 101 - } 98 + logger.Info("%s", buf.String()) 102 99 }) 103 100 } 104 101 }
+44 -30
modules/doctor/doctor.go
··· 6 6 import ( 7 7 "context" 8 8 "fmt" 9 + "os" 9 10 "sort" 10 11 "strings" 11 12 ··· 26 27 Priority int 27 28 } 28 29 29 - type wrappedLevelLogger struct { 30 - log.LevelLogger 31 - } 32 - 33 - func (w *wrappedLevelLogger) Log(skip int, level log.Level, format string, v ...interface{}) error { 34 - return w.LevelLogger.Log( 35 - skip+1, 36 - level, 37 - " - %s "+format, 38 - append( 39 - []interface{}{ 40 - log.NewColoredValueBytes( 41 - fmt.Sprintf("[%s]", strings.ToUpper(level.String()[0:1])), 42 - level.Color()), 43 - }, v...)...) 44 - } 45 - 46 - func initDBDisableConsole(ctx context.Context, disableConsole bool) error { 30 + func initDBSkipLogger(ctx context.Context) error { 47 31 setting.Init(&setting.Options{}) 48 32 setting.LoadDBSetting() 49 - setting.InitSQLLog(disableConsole) 50 33 if err := db.InitEngine(ctx); err != nil { 51 34 return fmt.Errorf("db.InitEngine: %w", err) 52 35 } ··· 57 40 return nil 58 41 } 59 42 43 + type doctorCheckLogger struct { 44 + colorize bool 45 + } 46 + 47 + var _ log.BaseLogger = (*doctorCheckLogger)(nil) 48 + 49 + func (d *doctorCheckLogger) Log(skip int, level log.Level, format string, v ...any) { 50 + _, _ = fmt.Fprintf(os.Stdout, format+"\n", v...) 51 + } 52 + 53 + func (d *doctorCheckLogger) GetLevel() log.Level { 54 + return log.TRACE 55 + } 56 + 57 + type doctorCheckStepLogger struct { 58 + colorize bool 59 + } 60 + 61 + var _ log.BaseLogger = (*doctorCheckStepLogger)(nil) 62 + 63 + func (d *doctorCheckStepLogger) Log(skip int, level log.Level, format string, v ...any) { 64 + levelChar := fmt.Sprintf("[%s]", strings.ToUpper(level.String()[0:1])) 65 + var levelArg any = levelChar 66 + if d.colorize { 67 + levelArg = log.NewColoredValue(levelChar, level.ColorAttributes()...) 68 + } 69 + args := append([]any{levelArg}, v...) 70 + _, _ = fmt.Fprintf(os.Stdout, " - %s "+format+"\n", args...) 71 + } 72 + 73 + func (d *doctorCheckStepLogger) GetLevel() log.Level { 74 + return log.TRACE 75 + } 76 + 60 77 // Checks is the list of available commands 61 78 var Checks []*Check 62 79 63 80 // RunChecks runs the doctor checks for the provided list 64 - func RunChecks(ctx context.Context, logger log.Logger, autofix bool, checks []*Check) error { 65 - wrappedLogger := log.LevelLoggerLogger{ 66 - LevelLogger: &wrappedLevelLogger{logger}, 67 - } 68 - 81 + func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error { 82 + // the checks output logs by a special logger, they do not use the default logger 83 + logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize}) 84 + loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize}) 69 85 dbIsInit := false 70 86 for i, check := range checks { 71 87 if !dbIsInit && !check.SkipDatabaseInitialization { 72 88 // Only open database after the most basic configuration check 73 - setting.Log.EnableXORMLog = false 74 - if err := initDBDisableConsole(ctx, true); err != nil { 89 + if err := initDBSkipLogger(ctx); err != nil { 75 90 logger.Error("Error whilst initializing the database: %v", err) 76 91 logger.Error("Check if you are using the right config file. You can use a --config directive to specify one.") 77 92 return nil 78 93 } 79 94 dbIsInit = true 80 95 } 81 - logger.Info("[%d] %s", log.NewColoredIDValue(i+1), check.Title) 82 - logger.Flush() 83 - if err := check.Run(ctx, &wrappedLogger, autofix); err != nil { 96 + logger.Info("\n[%d] %s", i+1, check.Title) 97 + if err := check.Run(ctx, loggerStep, autofix); err != nil { 84 98 if check.AbortIfFailed { 85 99 logger.Critical("FAIL") 86 100 return err ··· 88 102 logger.Error("ERROR") 89 103 } else { 90 104 logger.Info("OK") 91 - logger.Flush() 92 105 } 93 106 } 107 + logger.Info("\nAll done.") 94 108 return nil 95 109 } 96 110
-3
modules/git/git_test.go
··· 10 10 "strings" 11 11 "testing" 12 12 13 - "code.gitea.io/gitea/modules/log" 14 13 "code.gitea.io/gitea/modules/setting" 15 14 "code.gitea.io/gitea/modules/util" 16 15 ··· 18 17 ) 19 18 20 19 func testRun(m *testing.M) error { 21 - _ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`) 22 - 23 20 gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home") 24 21 if err != nil { 25 22 return fmt.Errorf("unable to create temp dir: %w", err)
+1 -1
modules/graceful/manager.go
··· 30 30 // * HTTP redirection fallback 31 31 // * Builtin SSH listener 32 32 // 33 - // If you add an additional place you must increment this number 33 + // If you add a new place you must increment this number 34 34 // and add a function to call manager.InformCleanup if it's not going to be used 35 35 const numberOfServersToCreate = 4 36 36
+2 -1
modules/graceful/manager_unix.go
··· 16 16 "syscall" 17 17 "time" 18 18 19 + "code.gitea.io/gitea/modules/graceful/releasereopen" 19 20 "code.gitea.io/gitea/modules/log" 20 21 "code.gitea.io/gitea/modules/process" 21 22 "code.gitea.io/gitea/modules/setting" ··· 185 186 case syscall.SIGUSR1: 186 187 log.Warn("PID %d. Received SIGUSR1. Releasing and reopening logs", pid) 187 188 g.notify(statusMsg("Releasing and reopening logs")) 188 - if err := log.ReleaseReopen(); err != nil { 189 + if err := releasereopen.GetManager().ReleaseReopen(); err != nil { 189 190 log.Error("Error whilst releasing and reopening logs: %v", err) 190 191 } 191 192 case syscall.SIGUSR2:
+61
modules/graceful/releasereopen/releasereopen.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package releasereopen 5 + 6 + import ( 7 + "errors" 8 + "sync" 9 + ) 10 + 11 + type ReleaseReopener interface { 12 + ReleaseReopen() error 13 + } 14 + 15 + type Manager struct { 16 + mu sync.Mutex 17 + counter int64 18 + 19 + releaseReopeners map[int64]ReleaseReopener 20 + } 21 + 22 + func (r *Manager) Register(rr ReleaseReopener) (cancel func()) { 23 + r.mu.Lock() 24 + defer r.mu.Unlock() 25 + 26 + r.counter++ 27 + currentCounter := r.counter 28 + r.releaseReopeners[r.counter] = rr 29 + 30 + return func() { 31 + r.mu.Lock() 32 + defer r.mu.Unlock() 33 + 34 + delete(r.releaseReopeners, currentCounter) 35 + } 36 + } 37 + 38 + func (r *Manager) ReleaseReopen() error { 39 + r.mu.Lock() 40 + defer r.mu.Unlock() 41 + 42 + var errs []error 43 + for _, rr := range r.releaseReopeners { 44 + if err := rr.ReleaseReopen(); err != nil { 45 + errs = append(errs, err) 46 + } 47 + } 48 + return errors.Join(errs...) 49 + } 50 + 51 + func GetManager() *Manager { 52 + return manager 53 + } 54 + 55 + func NewManager() *Manager { 56 + return &Manager{ 57 + releaseReopeners: make(map[int64]ReleaseReopener), 58 + } 59 + } 60 + 61 + var manager = NewManager()
+43
modules/graceful/releasereopen/releasereopen_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package releasereopen 5 + 6 + import ( 7 + "testing" 8 + 9 + "github.com/stretchr/testify/assert" 10 + ) 11 + 12 + type testReleaseReopener struct { 13 + count int 14 + } 15 + 16 + func (t *testReleaseReopener) ReleaseReopen() error { 17 + t.count++ 18 + return nil 19 + } 20 + 21 + func TestManager(t *testing.T) { 22 + m := NewManager() 23 + 24 + t1 := &testReleaseReopener{} 25 + t2 := &testReleaseReopener{} 26 + t3 := &testReleaseReopener{} 27 + 28 + _ = m.Register(t1) 29 + c2 := m.Register(t2) 30 + _ = m.Register(t3) 31 + 32 + assert.NoError(t, m.ReleaseReopen()) 33 + assert.EqualValues(t, 1, t1.count) 34 + assert.EqualValues(t, 1, t2.count) 35 + assert.EqualValues(t, 1, t3.count) 36 + 37 + c2() 38 + 39 + assert.NoError(t, m.ReleaseReopen()) 40 + assert.EqualValues(t, 2, t1.count) 41 + assert.EqualValues(t, 1, t2.count) 42 + assert.EqualValues(t, 2, t3.count) 43 + }
+4 -16
modules/indexer/code/elastic_search.go
··· 49 49 lock sync.RWMutex 50 50 } 51 51 52 - type elasticLogger struct { 53 - log.Logger 54 - } 55 - 56 - func (l elasticLogger) Printf(format string, args ...interface{}) { 57 - _ = l.Logger.Log(2, l.Logger.GetLevel(), format, args...) 58 - } 59 - 60 52 // NewElasticSearchIndexer creates a new elasticsearch indexer 61 53 func NewElasticSearchIndexer(url, indexerName string) (*ElasticSearchIndexer, bool, error) { 62 54 opts := []elastic.ClientOptionFunc{ ··· 66 58 elastic.SetGzip(false), 67 59 } 68 60 69 - logger := elasticLogger{log.GetLogger(log.DEFAULT)} 61 + logger := log.GetLogger(log.DEFAULT) 70 62 71 - if logger.GetLevel() == log.TRACE || logger.GetLevel() == log.DEBUG { 72 - opts = append(opts, elastic.SetTraceLog(logger)) 73 - } else if logger.GetLevel() == log.ERROR || logger.GetLevel() == log.CRITICAL || logger.GetLevel() == log.FATAL { 74 - opts = append(opts, elastic.SetErrorLog(logger)) 75 - } else if logger.GetLevel() == log.INFO || logger.GetLevel() == log.WARN { 76 - opts = append(opts, elastic.SetInfoLog(logger)) 77 - } 63 + opts = append(opts, elastic.SetTraceLog(&log.PrintfLogger{Logf: logger.Trace})) 64 + opts = append(opts, elastic.SetInfoLog(&log.PrintfLogger{Logf: logger.Info})) 65 + opts = append(opts, elastic.SetErrorLog(&log.PrintfLogger{Logf: logger.Error})) 78 66 79 67 client, err := elastic.NewClient(opts...) 80 68 if err != nil {
+4 -17
modules/indexer/issues/elastic_search.go
··· 29 29 lock sync.RWMutex 30 30 } 31 31 32 - type elasticLogger struct { 33 - log.LevelLogger 34 - } 35 - 36 - func (l elasticLogger) Printf(format string, args ...interface{}) { 37 - _ = l.Log(2, l.GetLevel(), format, args...) 38 - } 39 - 40 32 // NewElasticSearchIndexer creates a new elasticsearch indexer 41 33 func NewElasticSearchIndexer(url, indexerName string) (*ElasticSearchIndexer, error) { 42 34 opts := []elastic.ClientOptionFunc{ ··· 46 38 elastic.SetGzip(false), 47 39 } 48 40 49 - logger := elasticLogger{log.GetLogger(log.DEFAULT)} 50 - 51 - if logger.GetLevel() == log.TRACE || logger.GetLevel() == log.DEBUG { 52 - opts = append(opts, elastic.SetTraceLog(logger)) 53 - } else if logger.GetLevel() == log.ERROR || logger.GetLevel() == log.CRITICAL || logger.GetLevel() == log.FATAL { 54 - opts = append(opts, elastic.SetErrorLog(logger)) 55 - } else if logger.GetLevel() == log.INFO || logger.GetLevel() == log.WARN { 56 - opts = append(opts, elastic.SetInfoLog(logger)) 57 - } 41 + logger := log.GetLogger(log.DEFAULT) 42 + opts = append(opts, elastic.SetTraceLog(&log.PrintfLogger{Logf: logger.Trace})) 43 + opts = append(opts, elastic.SetInfoLog(&log.PrintfLogger{Logf: logger.Info})) 44 + opts = append(opts, elastic.SetErrorLog(&log.PrintfLogger{Logf: logger.Error})) 58 45 59 46 client, err := elastic.NewClient(opts...) 60 47 if err != nil {
+3 -9
modules/lfs/pointer.go
··· 13 13 "strconv" 14 14 "strings" 15 15 16 - "code.gitea.io/gitea/modules/log" 17 - 18 16 "github.com/minio/sha256-simd" 19 17 ) 20 18 ··· 113 111 return path.Join(p.Oid[0:2], p.Oid[2:4], p.Oid[4:]) 114 112 } 115 113 116 - // ColorFormat provides a basic color format for a Team 117 - func (p Pointer) ColorFormat(s fmt.State) { 114 + func (p Pointer) LogString() string { 118 115 if p.Oid == "" && p.Size == 0 { 119 - log.ColorFprintf(s, "<empty>") 120 - return 116 + return "<LFSPointer empty>" 121 117 } 122 - log.ColorFprintf(s, "%s:%d", 123 - log.NewColoredIDValue(p.Oid), 124 - p.Size) 118 + return fmt.Sprintf("<LFSPointer %s:%d>", p.Oid, p.Size) 125 119 } 126 120 127 121 // GeneratePointer generates a pointer for arbitrary content
+115
modules/log/color.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "fmt" 8 + "strconv" 9 + ) 10 + 11 + const escape = "\033" 12 + 13 + // ColorAttribute defines a single SGR Code 14 + type ColorAttribute int 15 + 16 + // Base ColorAttributes 17 + const ( 18 + Reset ColorAttribute = iota 19 + Bold 20 + Faint 21 + Italic 22 + Underline 23 + BlinkSlow 24 + BlinkRapid 25 + ReverseVideo 26 + Concealed 27 + CrossedOut 28 + ) 29 + 30 + // Foreground text colors 31 + const ( 32 + FgBlack ColorAttribute = iota + 30 33 + FgRed 34 + FgGreen 35 + FgYellow 36 + FgBlue 37 + FgMagenta 38 + FgCyan 39 + FgWhite 40 + ) 41 + 42 + // Foreground Hi-Intensity text colors 43 + const ( 44 + FgHiBlack ColorAttribute = iota + 90 45 + FgHiRed 46 + FgHiGreen 47 + FgHiYellow 48 + FgHiBlue 49 + FgHiMagenta 50 + FgHiCyan 51 + FgHiWhite 52 + ) 53 + 54 + // Background text colors 55 + const ( 56 + BgBlack ColorAttribute = iota + 40 57 + BgRed 58 + BgGreen 59 + BgYellow 60 + BgBlue 61 + BgMagenta 62 + BgCyan 63 + BgWhite 64 + ) 65 + 66 + // Background Hi-Intensity text colors 67 + const ( 68 + BgHiBlack ColorAttribute = iota + 100 69 + BgHiRed 70 + BgHiGreen 71 + BgHiYellow 72 + BgHiBlue 73 + BgHiMagenta 74 + BgHiCyan 75 + BgHiWhite 76 + ) 77 + 78 + var ( 79 + resetBytes = ColorBytes(Reset) 80 + fgCyanBytes = ColorBytes(FgCyan) 81 + fgGreenBytes = ColorBytes(FgGreen) 82 + ) 83 + 84 + type ColoredValue struct { 85 + v any 86 + colors []ColorAttribute 87 + } 88 + 89 + func (c *ColoredValue) Format(f fmt.State, verb rune) { 90 + _, _ = f.Write(ColorBytes(c.colors...)) 91 + s := fmt.Sprintf(fmt.FormatString(f, verb), c.v) 92 + _, _ = f.Write([]byte(s)) 93 + _, _ = f.Write(resetBytes) 94 + } 95 + 96 + func NewColoredValue(v any, color ...ColorAttribute) *ColoredValue { 97 + return &ColoredValue{v: v, colors: color} 98 + } 99 + 100 + // ColorBytes converts a list of ColorAttributes to a byte array 101 + func ColorBytes(attrs ...ColorAttribute) []byte { 102 + bytes := make([]byte, 0, 20) 103 + bytes = append(bytes, escape[0], '[') 104 + if len(attrs) > 0 { 105 + bytes = append(bytes, strconv.Itoa(int(attrs[0]))...) 106 + for _, a := range attrs[1:] { 107 + bytes = append(bytes, ';') 108 + bytes = append(bytes, strconv.Itoa(int(a))...) 109 + } 110 + } else { 111 + bytes = append(bytes, strconv.Itoa(int(Bold))...) 112 + } 113 + bytes = append(bytes, 'm') 114 + return bytes 115 + }
+14
modules/log/color_console.go
··· 1 + // Copyright 2014 The Gogs Authors. All rights reserved. 2 + // Copyright 2023 The Gitea Authors. All rights reserved. 3 + // SPDX-License-Identifier: MIT 4 + 5 + package log 6 + 7 + // CanColorStdout reports if we can color the Stdout 8 + // Although we could do terminal sniffing and the like - in reality 9 + // most tools on *nix are happy to display ansi colors. 10 + // We will terminal sniff on Windows in console_windows.go 11 + var CanColorStdout = true 12 + 13 + // CanColorStderr reports if we can color the Stderr 14 + var CanColorStderr = true
+87
modules/log/color_router.go
··· 1 + // Copyright 2019 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "fmt" 8 + "time" 9 + ) 10 + 11 + var statusToColor = map[int][]ColorAttribute{ 12 + 100: {Bold}, 13 + 200: {FgGreen}, 14 + 300: {FgYellow}, 15 + 304: {FgCyan}, 16 + 400: {Bold, FgRed}, 17 + 401: {Bold, FgMagenta}, 18 + 403: {Bold, FgMagenta}, 19 + 500: {Bold, BgRed}, 20 + } 21 + 22 + // ColoredStatus adds colors for HTTP status 23 + func ColoredStatus(status int, s ...string) *ColoredValue { 24 + color, ok := statusToColor[status] 25 + if !ok { 26 + color, ok = statusToColor[(status/100)*100] 27 + } 28 + if !ok { 29 + color = []ColorAttribute{Bold} 30 + } 31 + if len(s) > 0 { 32 + return NewColoredValue(s[0], color...) 33 + } 34 + return NewColoredValue(status, color...) 35 + } 36 + 37 + var methodToColor = map[string][]ColorAttribute{ 38 + "GET": {FgBlue}, 39 + "POST": {FgGreen}, 40 + "DELETE": {FgRed}, 41 + "PATCH": {FgCyan}, 42 + "PUT": {FgYellow, Faint}, 43 + "HEAD": {FgBlue, Faint}, 44 + } 45 + 46 + // ColoredMethod adds colors for HTTP methods on log 47 + func ColoredMethod(method string) *ColoredValue { 48 + color, ok := methodToColor[method] 49 + if !ok { 50 + return NewColoredValue(method, Bold) 51 + } 52 + return NewColoredValue(method, color...) 53 + } 54 + 55 + var ( 56 + durations = []time.Duration{ 57 + 10 * time.Millisecond, 58 + 100 * time.Millisecond, 59 + 1 * time.Second, 60 + 5 * time.Second, 61 + 10 * time.Second, 62 + } 63 + 64 + durationColors = [][]ColorAttribute{ 65 + {FgGreen}, 66 + {Bold}, 67 + {FgYellow}, 68 + {FgRed, Bold}, 69 + {BgRed}, 70 + } 71 + 72 + wayTooLong = BgMagenta 73 + ) 74 + 75 + // ColoredTime converts the provided time to a ColoredValue for logging. The duration is always formatted in milliseconds. 76 + func ColoredTime(duration time.Duration) *ColoredValue { 77 + // the output of duration in Millisecond is more readable: 78 + // * before: "100.1ms" "100.1μs" "100.1s" 79 + // * better: "100.1ms" "0.1ms" "100100.0ms", readers can compare the values at first glance. 80 + str := fmt.Sprintf("%.1fms", float64(duration.Microseconds())/1000) 81 + for i, k := range durations { 82 + if duration < k { 83 + return NewColoredValue(str, durationColors[i]...) 84 + } 85 + } 86 + return NewColoredValue(str, wayTooLong) 87 + }
-435
modules/log/colors.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "io" 9 - "reflect" 10 - "strconv" 11 - "strings" 12 - ) 13 - 14 - const escape = "\033" 15 - 16 - // ColorAttribute defines a single SGR Code 17 - type ColorAttribute int 18 - 19 - // Base ColorAttributes 20 - const ( 21 - Reset ColorAttribute = iota 22 - Bold 23 - Faint 24 - Italic 25 - Underline 26 - BlinkSlow 27 - BlinkRapid 28 - ReverseVideo 29 - Concealed 30 - CrossedOut 31 - ) 32 - 33 - // Foreground text colors 34 - const ( 35 - FgBlack ColorAttribute = iota + 30 36 - FgRed 37 - FgGreen 38 - FgYellow 39 - FgBlue 40 - FgMagenta 41 - FgCyan 42 - FgWhite 43 - ) 44 - 45 - // Foreground Hi-Intensity text colors 46 - const ( 47 - FgHiBlack ColorAttribute = iota + 90 48 - FgHiRed 49 - FgHiGreen 50 - FgHiYellow 51 - FgHiBlue 52 - FgHiMagenta 53 - FgHiCyan 54 - FgHiWhite 55 - ) 56 - 57 - // Background text colors 58 - const ( 59 - BgBlack ColorAttribute = iota + 40 60 - BgRed 61 - BgGreen 62 - BgYellow 63 - BgBlue 64 - BgMagenta 65 - BgCyan 66 - BgWhite 67 - ) 68 - 69 - // Background Hi-Intensity text colors 70 - const ( 71 - BgHiBlack ColorAttribute = iota + 100 72 - BgHiRed 73 - BgHiGreen 74 - BgHiYellow 75 - BgHiBlue 76 - BgHiMagenta 77 - BgHiCyan 78 - BgHiWhite 79 - ) 80 - 81 - var colorAttributeToString = map[ColorAttribute]string{ 82 - Reset: "Reset", 83 - Bold: "Bold", 84 - Faint: "Faint", 85 - Italic: "Italic", 86 - Underline: "Underline", 87 - BlinkSlow: "BlinkSlow", 88 - BlinkRapid: "BlinkRapid", 89 - ReverseVideo: "ReverseVideo", 90 - Concealed: "Concealed", 91 - CrossedOut: "CrossedOut", 92 - FgBlack: "FgBlack", 93 - FgRed: "FgRed", 94 - FgGreen: "FgGreen", 95 - FgYellow: "FgYellow", 96 - FgBlue: "FgBlue", 97 - FgMagenta: "FgMagenta", 98 - FgCyan: "FgCyan", 99 - FgWhite: "FgWhite", 100 - FgHiBlack: "FgHiBlack", 101 - FgHiRed: "FgHiRed", 102 - FgHiGreen: "FgHiGreen", 103 - FgHiYellow: "FgHiYellow", 104 - FgHiBlue: "FgHiBlue", 105 - FgHiMagenta: "FgHiMagenta", 106 - FgHiCyan: "FgHiCyan", 107 - FgHiWhite: "FgHiWhite", 108 - BgBlack: "BgBlack", 109 - BgRed: "BgRed", 110 - BgGreen: "BgGreen", 111 - BgYellow: "BgYellow", 112 - BgBlue: "BgBlue", 113 - BgMagenta: "BgMagenta", 114 - BgCyan: "BgCyan", 115 - BgWhite: "BgWhite", 116 - BgHiBlack: "BgHiBlack", 117 - BgHiRed: "BgHiRed", 118 - BgHiGreen: "BgHiGreen", 119 - BgHiYellow: "BgHiYellow", 120 - BgHiBlue: "BgHiBlue", 121 - BgHiMagenta: "BgHiMagenta", 122 - BgHiCyan: "BgHiCyan", 123 - BgHiWhite: "BgHiWhite", 124 - } 125 - 126 - func (c *ColorAttribute) String() string { 127 - return colorAttributeToString[*c] 128 - } 129 - 130 - var colorAttributeFromString = map[string]ColorAttribute{} 131 - 132 - // ColorAttributeFromString will return a ColorAttribute given a string 133 - func ColorAttributeFromString(from string) ColorAttribute { 134 - lowerFrom := strings.TrimSpace(strings.ToLower(from)) 135 - return colorAttributeFromString[lowerFrom] 136 - } 137 - 138 - // ColorString converts a list of ColorAttributes to a color string 139 - func ColorString(attrs ...ColorAttribute) string { 140 - return string(ColorBytes(attrs...)) 141 - } 142 - 143 - // ColorBytes converts a list of ColorAttributes to a byte array 144 - func ColorBytes(attrs ...ColorAttribute) []byte { 145 - bytes := make([]byte, 0, 20) 146 - bytes = append(bytes, escape[0], '[') 147 - if len(attrs) > 0 { 148 - bytes = append(bytes, strconv.Itoa(int(attrs[0]))...) 149 - for _, a := range attrs[1:] { 150 - bytes = append(bytes, ';') 151 - bytes = append(bytes, strconv.Itoa(int(a))...) 152 - } 153 - } else { 154 - bytes = append(bytes, strconv.Itoa(int(Bold))...) 155 - } 156 - bytes = append(bytes, 'm') 157 - return bytes 158 - } 159 - 160 - var levelToColor = map[Level][]byte{ 161 - TRACE: ColorBytes(Bold, FgCyan), 162 - DEBUG: ColorBytes(Bold, FgBlue), 163 - INFO: ColorBytes(Bold, FgGreen), 164 - WARN: ColorBytes(Bold, FgYellow), 165 - ERROR: ColorBytes(Bold, FgRed), 166 - CRITICAL: ColorBytes(Bold, BgMagenta), 167 - FATAL: ColorBytes(Bold, BgRed), 168 - NONE: ColorBytes(Reset), 169 - } 170 - 171 - var ( 172 - resetBytes = ColorBytes(Reset) 173 - fgCyanBytes = ColorBytes(FgCyan) 174 - fgGreenBytes = ColorBytes(FgGreen) 175 - fgBoldBytes = ColorBytes(Bold) 176 - ) 177 - 178 - type protectedANSIWriterMode int 179 - 180 - const ( 181 - escapeAll protectedANSIWriterMode = iota 182 - allowColor 183 - removeColor 184 - ) 185 - 186 - type protectedANSIWriter struct { 187 - w io.Writer 188 - mode protectedANSIWriterMode 189 - } 190 - 191 - // Write will protect against unusual characters 192 - func (c *protectedANSIWriter) Write(bytes []byte) (int, error) { 193 - end := len(bytes) 194 - totalWritten := 0 195 - normalLoop: 196 - for i := 0; i < end; { 197 - lasti := i 198 - 199 - if c.mode == escapeAll { 200 - for i < end && (bytes[i] >= ' ' || bytes[i] == '\n' || bytes[i] == '\t') { 201 - i++ 202 - } 203 - } else { 204 - // Allow tabs if we're not escaping everything 205 - for i < end && (bytes[i] >= ' ' || bytes[i] == '\t') { 206 - i++ 207 - } 208 - } 209 - 210 - if i > lasti { 211 - written, err := c.w.Write(bytes[lasti:i]) 212 - totalWritten += written 213 - if err != nil { 214 - return totalWritten, err 215 - } 216 - 217 - } 218 - if i >= end { 219 - break 220 - } 221 - 222 - // If we're not just escaping all we should prefix all newlines with a \t 223 - if c.mode != escapeAll { 224 - if bytes[i] == '\n' { 225 - written, err := c.w.Write([]byte{'\n', '\t'}) 226 - if written > 0 { 227 - totalWritten++ 228 - } 229 - if err != nil { 230 - return totalWritten, err 231 - } 232 - i++ 233 - continue normalLoop 234 - } 235 - 236 - if bytes[i] == escape[0] && i+1 < end && bytes[i+1] == '[' { 237 - for j := i + 2; j < end; j++ { 238 - if bytes[j] >= '0' && bytes[j] <= '9' { 239 - continue 240 - } 241 - if bytes[j] == ';' { 242 - continue 243 - } 244 - if bytes[j] == 'm' { 245 - if c.mode == allowColor { 246 - written, err := c.w.Write(bytes[i : j+1]) 247 - totalWritten += written 248 - if err != nil { 249 - return totalWritten, err 250 - } 251 - } else { 252 - totalWritten = j 253 - } 254 - i = j + 1 255 - continue normalLoop 256 - } 257 - break 258 - } 259 - } 260 - } 261 - 262 - // Process naughty character 263 - if _, err := fmt.Fprintf(c.w, `\%#03o`, bytes[i]); err != nil { 264 - return totalWritten, err 265 - } 266 - i++ 267 - totalWritten++ 268 - } 269 - return totalWritten, nil 270 - } 271 - 272 - // ColorSprintf returns a colored string from a format and arguments 273 - // arguments will be wrapped in ColoredValues to protect against color spoofing 274 - func ColorSprintf(format string, args ...interface{}) string { 275 - if len(args) > 0 { 276 - v := make([]interface{}, len(args)) 277 - for i := 0; i < len(v); i++ { 278 - v[i] = NewColoredValuePointer(&args[i]) 279 - } 280 - return fmt.Sprintf(format, v...) 281 - } 282 - return format 283 - } 284 - 285 - // ColorFprintf will write to the provided writer similar to ColorSprintf 286 - func ColorFprintf(w io.Writer, format string, args ...interface{}) (int, error) { 287 - if len(args) > 0 { 288 - v := make([]interface{}, len(args)) 289 - for i := 0; i < len(v); i++ { 290 - v[i] = NewColoredValuePointer(&args[i]) 291 - } 292 - return fmt.Fprintf(w, format, v...) 293 - } 294 - return fmt.Fprint(w, format) 295 - } 296 - 297 - // ColorFormatted structs provide their own colored string when formatted with ColorSprintf 298 - type ColorFormatted interface { 299 - // ColorFormat provides the colored representation of the value 300 - ColorFormat(s fmt.State) 301 - } 302 - 303 - var colorFormattedType = reflect.TypeOf((*ColorFormatted)(nil)).Elem() 304 - 305 - // ColoredValue will Color the provided value 306 - type ColoredValue struct { 307 - colorBytes *[]byte 308 - resetBytes *[]byte 309 - Value *interface{} 310 - } 311 - 312 - // NewColoredValue is a helper function to create a ColoredValue from a Value 313 - // If no color is provided it defaults to Bold with standard Reset 314 - // If a ColoredValue is provided it is not changed 315 - func NewColoredValue(value interface{}, color ...ColorAttribute) *ColoredValue { 316 - return NewColoredValuePointer(&value, color...) 317 - } 318 - 319 - // NewColoredValuePointer is a helper function to create a ColoredValue from a Value Pointer 320 - // If no color is provided it defaults to Bold with standard Reset 321 - // If a ColoredValue is provided it is not changed 322 - func NewColoredValuePointer(value *interface{}, color ...ColorAttribute) *ColoredValue { 323 - if val, ok := (*value).(*ColoredValue); ok { 324 - return val 325 - } 326 - if len(color) > 0 { 327 - bytes := ColorBytes(color...) 328 - return &ColoredValue{ 329 - colorBytes: &bytes, 330 - resetBytes: &resetBytes, 331 - Value: value, 332 - } 333 - } 334 - return &ColoredValue{ 335 - colorBytes: &fgBoldBytes, 336 - resetBytes: &resetBytes, 337 - Value: value, 338 - } 339 - } 340 - 341 - // NewColoredValueBytes creates a value from the provided value with color bytes 342 - // If a ColoredValue is provided it is not changed 343 - func NewColoredValueBytes(value interface{}, colorBytes *[]byte) *ColoredValue { 344 - if val, ok := value.(*ColoredValue); ok { 345 - return val 346 - } 347 - return &ColoredValue{ 348 - colorBytes: colorBytes, 349 - resetBytes: &resetBytes, 350 - Value: &value, 351 - } 352 - } 353 - 354 - // NewColoredIDValue is a helper function to create a ColoredValue from a Value 355 - // The Value will be colored with FgCyan 356 - // If a ColoredValue is provided it is not changed 357 - func NewColoredIDValue(value interface{}) *ColoredValue { 358 - return NewColoredValueBytes(value, &fgCyanBytes) 359 - } 360 - 361 - // Format will format the provided value and protect against ANSI color spoofing within the value 362 - // If the wrapped value is ColorFormatted and the format is "%-v" then its ColorString will 363 - // be used. It is presumed that this ColorString is safe. 364 - func (cv *ColoredValue) Format(s fmt.State, c rune) { 365 - if c == 'v' && s.Flag('-') { 366 - if val, ok := (*cv.Value).(ColorFormatted); ok { 367 - val.ColorFormat(s) 368 - return 369 - } 370 - v := reflect.ValueOf(*cv.Value) 371 - t := v.Type() 372 - 373 - if reflect.PtrTo(t).Implements(colorFormattedType) { 374 - vp := reflect.New(t) 375 - vp.Elem().Set(v) 376 - val := vp.Interface().(ColorFormatted) 377 - val.ColorFormat(s) 378 - return 379 - } 380 - } 381 - s.Write(*cv.colorBytes) 382 - fmt.Fprintf(&protectedANSIWriter{w: s}, fmtString(s, c), *(cv.Value)) 383 - s.Write(*cv.resetBytes) 384 - } 385 - 386 - // ColorFormatAsString returns the result of the ColorFormat without the color 387 - func ColorFormatAsString(colorVal ColorFormatted) string { 388 - s := new(strings.Builder) 389 - _, _ = ColorFprintf(&protectedANSIWriter{w: s, mode: removeColor}, "%-v", colorVal) 390 - return s.String() 391 - } 392 - 393 - // SetColorBytes will allow a user to set the colorBytes of a colored value 394 - func (cv *ColoredValue) SetColorBytes(colorBytes []byte) { 395 - cv.colorBytes = &colorBytes 396 - } 397 - 398 - // SetColorBytesPointer will allow a user to set the colorBytes pointer of a colored value 399 - func (cv *ColoredValue) SetColorBytesPointer(colorBytes *[]byte) { 400 - cv.colorBytes = colorBytes 401 - } 402 - 403 - // SetResetBytes will allow a user to set the resetBytes pointer of a colored value 404 - func (cv *ColoredValue) SetResetBytes(resetBytes []byte) { 405 - cv.resetBytes = &resetBytes 406 - } 407 - 408 - // SetResetBytesPointer will allow a user to set the resetBytes pointer of a colored value 409 - func (cv *ColoredValue) SetResetBytesPointer(resetBytes *[]byte) { 410 - cv.resetBytes = resetBytes 411 - } 412 - 413 - func fmtString(s fmt.State, c rune) string { 414 - var width, precision string 415 - base := make([]byte, 0, 8) 416 - base = append(base, '%') 417 - for _, c := range []byte(" +-#0") { 418 - if s.Flag(int(c)) { 419 - base = append(base, c) 420 - } 421 - } 422 - if w, ok := s.Width(); ok { 423 - width = strconv.Itoa(w) 424 - } 425 - if p, ok := s.Precision(); ok { 426 - precision = "." + strconv.Itoa(p) 427 - } 428 - return fmt.Sprintf("%s%s%s%c", base, width, precision, c) 429 - } 430 - 431 - func init() { 432 - for attr, from := range colorAttributeToString { 433 - colorAttributeFromString[strings.ToLower(from)] = attr 434 - } 435 - }
-87
modules/log/colors_router.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "time" 9 - ) 10 - 11 - var statusToColor = map[int][]byte{ 12 - 100: ColorBytes(Bold), 13 - 200: ColorBytes(FgGreen), 14 - 300: ColorBytes(FgYellow), 15 - 304: ColorBytes(FgCyan), 16 - 400: ColorBytes(Bold, FgRed), 17 - 401: ColorBytes(Bold, FgMagenta), 18 - 403: ColorBytes(Bold, FgMagenta), 19 - 500: ColorBytes(Bold, BgRed), 20 - } 21 - 22 - // ColoredStatus adds colors for HTTP status 23 - func ColoredStatus(status int, s ...string) *ColoredValue { 24 - color, ok := statusToColor[status] 25 - if !ok { 26 - color, ok = statusToColor[(status/100)*100] 27 - } 28 - if !ok { 29 - color = fgBoldBytes 30 - } 31 - if len(s) > 0 { 32 - return NewColoredValueBytes(s[0], &color) 33 - } 34 - return NewColoredValueBytes(status, &color) 35 - } 36 - 37 - var methodToColor = map[string][]byte{ 38 - "GET": ColorBytes(FgBlue), 39 - "POST": ColorBytes(FgGreen), 40 - "DELETE": ColorBytes(FgRed), 41 - "PATCH": ColorBytes(FgCyan), 42 - "PUT": ColorBytes(FgYellow, Faint), 43 - "HEAD": ColorBytes(FgBlue, Faint), 44 - } 45 - 46 - // ColoredMethod adds colors for HTTP methods on log 47 - func ColoredMethod(method string) *ColoredValue { 48 - color, ok := methodToColor[method] 49 - if !ok { 50 - return NewColoredValueBytes(method, &fgBoldBytes) 51 - } 52 - return NewColoredValueBytes(method, &color) 53 - } 54 - 55 - var ( 56 - durations = []time.Duration{ 57 - 10 * time.Millisecond, 58 - 100 * time.Millisecond, 59 - 1 * time.Second, 60 - 5 * time.Second, 61 - 10 * time.Second, 62 - } 63 - 64 - durationColors = [][]byte{ 65 - ColorBytes(FgGreen), 66 - ColorBytes(Bold), 67 - ColorBytes(FgYellow), 68 - ColorBytes(FgRed, Bold), 69 - ColorBytes(BgRed), 70 - } 71 - 72 - wayTooLong = ColorBytes(BgMagenta) 73 - ) 74 - 75 - // ColoredTime converts the provided time to a ColoredValue for logging. The duration is always formatted in milliseconds. 76 - func ColoredTime(duration time.Duration) *ColoredValue { 77 - // the output of duration in Millisecond is more readable: 78 - // * before: "100.1ms" "100.1μs" "100.1s" 79 - // * better: "100.1ms" "0.1ms" "100100.0ms", readers can compare the values at first glance. 80 - str := fmt.Sprintf("%.1fms", float64(duration.Microseconds())/1000) 81 - for i, k := range durations { 82 - if duration < k { 83 - return NewColoredValueBytes(str, &durationColors[i]) 84 - } 85 - } 86 - return NewColoredValueBytes(str, &wayTooLong) 87 - }
-137
modules/log/conn.go
··· 1 - // Copyright 2014 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - package log 6 - 7 - import ( 8 - "fmt" 9 - "io" 10 - "net" 11 - 12 - "code.gitea.io/gitea/modules/json" 13 - ) 14 - 15 - type connWriter struct { 16 - innerWriter io.WriteCloser 17 - ReconnectOnMsg bool `json:"reconnectOnMsg"` 18 - Reconnect bool `json:"reconnect"` 19 - Net string `json:"net"` 20 - Addr string `json:"addr"` 21 - } 22 - 23 - // Close the inner writer 24 - func (i *connWriter) Close() error { 25 - if i.innerWriter != nil { 26 - return i.innerWriter.Close() 27 - } 28 - return nil 29 - } 30 - 31 - // Write the data to the connection 32 - func (i *connWriter) Write(p []byte) (int, error) { 33 - if i.neededConnectOnMsg() { 34 - if err := i.connect(); err != nil { 35 - return 0, err 36 - } 37 - } 38 - 39 - if i.ReconnectOnMsg { 40 - defer i.innerWriter.Close() 41 - } 42 - 43 - return i.innerWriter.Write(p) 44 - } 45 - 46 - func (i *connWriter) neededConnectOnMsg() bool { 47 - if i.Reconnect { 48 - i.Reconnect = false 49 - return true 50 - } 51 - 52 - if i.innerWriter == nil { 53 - return true 54 - } 55 - 56 - return i.ReconnectOnMsg 57 - } 58 - 59 - func (i *connWriter) connect() error { 60 - if i.innerWriter != nil { 61 - i.innerWriter.Close() 62 - i.innerWriter = nil 63 - } 64 - 65 - conn, err := net.Dial(i.Net, i.Addr) 66 - if err != nil { 67 - return err 68 - } 69 - 70 - if tcpConn, ok := conn.(*net.TCPConn); ok { 71 - err = tcpConn.SetKeepAlive(true) 72 - if err != nil { 73 - return err 74 - } 75 - } 76 - 77 - i.innerWriter = conn 78 - return nil 79 - } 80 - 81 - func (i *connWriter) releaseReopen() error { 82 - if i.innerWriter != nil { 83 - return i.connect() 84 - } 85 - return nil 86 - } 87 - 88 - // ConnLogger implements LoggerProvider. 89 - // it writes messages in keep-live tcp connection. 90 - type ConnLogger struct { 91 - WriterLogger 92 - ReconnectOnMsg bool `json:"reconnectOnMsg"` 93 - Reconnect bool `json:"reconnect"` 94 - Net string `json:"net"` 95 - Addr string `json:"addr"` 96 - } 97 - 98 - // NewConn creates new ConnLogger returning as LoggerProvider. 99 - func NewConn() LoggerProvider { 100 - conn := new(ConnLogger) 101 - conn.Level = TRACE 102 - return conn 103 - } 104 - 105 - // Init inits connection writer with json config. 106 - // json config only need key "level". 107 - func (log *ConnLogger) Init(jsonconfig string) error { 108 - err := json.Unmarshal([]byte(jsonconfig), log) 109 - if err != nil { 110 - return fmt.Errorf("Unable to parse JSON: %w", err) 111 - } 112 - log.NewWriterLogger(&connWriter{ 113 - ReconnectOnMsg: log.ReconnectOnMsg, 114 - Reconnect: log.Reconnect, 115 - Net: log.Net, 116 - Addr: log.Addr, 117 - }, log.Level) 118 - return nil 119 - } 120 - 121 - // Flush does nothing for this implementation 122 - func (log *ConnLogger) Flush() { 123 - } 124 - 125 - // GetName returns the default name for this implementation 126 - func (log *ConnLogger) GetName() string { 127 - return "conn" 128 - } 129 - 130 - // ReleaseReopen causes the ConnLogger to reconnect to the server 131 - func (log *ConnLogger) ReleaseReopen() error { 132 - return log.out.(*connWriter).releaseReopen() 133 - } 134 - 135 - func init() { 136 - Register("conn", NewConn) 137 - }
-230
modules/log/conn_test.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "io" 9 - "net" 10 - "strings" 11 - "sync" 12 - "testing" 13 - "time" 14 - 15 - "github.com/stretchr/testify/assert" 16 - ) 17 - 18 - func listenReadAndClose(t *testing.T, l net.Listener, expected string) { 19 - conn, err := l.Accept() 20 - assert.NoError(t, err) 21 - defer conn.Close() 22 - written, err := io.ReadAll(conn) 23 - 24 - assert.NoError(t, err) 25 - assert.Equal(t, expected, string(written)) 26 - } 27 - 28 - func TestConnLogger(t *testing.T) { 29 - protocol := "tcp" 30 - address := ":3099" 31 - 32 - l, err := net.Listen(protocol, address) 33 - if err != nil { 34 - t.Fatal(err) 35 - } 36 - defer l.Close() 37 - 38 - prefix := "TestPrefix " 39 - level := INFO 40 - flags := LstdFlags | LUTC | Lfuncname 41 - 42 - logger := NewConn() 43 - connLogger := logger.(*ConnLogger) 44 - 45 - logger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"reconnectOnMsg\":%t,\"reconnect\":%t,\"net\":\"%s\",\"addr\":\"%s\"}", prefix, level.String(), flags, true, true, protocol, address)) 46 - 47 - assert.Equal(t, flags, connLogger.Flags) 48 - assert.Equal(t, level, connLogger.Level) 49 - assert.Equal(t, level, logger.GetLevel()) 50 - 51 - location, _ := time.LoadLocation("EST") 52 - 53 - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 54 - 55 - dateString := date.UTC().Format("2006/01/02 15:04:05") 56 - 57 - event := Event{ 58 - level: INFO, 59 - msg: "TEST MSG", 60 - caller: "CALLER", 61 - filename: "FULL/FILENAME", 62 - line: 1, 63 - time: date, 64 - } 65 - expected := fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 66 - var wg sync.WaitGroup 67 - wg.Add(2) 68 - go func() { 69 - defer wg.Done() 70 - listenReadAndClose(t, l, expected) 71 - }() 72 - go func() { 73 - defer wg.Done() 74 - err := logger.LogEvent(&event) 75 - assert.NoError(t, err) 76 - }() 77 - wg.Wait() 78 - 79 - event.level = WARN 80 - expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 81 - wg.Add(2) 82 - go func() { 83 - defer wg.Done() 84 - listenReadAndClose(t, l, expected) 85 - }() 86 - go func() { 87 - defer wg.Done() 88 - err := logger.LogEvent(&event) 89 - assert.NoError(t, err) 90 - }() 91 - wg.Wait() 92 - 93 - logger.Close() 94 - } 95 - 96 - func TestConnLoggerBadConfig(t *testing.T) { 97 - logger := NewConn() 98 - 99 - err := logger.Init("{") 100 - assert.Error(t, err) 101 - assert.Contains(t, err.Error(), "Unable to parse JSON") 102 - logger.Close() 103 - } 104 - 105 - func TestConnLoggerCloseBeforeSend(t *testing.T) { 106 - protocol := "tcp" 107 - address := ":3099" 108 - 109 - prefix := "TestPrefix " 110 - level := INFO 111 - flags := LstdFlags | LUTC | Lfuncname 112 - 113 - logger := NewConn() 114 - 115 - logger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"reconnectOnMsg\":%t,\"reconnect\":%t,\"net\":\"%s\",\"addr\":\"%s\"}", prefix, level.String(), flags, false, false, protocol, address)) 116 - logger.Close() 117 - } 118 - 119 - func TestConnLoggerFailConnect(t *testing.T) { 120 - protocol := "tcp" 121 - address := ":3099" 122 - 123 - prefix := "TestPrefix " 124 - level := INFO 125 - flags := LstdFlags | LUTC | Lfuncname 126 - 127 - logger := NewConn() 128 - 129 - logger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"reconnectOnMsg\":%t,\"reconnect\":%t,\"net\":\"%s\",\"addr\":\"%s\"}", prefix, level.String(), flags, false, false, protocol, address)) 130 - 131 - assert.Equal(t, level, logger.GetLevel()) 132 - 133 - location, _ := time.LoadLocation("EST") 134 - 135 - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 136 - 137 - // dateString := date.UTC().Format("2006/01/02 15:04:05") 138 - 139 - event := Event{ 140 - level: INFO, 141 - msg: "TEST MSG", 142 - caller: "CALLER", 143 - filename: "FULL/FILENAME", 144 - line: 1, 145 - time: date, 146 - } 147 - 148 - err := logger.LogEvent(&event) 149 - assert.Error(t, err) 150 - 151 - logger.Close() 152 - } 153 - 154 - func TestConnLoggerClose(t *testing.T) { 155 - protocol := "tcp" 156 - address := ":3099" 157 - 158 - l, err := net.Listen(protocol, address) 159 - if err != nil { 160 - t.Fatal(err) 161 - } 162 - defer l.Close() 163 - 164 - prefix := "TestPrefix " 165 - level := INFO 166 - flags := LstdFlags | LUTC | Lfuncname 167 - 168 - logger := NewConn() 169 - connLogger := logger.(*ConnLogger) 170 - 171 - logger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"reconnectOnMsg\":%t,\"reconnect\":%t,\"net\":\"%s\",\"addr\":\"%s\"}", prefix, level.String(), flags, false, false, protocol, address)) 172 - 173 - assert.Equal(t, flags, connLogger.Flags) 174 - assert.Equal(t, level, connLogger.Level) 175 - assert.Equal(t, level, logger.GetLevel()) 176 - location, _ := time.LoadLocation("EST") 177 - 178 - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 179 - 180 - dateString := date.UTC().Format("2006/01/02 15:04:05") 181 - 182 - event := Event{ 183 - level: INFO, 184 - msg: "TEST MSG", 185 - caller: "CALLER", 186 - filename: "FULL/FILENAME", 187 - line: 1, 188 - time: date, 189 - } 190 - expected := fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 191 - var wg sync.WaitGroup 192 - wg.Add(2) 193 - go func() { 194 - defer wg.Done() 195 - err := logger.LogEvent(&event) 196 - assert.NoError(t, err) 197 - logger.Close() 198 - }() 199 - go func() { 200 - defer wg.Done() 201 - listenReadAndClose(t, l, expected) 202 - }() 203 - wg.Wait() 204 - 205 - logger = NewConn() 206 - connLogger = logger.(*ConnLogger) 207 - 208 - logger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"reconnectOnMsg\":%t,\"reconnect\":%t,\"net\":\"%s\",\"addr\":\"%s\"}", prefix, level.String(), flags, false, true, protocol, address)) 209 - 210 - assert.Equal(t, flags, connLogger.Flags) 211 - assert.Equal(t, level, connLogger.Level) 212 - assert.Equal(t, level, logger.GetLevel()) 213 - 214 - event.level = WARN 215 - expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 216 - wg.Add(2) 217 - go func() { 218 - defer wg.Done() 219 - listenReadAndClose(t, l, expected) 220 - }() 221 - go func() { 222 - defer wg.Done() 223 - err := logger.LogEvent(&event) 224 - assert.NoError(t, err) 225 - logger.Close() 226 - }() 227 - wg.Wait() 228 - logger.Flush() 229 - logger.Close() 230 - }
-93
modules/log/console.go
··· 1 - // Copyright 2014 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - package log 6 - 7 - import ( 8 - "fmt" 9 - "io" 10 - "os" 11 - 12 - "code.gitea.io/gitea/modules/json" 13 - ) 14 - 15 - // CanColorStdout reports if we can color the Stdout 16 - // Although we could do terminal sniffing and the like - in reality 17 - // most tools on *nix are happy to display ansi colors. 18 - // We will terminal sniff on Windows in console_windows.go 19 - var CanColorStdout = true 20 - 21 - // CanColorStderr reports if we can color the Stderr 22 - var CanColorStderr = true 23 - 24 - type nopWriteCloser struct { 25 - w io.WriteCloser 26 - } 27 - 28 - func (n *nopWriteCloser) Write(p []byte) (int, error) { 29 - return n.w.Write(p) 30 - } 31 - 32 - func (n *nopWriteCloser) Close() error { 33 - return nil 34 - } 35 - 36 - // ConsoleLogger implements LoggerProvider and writes messages to terminal. 37 - type ConsoleLogger struct { 38 - WriterLogger 39 - Stderr bool `json:"stderr"` 40 - } 41 - 42 - // NewConsoleLogger create ConsoleLogger returning as LoggerProvider. 43 - func NewConsoleLogger() LoggerProvider { 44 - log := &ConsoleLogger{} 45 - log.NewWriterLogger(&nopWriteCloser{ 46 - w: os.Stdout, 47 - }) 48 - return log 49 - } 50 - 51 - // Init inits connection writer with json config. 52 - // json config only need key "level". 53 - func (log *ConsoleLogger) Init(config string) error { 54 - err := json.Unmarshal([]byte(config), log) 55 - if err != nil { 56 - return fmt.Errorf("Unable to parse JSON: %w", err) 57 - } 58 - if log.Stderr { 59 - log.NewWriterLogger(&nopWriteCloser{ 60 - w: os.Stderr, 61 - }) 62 - } else { 63 - log.NewWriterLogger(log.out) 64 - } 65 - return nil 66 - } 67 - 68 - // Flush when log should be flushed 69 - func (log *ConsoleLogger) Flush() { 70 - } 71 - 72 - // ReleaseReopen causes the console logger to reconnect to os.Stdout 73 - func (log *ConsoleLogger) ReleaseReopen() error { 74 - if log.Stderr { 75 - log.NewWriterLogger(&nopWriteCloser{ 76 - w: os.Stderr, 77 - }) 78 - } else { 79 - log.NewWriterLogger(&nopWriteCloser{ 80 - w: os.Stdout, 81 - }) 82 - } 83 - return nil 84 - } 85 - 86 - // GetName returns the default name for this implementation 87 - func (log *ConsoleLogger) GetName() string { 88 - return "console" 89 - } 90 - 91 - func init() { 92 - Register("console", NewConsoleLogger) 93 - }
modules/log/console_other.go modules/log/color_console_other.go
-137
modules/log/console_test.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "strings" 9 - "testing" 10 - "time" 11 - 12 - "github.com/stretchr/testify/assert" 13 - ) 14 - 15 - func TestConsoleLoggerBadConfig(t *testing.T) { 16 - logger := NewConsoleLogger() 17 - 18 - err := logger.Init("{") 19 - assert.Error(t, err) 20 - assert.Contains(t, err.Error(), "Unable to parse JSON") 21 - logger.Close() 22 - } 23 - 24 - func TestConsoleLoggerMinimalConfig(t *testing.T) { 25 - for _, level := range Levels() { 26 - var written []byte 27 - var closed bool 28 - 29 - c := CallbackWriteCloser{ 30 - callback: func(p []byte, close bool) { 31 - written = p 32 - closed = close 33 - }, 34 - } 35 - prefix := "" 36 - flags := LstdFlags 37 - 38 - cw := NewConsoleLogger() 39 - realCW := cw.(*ConsoleLogger) 40 - cw.Init(fmt.Sprintf("{\"level\":\"%s\"}", level)) 41 - nwc := realCW.out.(*nopWriteCloser) 42 - nwc.w = c 43 - 44 - assert.Equal(t, flags, realCW.Flags) 45 - assert.Equal(t, FromString(level), realCW.Level) 46 - assert.Equal(t, FromString(level), cw.GetLevel()) 47 - assert.Equal(t, prefix, realCW.Prefix) 48 - assert.Equal(t, "", string(written)) 49 - cw.Close() 50 - assert.False(t, closed) 51 - 52 - } 53 - } 54 - 55 - func TestConsoleLogger(t *testing.T) { 56 - var written []byte 57 - var closed bool 58 - 59 - c := CallbackWriteCloser{ 60 - callback: func(p []byte, close bool) { 61 - written = p 62 - closed = close 63 - }, 64 - } 65 - prefix := "TestPrefix " 66 - level := INFO 67 - flags := LstdFlags | LUTC | Lfuncname 68 - 69 - cw := NewConsoleLogger() 70 - realCW := cw.(*ConsoleLogger) 71 - realCW.Colorize = false 72 - nwc := realCW.out.(*nopWriteCloser) 73 - nwc.w = c 74 - 75 - cw.Init(fmt.Sprintf("{\"expression\":\"FILENAME\",\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d}", prefix, level.String(), flags)) 76 - 77 - assert.Equal(t, flags, realCW.Flags) 78 - assert.Equal(t, level, realCW.Level) 79 - assert.Equal(t, level, cw.GetLevel()) 80 - 81 - location, _ := time.LoadLocation("EST") 82 - 83 - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 84 - 85 - dateString := date.UTC().Format("2006/01/02 15:04:05") 86 - 87 - event := Event{ 88 - level: INFO, 89 - msg: "TEST MSG", 90 - caller: "CALLER", 91 - filename: "FULL/FILENAME", 92 - line: 1, 93 - time: date, 94 - } 95 - 96 - expected := fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 97 - cw.LogEvent(&event) 98 - assert.Equal(t, expected, string(written)) 99 - assert.False(t, closed) 100 - written = written[:0] 101 - 102 - event.level = DEBUG 103 - expected = "" 104 - cw.LogEvent(&event) 105 - assert.Equal(t, expected, string(written)) 106 - assert.False(t, closed) 107 - 108 - event.level = TRACE 109 - expected = "" 110 - cw.LogEvent(&event) 111 - assert.Equal(t, expected, string(written)) 112 - assert.False(t, closed) 113 - 114 - nonMatchEvent := Event{ 115 - level: INFO, 116 - msg: "TEST MSG", 117 - caller: "CALLER", 118 - filename: "FULL/FI_LENAME", 119 - line: 1, 120 - time: date, 121 - } 122 - event.level = INFO 123 - expected = "" 124 - cw.LogEvent(&nonMatchEvent) 125 - assert.Equal(t, expected, string(written)) 126 - assert.False(t, closed) 127 - 128 - event.level = WARN 129 - expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 130 - cw.LogEvent(&event) 131 - assert.Equal(t, expected, string(written)) 132 - assert.False(t, closed) 133 - written = written[:0] 134 - 135 - cw.Close() 136 - assert.False(t, closed) 137 - }
+1 -1
modules/log/console_windows.go modules/log/color_console_windows.go
··· 20 20 // EnableVirtualTerminalProcessing is the console mode to allow ANSI code 21 21 // interpretation on the console. See: 22 22 // https://docs.microsoft.com/en-us/windows/console/setconsolemode 23 - // It only works on windows 10. Earlier terminals will fail with an err which we will 23 + // It only works on Windows 10. Earlier terminals will fail with an err which we will 24 24 // handle to say don't color 25 25 mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING 26 26 err = windows.SetConsoleMode(console, mode)
-61
modules/log/errors.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import "fmt" 7 - 8 - // ErrTimeout represents a "Timeout" kind of error. 9 - type ErrTimeout struct { 10 - Name string 11 - Provider string 12 - } 13 - 14 - // IsErrTimeout checks if an error is a ErrTimeout. 15 - func IsErrTimeout(err error) bool { 16 - if err == nil { 17 - return false 18 - } 19 - _, ok := err.(ErrTimeout) 20 - return ok 21 - } 22 - 23 - func (err ErrTimeout) Error() string { 24 - return fmt.Sprintf("Log Timeout for %s (%s)", err.Name, err.Provider) 25 - } 26 - 27 - // ErrUnknownProvider represents a "Unknown Provider" kind of error. 28 - type ErrUnknownProvider struct { 29 - Provider string 30 - } 31 - 32 - // IsErrUnknownProvider checks if an error is a ErrUnknownProvider. 33 - func IsErrUnknownProvider(err error) bool { 34 - if err == nil { 35 - return false 36 - } 37 - _, ok := err.(ErrUnknownProvider) 38 - return ok 39 - } 40 - 41 - func (err ErrUnknownProvider) Error() string { 42 - return fmt.Sprintf("Unknown Log Provider \"%s\" (Was it registered?)", err.Provider) 43 - } 44 - 45 - // ErrDuplicateName represents a Duplicate Name error 46 - type ErrDuplicateName struct { 47 - Name string 48 - } 49 - 50 - // IsErrDuplicateName checks if an error is a ErrDuplicateName. 51 - func IsErrDuplicateName(err error) bool { 52 - if err == nil { 53 - return false 54 - } 55 - _, ok := err.(ErrDuplicateName) 56 - return ok 57 - } 58 - 59 - func (err ErrDuplicateName) Error() string { 60 - return fmt.Sprintf("Duplicate named logger: %s", err.Name) 61 - }
-460
modules/log/event.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "context" 8 - "fmt" 9 - "runtime/pprof" 10 - "sync" 11 - "time" 12 - 13 - "code.gitea.io/gitea/modules/process" 14 - ) 15 - 16 - // Event represents a logging event 17 - type Event struct { 18 - level Level 19 - msg string 20 - caller string 21 - filename string 22 - line int 23 - time time.Time 24 - stacktrace string 25 - } 26 - 27 - // EventLogger represents the behaviours of a logger 28 - type EventLogger interface { 29 - LogEvent(event *Event) error 30 - Close() 31 - Flush() 32 - GetLevel() Level 33 - GetStacktraceLevel() Level 34 - GetName() string 35 - ReleaseReopen() error 36 - } 37 - 38 - // ChannelledLog represents a cached channel to a LoggerProvider 39 - type ChannelledLog struct { 40 - ctx context.Context 41 - finished context.CancelFunc 42 - name string 43 - provider string 44 - queue chan *Event 45 - loggerProvider LoggerProvider 46 - flush chan bool 47 - close chan bool 48 - closed chan bool 49 - } 50 - 51 - // NewChannelledLog a new logger instance with given logger provider and config. 52 - func NewChannelledLog(parent context.Context, name, provider, config string, bufferLength int64) (*ChannelledLog, error) { 53 - if log, ok := providers[provider]; ok { 54 - 55 - l := &ChannelledLog{ 56 - queue: make(chan *Event, bufferLength), 57 - flush: make(chan bool), 58 - close: make(chan bool), 59 - closed: make(chan bool), 60 - } 61 - l.loggerProvider = log() 62 - if err := l.loggerProvider.Init(config); err != nil { 63 - return nil, err 64 - } 65 - l.name = name 66 - l.provider = provider 67 - l.ctx, _, l.finished = process.GetManager().AddTypedContext(parent, fmt.Sprintf("Logger: %s(%s)", l.name, l.provider), process.SystemProcessType, false) 68 - go l.Start() 69 - return l, nil 70 - } 71 - return nil, ErrUnknownProvider{provider} 72 - } 73 - 74 - // Start processing the ChannelledLog 75 - func (l *ChannelledLog) Start() { 76 - pprof.SetGoroutineLabels(l.ctx) 77 - defer l.finished() 78 - for { 79 - select { 80 - case event, ok := <-l.queue: 81 - if !ok { 82 - l.closeLogger() 83 - return 84 - } 85 - l.loggerProvider.LogEvent(event) //nolint:errcheck 86 - case _, ok := <-l.flush: 87 - if !ok { 88 - l.closeLogger() 89 - return 90 - } 91 - l.emptyQueue() 92 - l.loggerProvider.Flush() 93 - case <-l.close: 94 - l.emptyQueue() 95 - l.closeLogger() 96 - return 97 - } 98 - } 99 - } 100 - 101 - // LogEvent logs an event to this ChannelledLog 102 - func (l *ChannelledLog) LogEvent(event *Event) error { 103 - select { 104 - case l.queue <- event: 105 - return nil 106 - case <-time.After(60 * time.Second): 107 - // We're blocked! 108 - return ErrTimeout{ 109 - Name: l.name, 110 - Provider: l.provider, 111 - } 112 - } 113 - } 114 - 115 - func (l *ChannelledLog) emptyQueue() bool { 116 - for { 117 - select { 118 - case event, ok := <-l.queue: 119 - if !ok { 120 - return false 121 - } 122 - l.loggerProvider.LogEvent(event) //nolint:errcheck 123 - default: 124 - return true 125 - } 126 - } 127 - } 128 - 129 - func (l *ChannelledLog) closeLogger() { 130 - l.loggerProvider.Flush() 131 - l.loggerProvider.Close() 132 - l.closed <- true 133 - } 134 - 135 - // Close this ChannelledLog 136 - func (l *ChannelledLog) Close() { 137 - l.close <- true 138 - <-l.closed 139 - } 140 - 141 - // Flush this ChannelledLog 142 - func (l *ChannelledLog) Flush() { 143 - l.flush <- true 144 - } 145 - 146 - // ReleaseReopen this ChannelledLog 147 - func (l *ChannelledLog) ReleaseReopen() error { 148 - return l.loggerProvider.ReleaseReopen() 149 - } 150 - 151 - // GetLevel gets the level of this ChannelledLog 152 - func (l *ChannelledLog) GetLevel() Level { 153 - return l.loggerProvider.GetLevel() 154 - } 155 - 156 - // GetStacktraceLevel gets the level of this ChannelledLog 157 - func (l *ChannelledLog) GetStacktraceLevel() Level { 158 - return l.loggerProvider.GetStacktraceLevel() 159 - } 160 - 161 - // GetName returns the name of this ChannelledLog 162 - func (l *ChannelledLog) GetName() string { 163 - return l.name 164 - } 165 - 166 - // MultiChannelledLog represents a cached channel to a LoggerProvider 167 - type MultiChannelledLog struct { 168 - ctx context.Context 169 - finished context.CancelFunc 170 - name string 171 - bufferLength int64 172 - queue chan *Event 173 - rwmutex sync.RWMutex 174 - loggers map[string]EventLogger 175 - flush chan bool 176 - close chan bool 177 - started bool 178 - level Level 179 - stacktraceLevel Level 180 - closed chan bool 181 - paused chan bool 182 - } 183 - 184 - // NewMultiChannelledLog a new logger instance with given logger provider and config. 185 - func NewMultiChannelledLog(name string, bufferLength int64) *MultiChannelledLog { 186 - ctx, _, finished := process.GetManager().AddTypedContext(context.Background(), fmt.Sprintf("Logger: %s", name), process.SystemProcessType, false) 187 - 188 - m := &MultiChannelledLog{ 189 - ctx: ctx, 190 - finished: finished, 191 - name: name, 192 - queue: make(chan *Event, bufferLength), 193 - flush: make(chan bool), 194 - bufferLength: bufferLength, 195 - loggers: make(map[string]EventLogger), 196 - level: NONE, 197 - stacktraceLevel: NONE, 198 - close: make(chan bool), 199 - closed: make(chan bool), 200 - paused: make(chan bool), 201 - } 202 - return m 203 - } 204 - 205 - // AddLogger adds a logger to this MultiChannelledLog 206 - func (m *MultiChannelledLog) AddLogger(logger EventLogger) error { 207 - m.rwmutex.Lock() 208 - name := logger.GetName() 209 - if _, has := m.loggers[name]; has { 210 - m.rwmutex.Unlock() 211 - return ErrDuplicateName{name} 212 - } 213 - m.loggers[name] = logger 214 - if logger.GetLevel() < m.level { 215 - m.level = logger.GetLevel() 216 - } 217 - if logger.GetStacktraceLevel() < m.stacktraceLevel { 218 - m.stacktraceLevel = logger.GetStacktraceLevel() 219 - } 220 - m.rwmutex.Unlock() 221 - go m.Start() 222 - return nil 223 - } 224 - 225 - // DelLogger removes a sub logger from this MultiChannelledLog 226 - // NB: If you delete the last sublogger this logger will simply drop 227 - // log events 228 - func (m *MultiChannelledLog) DelLogger(name string) bool { 229 - m.rwmutex.Lock() 230 - logger, has := m.loggers[name] 231 - if !has { 232 - m.rwmutex.Unlock() 233 - return false 234 - } 235 - delete(m.loggers, name) 236 - m.internalResetLevel() 237 - m.rwmutex.Unlock() 238 - logger.Flush() 239 - logger.Close() 240 - return true 241 - } 242 - 243 - // GetEventLogger returns a sub logger from this MultiChannelledLog 244 - func (m *MultiChannelledLog) GetEventLogger(name string) EventLogger { 245 - m.rwmutex.RLock() 246 - defer m.rwmutex.RUnlock() 247 - return m.loggers[name] 248 - } 249 - 250 - // GetEventLoggerNames returns a list of names 251 - func (m *MultiChannelledLog) GetEventLoggerNames() []string { 252 - m.rwmutex.RLock() 253 - defer m.rwmutex.RUnlock() 254 - var keys []string 255 - for k := range m.loggers { 256 - keys = append(keys, k) 257 - } 258 - return keys 259 - } 260 - 261 - func (m *MultiChannelledLog) closeLoggers() { 262 - m.rwmutex.Lock() 263 - for _, logger := range m.loggers { 264 - logger.Flush() 265 - logger.Close() 266 - } 267 - m.rwmutex.Unlock() 268 - m.closed <- true 269 - } 270 - 271 - // Pause pauses this Logger 272 - func (m *MultiChannelledLog) Pause() { 273 - m.paused <- true 274 - } 275 - 276 - // Resume resumes this Logger 277 - func (m *MultiChannelledLog) Resume() { 278 - m.paused <- false 279 - } 280 - 281 - // ReleaseReopen causes this logger to tell its subloggers to release and reopen 282 - func (m *MultiChannelledLog) ReleaseReopen() error { 283 - m.rwmutex.Lock() 284 - defer m.rwmutex.Unlock() 285 - var accumulatedErr error 286 - for _, logger := range m.loggers { 287 - if err := logger.ReleaseReopen(); err != nil { 288 - if accumulatedErr == nil { 289 - accumulatedErr = fmt.Errorf("Error whilst reopening: %s Error: %w", logger.GetName(), err) 290 - } else { 291 - accumulatedErr = fmt.Errorf("Error whilst reopening: %s Error: %v & %w", logger.GetName(), err, accumulatedErr) 292 - } 293 - } 294 - } 295 - return accumulatedErr 296 - } 297 - 298 - // Start processing the MultiChannelledLog 299 - func (m *MultiChannelledLog) Start() { 300 - m.rwmutex.Lock() 301 - if m.started { 302 - m.rwmutex.Unlock() 303 - return 304 - } 305 - pprof.SetGoroutineLabels(m.ctx) 306 - defer m.finished() 307 - 308 - m.started = true 309 - m.rwmutex.Unlock() 310 - paused := false 311 - for { 312 - if paused { 313 - select { 314 - case paused = <-m.paused: 315 - if !paused { 316 - m.ResetLevel() 317 - } 318 - case _, ok := <-m.flush: 319 - if !ok { 320 - m.closeLoggers() 321 - return 322 - } 323 - m.rwmutex.RLock() 324 - for _, logger := range m.loggers { 325 - logger.Flush() 326 - } 327 - m.rwmutex.RUnlock() 328 - case <-m.close: 329 - m.closeLoggers() 330 - return 331 - } 332 - continue 333 - } 334 - select { 335 - case paused = <-m.paused: 336 - if paused && m.level < INFO { 337 - m.level = INFO 338 - } 339 - case event, ok := <-m.queue: 340 - if !ok { 341 - m.closeLoggers() 342 - return 343 - } 344 - m.rwmutex.RLock() 345 - for _, logger := range m.loggers { 346 - err := logger.LogEvent(event) 347 - if err != nil { 348 - fmt.Println(err) //nolint:forbidigo 349 - } 350 - } 351 - m.rwmutex.RUnlock() 352 - case _, ok := <-m.flush: 353 - if !ok { 354 - m.closeLoggers() 355 - return 356 - } 357 - m.emptyQueue() 358 - m.rwmutex.RLock() 359 - for _, logger := range m.loggers { 360 - logger.Flush() 361 - } 362 - m.rwmutex.RUnlock() 363 - case <-m.close: 364 - m.emptyQueue() 365 - m.closeLoggers() 366 - return 367 - } 368 - } 369 - } 370 - 371 - func (m *MultiChannelledLog) emptyQueue() bool { 372 - for { 373 - select { 374 - case event, ok := <-m.queue: 375 - if !ok { 376 - return false 377 - } 378 - m.rwmutex.RLock() 379 - for _, logger := range m.loggers { 380 - err := logger.LogEvent(event) 381 - if err != nil { 382 - fmt.Println(err) //nolint:forbidigo 383 - } 384 - } 385 - m.rwmutex.RUnlock() 386 - default: 387 - return true 388 - } 389 - } 390 - } 391 - 392 - // LogEvent logs an event to this MultiChannelledLog 393 - func (m *MultiChannelledLog) LogEvent(event *Event) error { 394 - select { 395 - case m.queue <- event: 396 - return nil 397 - case <-time.After(100 * time.Millisecond): 398 - // We're blocked! 399 - return ErrTimeout{ 400 - Name: m.name, 401 - Provider: "MultiChannelledLog", 402 - } 403 - } 404 - } 405 - 406 - // Close this MultiChannelledLog 407 - func (m *MultiChannelledLog) Close() { 408 - m.close <- true 409 - <-m.closed 410 - } 411 - 412 - // Flush this ChannelledLog 413 - func (m *MultiChannelledLog) Flush() { 414 - m.flush <- true 415 - } 416 - 417 - // GetLevel gets the level of this MultiChannelledLog 418 - func (m *MultiChannelledLog) GetLevel() Level { 419 - m.rwmutex.RLock() 420 - defer m.rwmutex.RUnlock() 421 - return m.level 422 - } 423 - 424 - // GetStacktraceLevel gets the level of this MultiChannelledLog 425 - func (m *MultiChannelledLog) GetStacktraceLevel() Level { 426 - m.rwmutex.RLock() 427 - defer m.rwmutex.RUnlock() 428 - return m.stacktraceLevel 429 - } 430 - 431 - func (m *MultiChannelledLog) internalResetLevel() Level { 432 - m.level = NONE 433 - for _, logger := range m.loggers { 434 - level := logger.GetLevel() 435 - if level < m.level { 436 - m.level = level 437 - } 438 - level = logger.GetStacktraceLevel() 439 - if level < m.stacktraceLevel { 440 - m.stacktraceLevel = level 441 - } 442 - } 443 - return m.level 444 - } 445 - 446 - // ResetLevel will reset the level of this MultiChannelledLog 447 - func (m *MultiChannelledLog) ResetLevel() Level { 448 - m.rwmutex.Lock() 449 - defer m.rwmutex.Unlock() 450 - return m.internalResetLevel() 451 - } 452 - 453 - // GetName gets the name of this MultiChannelledLog 454 - func (m *MultiChannelledLog) GetName() string { 455 - return m.name 456 - } 457 - 458 - func (e *Event) GetMsg() string { 459 - return e.msg 460 - }
+246
modules/log/event_format.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "bytes" 8 + "fmt" 9 + "strings" 10 + "time" 11 + ) 12 + 13 + type Event struct { 14 + Time time.Time 15 + 16 + GoroutinePid string 17 + Caller string 18 + Filename string 19 + Line int 20 + 21 + Level Level 22 + 23 + MsgSimpleText string 24 + 25 + msgFormat string // the format and args is only valid in the caller's goroutine 26 + msgArgs []any // they are discarded before the event is passed to the writer's channel 27 + 28 + Stacktrace string 29 + } 30 + 31 + type EventFormatted struct { 32 + Origin *Event 33 + Msg any // the message formatted by the writer's formatter, the writer knows its type 34 + } 35 + 36 + type EventFormatter func(mode *WriterMode, event *Event, msgFormat string, msgArgs ...any) []byte 37 + 38 + type logStringFormatter struct { 39 + v LogStringer 40 + } 41 + 42 + var _ fmt.Formatter = logStringFormatter{} 43 + 44 + func (l logStringFormatter) Format(f fmt.State, verb rune) { 45 + if f.Flag('#') && verb == 'v' { 46 + _, _ = fmt.Fprintf(f, "%#v", l.v) 47 + return 48 + } 49 + _, _ = f.Write([]byte(l.v.LogString())) 50 + } 51 + 52 + // Copy of cheap integer to fixed-width decimal to ascii from logger. 53 + // TODO: legacy bugs: doesn't support negative number, overflow if wid it too large. 54 + func itoa(buf []byte, i, wid int) []byte { 55 + var s [20]byte 56 + bp := len(s) - 1 57 + for i >= 10 || wid > 1 { 58 + wid-- 59 + q := i / 10 60 + s[bp] = byte('0' + i - q*10) 61 + bp-- 62 + i = q 63 + } 64 + // i < 10 65 + s[bp] = byte('0' + i) 66 + return append(buf, s[bp:]...) 67 + } 68 + 69 + func colorSprintf(colorize bool, format string, args ...any) string { 70 + hasColorValue := false 71 + for _, v := range args { 72 + if _, hasColorValue = v.(*ColoredValue); hasColorValue { 73 + break 74 + } 75 + } 76 + if colorize || !hasColorValue { 77 + return fmt.Sprintf(format, args...) 78 + } 79 + 80 + noColors := make([]any, len(args)) 81 + copy(noColors, args) 82 + for i, v := range args { 83 + if cv, ok := v.(*ColoredValue); ok { 84 + noColors[i] = cv.v 85 + } 86 + } 87 + return fmt.Sprintf(format, noColors...) 88 + } 89 + 90 + // EventFormatTextMessage makes the log message for a writer with its mode. This function is a copy of the original package 91 + func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, msgArgs ...any) []byte { 92 + buf := make([]byte, 0, 1024) 93 + buf = append(buf, mode.Prefix...) 94 + t := event.Time 95 + flags := mode.Flags.Bits() 96 + if flags&(Ldate|Ltime|Lmicroseconds) != 0 { 97 + if mode.Colorize { 98 + buf = append(buf, fgCyanBytes...) 99 + } 100 + if flags&LUTC != 0 { 101 + t = t.UTC() 102 + } 103 + if flags&Ldate != 0 { 104 + year, month, day := t.Date() 105 + buf = itoa(buf, year, 4) 106 + buf = append(buf, '/') 107 + buf = itoa(buf, int(month), 2) 108 + buf = append(buf, '/') 109 + buf = itoa(buf, day, 2) 110 + buf = append(buf, ' ') 111 + } 112 + if flags&(Ltime|Lmicroseconds) != 0 { 113 + hour, min, sec := t.Clock() 114 + buf = itoa(buf, hour, 2) 115 + buf = append(buf, ':') 116 + buf = itoa(buf, min, 2) 117 + buf = append(buf, ':') 118 + buf = itoa(buf, sec, 2) 119 + if flags&Lmicroseconds != 0 { 120 + buf = append(buf, '.') 121 + buf = itoa(buf, t.Nanosecond()/1e3, 6) 122 + } 123 + buf = append(buf, ' ') 124 + } 125 + if mode.Colorize { 126 + buf = append(buf, resetBytes...) 127 + } 128 + 129 + } 130 + if flags&(Lshortfile|Llongfile) != 0 { 131 + if mode.Colorize { 132 + buf = append(buf, fgGreenBytes...) 133 + } 134 + file := event.Filename 135 + if flags&Lmedfile == Lmedfile { 136 + startIndex := len(file) - 20 137 + if startIndex > 0 { 138 + file = "..." + file[startIndex:] 139 + } 140 + } else if flags&Lshortfile != 0 { 141 + startIndex := strings.LastIndexByte(file, '/') 142 + if startIndex > 0 && startIndex < len(file) { 143 + file = file[startIndex+1:] 144 + } 145 + } 146 + buf = append(buf, file...) 147 + buf = append(buf, ':') 148 + buf = itoa(buf, event.Line, -1) 149 + if flags&(Lfuncname|Lshortfuncname) != 0 { 150 + buf = append(buf, ':') 151 + } else { 152 + if mode.Colorize { 153 + buf = append(buf, resetBytes...) 154 + } 155 + buf = append(buf, ' ') 156 + } 157 + } 158 + if flags&(Lfuncname|Lshortfuncname) != 0 { 159 + if mode.Colorize { 160 + buf = append(buf, fgGreenBytes...) 161 + } 162 + funcname := event.Caller 163 + if flags&Lshortfuncname != 0 { 164 + lastIndex := strings.LastIndexByte(funcname, '.') 165 + if lastIndex > 0 && len(funcname) > lastIndex+1 { 166 + funcname = funcname[lastIndex+1:] 167 + } 168 + } 169 + buf = append(buf, funcname...) 170 + if mode.Colorize { 171 + buf = append(buf, resetBytes...) 172 + } 173 + buf = append(buf, ' ') 174 + } 175 + 176 + if flags&(Llevel|Llevelinitial) != 0 { 177 + level := strings.ToUpper(event.Level.String()) 178 + if mode.Colorize { 179 + buf = append(buf, ColorBytes(levelToColor[event.Level]...)...) 180 + } 181 + buf = append(buf, '[') 182 + if flags&Llevelinitial != 0 { 183 + buf = append(buf, level[0]) 184 + } else { 185 + buf = append(buf, level...) 186 + } 187 + buf = append(buf, ']') 188 + if mode.Colorize { 189 + buf = append(buf, resetBytes...) 190 + } 191 + buf = append(buf, ' ') 192 + } 193 + 194 + var msg []byte 195 + 196 + // if the log needs colorizing, do it 197 + if mode.Colorize && len(msgArgs) > 0 { 198 + hasColorValue := false 199 + for _, v := range msgArgs { 200 + if _, hasColorValue = v.(*ColoredValue); hasColorValue { 201 + break 202 + } 203 + } 204 + if hasColorValue { 205 + msg = []byte(fmt.Sprintf(msgFormat, msgArgs...)) 206 + } 207 + } 208 + // try to re-use the pre-formatted simple text message 209 + if len(msg) == 0 { 210 + msg = []byte(event.MsgSimpleText) 211 + } 212 + // if still no message, do the normal Sprintf for the message 213 + if len(msg) == 0 { 214 + msg = []byte(colorSprintf(mode.Colorize, msgFormat, msgArgs...)) 215 + } 216 + // remove at most one trailing new line 217 + if len(msg) > 0 && msg[len(msg)-1] == '\n' { 218 + msg = msg[:len(msg)-1] 219 + } 220 + 221 + if flags&Lgopid == Lgopid { 222 + if event.GoroutinePid != "" { 223 + buf = append(buf, '[') 224 + if mode.Colorize { 225 + buf = append(buf, ColorBytes(FgHiYellow)...) 226 + } 227 + buf = append(buf, event.GoroutinePid...) 228 + if mode.Colorize { 229 + buf = append(buf, resetBytes...) 230 + } 231 + buf = append(buf, ']', ' ') 232 + } 233 + } 234 + buf = append(buf, msg...) 235 + 236 + if event.Stacktrace != "" && mode.StacktraceLevel <= event.Level { 237 + lines := bytes.Split([]byte(event.Stacktrace), []byte("\n")) 238 + for _, line := range lines { 239 + buf = append(buf, "\n\t"...) 240 + buf = append(buf, line...) 241 + } 242 + buf = append(buf, '\n') 243 + } 244 + buf = append(buf, '\n') 245 + return buf 246 + }
+57
modules/log/event_format_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "testing" 8 + "time" 9 + 10 + "github.com/stretchr/testify/assert" 11 + ) 12 + 13 + func TestItoa(t *testing.T) { 14 + b := itoa(nil, 0, 0) 15 + assert.Equal(t, "0", string(b)) 16 + 17 + b = itoa(nil, 0, 1) 18 + assert.Equal(t, "0", string(b)) 19 + 20 + b = itoa(nil, 0, 2) 21 + assert.Equal(t, "00", string(b)) 22 + } 23 + 24 + func TestEventFormatTextMessage(t *testing.T) { 25 + res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: false, Flags: Flags{defined: true, flags: 0xffffffff}}, 26 + &Event{ 27 + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), 28 + Caller: "caller", 29 + Filename: "filename", 30 + Line: 123, 31 + GoroutinePid: "pid", 32 + Level: ERROR, 33 + Stacktrace: "stacktrace", 34 + }, 35 + "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), 36 + ) 37 + 38 + assert.Equal(t, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [pid] msg format: arg0 arg1 39 + stacktrace 40 + 41 + `, string(res)) 42 + 43 + res = EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: true, Flags: Flags{defined: true, flags: 0xffffffff}}, 44 + &Event{ 45 + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), 46 + Caller: "caller", 47 + Filename: "filename", 48 + Line: 123, 49 + GoroutinePid: "pid", 50 + Level: ERROR, 51 + Stacktrace: "stacktrace", 52 + }, 53 + "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), 54 + ) 55 + 56 + assert.Equal(t, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mpid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res)) 57 + }
+54
modules/log/event_writer.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "fmt" 8 + ) 9 + 10 + // EventWriter is the general interface for all event writers 11 + // EventWriterBase is only used as its base interface 12 + // A writer implementation could override the default EventWriterBase functions 13 + // eg: a writer can override the Run to handle events in its own way with its own goroutine 14 + type EventWriter interface { 15 + EventWriterBase 16 + } 17 + 18 + // WriterMode is the mode for creating a new EventWriter, it contains common options for all writers 19 + // Its WriterOption field is the specified options for a writer, it should be passed by value but not by pointer 20 + type WriterMode struct { 21 + BufferLen int 22 + 23 + Level Level 24 + Prefix string 25 + Colorize bool 26 + Flags Flags 27 + 28 + Expression string 29 + 30 + StacktraceLevel Level 31 + 32 + WriterOption any 33 + } 34 + 35 + // EventWriterProvider is the function for creating a new EventWriter 36 + type EventWriterProvider func(writerName string, writerMode WriterMode) EventWriter 37 + 38 + var eventWriterProviders = map[string]EventWriterProvider{} 39 + 40 + func RegisterEventWriter(writerType string, p EventWriterProvider) { 41 + eventWriterProviders[writerType] = p 42 + } 43 + 44 + func HasEventWriter(writerType string) bool { 45 + _, ok := eventWriterProviders[writerType] 46 + return ok 47 + } 48 + 49 + func NewEventWriter(name, writerType string, mode WriterMode) (EventWriter, error) { 50 + if p, ok := eventWriterProviders[writerType]; ok { 51 + return p(name, mode), nil 52 + } 53 + return nil, fmt.Errorf("unknown event writer type %q for writer %q", writerType, name) 54 + }
+160
modules/log/event_writer_base.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "context" 8 + "fmt" 9 + "io" 10 + "regexp" 11 + "time" 12 + ) 13 + 14 + // EventWriterBase is the base interface for most event writers 15 + // It provides default implementations for most methods 16 + type EventWriterBase interface { 17 + Base() *EventWriterBaseImpl 18 + GetWriterType() string 19 + GetWriterName() string 20 + GetLevel() Level 21 + 22 + Run(ctx context.Context) 23 + } 24 + 25 + type EventWriterBaseImpl struct { 26 + writerType string 27 + 28 + Name string 29 + Mode *WriterMode 30 + Queue chan *EventFormatted 31 + 32 + FormatMessage EventFormatter // format the Event to a message and write it to output 33 + OutputWriteCloser io.WriteCloser // it will be closed when the event writer is stopped 34 + GetPauseChan func() chan struct{} 35 + 36 + shared bool 37 + stopped chan struct{} 38 + } 39 + 40 + var _ EventWriterBase = (*EventWriterBaseImpl)(nil) 41 + 42 + func (b *EventWriterBaseImpl) Base() *EventWriterBaseImpl { 43 + return b 44 + } 45 + 46 + func (b *EventWriterBaseImpl) GetWriterType() string { 47 + return b.writerType 48 + } 49 + 50 + func (b *EventWriterBaseImpl) GetWriterName() string { 51 + return b.Name 52 + } 53 + 54 + func (b *EventWriterBaseImpl) GetLevel() Level { 55 + return b.Mode.Level 56 + } 57 + 58 + // Run is the default implementation for EventWriter.Run 59 + func (b *EventWriterBaseImpl) Run(ctx context.Context) { 60 + defer b.OutputWriteCloser.Close() 61 + 62 + var exprRegexp *regexp.Regexp 63 + if b.Mode.Expression != "" { 64 + var err error 65 + if exprRegexp, err = regexp.Compile(b.Mode.Expression); err != nil { 66 + FallbackErrorf("unable to compile expression %q for writer %q: %v", b.Mode.Expression, b.Name, err) 67 + } 68 + } 69 + 70 + for { 71 + if b.GetPauseChan != nil { 72 + pause := b.GetPauseChan() 73 + if pause != nil { 74 + select { 75 + case <-pause: 76 + case <-ctx.Done(): 77 + return 78 + } 79 + } 80 + } 81 + 82 + select { 83 + case <-ctx.Done(): 84 + return 85 + case event, ok := <-b.Queue: 86 + if !ok { 87 + return 88 + } 89 + 90 + if exprRegexp != nil { 91 + fileLineCaller := fmt.Sprintf("%s:%d:%s", event.Origin.Filename, event.Origin.Line, event.Origin.Caller) 92 + matched := exprRegexp.Match([]byte(fileLineCaller)) || exprRegexp.Match([]byte(event.Origin.MsgSimpleText)) 93 + if !matched { 94 + continue 95 + } 96 + } 97 + 98 + var err error 99 + switch msg := event.Msg.(type) { 100 + case string: 101 + _, err = b.OutputWriteCloser.Write([]byte(msg)) 102 + case []byte: 103 + _, err = b.OutputWriteCloser.Write(msg) 104 + case io.WriterTo: 105 + _, err = msg.WriteTo(b.OutputWriteCloser) 106 + default: 107 + _, err = b.OutputWriteCloser.Write([]byte(fmt.Sprint(msg))) 108 + } 109 + if err != nil { 110 + FallbackErrorf("unable to write log message of %q (%v): %v", b.Name, err, event.Msg) 111 + } 112 + } 113 + } 114 + } 115 + 116 + func NewEventWriterBase(name, writerType string, mode WriterMode) *EventWriterBaseImpl { 117 + if mode.BufferLen == 0 { 118 + mode.BufferLen = 1000 119 + } 120 + if mode.Level == UNDEFINED { 121 + mode.Level = INFO 122 + } 123 + if mode.StacktraceLevel == UNDEFINED { 124 + mode.StacktraceLevel = NONE 125 + } 126 + b := &EventWriterBaseImpl{ 127 + writerType: writerType, 128 + 129 + Name: name, 130 + Mode: &mode, 131 + Queue: make(chan *EventFormatted, mode.BufferLen), 132 + 133 + GetPauseChan: GetManager().GetPauseChan, // by default, use the global pause channel 134 + FormatMessage: EventFormatTextMessage, 135 + } 136 + return b 137 + } 138 + 139 + // eventWriterStartGo use "go" to start an event worker's Run method 140 + func eventWriterStartGo(ctx context.Context, w EventWriter, shared bool) { 141 + if w.Base().stopped != nil { 142 + return // already started 143 + } 144 + w.Base().shared = shared 145 + w.Base().stopped = make(chan struct{}) 146 + go func() { 147 + defer close(w.Base().stopped) 148 + w.Run(ctx) 149 + }() 150 + } 151 + 152 + // eventWriterStopWait stops an event writer and waits for it to finish flushing (with a timeout) 153 + func eventWriterStopWait(w EventWriter) { 154 + close(w.Base().Queue) 155 + select { 156 + case <-w.Base().stopped: 157 + case <-time.After(2 * time.Second): 158 + FallbackErrorf("unable to stop log writer %q in time, skip", w.GetWriterName()) 159 + } 160 + }
+111
modules/log/event_writer_conn.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "io" 8 + "net" 9 + ) 10 + 11 + type WriterConnOption struct { 12 + Addr string 13 + Protocol string 14 + Reconnect bool 15 + ReconnectOnMsg bool 16 + } 17 + 18 + type eventWriterConn struct { 19 + *EventWriterBaseImpl 20 + connWriter connWriter 21 + } 22 + 23 + var _ EventWriter = (*eventWriterConn)(nil) 24 + 25 + func NewEventWriterConn(writerName string, writerMode WriterMode) EventWriter { 26 + w := &eventWriterConn{EventWriterBaseImpl: NewEventWriterBase(writerName, "conn", writerMode)} 27 + opt := writerMode.WriterOption.(WriterConnOption) 28 + w.connWriter = connWriter{ 29 + ReconnectOnMsg: opt.ReconnectOnMsg, 30 + Reconnect: opt.Reconnect, 31 + Net: opt.Protocol, 32 + Addr: opt.Addr, 33 + } 34 + w.OutputWriteCloser = &w.connWriter 35 + return w 36 + } 37 + 38 + func init() { 39 + RegisterEventWriter("conn", NewEventWriterConn) 40 + } 41 + 42 + // below is copied from old code 43 + 44 + type connWriter struct { 45 + innerWriter io.WriteCloser 46 + 47 + ReconnectOnMsg bool 48 + Reconnect bool 49 + Net string `json:"net"` 50 + Addr string `json:"addr"` 51 + } 52 + 53 + var _ io.WriteCloser = (*connWriter)(nil) 54 + 55 + // Close the inner writer 56 + func (i *connWriter) Close() error { 57 + if i.innerWriter != nil { 58 + return i.innerWriter.Close() 59 + } 60 + return nil 61 + } 62 + 63 + // Write the data to the connection 64 + func (i *connWriter) Write(p []byte) (int, error) { 65 + if i.neededConnectOnMsg() { 66 + if err := i.connect(); err != nil { 67 + return 0, err 68 + } 69 + } 70 + 71 + if i.ReconnectOnMsg { 72 + defer i.innerWriter.Close() 73 + } 74 + 75 + return i.innerWriter.Write(p) 76 + } 77 + 78 + func (i *connWriter) neededConnectOnMsg() bool { 79 + if i.Reconnect { 80 + i.Reconnect = false 81 + return true 82 + } 83 + 84 + if i.innerWriter == nil { 85 + return true 86 + } 87 + 88 + return i.ReconnectOnMsg 89 + } 90 + 91 + func (i *connWriter) connect() error { 92 + if i.innerWriter != nil { 93 + _ = i.innerWriter.Close() 94 + i.innerWriter = nil 95 + } 96 + 97 + conn, err := net.Dial(i.Net, i.Addr) 98 + if err != nil { 99 + return err 100 + } 101 + 102 + if tcpConn, ok := conn.(*net.TCPConn); ok { 103 + err = tcpConn.SetKeepAlive(true) 104 + if err != nil { 105 + return err 106 + } 107 + } 108 + 109 + i.innerWriter = conn 110 + return nil 111 + }
+75
modules/log/event_writer_conn_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "context" 8 + "fmt" 9 + "io" 10 + "net" 11 + "strings" 12 + "sync" 13 + "testing" 14 + "time" 15 + 16 + "github.com/stretchr/testify/assert" 17 + ) 18 + 19 + func listenReadAndClose(t *testing.T, l net.Listener, expected string) { 20 + conn, err := l.Accept() 21 + assert.NoError(t, err) 22 + defer conn.Close() 23 + written, err := io.ReadAll(conn) 24 + 25 + assert.NoError(t, err) 26 + assert.Equal(t, expected, string(written)) 27 + } 28 + 29 + func TestConnLogger(t *testing.T) { 30 + protocol := "tcp" 31 + address := ":3099" 32 + 33 + l, err := net.Listen(protocol, address) 34 + if err != nil { 35 + t.Fatal(err) 36 + } 37 + defer l.Close() 38 + 39 + prefix := "TestPrefix " 40 + level := INFO 41 + flags := LstdFlags | LUTC | Lfuncname 42 + 43 + logger := NewLoggerWithWriters(context.Background(), NewEventWriterConn("test-conn", WriterMode{ 44 + Level: level, 45 + Prefix: prefix, 46 + Flags: FlagsFromBits(flags), 47 + WriterOption: WriterConnOption{Addr: address, Protocol: protocol, Reconnect: true, ReconnectOnMsg: true}, 48 + })) 49 + 50 + location, _ := time.LoadLocation("EST") 51 + 52 + date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 53 + 54 + dateString := date.UTC().Format("2006/01/02 15:04:05") 55 + 56 + event := Event{ 57 + Level: INFO, 58 + MsgSimpleText: "TEST MSG", 59 + Caller: "CALLER", 60 + Filename: "FULL/FILENAME", 61 + Line: 1, 62 + Time: date, 63 + } 64 + expected := fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.Filename, event.Line, event.Caller, strings.ToUpper(event.Level.String())[0], event.MsgSimpleText) 65 + var wg sync.WaitGroup 66 + wg.Add(1) 67 + go func() { 68 + defer wg.Done() 69 + listenReadAndClose(t, l, expected) 70 + }() 71 + logger.SendLogEvent(&event) 72 + wg.Wait() 73 + 74 + logger.Close() 75 + }
+40
modules/log/event_writer_console.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "io" 8 + "os" 9 + ) 10 + 11 + type WriterConsoleOption struct { 12 + Stderr bool 13 + } 14 + 15 + type eventWriterConsole struct { 16 + *EventWriterBaseImpl 17 + } 18 + 19 + var _ EventWriter = (*eventWriterConsole)(nil) 20 + 21 + type nopCloser struct { 22 + io.Writer 23 + } 24 + 25 + func (nopCloser) Close() error { return nil } 26 + 27 + func NewEventWriterConsole(name string, mode WriterMode) EventWriter { 28 + w := &eventWriterConsole{EventWriterBaseImpl: NewEventWriterBase(name, "console", mode)} 29 + opt := mode.WriterOption.(WriterConsoleOption) 30 + if opt.Stderr { 31 + w.OutputWriteCloser = nopCloser{os.Stderr} 32 + } else { 33 + w.OutputWriteCloser = nopCloser{os.Stdout} 34 + } 35 + return w 36 + } 37 + 38 + func init() { 39 + RegisterEventWriter("console", NewEventWriterConsole) 40 + }
+48
modules/log/event_writer_file.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "code.gitea.io/gitea/modules/util/rotatingfilewriter" 8 + ) 9 + 10 + type WriterFileOption struct { 11 + FileName string 12 + MaxSize int64 13 + LogRotate bool 14 + DailyRotate bool 15 + MaxDays int 16 + Compress bool 17 + CompressionLevel int 18 + } 19 + 20 + type eventWriterFile struct { 21 + *EventWriterBaseImpl 22 + fileWriter *rotatingfilewriter.RotatingFileWriter 23 + } 24 + 25 + var _ EventWriter = (*eventWriterFile)(nil) 26 + 27 + func NewEventWriterFile(name string, mode WriterMode) EventWriter { 28 + w := &eventWriterFile{EventWriterBaseImpl: NewEventWriterBase(name, "file", mode)} 29 + opt := mode.WriterOption.(WriterFileOption) 30 + var err error 31 + w.fileWriter, err = rotatingfilewriter.Open(opt.FileName, &rotatingfilewriter.Options{ 32 + Rotate: opt.LogRotate, 33 + MaximumSize: opt.MaxSize, 34 + RotateDaily: opt.DailyRotate, 35 + KeepDays: opt.MaxDays, 36 + Compress: opt.Compress, 37 + CompressionLevel: opt.CompressionLevel, 38 + }) 39 + if err != nil { 40 + FallbackErrorf("unable to open log file %q: %v", opt.FileName, err) 41 + } 42 + w.OutputWriteCloser = w.fileWriter 43 + return w 44 + } 45 + 46 + func init() { 47 + RegisterEventWriter("file", NewEventWriterFile) 48 + }
-283
modules/log/file.go
··· 1 - // Copyright 2014 The Gogs Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "bufio" 8 - "compress/gzip" 9 - "errors" 10 - "fmt" 11 - "os" 12 - "path/filepath" 13 - "strings" 14 - "sync" 15 - "time" 16 - 17 - "code.gitea.io/gitea/modules/json" 18 - "code.gitea.io/gitea/modules/util" 19 - ) 20 - 21 - // FileLogger implements LoggerProvider. 22 - // It writes messages by lines limit, file size limit, or time frequency. 23 - type FileLogger struct { 24 - WriterLogger 25 - mw *MuxWriter 26 - // The opened file 27 - Filename string `json:"filename"` 28 - 29 - // Rotate at size 30 - Maxsize int `json:"maxsize"` 31 - maxsizeCursize int 32 - 33 - // Rotate daily 34 - Daily bool `json:"daily"` 35 - Maxdays int64 `json:"maxdays"` 36 - dailyOpenDate int 37 - 38 - Rotate bool `json:"rotate"` 39 - 40 - Compress bool `json:"compress"` 41 - CompressionLevel int `json:"compressionLevel"` 42 - 43 - startLock sync.Mutex // Only one log can write to the file 44 - } 45 - 46 - // MuxWriter an *os.File writer with locker. 47 - type MuxWriter struct { 48 - mu sync.Mutex 49 - fd *os.File 50 - owner *FileLogger 51 - } 52 - 53 - // Write writes to os.File. 54 - func (mw *MuxWriter) Write(b []byte) (int, error) { 55 - mw.mu.Lock() 56 - defer mw.mu.Unlock() 57 - mw.owner.docheck(len(b)) 58 - return mw.fd.Write(b) 59 - } 60 - 61 - // Close the internal writer 62 - func (mw *MuxWriter) Close() error { 63 - return mw.fd.Close() 64 - } 65 - 66 - // SetFd sets os.File in writer. 67 - func (mw *MuxWriter) SetFd(fd *os.File) { 68 - if mw.fd != nil { 69 - mw.fd.Close() 70 - } 71 - mw.fd = fd 72 - } 73 - 74 - // NewFileLogger create a FileLogger returning as LoggerProvider. 75 - func NewFileLogger() LoggerProvider { 76 - log := &FileLogger{ 77 - Filename: "", 78 - Maxsize: 1 << 28, // 256 MB 79 - Daily: true, 80 - Maxdays: 7, 81 - Rotate: true, 82 - Compress: true, 83 - CompressionLevel: gzip.DefaultCompression, 84 - } 85 - log.Level = TRACE 86 - // use MuxWriter instead direct use os.File for lock write when rotate 87 - log.mw = new(MuxWriter) 88 - log.mw.owner = log 89 - 90 - return log 91 - } 92 - 93 - // Init file logger with json config. 94 - // config like: 95 - // 96 - // { 97 - // "filename":"log/gogs.log", 98 - // "maxsize":1<<30, 99 - // "daily":true, 100 - // "maxdays":15, 101 - // "rotate":true 102 - // } 103 - func (log *FileLogger) Init(config string) error { 104 - if err := json.Unmarshal([]byte(config), log); err != nil { 105 - return fmt.Errorf("Unable to parse JSON: %w", err) 106 - } 107 - if len(log.Filename) == 0 { 108 - return errors.New("config must have filename") 109 - } 110 - // set MuxWriter as Logger's io.Writer 111 - log.NewWriterLogger(log.mw) 112 - return log.StartLogger() 113 - } 114 - 115 - // StartLogger start file logger. create log file and set to locker-inside file writer. 116 - func (log *FileLogger) StartLogger() error { 117 - fd, err := log.createLogFile() 118 - if err != nil { 119 - return err 120 - } 121 - log.mw.SetFd(fd) 122 - return log.initFd() 123 - } 124 - 125 - func (log *FileLogger) docheck(size int) { 126 - log.startLock.Lock() 127 - defer log.startLock.Unlock() 128 - if log.Rotate && ((log.Maxsize > 0 && log.maxsizeCursize >= log.Maxsize) || 129 - (log.Daily && time.Now().Day() != log.dailyOpenDate)) { 130 - if err := log.DoRotate(); err != nil { 131 - fmt.Fprintf(os.Stderr, "FileLogger(%q): %s\n", log.Filename, err) 132 - return 133 - } 134 - } 135 - log.maxsizeCursize += size 136 - } 137 - 138 - func (log *FileLogger) createLogFile() (*os.File, error) { 139 - // Open the log file 140 - return os.OpenFile(log.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o660) 141 - } 142 - 143 - func (log *FileLogger) initFd() error { 144 - fd := log.mw.fd 145 - finfo, err := fd.Stat() 146 - if err != nil { 147 - return fmt.Errorf("get stat: %w", err) 148 - } 149 - log.maxsizeCursize = int(finfo.Size()) 150 - log.dailyOpenDate = time.Now().Day() 151 - return nil 152 - } 153 - 154 - // DoRotate means it need to write file in new file. 155 - // new file name like xx.log.2013-01-01.2 156 - func (log *FileLogger) DoRotate() error { 157 - _, err := os.Lstat(log.Filename) 158 - if err == nil { // file exists 159 - // Find the next available number 160 - num := 1 161 - fname := "" 162 - for ; err == nil && num <= 999; num++ { 163 - fname = log.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num) 164 - _, err = os.Lstat(fname) 165 - if log.Compress && err != nil { 166 - _, err = os.Lstat(fname + ".gz") 167 - } 168 - } 169 - // return error if the last file checked still existed 170 - if err == nil { 171 - return fmt.Errorf("rotate: cannot find free log number to rename %s", log.Filename) 172 - } 173 - 174 - fd := log.mw.fd 175 - fd.Close() 176 - 177 - // close fd before rename 178 - // Rename the file to its newfound home 179 - if err = util.Rename(log.Filename, fname); err != nil { 180 - return fmt.Errorf("Rotate: %w", err) 181 - } 182 - 183 - if log.Compress { 184 - go compressOldLogFile(fname, log.CompressionLevel) //nolint:errcheck 185 - } 186 - 187 - // re-start logger 188 - if err = log.StartLogger(); err != nil { 189 - return fmt.Errorf("Rotate StartLogger: %w", err) 190 - } 191 - 192 - go log.deleteOldLog() 193 - } 194 - 195 - return nil 196 - } 197 - 198 - func compressOldLogFile(fname string, compressionLevel int) error { 199 - reader, err := os.Open(fname) 200 - if err != nil { 201 - return err 202 - } 203 - defer reader.Close() 204 - buffer := bufio.NewReader(reader) 205 - fw, err := os.OpenFile(fname+".gz", os.O_WRONLY|os.O_CREATE, 0o660) 206 - if err != nil { 207 - return err 208 - } 209 - defer fw.Close() 210 - zw, err := gzip.NewWriterLevel(fw, compressionLevel) 211 - if err != nil { 212 - return err 213 - } 214 - defer zw.Close() 215 - _, err = buffer.WriteTo(zw) 216 - if err != nil { 217 - zw.Close() 218 - fw.Close() 219 - util.Remove(fname + ".gz") //nolint:errcheck 220 - return err 221 - } 222 - reader.Close() 223 - return util.Remove(fname) 224 - } 225 - 226 - func (log *FileLogger) deleteOldLog() { 227 - dir := filepath.Dir(log.Filename) 228 - _ = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) (returnErr error) { 229 - defer func() { 230 - if r := recover(); r != nil { 231 - returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r) 232 - } 233 - }() 234 - 235 - if err != nil { 236 - return err 237 - } 238 - if d.IsDir() { 239 - return nil 240 - } 241 - info, err := d.Info() 242 - if err != nil { 243 - return err 244 - } 245 - if info.ModTime().Unix() < (time.Now().Unix() - 60*60*24*log.Maxdays) { 246 - if strings.HasPrefix(filepath.Base(path), filepath.Base(log.Filename)) { 247 - if err := util.Remove(path); err != nil { 248 - returnErr = fmt.Errorf("Failed to remove %s: %w", path, err) 249 - } 250 - } 251 - } 252 - return returnErr 253 - }) 254 - } 255 - 256 - // Flush flush file logger. 257 - // there are no buffering messages in file logger in memory. 258 - // flush file means sync file from disk. 259 - func (log *FileLogger) Flush() { 260 - _ = log.mw.fd.Sync() 261 - } 262 - 263 - // ReleaseReopen releases and reopens log files 264 - func (log *FileLogger) ReleaseReopen() error { 265 - closingErr := log.mw.fd.Close() 266 - startingErr := log.StartLogger() 267 - if startingErr != nil { 268 - if closingErr != nil { 269 - return fmt.Errorf("Error during closing: %v Error during starting: %v", closingErr, startingErr) 270 - } 271 - return startingErr 272 - } 273 - return closingErr 274 - } 275 - 276 - // GetName returns the default name for this implementation 277 - func (log *FileLogger) GetName() string { 278 - return "file" 279 - } 280 - 281 - func init() { 282 - Register("file", NewFileLogger) 283 - }
-235
modules/log/file_test.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "compress/gzip" 8 - "fmt" 9 - "io" 10 - "os" 11 - "path/filepath" 12 - "strings" 13 - "testing" 14 - "time" 15 - 16 - "github.com/stretchr/testify/assert" 17 - ) 18 - 19 - func TestFileLoggerFails(t *testing.T) { 20 - tmpDir := t.TempDir() 21 - 22 - prefix := "TestPrefix " 23 - level := INFO 24 - flags := LstdFlags | LUTC | Lfuncname 25 - // filename := filepath.Join(tmpDir, "test.log") 26 - 27 - fileLogger := NewFileLogger() 28 - // realFileLogger, ok := fileLogger.(*FileLogger) 29 - // assert.True(t, ok) 30 - 31 - // Fail if there is bad json 32 - err := fileLogger.Init("{") 33 - assert.Error(t, err) 34 - 35 - // Fail if there is no filename 36 - err = fileLogger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"filename\":\"%s\"}", prefix, level.String(), flags, "")) 37 - assert.Error(t, err) 38 - 39 - // Fail if the file isn't a filename 40 - err = fileLogger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"filename\":\"%s\"}", prefix, level.String(), flags, filepath.ToSlash(tmpDir))) 41 - assert.Error(t, err) 42 - } 43 - 44 - func TestFileLogger(t *testing.T) { 45 - tmpDir := t.TempDir() 46 - 47 - prefix := "TestPrefix " 48 - level := INFO 49 - flags := LstdFlags | LUTC | Lfuncname 50 - filename := filepath.Join(tmpDir, "test.log") 51 - 52 - fileLogger := NewFileLogger() 53 - realFileLogger, ok := fileLogger.(*FileLogger) 54 - assert.True(t, ok) 55 - 56 - location, _ := time.LoadLocation("EST") 57 - 58 - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 59 - 60 - dateString := date.UTC().Format("2006/01/02 15:04:05") 61 - 62 - event := Event{ 63 - level: INFO, 64 - msg: "TEST MSG", 65 - caller: "CALLER", 66 - filename: "FULL/FILENAME", 67 - line: 1, 68 - time: date, 69 - } 70 - 71 - expected := fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 72 - 73 - fileLogger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"filename\":\"%s\",\"maxsize\":%d,\"compress\":false}", prefix, level.String(), flags, filepath.ToSlash(filename), len(expected)*2)) 74 - 75 - assert.Equal(t, flags, realFileLogger.Flags) 76 - assert.Equal(t, level, realFileLogger.Level) 77 - assert.Equal(t, level, fileLogger.GetLevel()) 78 - 79 - fileLogger.LogEvent(&event) 80 - fileLogger.Flush() 81 - logData, err := os.ReadFile(filename) 82 - assert.NoError(t, err) 83 - assert.Equal(t, expected, string(logData)) 84 - 85 - event.level = DEBUG 86 - fileLogger.LogEvent(&event) 87 - fileLogger.Flush() 88 - logData, err = os.ReadFile(filename) 89 - assert.NoError(t, err) 90 - assert.Equal(t, expected, string(logData)) 91 - 92 - event.level = TRACE 93 - fileLogger.LogEvent(&event) 94 - fileLogger.Flush() 95 - logData, err = os.ReadFile(filename) 96 - assert.NoError(t, err) 97 - assert.Equal(t, expected, string(logData)) 98 - 99 - event.level = WARN 100 - expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 101 - fileLogger.LogEvent(&event) 102 - fileLogger.Flush() 103 - logData, err = os.ReadFile(filename) 104 - assert.NoError(t, err) 105 - assert.Equal(t, expected, string(logData)) 106 - 107 - // Should rotate 108 - fileLogger.LogEvent(&event) 109 - fileLogger.Flush() 110 - logData, err = os.ReadFile(filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1)) 111 - assert.NoError(t, err) 112 - assert.Equal(t, expected, string(logData)) 113 - 114 - logData, err = os.ReadFile(filename) 115 - assert.NoError(t, err) 116 - expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 117 - assert.Equal(t, expected, string(logData)) 118 - 119 - for num := 2; num <= 999; num++ { 120 - file, err := os.OpenFile(filename+fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num), os.O_RDONLY|os.O_CREATE, 0o666) 121 - assert.NoError(t, err) 122 - file.Close() 123 - } 124 - err = realFileLogger.DoRotate() 125 - assert.Error(t, err) 126 - 127 - expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 128 - fileLogger.LogEvent(&event) 129 - fileLogger.Flush() 130 - logData, err = os.ReadFile(filename) 131 - assert.NoError(t, err) 132 - assert.Equal(t, expected, string(logData)) 133 - 134 - // Should fail to rotate 135 - expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 136 - fileLogger.LogEvent(&event) 137 - fileLogger.Flush() 138 - logData, err = os.ReadFile(filename) 139 - assert.NoError(t, err) 140 - assert.Equal(t, expected, string(logData)) 141 - 142 - fileLogger.Close() 143 - } 144 - 145 - func TestCompressFileLogger(t *testing.T) { 146 - tmpDir := t.TempDir() 147 - 148 - prefix := "TestPrefix " 149 - level := INFO 150 - flags := LstdFlags | LUTC | Lfuncname 151 - filename := filepath.Join(tmpDir, "test.log") 152 - 153 - fileLogger := NewFileLogger() 154 - realFileLogger, ok := fileLogger.(*FileLogger) 155 - assert.True(t, ok) 156 - 157 - location, _ := time.LoadLocation("EST") 158 - 159 - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 160 - 161 - dateString := date.UTC().Format("2006/01/02 15:04:05") 162 - 163 - event := Event{ 164 - level: INFO, 165 - msg: "TEST MSG", 166 - caller: "CALLER", 167 - filename: "FULL/FILENAME", 168 - line: 1, 169 - time: date, 170 - } 171 - 172 - expected := fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 173 - 174 - fileLogger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"filename\":\"%s\",\"maxsize\":%d,\"compress\":true}", prefix, level.String(), flags, filepath.ToSlash(filename), len(expected)*2)) 175 - 176 - fileLogger.LogEvent(&event) 177 - fileLogger.Flush() 178 - logData, err := os.ReadFile(filename) 179 - assert.NoError(t, err) 180 - assert.Equal(t, expected, string(logData)) 181 - 182 - event.level = WARN 183 - expected += fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 184 - fileLogger.LogEvent(&event) 185 - fileLogger.Flush() 186 - logData, err = os.ReadFile(filename) 187 - assert.NoError(t, err) 188 - assert.Equal(t, expected, string(logData)) 189 - 190 - // Should rotate 191 - fileLogger.LogEvent(&event) 192 - fileLogger.Flush() 193 - 194 - for num := 2; num <= 999; num++ { 195 - file, err := os.OpenFile(filename+fmt.Sprintf(".%s.%03d.gz", time.Now().Format("2006-01-02"), num), os.O_RDONLY|os.O_CREATE, 0o666) 196 - assert.NoError(t, err) 197 - file.Close() 198 - } 199 - err = realFileLogger.DoRotate() 200 - assert.Error(t, err) 201 - } 202 - 203 - func TestCompressOldFile(t *testing.T) { 204 - tmpDir := t.TempDir() 205 - fname := filepath.Join(tmpDir, "test") 206 - nonGzip := filepath.Join(tmpDir, "test-nonGzip") 207 - 208 - f, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY, 0o660) 209 - assert.NoError(t, err) 210 - ng, err := os.OpenFile(nonGzip, os.O_CREATE|os.O_WRONLY, 0o660) 211 - assert.NoError(t, err) 212 - 213 - for i := 0; i < 999; i++ { 214 - f.WriteString("This is a test file\n") 215 - ng.WriteString("This is a test file\n") 216 - } 217 - f.Close() 218 - ng.Close() 219 - 220 - err = compressOldLogFile(fname, -1) 221 - assert.NoError(t, err) 222 - 223 - _, err = os.Lstat(fname + ".gz") 224 - assert.NoError(t, err) 225 - 226 - f, err = os.Open(fname + ".gz") 227 - assert.NoError(t, err) 228 - zr, err := gzip.NewReader(f) 229 - assert.NoError(t, err) 230 - data, err := io.ReadAll(zr) 231 - assert.NoError(t, err) 232 - original, err := os.ReadFile(nonGzip) 233 - assert.NoError(t, err) 234 - assert.Equal(t, original, data) 235 - }
+100 -32
modules/log/flags.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 4 package log 5 5 6 - import "strings" 6 + import ( 7 + "sort" 8 + "strings" 9 + 10 + "code.gitea.io/gitea/modules/json" 11 + ) 7 12 8 13 // These flags define which text to prefix to each log entry generated 9 14 // by the Logger. Bits are or'ed together to control what's printed. ··· 15 20 // The standard is: 16 21 // 2009/01/23 01:23:23 ...a/logger/c/d.go:23:runtime.Caller() [I]: message 17 22 const ( 18 - Ldate = 1 << iota // the date in the local time zone: 2009/01/23 19 - Ltime // the time in the local time zone: 01:23:23 20 - Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. 21 - Llongfile // full file name and line number: /a/logger/c/d.go:23 22 - Lshortfile // final file name element and line number: d.go:23. overrides Llongfile 23 - Lfuncname // function name of the caller: runtime.Caller() 24 - Lshortfuncname // last part of the function name 25 - LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone 26 - Llevelinitial // Initial character of the provided level in brackets eg. [I] for info 27 - Llevel // Provided level in brackets [INFO] 28 - 29 - // Last 20 characters of the filename 30 - Lmedfile = Lshortfile | Llongfile 23 + Ldate uint32 = 1 << iota // the date in the local time zone: 2009/01/23 24 + Ltime // the time in the local time zone: 01:23:23 25 + Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. 26 + Llongfile // full file name and line number: /a/logger/c/d.go:23 27 + Lshortfile // final file name element and line number: d.go:23. overrides Llongfile 28 + Lfuncname // function name of the caller: runtime.Caller() 29 + Lshortfuncname // last part of the function name 30 + LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone 31 + Llevelinitial // Initial character of the provided level in brackets, eg. [I] for info 32 + Llevel // Provided level in brackets [INFO] 33 + Lgopid // the Goroutine-PID of the context 31 34 32 - // LstdFlags is the initial value for the standard logger 33 - LstdFlags = Ldate | Ltime | Lmedfile | Lshortfuncname | Llevelinitial 35 + Lmedfile = Lshortfile | Llongfile // last 20 characters of the filename 36 + LstdFlags = Ldate | Ltime | Lmedfile | Lshortfuncname | Llevelinitial // default 34 37 ) 35 38 36 - var flagFromString = map[string]int{ 37 - "none": 0, 39 + const Ldefault = LstdFlags 40 + 41 + type Flags struct { 42 + defined bool 43 + flags uint32 44 + } 45 + 46 + var flagFromString = map[string]uint32{ 38 47 "date": Ldate, 39 48 "time": Ltime, 40 49 "microseconds": Lmicroseconds, ··· 45 54 "utc": LUTC, 46 55 "levelinitial": Llevelinitial, 47 56 "level": Llevel, 48 - "medfile": Lmedfile, 49 - "stdflags": LstdFlags, 57 + "gopid": Lgopid, 58 + 59 + "medfile": Lmedfile, 60 + "stdflags": LstdFlags, 50 61 } 51 62 52 - // FlagsFromString takes a comma separated list of flags and returns 53 - // the flags for this string 54 - func FlagsFromString(from string) int { 55 - flags := 0 56 - for _, flag := range strings.Split(strings.ToLower(from), ",") { 57 - f, ok := flagFromString[strings.TrimSpace(flag)] 58 - if ok { 59 - flags |= f 63 + var flagComboToString = []struct { 64 + flag uint32 65 + name string 66 + }{ 67 + // name with more bits comes first 68 + {LstdFlags, "stdflags"}, 69 + {Lmedfile, "medfile"}, 70 + 71 + {Ldate, "date"}, 72 + {Ltime, "time"}, 73 + {Lmicroseconds, "microseconds"}, 74 + {Llongfile, "longfile"}, 75 + {Lshortfile, "shortfile"}, 76 + {Lfuncname, "funcname"}, 77 + {Lshortfuncname, "shortfuncname"}, 78 + {LUTC, "utc"}, 79 + {Llevelinitial, "levelinitial"}, 80 + {Llevel, "level"}, 81 + {Lgopid, "gopid"}, 82 + } 83 + 84 + func (f Flags) Bits() uint32 { 85 + if !f.defined { 86 + return Ldefault 87 + } 88 + return f.flags 89 + } 90 + 91 + func (f Flags) String() string { 92 + flags := f.Bits() 93 + var flagNames []string 94 + for _, it := range flagComboToString { 95 + if flags&it.flag == it.flag { 96 + flags &^= it.flag 97 + flagNames = append(flagNames, it.name) 60 98 } 61 99 } 62 - if flags == 0 { 63 - return -1 100 + if len(flagNames) == 0 { 101 + return "none" 64 102 } 65 - return flags 103 + sort.Strings(flagNames) 104 + return strings.Join(flagNames, ",") 105 + } 106 + 107 + func (f *Flags) UnmarshalJSON(bytes []byte) error { 108 + var s string 109 + if err := json.Unmarshal(bytes, &s); err != nil { 110 + return err 111 + } 112 + *f = FlagsFromString(s) 113 + return nil 114 + } 115 + 116 + func (f Flags) MarshalJSON() ([]byte, error) { 117 + return []byte(`"` + f.String() + `"`), nil 118 + } 119 + 120 + func FlagsFromString(from string, def ...uint32) Flags { 121 + from = strings.TrimSpace(from) 122 + if from == "" && len(def) > 0 { 123 + return Flags{defined: true, flags: def[0]} 124 + } 125 + flags := uint32(0) 126 + for _, flag := range strings.Split(strings.ToLower(from), ",") { 127 + flags |= flagFromString[strings.TrimSpace(flag)] 128 + } 129 + return Flags{defined: true, flags: flags} 130 + } 131 + 132 + func FlagsFromBits(flags uint32) Flags { 133 + return Flags{defined: true, flags: flags} 66 134 }
+30
modules/log/flags_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "testing" 8 + 9 + "code.gitea.io/gitea/modules/json" 10 + 11 + "github.com/stretchr/testify/assert" 12 + ) 13 + 14 + func TestFlags(t *testing.T) { 15 + assert.EqualValues(t, Ldefault, Flags{}.Bits()) 16 + assert.EqualValues(t, 0, FlagsFromString("").Bits()) 17 + assert.EqualValues(t, Lgopid, FlagsFromString("", Lgopid).Bits()) 18 + assert.EqualValues(t, 0, FlagsFromString("none", Lgopid).Bits()) 19 + assert.EqualValues(t, Ldate|Ltime, FlagsFromString("date,time", Lgopid).Bits()) 20 + 21 + assert.EqualValues(t, "stdflags", FlagsFromString("stdflags").String()) 22 + assert.EqualValues(t, "medfile", FlagsFromString("medfile").String()) 23 + 24 + bs, err := json.Marshal(FlagsFromString("utc,level")) 25 + assert.NoError(t, err) 26 + assert.EqualValues(t, `"level,utc"`, string(bs)) 27 + var flags Flags 28 + assert.NoError(t, json.Unmarshal(bs, &flags)) 29 + assert.EqualValues(t, LUTC|Llevel, flags.Bits()) 30 + }
+35
modules/log/init.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "runtime" 8 + "strings" 9 + 10 + "code.gitea.io/gitea/modules/process" 11 + "code.gitea.io/gitea/modules/util/rotatingfilewriter" 12 + ) 13 + 14 + var projectPackagePrefix string 15 + 16 + func init() { 17 + _, filename, _, _ := runtime.Caller(0) 18 + projectPackagePrefix = strings.TrimSuffix(filename, "modules/log/init.go") 19 + if projectPackagePrefix == filename { 20 + // in case the source code file is moved, we can not trim the suffix, the code above should also be updated. 21 + panic("unable to detect correct package prefix, please update file: " + filename) 22 + } 23 + 24 + rotatingfilewriter.ErrorPrintf = FallbackErrorf 25 + 26 + process.Trace = func(start bool, pid process.IDType, description string, parentPID process.IDType, typ string) { 27 + if start && parentPID != "" { 28 + Log(1, TRACE, "Start %s: %s (from %s) (%s)", NewColoredValue(pid, FgHiYellow), description, NewColoredValue(parentPID, FgYellow), NewColoredValue(typ, Reset)) 29 + } else if start { 30 + Log(1, TRACE, "Start %s: %s (%s)", NewColoredValue(pid, FgHiYellow), description, NewColoredValue(typ, Reset)) 31 + } else { 32 + Log(1, TRACE, "Done %s: %s", NewColoredValue(pid, FgHiYellow), NewColoredValue(description, Reset)) 33 + } 34 + } 35 + }
+47 -52
modules/log/level.go
··· 5 5 6 6 import ( 7 7 "bytes" 8 - "fmt" 9 - "os" 10 8 "strings" 11 9 12 10 "code.gitea.io/gitea/modules/json" ··· 16 14 type Level int 17 15 18 16 const ( 19 - // TRACE represents the lowest log level 20 - TRACE Level = iota 21 - // DEBUG is for debug logging 17 + UNDEFINED Level = iota 18 + TRACE 22 19 DEBUG 23 - // INFO is for information 24 20 INFO 25 - // WARN is for warning information 26 21 WARN 27 - // ERROR is for error reporting 28 22 ERROR 29 - // CRITICAL is for critical errors 30 - CRITICAL 31 - // FATAL is for fatal errors 32 23 FATAL 33 - // NONE is for no logging 34 24 NONE 35 25 ) 26 + 27 + const CRITICAL = ERROR // most logger frameworks doesn't support CRITICAL, and it doesn't seem useful 36 28 37 29 var toString = map[Level]string{ 38 - TRACE: "trace", 39 - DEBUG: "debug", 40 - INFO: "info", 41 - WARN: "warn", 42 - ERROR: "error", 43 - CRITICAL: "critical", 44 - FATAL: "fatal", 45 - NONE: "none", 30 + UNDEFINED: "undefined", 31 + 32 + TRACE: "trace", 33 + DEBUG: "debug", 34 + INFO: "info", 35 + WARN: "warn", 36 + ERROR: "error", 37 + 38 + FATAL: "fatal", 39 + NONE: "none", 46 40 } 47 41 48 42 var toLevel = map[string]Level{ 49 - "trace": TRACE, 50 - "debug": DEBUG, 51 - "info": INFO, 52 - "warn": WARN, 53 - "error": ERROR, 54 - "critical": CRITICAL, 55 - "fatal": FATAL, 56 - "none": NONE, 43 + "undefined": UNDEFINED, 44 + 45 + "trace": TRACE, 46 + "debug": DEBUG, 47 + "info": INFO, 48 + "warn": WARN, 49 + "warning": WARN, 50 + "error": ERROR, 51 + 52 + "fatal": FATAL, 53 + "none": NONE, 57 54 } 58 55 59 - // Levels returns all the possible logging levels 60 - func Levels() []string { 61 - keys := make([]string, 0) 62 - for key := range toLevel { 63 - keys = append(keys, key) 64 - } 65 - return keys 56 + var levelToColor = map[Level][]ColorAttribute{ 57 + TRACE: {Bold, FgCyan}, 58 + DEBUG: {Bold, FgBlue}, 59 + INFO: {Bold, FgGreen}, 60 + WARN: {Bold, FgYellow}, 61 + ERROR: {Bold, FgRed}, 62 + FATAL: {Bold, BgRed}, 63 + NONE: {Reset}, 66 64 } 67 65 68 66 func (l Level) String() string { ··· 73 71 return "info" 74 72 } 75 73 76 - // Color returns the color string for this Level 77 - func (l Level) Color() *[]byte { 74 + func (l Level) ColorAttributes() []ColorAttribute { 78 75 color, ok := levelToColor[l] 79 76 if ok { 80 - return &(color) 77 + return color 81 78 } 82 79 none := levelToColor[NONE] 83 - return &none 80 + return none 84 81 } 85 82 86 83 // MarshalJSON takes a Level and turns it into text ··· 91 88 return buffer.Bytes(), nil 92 89 } 93 90 94 - // FromString takes a level string and returns a Level 95 - func FromString(level string) Level { 96 - temp, ok := toLevel[strings.ToLower(level)] 97 - if !ok { 98 - return INFO 99 - } 100 - return temp 101 - } 102 - 103 91 // UnmarshalJSON takes text and turns it into a Level 104 92 func (l *Level) UnmarshalJSON(b []byte) error { 105 - var tmp interface{} 93 + var tmp any 106 94 err := json.Unmarshal(b, &tmp) 107 95 if err != nil { 108 - fmt.Fprintf(os.Stderr, "Err: %v", err) 109 96 return err 110 97 } 111 98 112 99 switch v := tmp.(type) { 113 100 case string: 114 - *l = FromString(v) 101 + *l = LevelFromString(v) 115 102 case int: 116 - *l = FromString(Level(v).String()) 103 + *l = LevelFromString(Level(v).String()) 117 104 default: 118 105 *l = INFO 119 106 } 120 107 return nil 121 108 } 109 + 110 + // LevelFromString takes a level string and returns a Level 111 + func LevelFromString(level string) Level { 112 + if l, ok := toLevel[strings.ToLower(level)]; ok { 113 + return l 114 + } 115 + return INFO 116 + }
-305
modules/log/log.go
··· 1 - // Copyright 2014 The Gogs Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "os" 9 - "runtime" 10 - "strings" 11 - "sync" 12 - 13 - "code.gitea.io/gitea/modules/process" 14 - ) 15 - 16 - type loggerMap struct { 17 - sync.Map 18 - } 19 - 20 - func (m *loggerMap) Load(k string) (*MultiChannelledLogger, bool) { 21 - v, ok := m.Map.Load(k) 22 - if !ok { 23 - return nil, false 24 - } 25 - l, ok := v.(*MultiChannelledLogger) 26 - return l, ok 27 - } 28 - 29 - func (m *loggerMap) Store(k string, v *MultiChannelledLogger) { 30 - m.Map.Store(k, v) 31 - } 32 - 33 - func (m *loggerMap) Delete(k string) { 34 - m.Map.Delete(k) 35 - } 36 - 37 - var ( 38 - // DEFAULT is the name of the default logger 39 - DEFAULT = "default" 40 - // NamedLoggers map of named loggers 41 - NamedLoggers loggerMap 42 - prefix string 43 - ) 44 - 45 - // NewLogger create a logger for the default logger 46 - func NewLogger(bufLen int64, name, provider, config string) *MultiChannelledLogger { 47 - err := NewNamedLogger(DEFAULT, bufLen, name, provider, config) 48 - if err != nil { 49 - CriticalWithSkip(1, "Unable to create default logger: %v", err) 50 - panic(err) 51 - } 52 - l, _ := NamedLoggers.Load(DEFAULT) 53 - return l 54 - } 55 - 56 - // NewNamedLogger creates a new named logger for a given configuration 57 - func NewNamedLogger(name string, bufLen int64, subname, provider, config string) error { 58 - logger, ok := NamedLoggers.Load(name) 59 - if !ok { 60 - logger = newLogger(name, bufLen) 61 - NamedLoggers.Store(name, logger) 62 - } 63 - 64 - return logger.SetLogger(subname, provider, config) 65 - } 66 - 67 - // DelNamedLogger closes and deletes the named logger 68 - func DelNamedLogger(name string) { 69 - l, ok := NamedLoggers.Load(name) 70 - if ok { 71 - NamedLoggers.Delete(name) 72 - l.Close() 73 - } 74 - } 75 - 76 - // DelLogger removes the named sublogger from the default logger 77 - func DelLogger(name string) error { 78 - logger, _ := NamedLoggers.Load(DEFAULT) 79 - found, err := logger.DelLogger(name) 80 - if !found { 81 - Trace("Log %s not found, no need to delete", name) 82 - } 83 - return err 84 - } 85 - 86 - // GetLogger returns either a named logger or the default logger 87 - func GetLogger(name string) *MultiChannelledLogger { 88 - logger, ok := NamedLoggers.Load(name) 89 - if ok { 90 - return logger 91 - } 92 - logger, _ = NamedLoggers.Load(DEFAULT) 93 - return logger 94 - } 95 - 96 - // GetLevel returns the minimum logger level 97 - func GetLevel() Level { 98 - l, _ := NamedLoggers.Load(DEFAULT) 99 - return l.GetLevel() 100 - } 101 - 102 - // GetStacktraceLevel returns the minimum logger level 103 - func GetStacktraceLevel() Level { 104 - l, _ := NamedLoggers.Load(DEFAULT) 105 - return l.GetStacktraceLevel() 106 - } 107 - 108 - // Trace records trace log 109 - func Trace(format string, v ...interface{}) { 110 - Log(1, TRACE, format, v...) 111 - } 112 - 113 - // IsTrace returns true if at least one logger is TRACE 114 - func IsTrace() bool { 115 - return GetLevel() <= TRACE 116 - } 117 - 118 - // Debug records debug log 119 - func Debug(format string, v ...interface{}) { 120 - Log(1, DEBUG, format, v...) 121 - } 122 - 123 - // IsDebug returns true if at least one logger is DEBUG 124 - func IsDebug() bool { 125 - return GetLevel() <= DEBUG 126 - } 127 - 128 - // Info records info log 129 - func Info(format string, v ...interface{}) { 130 - Log(1, INFO, format, v...) 131 - } 132 - 133 - // IsInfo returns true if at least one logger is INFO 134 - func IsInfo() bool { 135 - return GetLevel() <= INFO 136 - } 137 - 138 - // Warn records warning log 139 - func Warn(format string, v ...interface{}) { 140 - Log(1, WARN, format, v...) 141 - } 142 - 143 - // IsWarn returns true if at least one logger is WARN 144 - func IsWarn() bool { 145 - return GetLevel() <= WARN 146 - } 147 - 148 - // Error records error log 149 - func Error(format string, v ...interface{}) { 150 - Log(1, ERROR, format, v...) 151 - } 152 - 153 - // ErrorWithSkip records error log from "skip" calls back from this function 154 - func ErrorWithSkip(skip int, format string, v ...interface{}) { 155 - Log(skip+1, ERROR, format, v...) 156 - } 157 - 158 - // IsError returns true if at least one logger is ERROR 159 - func IsError() bool { 160 - return GetLevel() <= ERROR 161 - } 162 - 163 - // Critical records critical log 164 - func Critical(format string, v ...interface{}) { 165 - Log(1, CRITICAL, format, v...) 166 - } 167 - 168 - // CriticalWithSkip records critical log from "skip" calls back from this function 169 - func CriticalWithSkip(skip int, format string, v ...interface{}) { 170 - Log(skip+1, CRITICAL, format, v...) 171 - } 172 - 173 - // IsCritical returns true if at least one logger is CRITICAL 174 - func IsCritical() bool { 175 - return GetLevel() <= CRITICAL 176 - } 177 - 178 - // Fatal records fatal log and exit process 179 - func Fatal(format string, v ...interface{}) { 180 - Log(1, FATAL, format, v...) 181 - Close() 182 - os.Exit(1) 183 - } 184 - 185 - // FatalWithSkip records fatal log from "skip" calls back from this function 186 - func FatalWithSkip(skip int, format string, v ...interface{}) { 187 - Log(skip+1, FATAL, format, v...) 188 - Close() 189 - os.Exit(1) 190 - } 191 - 192 - // IsFatal returns true if at least one logger is FATAL 193 - func IsFatal() bool { 194 - return GetLevel() <= FATAL 195 - } 196 - 197 - // Pause pauses all the loggers 198 - func Pause() { 199 - NamedLoggers.Range(func(key, value interface{}) bool { 200 - logger := value.(*MultiChannelledLogger) 201 - logger.Pause() 202 - logger.Flush() 203 - return true 204 - }) 205 - } 206 - 207 - // Resume resumes all the loggers 208 - func Resume() { 209 - NamedLoggers.Range(func(key, value interface{}) bool { 210 - logger := value.(*MultiChannelledLogger) 211 - logger.Resume() 212 - return true 213 - }) 214 - } 215 - 216 - // ReleaseReopen releases and reopens logging files 217 - func ReleaseReopen() error { 218 - var accumulatedErr error 219 - NamedLoggers.Range(func(key, value interface{}) bool { 220 - logger := value.(*MultiChannelledLogger) 221 - if err := logger.ReleaseReopen(); err != nil { 222 - if accumulatedErr == nil { 223 - accumulatedErr = fmt.Errorf("Error reopening %s: %w", key.(string), err) 224 - } else { 225 - accumulatedErr = fmt.Errorf("Error reopening %s: %v & %w", key.(string), err, accumulatedErr) 226 - } 227 - } 228 - return true 229 - }) 230 - return accumulatedErr 231 - } 232 - 233 - // Close closes all the loggers 234 - func Close() { 235 - l, ok := NamedLoggers.Load(DEFAULT) 236 - if !ok { 237 - return 238 - } 239 - NamedLoggers.Delete(DEFAULT) 240 - l.Close() 241 - } 242 - 243 - // Log a message with defined skip and at logging level 244 - // A skip of 0 refers to the caller of this command 245 - func Log(skip int, level Level, format string, v ...interface{}) { 246 - l, ok := NamedLoggers.Load(DEFAULT) 247 - if ok { 248 - l.Log(skip+1, level, format, v...) //nolint:errcheck 249 - } 250 - } 251 - 252 - // LoggerAsWriter is a io.Writer shim around the gitea log 253 - type LoggerAsWriter struct { 254 - ourLoggers []*MultiChannelledLogger 255 - level Level 256 - } 257 - 258 - // NewLoggerAsWriter creates a Writer representation of the logger with setable log level 259 - func NewLoggerAsWriter(level string, ourLoggers ...*MultiChannelledLogger) *LoggerAsWriter { 260 - if len(ourLoggers) == 0 { 261 - l, _ := NamedLoggers.Load(DEFAULT) 262 - ourLoggers = []*MultiChannelledLogger{l} 263 - } 264 - l := &LoggerAsWriter{ 265 - ourLoggers: ourLoggers, 266 - level: FromString(level), 267 - } 268 - return l 269 - } 270 - 271 - // Write implements the io.Writer interface to allow spoofing of chi 272 - func (l *LoggerAsWriter) Write(p []byte) (int, error) { 273 - for _, logger := range l.ourLoggers { 274 - // Skip = 3 because this presumes that we have been called by log.Println() 275 - // If the caller has used log.Output or the like this will be wrong 276 - logger.Log(3, l.level, string(p)) //nolint:errcheck 277 - } 278 - return len(p), nil 279 - } 280 - 281 - // Log takes a given string and logs it at the set log-level 282 - func (l *LoggerAsWriter) Log(msg string) { 283 - for _, logger := range l.ourLoggers { 284 - // Set the skip to reference the call just above this 285 - _ = logger.Log(1, l.level, msg) 286 - } 287 - } 288 - 289 - func init() { 290 - process.Trace = func(start bool, pid process.IDType, description string, parentPID process.IDType, typ string) { 291 - if start && parentPID != "" { 292 - Log(1, TRACE, "Start %s: %s (from %s) (%s)", NewColoredValue(pid, FgHiYellow), description, NewColoredValue(parentPID, FgYellow), NewColoredValue(typ, Reset)) 293 - } else if start { 294 - Log(1, TRACE, "Start %s: %s (%s)", NewColoredValue(pid, FgHiYellow), description, NewColoredValue(typ, Reset)) 295 - } else { 296 - Log(1, TRACE, "Done %s: %s", NewColoredValue(pid, FgHiYellow), NewColoredValue(description, Reset)) 297 - } 298 - } 299 - _, filename, _, _ := runtime.Caller(0) 300 - prefix = strings.TrimSuffix(filename, "modules/log/log.go") 301 - if prefix == filename { 302 - // in case the source code file is moved, we can not trim the suffix, the code above should also be updated. 303 - panic("unable to detect correct package prefix, please update file: " + filename) 304 - } 305 - }
-152
modules/log/log_test.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "testing" 9 - 10 - "github.com/stretchr/testify/assert" 11 - ) 12 - 13 - func baseConsoleTest(t *testing.T, logger *MultiChannelledLogger) (chan []byte, chan bool) { 14 - written := make(chan []byte) 15 - closed := make(chan bool) 16 - 17 - c := CallbackWriteCloser{ 18 - callback: func(p []byte, close bool) { 19 - written <- p 20 - closed <- close 21 - }, 22 - } 23 - m := logger.MultiChannelledLog 24 - 25 - channelledLog := m.GetEventLogger("console") 26 - assert.NotEmpty(t, channelledLog) 27 - realChanLog, ok := channelledLog.(*ChannelledLog) 28 - assert.True(t, ok) 29 - realCL, ok := realChanLog.loggerProvider.(*ConsoleLogger) 30 - assert.True(t, ok) 31 - assert.Equal(t, INFO, realCL.Level) 32 - realCL.out = c 33 - 34 - format := "test: %s" 35 - args := []interface{}{"A"} 36 - 37 - logger.Log(0, INFO, format, args...) 38 - line := <-written 39 - assert.Contains(t, string(line), fmt.Sprintf(format, args...)) 40 - assert.False(t, <-closed) 41 - 42 - format = "test2: %s" 43 - logger.Warn(format, args...) 44 - line = <-written 45 - 46 - assert.Contains(t, string(line), fmt.Sprintf(format, args...)) 47 - assert.False(t, <-closed) 48 - 49 - format = "testerror: %s" 50 - logger.Error(format, args...) 51 - line = <-written 52 - assert.Contains(t, string(line), fmt.Sprintf(format, args...)) 53 - assert.False(t, <-closed) 54 - return written, closed 55 - } 56 - 57 - func TestNewLoggerUnexported(t *testing.T) { 58 - level := INFO 59 - logger := newLogger("UNEXPORTED", 0) 60 - err := logger.SetLogger("console", "console", fmt.Sprintf(`{"level":"%s"}`, level.String())) 61 - assert.NoError(t, err) 62 - out := logger.MultiChannelledLog.GetEventLogger("console") 63 - assert.NotEmpty(t, out) 64 - chanlog, ok := out.(*ChannelledLog) 65 - assert.True(t, ok) 66 - assert.Equal(t, "console", chanlog.provider) 67 - assert.Equal(t, INFO, logger.GetLevel()) 68 - baseConsoleTest(t, logger) 69 - } 70 - 71 - func TestNewLoggger(t *testing.T) { 72 - level := INFO 73 - logger := NewLogger(0, "console", "console", fmt.Sprintf(`{"level":"%s"}`, level.String())) 74 - 75 - assert.Equal(t, INFO, GetLevel()) 76 - assert.False(t, IsTrace()) 77 - assert.False(t, IsDebug()) 78 - assert.True(t, IsInfo()) 79 - assert.True(t, IsWarn()) 80 - assert.True(t, IsError()) 81 - 82 - written, closed := baseConsoleTest(t, logger) 83 - 84 - format := "test: %s" 85 - args := []interface{}{"A"} 86 - 87 - Log(0, INFO, format, args...) 88 - line := <-written 89 - assert.Contains(t, string(line), fmt.Sprintf(format, args...)) 90 - assert.False(t, <-closed) 91 - 92 - Info(format, args...) 93 - line = <-written 94 - assert.Contains(t, string(line), fmt.Sprintf(format, args...)) 95 - assert.False(t, <-closed) 96 - 97 - go DelLogger("console") 98 - line = <-written 99 - assert.Equal(t, "", string(line)) 100 - assert.True(t, <-closed) 101 - } 102 - 103 - func TestNewLogggerRecreate(t *testing.T) { 104 - level := INFO 105 - NewLogger(0, "console", "console", fmt.Sprintf(`{"level":"%s"}`, level.String())) 106 - 107 - assert.Equal(t, INFO, GetLevel()) 108 - assert.False(t, IsTrace()) 109 - assert.False(t, IsDebug()) 110 - assert.True(t, IsInfo()) 111 - assert.True(t, IsWarn()) 112 - assert.True(t, IsError()) 113 - 114 - format := "test: %s" 115 - args := []interface{}{"A"} 116 - 117 - Log(0, INFO, format, args...) 118 - 119 - NewLogger(0, "console", "console", fmt.Sprintf(`{"level":"%s"}`, level.String())) 120 - 121 - assert.Equal(t, INFO, GetLevel()) 122 - assert.False(t, IsTrace()) 123 - assert.False(t, IsDebug()) 124 - assert.True(t, IsInfo()) 125 - assert.True(t, IsWarn()) 126 - assert.True(t, IsError()) 127 - 128 - Log(0, INFO, format, args...) 129 - 130 - assert.Panics(t, func() { 131 - NewLogger(0, "console", "console", fmt.Sprintf(`{"level":"%s"`, level.String())) 132 - }) 133 - 134 - go DelLogger("console") 135 - 136 - // We should be able to redelete without a problem 137 - go DelLogger("console") 138 - } 139 - 140 - func TestNewNamedLogger(t *testing.T) { 141 - level := INFO 142 - err := NewNamedLogger("test", 0, "console", "console", fmt.Sprintf(`{"level":"%s"}`, level.String())) 143 - assert.NoError(t, err) 144 - logger, _ := NamedLoggers.Load("test") 145 - assert.Equal(t, level, logger.GetLevel()) 146 - 147 - written, closed := baseConsoleTest(t, logger) 148 - go DelNamedLogger("test") 149 - line := <-written 150 - assert.Equal(t, "", string(line)) 151 - assert.True(t, <-closed) 152 - }
+36 -127
modules/log/logger.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 2 // SPDX-License-Identifier: MIT 3 3 4 + // Package log provides logging capabilities for Gitea. 5 + // Concepts: 6 + // 7 + // * Logger: a Logger provides logging functions and dispatches log events to all its writers 8 + // 9 + // * EventWriter: written log Event to a destination (eg: file, console) 10 + // - EventWriterBase: the base struct of a writer, it contains common fields and functions for all writers 11 + // - WriterType: the type name of a writer, eg: console, file 12 + // - WriterName: aka Mode Name in document, the name of a writer instance, it's usually defined by the config file. 13 + // It is called "mode name" because old code use MODE as config key, to keep compatibility, keep this concept. 14 + // 15 + // * WriterMode: the common options for all writers, eg: log level. 16 + // - WriterConsoleOption and others: the specified options for a writer, eg: file path, remote address. 17 + // 18 + // Call graph: 19 + // -> log.Info() 20 + // -> LoggerImpl.Log() 21 + // -> LoggerImpl.SendLogEvent, then the event goes into writer's goroutines 22 + // -> EventWriter.Run() handles the events 4 23 package log 5 24 6 - import "os" 7 - 8 - // Logger is the basic interface for logging 9 - type Logger interface { 10 - LevelLogger 11 - Trace(format string, v ...interface{}) 12 - IsTrace() bool 13 - Debug(format string, v ...interface{}) 14 - IsDebug() bool 15 - Info(format string, v ...interface{}) 16 - IsInfo() bool 17 - Warn(format string, v ...interface{}) 18 - IsWarn() bool 19 - Error(format string, v ...interface{}) 20 - ErrorWithSkip(skip int, format string, v ...interface{}) 21 - IsError() bool 22 - Critical(format string, v ...interface{}) 23 - CriticalWithSkip(skip int, format string, v ...interface{}) 24 - IsCritical() bool 25 - Fatal(format string, v ...interface{}) 26 - FatalWithSkip(skip int, format string, v ...interface{}) 27 - IsFatal() bool 25 + // BaseLogger provides the basic logging functions 26 + type BaseLogger interface { 27 + Log(skip int, level Level, format string, v ...any) 28 + GetLevel() Level 28 29 } 29 30 30 - // LevelLogger is the simplest logging interface 31 + // LevelLogger provides level-related logging functions 31 32 type LevelLogger interface { 32 - Flush() 33 - Close() 34 - GetLevel() Level 35 - Log(skip int, level Level, format string, v ...interface{}) error 36 - } 33 + LevelEnabled(level Level) bool 37 34 38 - // SettableLogger is the interface of loggers which have subloggers 39 - type SettableLogger interface { 40 - SetLogger(name, provider, config string) error 41 - DelLogger(name string) (bool, error) 35 + Trace(format string, v ...any) 36 + Debug(format string, v ...any) 37 + Info(format string, v ...any) 38 + Warn(format string, v ...any) 39 + Error(format string, v ...any) 40 + Critical(format string, v ...any) 42 41 } 43 42 44 - // StacktraceLogger is a logger that can log stacktraces 45 - type StacktraceLogger interface { 46 - GetStacktraceLevel() Level 47 - } 48 - 49 - // LevelLoggerLogger wraps a LevelLogger as a Logger 50 - type LevelLoggerLogger struct { 43 + type Logger interface { 44 + BaseLogger 51 45 LevelLogger 52 46 } 53 47 54 - // Trace records trace log 55 - func (l *LevelLoggerLogger) Trace(format string, v ...interface{}) { 56 - l.Log(1, TRACE, format, v...) //nolint:errcheck 57 - } 58 - 59 - // IsTrace returns true if the logger is TRACE 60 - func (l *LevelLoggerLogger) IsTrace() bool { 61 - return l.GetLevel() <= TRACE 62 - } 63 - 64 - // Debug records debug log 65 - func (l *LevelLoggerLogger) Debug(format string, v ...interface{}) { 66 - l.Log(1, DEBUG, format, v...) //nolint:errcheck 67 - } 68 - 69 - // IsDebug returns true if the logger is DEBUG 70 - func (l *LevelLoggerLogger) IsDebug() bool { 71 - return l.GetLevel() <= DEBUG 72 - } 73 - 74 - // Info records information log 75 - func (l *LevelLoggerLogger) Info(format string, v ...interface{}) { 76 - l.Log(1, INFO, format, v...) //nolint:errcheck 77 - } 78 - 79 - // IsInfo returns true if the logger is INFO 80 - func (l *LevelLoggerLogger) IsInfo() bool { 81 - return l.GetLevel() <= INFO 82 - } 83 - 84 - // Warn records warning log 85 - func (l *LevelLoggerLogger) Warn(format string, v ...interface{}) { 86 - l.Log(1, WARN, format, v...) //nolint:errcheck 87 - } 88 - 89 - // IsWarn returns true if the logger is WARN 90 - func (l *LevelLoggerLogger) IsWarn() bool { 91 - return l.GetLevel() <= WARN 92 - } 93 - 94 - // Error records error log 95 - func (l *LevelLoggerLogger) Error(format string, v ...interface{}) { 96 - l.Log(1, ERROR, format, v...) //nolint:errcheck 97 - } 98 - 99 - // ErrorWithSkip records error log from "skip" calls back from this function 100 - func (l *LevelLoggerLogger) ErrorWithSkip(skip int, format string, v ...interface{}) { 101 - l.Log(skip+1, ERROR, format, v...) //nolint:errcheck 102 - } 103 - 104 - // IsError returns true if the logger is ERROR 105 - func (l *LevelLoggerLogger) IsError() bool { 106 - return l.GetLevel() <= ERROR 107 - } 108 - 109 - // Critical records critical log 110 - func (l *LevelLoggerLogger) Critical(format string, v ...interface{}) { 111 - l.Log(1, CRITICAL, format, v...) //nolint:errcheck 112 - } 113 - 114 - // CriticalWithSkip records critical log from "skip" calls back from this function 115 - func (l *LevelLoggerLogger) CriticalWithSkip(skip int, format string, v ...interface{}) { 116 - l.Log(skip+1, CRITICAL, format, v...) //nolint:errcheck 117 - } 118 - 119 - // IsCritical returns true if the logger is CRITICAL 120 - func (l *LevelLoggerLogger) IsCritical() bool { 121 - return l.GetLevel() <= CRITICAL 122 - } 123 - 124 - // Fatal records fatal log and exit the process 125 - func (l *LevelLoggerLogger) Fatal(format string, v ...interface{}) { 126 - l.Log(1, FATAL, format, v...) //nolint:errcheck 127 - l.Close() 128 - os.Exit(1) 129 - } 130 - 131 - // FatalWithSkip records fatal log from "skip" calls back from this function and exits the process 132 - func (l *LevelLoggerLogger) FatalWithSkip(skip int, format string, v ...interface{}) { 133 - l.Log(skip+1, FATAL, format, v...) //nolint:errcheck 134 - l.Close() 135 - os.Exit(1) 136 - } 137 - 138 - // IsFatal returns true if the logger is FATAL 139 - func (l *LevelLoggerLogger) IsFatal() bool { 140 - return l.GetLevel() <= FATAL 48 + type LogStringer interface { //nolint:revive 49 + LogString() string 141 50 }
+83
modules/log/logger_global.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "fmt" 8 + "os" 9 + ) 10 + 11 + // FallbackErrorf is the last chance to show an error if the logger has internal errors 12 + func FallbackErrorf(format string, args ...any) { 13 + _, _ = fmt.Fprintf(os.Stderr, format+"\n", args) 14 + } 15 + 16 + func GetLevel() Level { 17 + return GetLogger(DEFAULT).GetLevel() 18 + } 19 + 20 + func Log(skip int, level Level, format string, v ...any) { 21 + GetLogger(DEFAULT).Log(skip+1, level, format, v...) 22 + } 23 + 24 + func Trace(format string, v ...any) { 25 + Log(1, TRACE, format, v...) 26 + } 27 + 28 + func IsTrace() bool { 29 + return GetLevel() <= TRACE 30 + } 31 + 32 + func Debug(format string, v ...any) { 33 + Log(1, DEBUG, format, v...) 34 + } 35 + 36 + func IsDebug() bool { 37 + return GetLevel() <= DEBUG 38 + } 39 + 40 + func Info(format string, v ...any) { 41 + Log(1, INFO, format, v...) 42 + } 43 + 44 + func Warn(format string, v ...any) { 45 + Log(1, WARN, format, v...) 46 + } 47 + 48 + func Error(format string, v ...any) { 49 + Log(1, ERROR, format, v...) 50 + } 51 + 52 + func ErrorWithSkip(skip int, format string, v ...any) { 53 + Log(skip+1, ERROR, format, v...) 54 + } 55 + 56 + func Critical(format string, v ...any) { 57 + Log(1, ERROR, format, v...) 58 + } 59 + 60 + // Fatal records fatal log and exit process 61 + func Fatal(format string, v ...any) { 62 + Log(1, FATAL, format, v...) 63 + GetManager().Close() 64 + os.Exit(1) 65 + } 66 + 67 + func GetLogger(name string) Logger { 68 + return GetManager().GetLogger(name) 69 + } 70 + 71 + func IsLoggerEnabled(name string) bool { 72 + return GetManager().GetLogger(name).IsEnabled() 73 + } 74 + 75 + func SetConsoleLogger(loggerName, writerName string, level Level) { 76 + writer := NewEventWriterConsole(writerName, WriterMode{ 77 + Level: level, 78 + Flags: FlagsFromBits(LstdFlags), 79 + Colorize: CanColorStdout, 80 + WriterOption: WriterConsoleOption{}, 81 + }) 82 + GetManager().GetLogger(loggerName).RemoveAllWriters().AddWriters(writer) 83 + }
+239
modules/log/logger_impl.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "context" 8 + "runtime" 9 + "strings" 10 + "sync" 11 + "sync/atomic" 12 + "time" 13 + 14 + "code.gitea.io/gitea/modules/json" 15 + "code.gitea.io/gitea/modules/util" 16 + ) 17 + 18 + type LoggerImpl struct { 19 + LevelLogger 20 + 21 + ctx context.Context 22 + ctxCancel context.CancelFunc 23 + 24 + level atomic.Int32 25 + stacktraceLevel atomic.Int32 26 + 27 + eventWriterMu sync.RWMutex 28 + eventWriters map[string]EventWriter 29 + } 30 + 31 + var ( 32 + _ BaseLogger = (*LoggerImpl)(nil) 33 + _ LevelLogger = (*LoggerImpl)(nil) 34 + ) 35 + 36 + // SendLogEvent sends a log event to all writers 37 + func (l *LoggerImpl) SendLogEvent(event *Event) { 38 + l.eventWriterMu.RLock() 39 + defer l.eventWriterMu.RUnlock() 40 + 41 + if len(l.eventWriters) == 0 { 42 + FallbackErrorf("[no logger writer]: %s", event.MsgSimpleText) 43 + return 44 + } 45 + 46 + // the writers have their own goroutines, the message arguments (with Stringer) shouldn't be used in other goroutines 47 + // so the event message must be formatted here 48 + msgFormat, msgArgs := event.msgFormat, event.msgArgs 49 + event.msgFormat, event.msgArgs = "(already processed by formatters)", nil 50 + 51 + for _, w := range l.eventWriters { 52 + if event.Level < w.GetLevel() { 53 + continue 54 + } 55 + formatted := &EventFormatted{ 56 + Origin: event, 57 + Msg: w.Base().FormatMessage(w.Base().Mode, event, msgFormat, msgArgs...), 58 + } 59 + select { 60 + case w.Base().Queue <- formatted: 61 + default: 62 + bs, _ := json.Marshal(event) 63 + FallbackErrorf("log writer %q queue is full, event: %v", w.GetWriterName(), string(bs)) 64 + } 65 + } 66 + } 67 + 68 + // syncLevelInternal syncs the level of the logger with the levels of the writers 69 + func (l *LoggerImpl) syncLevelInternal() { 70 + lowestLevel := NONE 71 + for _, w := range l.eventWriters { 72 + if w.GetLevel() < lowestLevel { 73 + lowestLevel = w.GetLevel() 74 + } 75 + } 76 + l.level.Store(int32(lowestLevel)) 77 + 78 + lowestLevel = NONE 79 + for _, w := range l.eventWriters { 80 + if w.Base().Mode.StacktraceLevel < lowestLevel { 81 + lowestLevel = w.GetLevel() 82 + } 83 + } 84 + l.stacktraceLevel.Store(int32(lowestLevel)) 85 + } 86 + 87 + // removeWriterInternal removes a writer from the logger, and stops it if it's not shared 88 + func (l *LoggerImpl) removeWriterInternal(w EventWriter) { 89 + if !w.Base().shared { 90 + eventWriterStopWait(w) // only stop non-shared writers, shared writers are managed by the manager 91 + } 92 + delete(l.eventWriters, w.GetWriterName()) 93 + } 94 + 95 + // AddWriters adds writers to the logger, and starts them. Existing writers will be replaced by new ones. 96 + func (l *LoggerImpl) AddWriters(writer ...EventWriter) { 97 + l.eventWriterMu.Lock() 98 + defer l.eventWriterMu.Unlock() 99 + 100 + for _, w := range writer { 101 + if old, ok := l.eventWriters[w.GetWriterName()]; ok { 102 + l.removeWriterInternal(old) 103 + } 104 + } 105 + 106 + for _, w := range writer { 107 + l.eventWriters[w.GetWriterName()] = w 108 + eventWriterStartGo(l.ctx, w, false) 109 + } 110 + 111 + l.syncLevelInternal() 112 + } 113 + 114 + // RemoveWriter removes a writer from the logger, and the writer is closed and flushed if it is not shared 115 + func (l *LoggerImpl) RemoveWriter(modeName string) error { 116 + l.eventWriterMu.Lock() 117 + defer l.eventWriterMu.Unlock() 118 + 119 + w, ok := l.eventWriters[modeName] 120 + if !ok { 121 + return util.ErrNotExist 122 + } 123 + 124 + l.removeWriterInternal(w) 125 + l.syncLevelInternal() 126 + return nil 127 + } 128 + 129 + // RemoveAllWriters removes all writers from the logger, non-shared writers are closed and flushed 130 + func (l *LoggerImpl) RemoveAllWriters() *LoggerImpl { 131 + l.eventWriterMu.Lock() 132 + defer l.eventWriterMu.Unlock() 133 + 134 + for _, w := range l.eventWriters { 135 + l.removeWriterInternal(w) 136 + } 137 + l.eventWriters = map[string]EventWriter{} 138 + l.syncLevelInternal() 139 + return l 140 + } 141 + 142 + // DumpWriters dumps the writers as a JSON map, it's used for debugging and display purposes. 143 + func (l *LoggerImpl) DumpWriters() map[string]any { 144 + l.eventWriterMu.RLock() 145 + defer l.eventWriterMu.RUnlock() 146 + 147 + writers := make(map[string]any, len(l.eventWriters)) 148 + for k, w := range l.eventWriters { 149 + bs, err := json.Marshal(w.Base().Mode) 150 + if err != nil { 151 + FallbackErrorf("marshal writer %q to dump failed: %v", k, err) 152 + continue 153 + } 154 + m := map[string]any{} 155 + _ = json.Unmarshal(bs, &m) 156 + m["WriterType"] = w.GetWriterType() 157 + writers[k] = m 158 + } 159 + return writers 160 + } 161 + 162 + // Close closes the logger, non-shared writers are closed and flushed 163 + func (l *LoggerImpl) Close() { 164 + l.RemoveAllWriters() 165 + l.ctxCancel() 166 + } 167 + 168 + // IsEnabled returns true if the logger is enabled: it has a working level and has writers 169 + // Fatal is not considered as enabled, because it's a special case and the process just exits 170 + func (l *LoggerImpl) IsEnabled() bool { 171 + l.eventWriterMu.RLock() 172 + defer l.eventWriterMu.RUnlock() 173 + return l.level.Load() < int32(FATAL) && len(l.eventWriters) > 0 174 + } 175 + 176 + // Log prepares the log event, if the level matches, the event will be sent to the writers 177 + func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) { 178 + if Level(l.level.Load()) > level { 179 + return 180 + } 181 + 182 + event := &Event{ 183 + Time: time.Now(), 184 + Level: level, 185 + Caller: "?()", 186 + } 187 + 188 + pc, filename, line, ok := runtime.Caller(skip + 1) 189 + if ok { 190 + fn := runtime.FuncForPC(pc) 191 + if fn != nil { 192 + event.Caller = fn.Name() + "()" 193 + } 194 + } 195 + event.Filename, event.Line = strings.TrimPrefix(filename, projectPackagePrefix), line 196 + 197 + if l.stacktraceLevel.Load() <= int32(level) { 198 + event.Stacktrace = Stack(skip + 1) 199 + } 200 + 201 + labels := getGoroutineLabels() 202 + if labels != nil { 203 + event.GoroutinePid = labels["pid"] 204 + } 205 + 206 + // get a simple text message without color 207 + msgArgs := make([]any, len(logArgs)) 208 + copy(msgArgs, logArgs) 209 + 210 + // handle LogStringer values 211 + for i, v := range msgArgs { 212 + if cv, ok := v.(*ColoredValue); ok { 213 + if s, ok := cv.v.(LogStringer); ok { 214 + cv.v = logStringFormatter{v: s} 215 + } 216 + } else if s, ok := v.(LogStringer); ok { 217 + msgArgs[i] = logStringFormatter{v: s} 218 + } 219 + } 220 + 221 + event.MsgSimpleText = colorSprintf(false, format, msgArgs...) 222 + event.msgFormat = format 223 + event.msgArgs = msgArgs 224 + l.SendLogEvent(event) 225 + } 226 + 227 + func (l *LoggerImpl) GetLevel() Level { 228 + return Level(l.level.Load()) 229 + } 230 + 231 + func NewLoggerWithWriters(ctx context.Context, writer ...EventWriter) *LoggerImpl { 232 + l := &LoggerImpl{} 233 + l.ctx, l.ctxCancel = context.WithCancel(ctx) 234 + l.LevelLogger = BaseLoggerToGeneralLogger(l) 235 + l.eventWriters = map[string]EventWriter{} 236 + l.syncLevelInternal() 237 + l.AddWriters(writer...) 238 + return l 239 + }
+145
modules/log/logger_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "context" 8 + "sync" 9 + "testing" 10 + "time" 11 + 12 + "github.com/stretchr/testify/assert" 13 + ) 14 + 15 + type dummyWriter struct { 16 + *EventWriterBaseImpl 17 + 18 + delay time.Duration 19 + 20 + mu sync.Mutex 21 + logs []string 22 + } 23 + 24 + func (d *dummyWriter) Write(p []byte) (n int, err error) { 25 + if d.delay > 0 { 26 + time.Sleep(d.delay) 27 + } 28 + d.mu.Lock() 29 + defer d.mu.Unlock() 30 + d.logs = append(d.logs, string(p)) 31 + return len(p), nil 32 + } 33 + 34 + func (d *dummyWriter) Close() error { 35 + return nil 36 + } 37 + 38 + func (d *dummyWriter) GetLogs() []string { 39 + d.mu.Lock() 40 + defer d.mu.Unlock() 41 + logs := make([]string, len(d.logs)) 42 + copy(logs, d.logs) 43 + return logs 44 + } 45 + 46 + func newDummyWriter(name string, level Level, delay time.Duration) *dummyWriter { 47 + w := &dummyWriter{ 48 + EventWriterBaseImpl: NewEventWriterBase(name, "dummy", WriterMode{Level: level, Flags: FlagsFromBits(0)}), 49 + } 50 + w.delay = delay 51 + w.Base().OutputWriteCloser = w 52 + return w 53 + } 54 + 55 + func TestLogger(t *testing.T) { 56 + logger := NewLoggerWithWriters(context.Background()) 57 + 58 + dump := logger.DumpWriters() 59 + assert.EqualValues(t, 0, len(dump)) 60 + assert.EqualValues(t, NONE, logger.GetLevel()) 61 + assert.False(t, logger.IsEnabled()) 62 + 63 + w1 := newDummyWriter("dummy-1", DEBUG, 0) 64 + logger.AddWriters(w1) 65 + assert.EqualValues(t, DEBUG, logger.GetLevel()) 66 + 67 + w2 := newDummyWriter("dummy-2", WARN, 200*time.Millisecond) 68 + logger.AddWriters(w2) 69 + assert.EqualValues(t, DEBUG, logger.GetLevel()) 70 + 71 + dump = logger.DumpWriters() 72 + assert.EqualValues(t, 2, len(dump)) 73 + 74 + logger.Trace("trace-level") // this level is not logged 75 + logger.Debug("debug-level") 76 + logger.Error("error-level") 77 + 78 + // w2 is slow, so only w1 has logs 79 + time.Sleep(100 * time.Millisecond) 80 + assert.Equal(t, []string{"debug-level\n", "error-level\n"}, w1.GetLogs()) 81 + assert.Equal(t, []string{}, w2.GetLogs()) 82 + 83 + logger.Close() 84 + 85 + // after Close, all logs are flushed 86 + assert.Equal(t, []string{"debug-level\n", "error-level\n"}, w1.GetLogs()) 87 + assert.Equal(t, []string{"error-level\n"}, w2.GetLogs()) 88 + } 89 + 90 + func TestLoggerPause(t *testing.T) { 91 + logger := NewLoggerWithWriters(context.Background()) 92 + 93 + w1 := newDummyWriter("dummy-1", DEBUG, 0) 94 + logger.AddWriters(w1) 95 + 96 + GetManager().PauseAll() 97 + 98 + logger.Info("info-level") 99 + time.Sleep(100 * time.Millisecond) 100 + assert.Equal(t, []string{}, w1.GetLogs()) 101 + 102 + GetManager().ResumeAll() 103 + 104 + time.Sleep(100 * time.Millisecond) 105 + assert.Equal(t, []string{"info-level\n"}, w1.GetLogs()) 106 + 107 + logger.Close() 108 + } 109 + 110 + type testLogString struct { 111 + Field string 112 + } 113 + 114 + func (t testLogString) LogString() string { 115 + return "log-string" 116 + } 117 + 118 + func TestLoggerLogString(t *testing.T) { 119 + logger := NewLoggerWithWriters(context.Background()) 120 + 121 + w1 := newDummyWriter("dummy-1", DEBUG, 0) 122 + w1.Mode.Colorize = true 123 + logger.AddWriters(w1) 124 + 125 + logger.Info("%s %s %#v %v", testLogString{}, &testLogString{}, testLogString{Field: "detail"}, NewColoredValue(testLogString{}, FgRed)) 126 + logger.Close() 127 + 128 + assert.Equal(t, []string{"log-string log-string log.testLogString{Field:\"detail\"} \x1b[31mlog-string\x1b[0m\n"}, w1.GetLogs()) 129 + } 130 + 131 + func TestLoggerExpressionFilter(t *testing.T) { 132 + logger := NewLoggerWithWriters(context.Background()) 133 + 134 + w1 := newDummyWriter("dummy-1", DEBUG, 0) 135 + w1.Mode.Expression = "foo.*" 136 + logger.AddWriters(w1) 137 + 138 + logger.Info("foo") 139 + logger.Info("bar") 140 + logger.Info("foo bar") 141 + logger.SendLogEvent(&Event{Level: INFO, Filename: "foo.go", MsgSimpleText: "by filename"}) 142 + logger.Close() 143 + 144 + assert.Equal(t, []string{"foo\n", "foo bar\n", "by filename\n"}, w1.GetLogs()) 145 + }
+142
modules/log/manager.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "context" 8 + "fmt" 9 + "sync" 10 + "sync/atomic" 11 + ) 12 + 13 + const DEFAULT = "default" 14 + 15 + // LoggerManager manages loggers and shared event writers 16 + type LoggerManager struct { 17 + ctx context.Context 18 + ctxCancel context.CancelFunc 19 + 20 + mu sync.Mutex 21 + writers map[string]EventWriter 22 + loggers map[string]*LoggerImpl 23 + defaultLogger atomic.Pointer[LoggerImpl] 24 + 25 + pauseMu sync.RWMutex 26 + pauseChan chan struct{} 27 + } 28 + 29 + // GetLogger returns a logger with the given name. If the logger doesn't exist, a new empty one will be created. 30 + func (m *LoggerManager) GetLogger(name string) *LoggerImpl { 31 + if name == DEFAULT { 32 + if logger := m.defaultLogger.Load(); logger != nil { 33 + return logger 34 + } 35 + } 36 + 37 + m.mu.Lock() 38 + defer m.mu.Unlock() 39 + 40 + logger := m.loggers[name] 41 + if logger == nil { 42 + logger = NewLoggerWithWriters(m.ctx) 43 + m.loggers[name] = logger 44 + if name == DEFAULT { 45 + m.defaultLogger.Store(logger) 46 + } 47 + } 48 + 49 + return logger 50 + } 51 + 52 + // PauseAll pauses all event writers 53 + func (m *LoggerManager) PauseAll() { 54 + m.pauseMu.Lock() 55 + m.pauseChan = make(chan struct{}) 56 + m.pauseMu.Unlock() 57 + } 58 + 59 + // ResumeAll resumes all event writers 60 + func (m *LoggerManager) ResumeAll() { 61 + m.pauseMu.Lock() 62 + close(m.pauseChan) 63 + m.pauseChan = nil 64 + m.pauseMu.Unlock() 65 + } 66 + 67 + // GetPauseChan returns a channel for writer pausing 68 + func (m *LoggerManager) GetPauseChan() chan struct{} { 69 + m.pauseMu.RLock() 70 + defer m.pauseMu.RUnlock() 71 + return m.pauseChan 72 + } 73 + 74 + // Close closes the logger manager, all loggers and writers will be closed, the messages are flushed. 75 + func (m *LoggerManager) Close() { 76 + m.mu.Lock() 77 + defer m.mu.Unlock() 78 + 79 + for _, logger := range m.loggers { 80 + logger.Close() 81 + } 82 + m.loggers = map[string]*LoggerImpl{} 83 + 84 + for _, writer := range m.writers { 85 + eventWriterStopWait(writer) 86 + } 87 + m.writers = map[string]EventWriter{} 88 + 89 + m.ctxCancel() 90 + } 91 + 92 + // DumpLoggers returns a map of all loggers and their event writers, for debugging and display purposes. 93 + func (m *LoggerManager) DumpLoggers() map[string]any { 94 + m.mu.Lock() 95 + defer m.mu.Unlock() 96 + 97 + dump := map[string]any{} 98 + for name, logger := range m.loggers { 99 + loggerDump := map[string]any{ 100 + "IsEnabled": logger.IsEnabled(), 101 + "EventWriters": logger.DumpWriters(), 102 + } 103 + dump[name] = loggerDump 104 + } 105 + return dump 106 + } 107 + 108 + // NewSharedWriter creates a new shared event writer, it can be used by multiple loggers, and a shared writer won't be closed if a logger is closed. 109 + func (m *LoggerManager) NewSharedWriter(writerName, writerType string, mode WriterMode) (writer EventWriter, err error) { 110 + m.mu.Lock() 111 + defer m.mu.Unlock() 112 + 113 + if _, ok := m.writers[writerName]; ok { 114 + return nil, fmt.Errorf("log event writer %q has been added before", writerName) 115 + } 116 + 117 + if writer, err = NewEventWriter(writerName, writerType, mode); err != nil { 118 + return nil, err 119 + } 120 + 121 + m.writers[writerName] = writer 122 + eventWriterStartGo(m.ctx, writer, true) 123 + return writer, nil 124 + } 125 + 126 + func (m *LoggerManager) GetSharedWriter(writerName string) EventWriter { 127 + m.mu.Lock() 128 + defer m.mu.Unlock() 129 + return m.writers[writerName] 130 + } 131 + 132 + var loggerManager = NewManager() 133 + 134 + func GetManager() *LoggerManager { 135 + return loggerManager 136 + } 137 + 138 + func NewManager() *LoggerManager { 139 + m := &LoggerManager{writers: map[string]EventWriter{}, loggers: map[string]*LoggerImpl{}} 140 + m.ctx, m.ctxCancel = context.WithCancel(context.Background()) 141 + return m 142 + }
+42
modules/log/manager_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "testing" 8 + 9 + "github.com/stretchr/testify/assert" 10 + ) 11 + 12 + func TestSharedWorker(t *testing.T) { 13 + RegisterEventWriter("dummy", func(writerName string, writerMode WriterMode) EventWriter { 14 + return newDummyWriter(writerName, writerMode.Level, 0) 15 + }) 16 + 17 + m := NewManager() 18 + _, err := m.NewSharedWriter("dummy-1", "dummy", WriterMode{Level: DEBUG, Flags: FlagsFromBits(0)}) 19 + assert.NoError(t, err) 20 + 21 + w := m.GetSharedWriter("dummy-1") 22 + assert.NotNil(t, w) 23 + loggerTest := m.GetLogger("test") 24 + loggerTest.AddWriters(w) 25 + loggerTest.Info("msg-1") 26 + loggerTest.RemoveAllWriters() // the shared writer is not closed here 27 + loggerTest.Info("never seen") 28 + 29 + // the shared writer can still be used later 30 + w = m.GetSharedWriter("dummy-1") 31 + assert.NotNil(t, w) 32 + loggerTest.AddWriters(w) 33 + loggerTest.Info("msg-2") 34 + 35 + m.GetLogger("test-another").AddWriters(w) 36 + m.GetLogger("test-another").Info("msg-3") 37 + 38 + m.Close() 39 + 40 + logs := w.(*dummyWriter).GetLogs() 41 + assert.Equal(t, []string{"msg-1\n", "msg-2\n", "msg-3\n"}, logs) 42 + }
+78
modules/log/misc.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package log 5 + 6 + import ( 7 + "io" 8 + ) 9 + 10 + type baseToLogger struct { 11 + base BaseLogger 12 + } 13 + 14 + // BaseLoggerToGeneralLogger wraps a BaseLogger (which only has Log() function) to a Logger (which has Info() function) 15 + func BaseLoggerToGeneralLogger(b BaseLogger) Logger { 16 + l := &baseToLogger{base: b} 17 + return l 18 + } 19 + 20 + var _ Logger = (*baseToLogger)(nil) 21 + 22 + func (s *baseToLogger) Log(skip int, level Level, format string, v ...any) { 23 + s.base.Log(skip+1, level, format, v...) 24 + } 25 + 26 + func (s *baseToLogger) GetLevel() Level { 27 + return s.base.GetLevel() 28 + } 29 + 30 + func (s *baseToLogger) LevelEnabled(level Level) bool { 31 + return s.base.GetLevel() <= level 32 + } 33 + 34 + func (s *baseToLogger) Trace(format string, v ...any) { 35 + s.base.Log(1, TRACE, format, v...) 36 + } 37 + 38 + func (s *baseToLogger) Debug(format string, v ...any) { 39 + s.base.Log(1, DEBUG, format, v...) 40 + } 41 + 42 + func (s *baseToLogger) Info(format string, v ...any) { 43 + s.base.Log(1, INFO, format, v...) 44 + } 45 + 46 + func (s *baseToLogger) Warn(format string, v ...any) { 47 + s.base.Log(1, WARN, format, v...) 48 + } 49 + 50 + func (s *baseToLogger) Error(format string, v ...any) { 51 + s.base.Log(1, ERROR, format, v...) 52 + } 53 + 54 + func (s *baseToLogger) Critical(format string, v ...any) { 55 + s.base.Log(1, CRITICAL, format, v...) 56 + } 57 + 58 + type PrintfLogger struct { 59 + Logf func(format string, args ...any) 60 + } 61 + 62 + func (p *PrintfLogger) Printf(format string, args ...any) { 63 + p.Logf(format, args...) 64 + } 65 + 66 + type loggerToWriter struct { 67 + logf func(format string, args ...any) 68 + } 69 + 70 + func (p *loggerToWriter) Write(bs []byte) (int, error) { 71 + p.logf("%s", string(bs)) 72 + return len(bs), nil 73 + } 74 + 75 + // LoggerToWriter wraps a log function to an io.Writer 76 + func LoggerToWriter(logf func(format string, args ...any)) io.Writer { 77 + return &loggerToWriter{logf: logf} 78 + }
-104
modules/log/multichannel.go
··· 1 - // Copyright 2020 The Gogs Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "runtime" 9 - "strings" 10 - "time" 11 - ) 12 - 13 - // MultiChannelledLogger is default logger in the Gitea application. 14 - // it can contain several providers and log message into all providers. 15 - type MultiChannelledLogger struct { 16 - LevelLoggerLogger 17 - *MultiChannelledLog 18 - bufferLength int64 19 - } 20 - 21 - // newLogger initializes and returns a new logger. 22 - func newLogger(name string, buffer int64) *MultiChannelledLogger { 23 - l := &MultiChannelledLogger{ 24 - MultiChannelledLog: NewMultiChannelledLog(name, buffer), 25 - bufferLength: buffer, 26 - } 27 - l.LevelLogger = l 28 - return l 29 - } 30 - 31 - // SetLogger sets new logger instance with given logger provider and config. 32 - func (l *MultiChannelledLogger) SetLogger(name, provider, config string) error { 33 - eventLogger, err := NewChannelledLog(l.ctx, name, provider, config, l.bufferLength) 34 - if err != nil { 35 - return fmt.Errorf("failed to create sublogger (%s): %w", name, err) 36 - } 37 - 38 - l.MultiChannelledLog.DelLogger(name) 39 - 40 - err = l.MultiChannelledLog.AddLogger(eventLogger) 41 - if err != nil { 42 - if IsErrDuplicateName(err) { 43 - return fmt.Errorf("%w other names: %v", err, l.MultiChannelledLog.GetEventLoggerNames()) 44 - } 45 - return fmt.Errorf("failed to add sublogger (%s): %w", name, err) 46 - } 47 - 48 - return nil 49 - } 50 - 51 - // DelLogger deletes a sublogger from this logger. 52 - func (l *MultiChannelledLogger) DelLogger(name string) (bool, error) { 53 - return l.MultiChannelledLog.DelLogger(name), nil 54 - } 55 - 56 - // Log msg at the provided level with the provided caller defined by skip (0 being the function that calls this function) 57 - func (l *MultiChannelledLogger) Log(skip int, level Level, format string, v ...interface{}) error { 58 - if l.GetLevel() > level { 59 - return nil 60 - } 61 - caller := "?()" 62 - pc, filename, line, ok := runtime.Caller(skip + 1) 63 - if ok { 64 - // Get caller function name. 65 - fn := runtime.FuncForPC(pc) 66 - if fn != nil { 67 - caller = fn.Name() + "()" 68 - } 69 - } 70 - msg := format 71 - if len(v) > 0 { 72 - msg = ColorSprintf(format, v...) 73 - } 74 - labels := getGoroutineLabels() 75 - if labels != nil { 76 - pid, ok := labels["pid"] 77 - if ok { 78 - msg = "[" + ColorString(FgHiYellow) + pid + ColorString(Reset) + "] " + msg 79 - } 80 - } 81 - stack := "" 82 - if l.GetStacktraceLevel() <= level { 83 - stack = Stack(skip + 1) 84 - } 85 - return l.SendLog(level, caller, strings.TrimPrefix(filename, prefix), line, msg, stack) 86 - } 87 - 88 - // SendLog sends a log event at the provided level with the information given 89 - func (l *MultiChannelledLogger) SendLog(level Level, caller, filename string, line int, msg, stack string) error { 90 - if l.GetLevel() > level { 91 - return nil 92 - } 93 - event := &Event{ 94 - level: level, 95 - caller: caller, 96 - filename: filename, 97 - line: line, 98 - msg: msg, 99 - time: time.Now(), 100 - stacktrace: stack, 101 - } 102 - l.LogEvent(event) //nolint:errcheck 103 - return nil 104 - }
-25
modules/log/provider.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - // LoggerProvider represents behaviors of a logger provider. 7 - type LoggerProvider interface { 8 - Init(config string) error 9 - EventLogger 10 - } 11 - 12 - type loggerProvider func() LoggerProvider 13 - 14 - var providers = make(map[string]loggerProvider) 15 - 16 - // Register registers given logger provider to providers. 17 - func Register(name string, log loggerProvider) { 18 - if log == nil { 19 - panic("log: register provider is nil") 20 - } 21 - if _, dup := providers[name]; dup { 22 - panic("log: register called twice for provider \"" + name + "\"") 23 - } 24 - providers[name] = log 25 - }
-114
modules/log/smtp.go
··· 1 - // Copyright 2014 The Gogs Authors. All rights reserved. 2 - // Copyright 2019 The Gitea Authors. All rights reserved. 3 - // SPDX-License-Identifier: MIT 4 - 5 - package log 6 - 7 - import ( 8 - "fmt" 9 - "net/smtp" 10 - "strings" 11 - 12 - "code.gitea.io/gitea/modules/json" 13 - ) 14 - 15 - type smtpWriter struct { 16 - owner *SMTPLogger 17 - } 18 - 19 - // Write sends the message as an email 20 - func (s *smtpWriter) Write(p []byte) (int, error) { 21 - return s.owner.sendMail(p) 22 - } 23 - 24 - // Close does nothing 25 - func (s *smtpWriter) Close() error { 26 - return nil 27 - } 28 - 29 - // SMTPLogger implements LoggerProvider and is used to send emails via given SMTP-server. 30 - type SMTPLogger struct { 31 - WriterLogger 32 - Username string `json:"Username"` 33 - Password string `json:"password"` 34 - Host string `json:"host"` 35 - Subject string `json:"subject"` 36 - RecipientAddresses []string `json:"sendTos"` 37 - sendMailFn func(string, smtp.Auth, string, []string, []byte) error 38 - } 39 - 40 - // NewSMTPLogger creates smtp writer. 41 - func NewSMTPLogger() LoggerProvider { 42 - s := &SMTPLogger{} 43 - s.Level = TRACE 44 - s.sendMailFn = smtp.SendMail 45 - return s 46 - } 47 - 48 - // Init smtp writer with json config. 49 - // config like: 50 - // 51 - // { 52 - // "Username":"example@gmail.com", 53 - // "password:"password", 54 - // "host":"smtp.gmail.com:465", 55 - // "subject":"email title", 56 - // "sendTos":["email1","email2"], 57 - // "level":LevelError 58 - // } 59 - func (log *SMTPLogger) Init(jsonconfig string) error { 60 - err := json.Unmarshal([]byte(jsonconfig), log) 61 - if err != nil { 62 - return fmt.Errorf("Unable to parse JSON: %w", err) 63 - } 64 - log.NewWriterLogger(&smtpWriter{ 65 - owner: log, 66 - }) 67 - log.sendMailFn = smtp.SendMail 68 - return nil 69 - } 70 - 71 - // WriteMsg writes message in smtp writer. 72 - // it will send an email with subject and only this message. 73 - func (log *SMTPLogger) sendMail(p []byte) (int, error) { 74 - hp := strings.Split(log.Host, ":") 75 - 76 - // Set up authentication information. 77 - auth := smtp.PlainAuth( 78 - "", 79 - log.Username, 80 - log.Password, 81 - hp[0], 82 - ) 83 - // Connect to the server, authenticate, set the sender and recipient, 84 - // and send the email all in one step. 85 - contentType := "Content-Type: text/plain" + "; charset=UTF-8" 86 - mailmsg := []byte("To: " + strings.Join(log.RecipientAddresses, ";") + "\r\nFrom: " + log.Username + "<" + log.Username + 87 - ">\r\nSubject: " + log.Subject + "\r\n" + contentType + "\r\n\r\n") 88 - mailmsg = append(mailmsg, p...) 89 - return len(p), log.sendMailFn( 90 - log.Host, 91 - auth, 92 - log.Username, 93 - log.RecipientAddresses, 94 - mailmsg, 95 - ) 96 - } 97 - 98 - // Flush when log should be flushed 99 - func (log *SMTPLogger) Flush() { 100 - } 101 - 102 - // ReleaseReopen does nothing 103 - func (log *SMTPLogger) ReleaseReopen() error { 104 - return nil 105 - } 106 - 107 - // GetName returns the default name for this implementation 108 - func (log *SMTPLogger) GetName() string { 109 - return "smtp" 110 - } 111 - 112 - func init() { 113 - Register("smtp", NewSMTPLogger) 114 - }
-85
modules/log/smtp_test.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "net/smtp" 9 - "strings" 10 - "testing" 11 - "time" 12 - 13 - "github.com/stretchr/testify/assert" 14 - ) 15 - 16 - func TestSMTPLogger(t *testing.T) { 17 - prefix := "TestPrefix " 18 - level := INFO 19 - flags := LstdFlags | LUTC | Lfuncname 20 - username := "testuser" 21 - password := "testpassword" 22 - host := "testhost" 23 - subject := "testsubject" 24 - sendTos := []string{"testto1", "testto2"} 25 - 26 - logger := NewSMTPLogger() 27 - smtpLogger, ok := logger.(*SMTPLogger) 28 - assert.True(t, ok) 29 - 30 - err := logger.Init(fmt.Sprintf("{\"prefix\":\"%s\",\"level\":\"%s\",\"flags\":%d,\"username\":\"%s\",\"password\":\"%s\",\"host\":\"%s\",\"subject\":\"%s\",\"sendTos\":[\"%s\",\"%s\"]}", prefix, level.String(), flags, username, password, host, subject, sendTos[0], sendTos[1])) 31 - assert.NoError(t, err) 32 - 33 - assert.Equal(t, flags, smtpLogger.Flags) 34 - assert.Equal(t, level, smtpLogger.Level) 35 - assert.Equal(t, level, logger.GetLevel()) 36 - 37 - location, _ := time.LoadLocation("EST") 38 - 39 - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 40 - 41 - dateString := date.UTC().Format("2006/01/02 15:04:05") 42 - 43 - event := Event{ 44 - level: INFO, 45 - msg: "TEST MSG", 46 - caller: "CALLER", 47 - filename: "FULL/FILENAME", 48 - line: 1, 49 - time: date, 50 - } 51 - 52 - expected := fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 53 - 54 - var envToHost string 55 - var envFrom string 56 - var envTo []string 57 - var envMsg []byte 58 - smtpLogger.sendMailFn = func(addr string, a smtp.Auth, from string, to []string, msg []byte) error { 59 - envToHost = addr 60 - envFrom = from 61 - envTo = to 62 - envMsg = msg 63 - return nil 64 - } 65 - 66 - err = logger.LogEvent(&event) 67 - assert.NoError(t, err) 68 - assert.Equal(t, host, envToHost) 69 - assert.Equal(t, username, envFrom) 70 - assert.Equal(t, sendTos, envTo) 71 - assert.Contains(t, string(envMsg), expected) 72 - 73 - logger.Flush() 74 - 75 - event.level = WARN 76 - expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 77 - err = logger.LogEvent(&event) 78 - assert.NoError(t, err) 79 - assert.Equal(t, host, envToHost) 80 - assert.Equal(t, username, envFrom) 81 - assert.Equal(t, sendTos, envTo) 82 - assert.Contains(t, string(envMsg), expected) 83 - 84 - logger.Close() 85 - }
+3 -3
modules/log/stack.go
··· 32 32 } 33 33 34 34 // Print equivalent of debug.Stack() 35 - fmt.Fprintf(buf, "%s:%d (0x%x)\n", filename, lineNumber, programCounter) 35 + _, _ = fmt.Fprintf(buf, "%s:%d (0x%x)\n", filename, lineNumber, programCounter) 36 36 // Now try to print the offending line 37 37 if filename != lastFilename { 38 38 data, err := os.ReadFile(filename) 39 39 if err != nil { 40 - // can't read this sourcefile 40 + // can't read this source file 41 41 // likely we don't have the sourcecode available 42 42 continue 43 43 } 44 44 lines = bytes.Split(data, []byte{'\n'}) 45 45 lastFilename = filename 46 46 } 47 - fmt.Fprintf(buf, "\t%s: %s\n", functionName(programCounter), source(lines, lineNumber)) 47 + _, _ = fmt.Fprintf(buf, "\t%s: %s\n", functionName(programCounter), source(lines, lineNumber)) 48 48 } 49 49 return buf.String() 50 50 }
-269
modules/log/writer.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "bytes" 8 - "fmt" 9 - "io" 10 - "regexp" 11 - "strings" 12 - "sync" 13 - ) 14 - 15 - type byteArrayWriter []byte 16 - 17 - func (b *byteArrayWriter) Write(p []byte) (int, error) { 18 - *b = append(*b, p...) 19 - return len(p), nil 20 - } 21 - 22 - // WriterLogger represent a basic logger for Gitea 23 - type WriterLogger struct { 24 - out io.WriteCloser 25 - mu sync.Mutex 26 - 27 - Level Level `json:"level"` 28 - StacktraceLevel Level `json:"stacktraceLevel"` 29 - Flags int `json:"flags"` 30 - Prefix string `json:"prefix"` 31 - Colorize bool `json:"colorize"` 32 - Expression string `json:"expression"` 33 - regexp *regexp.Regexp 34 - } 35 - 36 - // NewWriterLogger creates a new WriterLogger from the provided WriteCloser. 37 - // Optionally the level can be changed at the same time. 38 - func (logger *WriterLogger) NewWriterLogger(out io.WriteCloser, level ...Level) { 39 - logger.mu.Lock() 40 - defer logger.mu.Unlock() 41 - logger.out = out 42 - switch logger.Flags { 43 - case 0: 44 - logger.Flags = LstdFlags 45 - case -1: 46 - logger.Flags = 0 47 - } 48 - if len(level) > 0 { 49 - logger.Level = level[0] 50 - } 51 - logger.createExpression() 52 - } 53 - 54 - func (logger *WriterLogger) createExpression() { 55 - if len(logger.Expression) > 0 { 56 - var err error 57 - logger.regexp, err = regexp.Compile(logger.Expression) 58 - if err != nil { 59 - logger.regexp = nil 60 - } 61 - } 62 - } 63 - 64 - // GetLevel returns the logging level for this logger 65 - func (logger *WriterLogger) GetLevel() Level { 66 - return logger.Level 67 - } 68 - 69 - // GetStacktraceLevel returns the stacktrace logging level for this logger 70 - func (logger *WriterLogger) GetStacktraceLevel() Level { 71 - return logger.StacktraceLevel 72 - } 73 - 74 - // Copy of cheap integer to fixed-width decimal to ascii from logger. 75 - func itoa(buf *[]byte, i, wid int) { 76 - var logger [20]byte 77 - bp := len(logger) - 1 78 - for i >= 10 || wid > 1 { 79 - wid-- 80 - q := i / 10 81 - logger[bp] = byte('0' + i - q*10) 82 - bp-- 83 - i = q 84 - } 85 - // i < 10 86 - logger[bp] = byte('0' + i) 87 - *buf = append(*buf, logger[bp:]...) 88 - } 89 - 90 - func (logger *WriterLogger) createMsg(buf *[]byte, event *Event) { 91 - *buf = append(*buf, logger.Prefix...) 92 - t := event.time 93 - if logger.Flags&(Ldate|Ltime|Lmicroseconds) != 0 { 94 - if logger.Colorize { 95 - *buf = append(*buf, fgCyanBytes...) 96 - } 97 - if logger.Flags&LUTC != 0 { 98 - t = t.UTC() 99 - } 100 - if logger.Flags&Ldate != 0 { 101 - year, month, day := t.Date() 102 - itoa(buf, year, 4) 103 - *buf = append(*buf, '/') 104 - itoa(buf, int(month), 2) 105 - *buf = append(*buf, '/') 106 - itoa(buf, day, 2) 107 - *buf = append(*buf, ' ') 108 - } 109 - if logger.Flags&(Ltime|Lmicroseconds) != 0 { 110 - hour, min, sec := t.Clock() 111 - itoa(buf, hour, 2) 112 - *buf = append(*buf, ':') 113 - itoa(buf, min, 2) 114 - *buf = append(*buf, ':') 115 - itoa(buf, sec, 2) 116 - if logger.Flags&Lmicroseconds != 0 { 117 - *buf = append(*buf, '.') 118 - itoa(buf, t.Nanosecond()/1e3, 6) 119 - } 120 - *buf = append(*buf, ' ') 121 - } 122 - if logger.Colorize { 123 - *buf = append(*buf, resetBytes...) 124 - } 125 - 126 - } 127 - if logger.Flags&(Lshortfile|Llongfile) != 0 { 128 - if logger.Colorize { 129 - *buf = append(*buf, fgGreenBytes...) 130 - } 131 - file := event.filename 132 - if logger.Flags&Lmedfile == Lmedfile { 133 - startIndex := len(file) - 20 134 - if startIndex > 0 { 135 - file = "..." + file[startIndex:] 136 - } 137 - } else if logger.Flags&Lshortfile != 0 { 138 - startIndex := strings.LastIndexByte(file, '/') 139 - if startIndex > 0 && startIndex < len(file) { 140 - file = file[startIndex+1:] 141 - } 142 - } 143 - *buf = append(*buf, file...) 144 - *buf = append(*buf, ':') 145 - itoa(buf, event.line, -1) 146 - if logger.Flags&(Lfuncname|Lshortfuncname) != 0 { 147 - *buf = append(*buf, ':') 148 - } else { 149 - if logger.Colorize { 150 - *buf = append(*buf, resetBytes...) 151 - } 152 - *buf = append(*buf, ' ') 153 - } 154 - } 155 - if logger.Flags&(Lfuncname|Lshortfuncname) != 0 { 156 - if logger.Colorize { 157 - *buf = append(*buf, fgGreenBytes...) 158 - } 159 - funcname := event.caller 160 - if logger.Flags&Lshortfuncname != 0 { 161 - lastIndex := strings.LastIndexByte(funcname, '.') 162 - if lastIndex > 0 && len(funcname) > lastIndex+1 { 163 - funcname = funcname[lastIndex+1:] 164 - } 165 - } 166 - *buf = append(*buf, funcname...) 167 - if logger.Colorize { 168 - *buf = append(*buf, resetBytes...) 169 - } 170 - *buf = append(*buf, ' ') 171 - 172 - } 173 - if logger.Flags&(Llevel|Llevelinitial) != 0 { 174 - level := strings.ToUpper(event.level.String()) 175 - if logger.Colorize { 176 - *buf = append(*buf, levelToColor[event.level]...) 177 - } 178 - *buf = append(*buf, '[') 179 - if logger.Flags&Llevelinitial != 0 { 180 - *buf = append(*buf, level[0]) 181 - } else { 182 - *buf = append(*buf, level...) 183 - } 184 - *buf = append(*buf, ']') 185 - if logger.Colorize { 186 - *buf = append(*buf, resetBytes...) 187 - } 188 - *buf = append(*buf, ' ') 189 - } 190 - 191 - msg := []byte(event.msg) 192 - if len(msg) > 0 && msg[len(msg)-1] == '\n' { 193 - msg = msg[:len(msg)-1] 194 - } 195 - 196 - pawMode := allowColor 197 - if !logger.Colorize { 198 - pawMode = removeColor 199 - } 200 - 201 - baw := byteArrayWriter(*buf) 202 - (&protectedANSIWriter{ 203 - w: &baw, 204 - mode: pawMode, 205 - }).Write(msg) //nolint:errcheck 206 - *buf = baw 207 - 208 - if event.stacktrace != "" && logger.StacktraceLevel <= event.level { 209 - lines := bytes.Split([]byte(event.stacktrace), []byte("\n")) 210 - if len(lines) > 1 { 211 - for _, line := range lines { 212 - *buf = append(*buf, "\n\t"...) 213 - *buf = append(*buf, line...) 214 - } 215 - } 216 - *buf = append(*buf, '\n') 217 - } 218 - *buf = append(*buf, '\n') 219 - } 220 - 221 - // LogEvent logs the event to the internal writer 222 - func (logger *WriterLogger) LogEvent(event *Event) error { 223 - if logger.Level > event.level { 224 - return nil 225 - } 226 - 227 - logger.mu.Lock() 228 - defer logger.mu.Unlock() 229 - if !logger.Match(event) { 230 - return nil 231 - } 232 - var buf []byte 233 - logger.createMsg(&buf, event) 234 - _, err := logger.out.Write(buf) 235 - return err 236 - } 237 - 238 - // Match checks if the given event matches the logger's regexp expression 239 - func (logger *WriterLogger) Match(event *Event) bool { 240 - if logger.regexp == nil { 241 - return true 242 - } 243 - if logger.regexp.Match([]byte(fmt.Sprintf("%s:%d:%s", event.filename, event.line, event.caller))) { 244 - return true 245 - } 246 - // Match on the non-colored msg - therefore strip out colors 247 - var msg []byte 248 - baw := byteArrayWriter(msg) 249 - (&protectedANSIWriter{ 250 - w: &baw, 251 - mode: removeColor, 252 - }).Write([]byte(event.msg)) //nolint:errcheck 253 - msg = baw 254 - return logger.regexp.Match(msg) 255 - } 256 - 257 - // Close the base logger 258 - func (logger *WriterLogger) Close() { 259 - logger.mu.Lock() 260 - defer logger.mu.Unlock() 261 - if logger.out != nil { 262 - logger.out.Close() 263 - } 264 - } 265 - 266 - // GetName returns empty for these provider loggers 267 - func (logger *WriterLogger) GetName() string { 268 - return "" 269 - }
-275
modules/log/writer_test.go
··· 1 - // Copyright 2019 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package log 5 - 6 - import ( 7 - "fmt" 8 - "strings" 9 - "testing" 10 - "time" 11 - 12 - "github.com/stretchr/testify/assert" 13 - ) 14 - 15 - type CallbackWriteCloser struct { 16 - callback func([]byte, bool) 17 - } 18 - 19 - func (c CallbackWriteCloser) Write(p []byte) (int, error) { 20 - c.callback(p, false) 21 - return len(p), nil 22 - } 23 - 24 - func (c CallbackWriteCloser) Close() error { 25 - c.callback(nil, true) 26 - return nil 27 - } 28 - 29 - func TestBaseLogger(t *testing.T) { 30 - var written []byte 31 - var closed bool 32 - 33 - c := CallbackWriteCloser{ 34 - callback: func(p []byte, close bool) { 35 - written = p 36 - closed = close 37 - }, 38 - } 39 - prefix := "TestPrefix " 40 - b := WriterLogger{ 41 - out: c, 42 - Level: INFO, 43 - Flags: LstdFlags | LUTC, 44 - Prefix: prefix, 45 - } 46 - location, _ := time.LoadLocation("EST") 47 - 48 - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) 49 - 50 - dateString := date.UTC().Format("2006/01/02 15:04:05") 51 - 52 - event := Event{ 53 - level: INFO, 54 - msg: "TEST MSG", 55 - caller: "CALLER", 56 - filename: "FULL/FILENAME", 57 - line: 1, 58 - time: date, 59 - } 60 - 61 - assert.Equal(t, INFO, b.GetLevel()) 62 - 63 - expected := fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 64 - b.LogEvent(&event) 65 - assert.Equal(t, expected, string(written)) 66 - assert.False(t, closed) 67 - written = written[:0] 68 - 69 - event.level = DEBUG 70 - expected = "" 71 - b.LogEvent(&event) 72 - assert.Equal(t, expected, string(written)) 73 - assert.False(t, closed) 74 - 75 - event.level = TRACE 76 - expected = "" 77 - b.LogEvent(&event) 78 - assert.Equal(t, expected, string(written)) 79 - assert.False(t, closed) 80 - 81 - event.level = WARN 82 - expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 83 - b.LogEvent(&event) 84 - assert.Equal(t, expected, string(written)) 85 - assert.False(t, closed) 86 - written = written[:0] 87 - 88 - event.level = ERROR 89 - expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 90 - b.LogEvent(&event) 91 - assert.Equal(t, expected, string(written)) 92 - assert.False(t, closed) 93 - written = written[:0] 94 - 95 - event.level = CRITICAL 96 - expected = fmt.Sprintf("%s%s %s:%d:%s [%c] %s\n", prefix, dateString, event.filename, event.line, event.caller, strings.ToUpper(event.level.String())[0], event.msg) 97 - b.LogEvent(&event) 98 - assert.Equal(t, expected, string(written)) 99 - assert.False(t, closed) 100 - written = written[:0] 101 - 102 - b.Close() 103 - assert.True(t, closed) 104 - } 105 - 106 - func TestBaseLoggerDated(t *testing.T) { 107 - var written []byte 108 - var closed bool 109 - 110 - c := CallbackWriteCloser{ 111 - callback: func(p []byte, close bool) { 112 - written = p 113 - closed = close 114 - }, 115 - } 116 - prefix := "" 117 - b := WriterLogger{ 118 - out: c, 119 - Level: WARN, 120 - Flags: Ldate | Ltime | Lmicroseconds | Lshortfile | Llevel, 121 - Prefix: prefix, 122 - } 123 - 124 - location, _ := time.LoadLocation("EST") 125 - 126 - date := time.Date(2019, time.January, 13, 22, 3, 30, 115, location) 127 - 128 - dateString := date.Format("2006/01/02 15:04:05.000000") 129 - 130 - event := Event{ 131 - level: WARN, 132 - msg: "TEST MESSAGE TEST\n", 133 - caller: "CALLER", 134 - filename: "FULL/FILENAME", 135 - line: 1, 136 - time: date, 137 - } 138 - 139 - assert.Equal(t, WARN, b.GetLevel()) 140 - 141 - expected := fmt.Sprintf("%s%s %s:%d [%s] %s", prefix, dateString, "FILENAME", event.line, strings.ToUpper(event.level.String()), event.msg) 142 - b.LogEvent(&event) 143 - assert.Equal(t, expected, string(written)) 144 - assert.False(t, closed) 145 - written = written[:0] 146 - 147 - event.level = INFO 148 - expected = "" 149 - b.LogEvent(&event) 150 - assert.Equal(t, expected, string(written)) 151 - assert.False(t, closed) 152 - written = written[:0] 153 - 154 - event.level = ERROR 155 - expected = fmt.Sprintf("%s%s %s:%d [%s] %s", prefix, dateString, "FILENAME", event.line, strings.ToUpper(event.level.String()), event.msg) 156 - b.LogEvent(&event) 157 - assert.Equal(t, expected, string(written)) 158 - assert.False(t, closed) 159 - written = written[:0] 160 - 161 - event.level = DEBUG 162 - expected = "" 163 - b.LogEvent(&event) 164 - assert.Equal(t, expected, string(written)) 165 - assert.False(t, closed) 166 - written = written[:0] 167 - 168 - event.level = CRITICAL 169 - expected = fmt.Sprintf("%s%s %s:%d [%s] %s", prefix, dateString, "FILENAME", event.line, strings.ToUpper(event.level.String()), event.msg) 170 - b.LogEvent(&event) 171 - assert.Equal(t, expected, string(written)) 172 - assert.False(t, closed) 173 - written = written[:0] 174 - 175 - event.level = TRACE 176 - expected = "" 177 - b.LogEvent(&event) 178 - assert.Equal(t, expected, string(written)) 179 - assert.False(t, closed) 180 - written = written[:0] 181 - 182 - b.Close() 183 - assert.True(t, closed) 184 - } 185 - 186 - func TestBaseLoggerMultiLineNoFlagsRegexp(t *testing.T) { 187 - var written []byte 188 - var closed bool 189 - 190 - c := CallbackWriteCloser{ 191 - callback: func(p []byte, close bool) { 192 - written = p 193 - closed = close 194 - }, 195 - } 196 - prefix := "" 197 - b := WriterLogger{ 198 - Level: DEBUG, 199 - StacktraceLevel: ERROR, 200 - Flags: -1, 201 - Prefix: prefix, 202 - Expression: "FILENAME", 203 - } 204 - b.NewWriterLogger(c) 205 - 206 - location, _ := time.LoadLocation("EST") 207 - 208 - date := time.Date(2019, time.January, 13, 22, 3, 30, 115, location) 209 - 210 - event := Event{ 211 - level: DEBUG, 212 - msg: "TEST\nMESSAGE\nTEST", 213 - caller: "CALLER", 214 - filename: "FULL/FILENAME", 215 - line: 1, 216 - time: date, 217 - } 218 - 219 - assert.Equal(t, DEBUG, b.GetLevel()) 220 - 221 - expected := "TEST\n\tMESSAGE\n\tTEST\n" 222 - b.LogEvent(&event) 223 - assert.Equal(t, expected, string(written)) 224 - assert.False(t, closed) 225 - written = written[:0] 226 - 227 - event.filename = "ELSEWHERE" 228 - 229 - b.LogEvent(&event) 230 - assert.Equal(t, "", string(written)) 231 - assert.False(t, closed) 232 - written = written[:0] 233 - 234 - event.caller = "FILENAME" 235 - b.LogEvent(&event) 236 - assert.Equal(t, expected, string(written)) 237 - assert.False(t, closed) 238 - written = written[:0] 239 - 240 - event = Event{ 241 - level: DEBUG, 242 - msg: "TEST\nFILENAME\nTEST", 243 - caller: "CALLER", 244 - filename: "FULL/ELSEWHERE", 245 - line: 1, 246 - time: date, 247 - } 248 - expected = "TEST\n\tFILENAME\n\tTEST\n" 249 - b.LogEvent(&event) 250 - assert.Equal(t, expected, string(written)) 251 - assert.False(t, closed) 252 - written = written[:0] 253 - } 254 - 255 - func TestBrokenRegexp(t *testing.T) { 256 - var closed bool 257 - 258 - c := CallbackWriteCloser{ 259 - callback: func(p []byte, close bool) { 260 - closed = close 261 - }, 262 - } 263 - 264 - b := WriterLogger{ 265 - Level: DEBUG, 266 - StacktraceLevel: ERROR, 267 - Flags: -1, 268 - Prefix: prefix, 269 - Expression: "\\", 270 - } 271 - b.NewWriterLogger(c) 272 - assert.Empty(t, b.regexp) 273 - b.Close() 274 - assert.True(t, closed) 275 - }
+7 -7
modules/private/manager.go
··· 75 75 76 76 // LoggerOptions represents the options for the add logger call 77 77 type LoggerOptions struct { 78 - Group string 79 - Name string 78 + Logger string 79 + Writer string 80 80 Mode string 81 81 Config map[string]interface{} 82 82 } 83 83 84 84 // AddLogger adds a logger 85 - func AddLogger(ctx context.Context, group, name, mode string, config map[string]interface{}) ResponseExtra { 85 + func AddLogger(ctx context.Context, logger, writer, mode string, config map[string]interface{}) ResponseExtra { 86 86 reqURL := setting.LocalURL + "api/internal/manager/add-logger" 87 87 req := newInternalRequest(ctx, reqURL, "POST", LoggerOptions{ 88 - Group: group, 89 - Name: name, 88 + Logger: logger, 89 + Writer: writer, 90 90 Mode: mode, 91 91 Config: config, 92 92 }) ··· 94 94 } 95 95 96 96 // RemoveLogger removes a logger 97 - func RemoveLogger(ctx context.Context, group, name string) ResponseExtra { 98 - reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/remove-logger/%s/%s", url.PathEscape(group), url.PathEscape(name)) 97 + func RemoveLogger(ctx context.Context, logger, writer string) ResponseExtra { 98 + reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/remove-logger/%s/%s", url.PathEscape(logger), url.PathEscape(writer)) 99 99 req := newInternalRequest(ctx, reqURL, "POST") 100 100 return requestJSONUserMsg(req, "Removed") 101 101 }
+52
modules/setting/config_provider.go
··· 33 33 Save() error 34 34 } 35 35 36 + // ConfigSectionKey only searches the keys in the given section, but it is O(n). 37 + // ini package has a special behavior: with "[sec] a=1" and an empty "[sec.sub]", 38 + // then in "[sec.sub]", Key()/HasKey() can always see "a=1" because it always tries parent sections. 39 + // It returns nil if the key doesn't exist. 40 + func ConfigSectionKey(sec ConfigSection, key string) *ini.Key { 41 + if sec == nil { 42 + return nil 43 + } 44 + for _, k := range sec.Keys() { 45 + if k.Name() == key { 46 + return k 47 + } 48 + } 49 + return nil 50 + } 51 + 52 + func ConfigSectionKeyString(sec ConfigSection, key string, def ...string) string { 53 + k := ConfigSectionKey(sec, key) 54 + if k != nil && k.String() != "" { 55 + return k.String() 56 + } 57 + if len(def) > 0 { 58 + return def[0] 59 + } 60 + return "" 61 + } 62 + 63 + // ConfigInheritedKey works like ini.Section.Key(), but it always returns a new key instance, it is O(n) because NewKey is O(n) 64 + // and the returned key is safe to be used with "MustXxx", it doesn't change the parent's values. 65 + // Otherwise, ini.Section.Key().MustXxx would pollute the parent section's keys. 66 + // It never returns nil. 67 + func ConfigInheritedKey(sec ConfigSection, key string) *ini.Key { 68 + k := sec.Key(key) 69 + if k != nil && k.String() != "" { 70 + newKey, _ := sec.NewKey(k.Name(), k.String()) 71 + return newKey 72 + } 73 + newKey, _ := sec.NewKey(key, "") 74 + return newKey 75 + } 76 + 77 + func ConfigInheritedKeyString(sec ConfigSection, key string, def ...string) string { 78 + k := sec.Key(key) 79 + if k != nil && k.String() != "" { 80 + return k.String() 81 + } 82 + if len(def) > 0 { 83 + return def[0] 84 + } 85 + return "" 86 + } 87 + 36 88 type iniFileConfigProvider struct { 37 89 opts *Options 38 90 *ini.File
+66
modules/setting/config_provider_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package setting 5 + 6 + import ( 7 + "testing" 8 + 9 + "github.com/stretchr/testify/assert" 10 + ) 11 + 12 + func TestConfigProviderBehaviors(t *testing.T) { 13 + t.Run("BuggyKeyOverwritten", func(t *testing.T) { 14 + cfg, _ := NewConfigProviderFromData(` 15 + [foo] 16 + key = 17 + `) 18 + sec := cfg.Section("foo") 19 + secSub := cfg.Section("foo.bar") 20 + secSub.Key("key").MustString("1") // try to read a key from subsection 21 + assert.Equal(t, "1", sec.Key("key").String()) // TODO: BUGGY! the key in [foo] is overwritten 22 + }) 23 + 24 + t.Run("SubsectionSeeParentKeys", func(t *testing.T) { 25 + cfg, _ := NewConfigProviderFromData(` 26 + [foo] 27 + key = 123 28 + `) 29 + secSub := cfg.Section("foo.bar.xxx") 30 + assert.Equal(t, "123", secSub.Key("key").String()) 31 + }) 32 + } 33 + 34 + func TestConfigProviderHelper(t *testing.T) { 35 + cfg, _ := NewConfigProviderFromData(` 36 + [foo] 37 + empty = 38 + key = 123 39 + `) 40 + 41 + sec := cfg.Section("foo") 42 + secSub := cfg.Section("foo.bar") 43 + 44 + // test empty key 45 + assert.Equal(t, "def", ConfigSectionKeyString(sec, "empty", "def")) 46 + assert.Equal(t, "xyz", ConfigSectionKeyString(secSub, "empty", "xyz")) 47 + 48 + // test non-inherited key, only see the keys in current section 49 + assert.NotNil(t, ConfigSectionKey(sec, "key")) 50 + assert.Nil(t, ConfigSectionKey(secSub, "key")) 51 + 52 + // test default behavior 53 + assert.Equal(t, "123", ConfigSectionKeyString(sec, "key")) 54 + assert.Equal(t, "", ConfigSectionKeyString(secSub, "key")) 55 + assert.Equal(t, "def", ConfigSectionKeyString(secSub, "key", "def")) 56 + 57 + assert.Equal(t, "123", ConfigInheritedKeyString(secSub, "key")) 58 + 59 + // Workaround for ini package's BuggyKeyOverwritten behavior 60 + assert.Equal(t, "", ConfigSectionKeyString(sec, "empty")) 61 + assert.Equal(t, "", ConfigSectionKeyString(secSub, "empty")) 62 + assert.Equal(t, "def", ConfigInheritedKey(secSub, "empty").MustString("def")) 63 + assert.Equal(t, "def", ConfigInheritedKey(secSub, "empty").MustString("xyz")) 64 + assert.Equal(t, "", ConfigSectionKeyString(sec, "empty")) 65 + assert.Equal(t, "def", ConfigSectionKeyString(secSub, "empty")) 66 + }
+1 -1
modules/setting/database.go
··· 92 92 Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0) 93 93 94 94 Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50) 95 - Database.LogSQL = sec.Key("LOG_SQL").MustBool(true) 95 + Database.LogSQL = sec.Key("LOG_SQL").MustBool(false) 96 96 Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10) 97 97 Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) 98 98 Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
+176 -309
modules/setting/log.go
··· 10 10 "path" 11 11 "path/filepath" 12 12 "strings" 13 - "sync" 14 13 15 - "code.gitea.io/gitea/modules/json" 16 14 "code.gitea.io/gitea/modules/log" 17 15 "code.gitea.io/gitea/modules/util" 18 16 ) 19 17 20 - var ( 21 - filenameSuffix = "" 22 - descriptionLock = sync.RWMutex{} 23 - logDescriptions = make(map[string]*LogDescription) 24 - ) 18 + type LogGlobalConfig struct { 19 + RootPath string 25 20 26 - // Log settings 27 - var Log struct { 21 + Mode string 28 22 Level log.Level 29 - StacktraceLogLevel string 30 - RootPath string 31 - EnableSSHLog bool 32 - EnableXORMLog bool 23 + StacktraceLogLevel log.Level 24 + BufferLen int 33 25 34 - DisableRouterLog bool 26 + EnableSSHLog bool 35 27 36 - EnableAccessLog bool 37 28 AccessLogTemplate string 38 - BufferLength int64 39 29 RequestIDHeaders []string 40 30 } 41 31 42 - // GetLogDescriptions returns a race safe set of descriptions 43 - func GetLogDescriptions() map[string]*LogDescription { 44 - descriptionLock.RLock() 45 - defer descriptionLock.RUnlock() 46 - descs := make(map[string]*LogDescription, len(logDescriptions)) 47 - for k, v := range logDescriptions { 48 - subLogDescriptions := make([]SubLogDescription, len(v.SubLogDescriptions)) 49 - copy(subLogDescriptions, v.SubLogDescriptions) 32 + var Log LogGlobalConfig 33 + 34 + 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}}"` 35 + 36 + func loadLogGlobalFrom(rootCfg ConfigProvider) { 37 + sec := rootCfg.Section("log") 38 + 39 + Log.Level = log.LevelFromString(sec.Key("LEVEL").MustString(log.INFO.String())) 40 + Log.StacktraceLogLevel = log.LevelFromString(sec.Key("STACKTRACE_LEVEL").MustString(log.NONE.String())) 41 + Log.BufferLen = sec.Key("BUFFER_LEN").MustInt(10000) 42 + Log.Mode = sec.Key("MODE").MustString("console") 50 43 51 - descs[k] = &LogDescription{ 52 - Name: v.Name, 53 - SubLogDescriptions: subLogDescriptions, 54 - } 44 + Log.RootPath = sec.Key("ROOT_PATH").MustString(path.Join(AppWorkPath, "log")) 45 + if !filepath.IsAbs(Log.RootPath) { 46 + Log.RootPath = filepath.Join(AppWorkPath, Log.RootPath) 55 47 } 56 - return descs 57 - } 48 + Log.RootPath = util.FilePathJoinAbs(Log.RootPath) 58 49 59 - // AddLogDescription adds a set of descriptions to the complete description 60 - func AddLogDescription(key string, description *LogDescription) { 61 - descriptionLock.Lock() 62 - defer descriptionLock.Unlock() 63 - logDescriptions[key] = description 50 + Log.EnableSSHLog = sec.Key("ENABLE_SSH_LOG").MustBool(false) 51 + 52 + Log.AccessLogTemplate = sec.Key("ACCESS_LOG_TEMPLATE").MustString(accessLogTemplateDefault) 53 + Log.RequestIDHeaders = sec.Key("REQUEST_ID_HEADERS").Strings(",") 64 54 } 65 55 66 - // AddSubLogDescription adds a sub log description 67 - func AddSubLogDescription(key string, subLogDescription SubLogDescription) bool { 68 - descriptionLock.Lock() 69 - defer descriptionLock.Unlock() 70 - desc, ok := logDescriptions[key] 71 - if !ok { 72 - return false 56 + func prepareLoggerConfig(rootCfg ConfigProvider) { 57 + sec := rootCfg.Section("log") 58 + 59 + if !sec.HasKey("logger.default.MODE") { 60 + sec.Key("logger.default.MODE").MustString(",") 61 + } 62 + 63 + deprecatedSetting(rootCfg, "log", "ACCESS", "log", "logger.access.MODE", "1.21") 64 + deprecatedSetting(rootCfg, "log", "ENABLE_ACCESS_LOG", "log", "logger.access.MODE", "1.21") 65 + if val := sec.Key("ACCESS").String(); val != "" { 66 + sec.Key("logger.access.MODE").MustString(val) 73 67 } 74 - for i, sub := range desc.SubLogDescriptions { 75 - if sub.Name == subLogDescription.Name { 76 - desc.SubLogDescriptions[i] = subLogDescription 77 - return true 78 - } 68 + if sec.HasKey("ENABLE_ACCESS_LOG") && !sec.Key("ENABLE_ACCESS_LOG").MustBool() { 69 + sec.Key("logger.access.MODE").SetValue("") 79 70 } 80 - desc.SubLogDescriptions = append(desc.SubLogDescriptions, subLogDescription) 81 - return true 82 - } 83 71 84 - // RemoveSubLogDescription removes a sub log description 85 - func RemoveSubLogDescription(key, name string) bool { 86 - descriptionLock.Lock() 87 - defer descriptionLock.Unlock() 88 - desc, ok := logDescriptions[key] 89 - if !ok { 90 - return false 72 + deprecatedSetting(rootCfg, "log", "ROUTER", "log", "logger.router.MODE", "1.21") 73 + deprecatedSetting(rootCfg, "log", "DISABLE_ROUTER_LOG", "log", "logger.router.MODE", "1.21") 74 + if val := sec.Key("ROUTER").String(); val != "" { 75 + sec.Key("logger.router.MODE").MustString(val) 91 76 } 92 - for i, sub := range desc.SubLogDescriptions { 93 - if sub.Name == name { 94 - desc.SubLogDescriptions = append(desc.SubLogDescriptions[:i], desc.SubLogDescriptions[i+1:]...) 95 - return true 96 - } 77 + if !sec.HasKey("logger.router.MODE") { 78 + sec.Key("logger.router.MODE").MustString(",") // use default logger 97 79 } 98 - return false 99 - } 80 + if sec.HasKey("DISABLE_ROUTER_LOG") && sec.Key("DISABLE_ROUTER_LOG").MustBool() { 81 + sec.Key("logger.router.MODE").SetValue("") 82 + } 100 83 101 - type defaultLogOptions struct { 102 - levelName string // LogLevel 103 - flags string 104 - filename string // path.Join(LogRootPath, "gitea.log") 105 - bufferLength int64 106 - disableConsole bool 84 + deprecatedSetting(rootCfg, "log", "XORM", "log", "logger.xorm.MODE", "1.21") 85 + deprecatedSetting(rootCfg, "log", "ENABLE_XORM_LOG", "log", "logger.xorm.MODE", "1.21") 86 + if val := sec.Key("XORM").String(); val != "" { 87 + sec.Key("logger.xorm.MODE").MustString(val) 88 + } 89 + if !sec.HasKey("logger.xorm.MODE") { 90 + sec.Key("logger.xorm.MODE").MustString(",") // use default logger 91 + } 92 + if sec.HasKey("ENABLE_XORM_LOG") && !sec.Key("ENABLE_XORM_LOG").MustBool() { 93 + sec.Key("logger.xorm.MODE").SetValue("") 94 + } 107 95 } 108 96 109 - func newDefaultLogOptions() defaultLogOptions { 110 - return defaultLogOptions{ 111 - levelName: Log.Level.String(), 112 - flags: "stdflags", 113 - filename: filepath.Join(Log.RootPath, "gitea.log"), 114 - bufferLength: 10000, 115 - disableConsole: false, 97 + func LogPrepareFilenameForWriter(fileName, defaultFileName string) string { 98 + if fileName == "" { 99 + fileName = defaultFileName 100 + } 101 + if !filepath.IsAbs(fileName) { 102 + fileName = filepath.Join(Log.RootPath, fileName) 103 + } else { 104 + fileName = filepath.Clean(fileName) 105 + } 106 + if err := os.MkdirAll(filepath.Dir(fileName), os.ModePerm); err != nil { 107 + panic(fmt.Sprintf("unable to create directory for log %q: %v", fileName, err.Error())) 116 108 } 109 + return fileName 117 110 } 118 111 119 - // SubLogDescription describes a sublogger 120 - type SubLogDescription struct { 121 - Name string 122 - Provider string 123 - Config string 124 - } 112 + func loadLogModeByName(rootCfg ConfigProvider, loggerName, modeName string) (writerName, writerType string, writerMode log.WriterMode, err error) { 113 + sec := rootCfg.Section("log." + modeName) 125 114 126 - // LogDescription describes a named logger 127 - type LogDescription struct { 128 - Name string 129 - SubLogDescriptions []SubLogDescription 130 - } 131 - 132 - func getLogLevel(section ConfigSection, key string, defaultValue log.Level) log.Level { 133 - value := section.Key(key).MustString(defaultValue.String()) 134 - return log.FromString(value) 135 - } 115 + writerMode = log.WriterMode{} 116 + writerType = ConfigSectionKeyString(sec, "MODE") 117 + if writerType == "" { 118 + writerType = modeName 119 + } 136 120 137 - func getStacktraceLogLevel(section ConfigSection, key, defaultValue string) string { 138 - value := section.Key(key).MustString(defaultValue) 139 - return log.FromString(value).String() 140 - } 141 - 142 - func loadLogFrom(rootCfg ConfigProvider) { 143 - sec := rootCfg.Section("log") 144 - Log.Level = getLogLevel(sec, "LEVEL", log.INFO) 145 - Log.StacktraceLogLevel = getStacktraceLogLevel(sec, "STACKTRACE_LEVEL", "None") 146 - Log.RootPath = sec.Key("ROOT_PATH").MustString(path.Join(AppWorkPath, "log")) 147 - forcePathSeparator(Log.RootPath) 148 - Log.BufferLength = sec.Key("BUFFER_LEN").MustInt64(10000) 149 - 150 - Log.EnableSSHLog = sec.Key("ENABLE_SSH_LOG").MustBool(false) 151 - Log.EnableAccessLog = sec.Key("ENABLE_ACCESS_LOG").MustBool(false) 152 - Log.AccessLogTemplate = sec.Key("ACCESS_LOG_TEMPLATE").MustString( 153 - `{{.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}}"`, 154 - ) 155 - Log.RequestIDHeaders = sec.Key("REQUEST_ID_HEADERS").Strings(",") 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 ConfigSection, 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 - } 121 + writerName = modeName 122 + defaultFlags := "stdflags" 123 + defaultFilaName := "gitea.log" 124 + if loggerName == "access" { 125 + // "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. 126 + // so "access" logger's writer name is usually "file.access" or "console.access" 127 + writerName += ".access" 128 + defaultFlags = "none" 129 + defaultFilaName = "access.log" 194 130 } 195 131 196 - logConfig := map[string]interface{}{ 197 - "level": level.String(), 198 - "expression": expression, 199 - "prefix": prefix, 200 - "flags": flags, 201 - "stacktraceLevel": stacktraceLevel.String(), 202 - } 132 + writerMode.Level = log.LevelFromString(ConfigInheritedKeyString(sec, "LEVEL", Log.Level.String())) 133 + writerMode.StacktraceLevel = log.LevelFromString(ConfigInheritedKeyString(sec, "STACKTRACE_LEVEL", Log.StacktraceLogLevel.String())) 134 + writerMode.Prefix = ConfigInheritedKeyString(sec, "PREFIX") 135 + writerMode.Expression = ConfigInheritedKeyString(sec, "EXPRESSION") 136 + writerMode.Flags = log.FlagsFromString(ConfigInheritedKeyString(sec, "FLAGS", defaultFlags)) 203 137 204 - // Generate log configuration. 205 - switch mode { 138 + switch writerType { 206 139 case "console": 207 - useStderr := sec.Key("STDERR").MustBool(false) 208 - logConfig["stderr"] = useStderr 140 + useStderr := ConfigInheritedKey(sec, "STDERR").MustBool(false) 141 + defaultCanColor := log.CanColorStdout 209 142 if useStderr { 210 - logConfig["colorize"] = sec.Key("COLORIZE").MustBool(log.CanColorStderr) 211 - } else { 212 - logConfig["colorize"] = sec.Key("COLORIZE").MustBool(log.CanColorStdout) 143 + defaultCanColor = log.CanColorStderr 213 144 } 214 - 145 + writerOption := log.WriterConsoleOption{Stderr: useStderr} 146 + writerMode.Colorize = ConfigInheritedKey(sec, "COLORIZE").MustBool(defaultCanColor) 147 + writerMode.WriterOption = writerOption 215 148 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) 149 + fileName := LogPrepareFilenameForWriter(ConfigInheritedKey(sec, "FILE_NAME").String(), defaultFilaName) 150 + writerOption := log.WriterFileOption{} 151 + writerOption.FileName = fileName + filenameSuffix // FIXME: the suffix doesn't seem right, see its related comments 152 + writerOption.LogRotate = ConfigInheritedKey(sec, "LOG_ROTATE").MustBool(true) 153 + writerOption.MaxSize = 1 << uint(ConfigInheritedKey(sec, "MAX_SIZE_SHIFT").MustInt(28)) 154 + writerOption.DailyRotate = ConfigInheritedKey(sec, "DAILY_ROTATE").MustBool(true) 155 + writerOption.MaxDays = ConfigInheritedKey(sec, "MAX_DAYS").MustInt(7) 156 + writerOption.Compress = ConfigInheritedKey(sec, "COMPRESS").MustBool(true) 157 + writerOption.CompressionLevel = ConfigInheritedKey(sec, "COMPRESSION_LEVEL").MustInt(-1) 158 + writerMode.WriterOption = writerOption 227 159 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) 160 + writerOption := log.WriterConnOption{} 161 + writerOption.ReconnectOnMsg = ConfigInheritedKey(sec, "RECONNECT_ON_MSG").MustBool() 162 + writerOption.Reconnect = ConfigInheritedKey(sec, "RECONNECT").MustBool() 163 + writerOption.Protocol = ConfigInheritedKey(sec, "PROTOCOL").In("tcp", []string{"tcp", "unix", "udp"}) 164 + writerOption.Addr = ConfigInheritedKey(sec, "ADDR").MustString(":7020") 165 + writerMode.WriterOption = writerOption 166 + default: 167 + if !log.HasEventWriter(writerType) { 168 + return "", "", writerMode, fmt.Errorf("invalid log writer type (mode): %s", writerType) 239 169 } 240 - logConfig["sendTos"] = sendTos 241 - logConfig["subject"] = sec.Key("SUBJECT").MustString("Diagnostic message from Gitea") 242 170 } 243 171 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 172 + return writerName, writerType, writerMode, nil 252 173 } 253 174 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 - } 175 + var filenameSuffix = "" 264 176 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 - } 177 + // RestartLogsWithPIDSuffix restarts the logs with a PID suffix on files 178 + // FIXME: it seems not right, it breaks log rotating or log collectors 179 + func RestartLogsWithPIDSuffix() { 180 + filenameSuffix = fmt.Sprintf(".%d", os.Getpid()) 181 + initAllLoggers() // when forking, before restarting, rename logger file and re-init all loggers 182 + } 273 183 274 - provider, config, levelName := generateLogConfig(sec, name, options) 184 + func InitLoggersForTest() { 185 + initAllLoggers() 186 + } 275 187 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 - } 188 + // initAllLoggers creates all the log services 189 + func initAllLoggers() { 190 + initManagedLoggers(log.GetManager(), CfgProvider) 280 191 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 - } 192 + golog.SetFlags(0) 193 + golog.SetPrefix("") 194 + golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) 195 + } 288 196 289 - AddLogDescription(key, &description) 197 + func initManagedLoggers(manager *log.LoggerManager, cfg ConfigProvider) { 198 + loadLogGlobalFrom(cfg) 199 + prepareLoggerConfig(cfg) 290 200 291 - return &description 201 + initLoggerByName(manager, cfg, log.DEFAULT) // default 202 + initLoggerByName(manager, cfg, "access") 203 + initLoggerByName(manager, cfg, "router") 204 + initLoggerByName(manager, cfg, "xorm") 292 205 } 293 206 294 - // initLogFrom initializes logging with settings from configuration provider 295 - func initLogFrom(rootCfg ConfigProvider) { 207 + func initLoggerByName(manager *log.LoggerManager, rootCfg ConfigProvider, loggerName string) { 296 208 sec := rootCfg.Section("log") 297 - options := newDefaultLogOptions() 298 - options.bufferLength = Log.BufferLength 209 + keyPrefix := "logger." + loggerName 299 210 300 - description := LogDescription{ 301 - Name: log.DEFAULT, 211 + disabled := sec.HasKey(keyPrefix+".MODE") && sec.Key(keyPrefix+".MODE").String() == "" 212 + if disabled { 213 + return 302 214 } 303 215 304 - sections := strings.Split(sec.Key("MODE").MustString("console"), ",") 216 + modeVal := sec.Key(keyPrefix + ".MODE").String() 217 + if modeVal == "," { 218 + modeVal = Log.Mode 219 + } 305 220 306 - useConsole := false 307 - for _, name := range sections { 308 - name = strings.TrimSpace(name) 309 - if name == "" { 221 + var eventWriters []log.EventWriter 222 + modes := strings.Split(modeVal, ",") 223 + for _, modeName := range modes { 224 + modeName = strings.TrimSpace(modeName) 225 + if modeName == "" { 226 + continue 227 + } 228 + writerName, writerType, writerMode, err := loadLogModeByName(rootCfg, loggerName, modeName) 229 + if err != nil { 230 + log.FallbackErrorf("Failed to load writer mode %q for logger %s: %v", modeName, loggerName, err) 310 231 continue 311 232 } 312 - if name == "console" { 313 - useConsole = true 233 + if writerMode.BufferLen == 0 { 234 + writerMode.BufferLen = Log.BufferLen 314 235 } 315 - 316 - sec, err := rootCfg.GetSection("log." + name + ".default") 317 - if err != nil { 318 - sec, err = rootCfg.GetSection("log." + name) 236 + eventWriter := manager.GetSharedWriter(writerName) 237 + if eventWriter == nil { 238 + eventWriter, err = manager.NewSharedWriter(writerName, writerType, writerMode) 319 239 if err != nil { 320 - sec, _ = rootCfg.NewSection("log." + name) 240 + log.FallbackErrorf("Failed to create event writer for logger %s: %v", loggerName, err) 241 + continue 321 242 } 322 243 } 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) 244 + eventWriters = append(eventWriters, eventWriter) 332 245 } 333 246 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) 247 + manager.GetLogger(loggerName).RemoveAllWriters().AddWriters(eventWriters...) 353 248 } 354 249 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) 250 + func InitSQLLoggersForCli(level log.Level) { 251 + log.SetConsoleLogger("xorm", "console", level) 376 252 } 377 253 378 - // InitSQLLog initializes xorm logger setting 379 - func InitSQLLog(disableConsole bool) { 380 - initSQLLogFrom(CfgProvider, disableConsole) 254 + func IsAccessLogEnabled() bool { 255 + return log.IsLoggerEnabled("access") 381 256 } 382 257 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 - } 258 + func IsRouteLogEnabled() bool { 259 + return log.IsLoggerEnabled("router") 393 260 }
+387
modules/setting/log_test.go
··· 1 + // Copyright 2019 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package setting 5 + 6 + import ( 7 + "path/filepath" 8 + "strings" 9 + "testing" 10 + 11 + "code.gitea.io/gitea/modules/json" 12 + "code.gitea.io/gitea/modules/log" 13 + 14 + "github.com/stretchr/testify/assert" 15 + "github.com/stretchr/testify/require" 16 + ) 17 + 18 + func initLoggersByConfig(t *testing.T, config string) (*log.LoggerManager, func()) { 19 + oldLogConfig := Log 20 + Log = LogGlobalConfig{} 21 + defer func() { 22 + Log = oldLogConfig 23 + }() 24 + 25 + cfg, err := NewConfigProviderFromData(config) 26 + assert.NoError(t, err) 27 + 28 + manager := log.NewManager() 29 + initManagedLoggers(manager, cfg) 30 + return manager, manager.Close 31 + } 32 + 33 + func toJSON(v interface{}) string { 34 + b, _ := json.MarshalIndent(v, "", "\t") 35 + return string(b) 36 + } 37 + 38 + func TestLogConfigDefault(t *testing.T) { 39 + manager, managerClose := initLoggersByConfig(t, ``) 40 + defer managerClose() 41 + 42 + writerDump := ` 43 + { 44 + "console": { 45 + "BufferLen": 10000, 46 + "Colorize": false, 47 + "Expression": "", 48 + "Flags": "stdflags", 49 + "Level": "info", 50 + "Prefix": "", 51 + "StacktraceLevel": "none", 52 + "WriterOption": { 53 + "Stderr": false 54 + }, 55 + "WriterType": "console" 56 + } 57 + } 58 + ` 59 + 60 + dump := manager.GetLogger(log.DEFAULT).DumpWriters() 61 + require.JSONEq(t, writerDump, toJSON(dump)) 62 + 63 + dump = manager.GetLogger("access").DumpWriters() 64 + require.JSONEq(t, "{}", toJSON(dump)) 65 + 66 + dump = manager.GetLogger("router").DumpWriters() 67 + require.JSONEq(t, writerDump, toJSON(dump)) 68 + 69 + dump = manager.GetLogger("xorm").DumpWriters() 70 + require.JSONEq(t, writerDump, toJSON(dump)) 71 + } 72 + 73 + func TestLogConfigDisable(t *testing.T) { 74 + manager, managerClose := initLoggersByConfig(t, ` 75 + [log] 76 + logger.router.MODE = 77 + logger.xorm.MODE = 78 + `) 79 + defer managerClose() 80 + 81 + writerDump := ` 82 + { 83 + "console": { 84 + "BufferLen": 10000, 85 + "Colorize": false, 86 + "Expression": "", 87 + "Flags": "stdflags", 88 + "Level": "info", 89 + "Prefix": "", 90 + "StacktraceLevel": "none", 91 + "WriterOption": { 92 + "Stderr": false 93 + }, 94 + "WriterType": "console" 95 + } 96 + } 97 + ` 98 + 99 + dump := manager.GetLogger(log.DEFAULT).DumpWriters() 100 + require.JSONEq(t, writerDump, toJSON(dump)) 101 + 102 + dump = manager.GetLogger("access").DumpWriters() 103 + require.JSONEq(t, "{}", toJSON(dump)) 104 + 105 + dump = manager.GetLogger("router").DumpWriters() 106 + require.JSONEq(t, "{}", toJSON(dump)) 107 + 108 + dump = manager.GetLogger("xorm").DumpWriters() 109 + require.JSONEq(t, "{}", toJSON(dump)) 110 + } 111 + 112 + func TestLogConfigLegacyDefault(t *testing.T) { 113 + manager, managerClose := initLoggersByConfig(t, ` 114 + [log] 115 + MODE = console 116 + `) 117 + defer managerClose() 118 + 119 + writerDump := ` 120 + { 121 + "console": { 122 + "BufferLen": 10000, 123 + "Colorize": false, 124 + "Expression": "", 125 + "Flags": "stdflags", 126 + "Level": "info", 127 + "Prefix": "", 128 + "StacktraceLevel": "none", 129 + "WriterOption": { 130 + "Stderr": false 131 + }, 132 + "WriterType": "console" 133 + } 134 + } 135 + ` 136 + 137 + dump := manager.GetLogger(log.DEFAULT).DumpWriters() 138 + require.JSONEq(t, writerDump, toJSON(dump)) 139 + 140 + dump = manager.GetLogger("access").DumpWriters() 141 + require.JSONEq(t, "{}", toJSON(dump)) 142 + 143 + dump = manager.GetLogger("router").DumpWriters() 144 + require.JSONEq(t, writerDump, toJSON(dump)) 145 + 146 + dump = manager.GetLogger("xorm").DumpWriters() 147 + require.JSONEq(t, writerDump, toJSON(dump)) 148 + } 149 + 150 + func TestLogConfigLegacyMode(t *testing.T) { 151 + tempDir := t.TempDir() 152 + 153 + tempPath := func(file string) string { 154 + return filepath.Join(tempDir, file) 155 + } 156 + 157 + manager, managerClose := initLoggersByConfig(t, ` 158 + [log] 159 + ROOT_PATH = `+tempDir+` 160 + MODE = file 161 + ROUTER = file 162 + ACCESS = file 163 + `) 164 + defer managerClose() 165 + 166 + writerDump := ` 167 + { 168 + "file": { 169 + "BufferLen": 10000, 170 + "Colorize": false, 171 + "Expression": "", 172 + "Flags": "stdflags", 173 + "Level": "info", 174 + "Prefix": "", 175 + "StacktraceLevel": "none", 176 + "WriterOption": { 177 + "Compress": true, 178 + "CompressionLevel": -1, 179 + "DailyRotate": true, 180 + "FileName": "$FILENAME", 181 + "LogRotate": true, 182 + "MaxDays": 7, 183 + "MaxSize": 268435456 184 + }, 185 + "WriterType": "file" 186 + } 187 + } 188 + ` 189 + writerDumpAccess := ` 190 + { 191 + "file.access": { 192 + "BufferLen": 10000, 193 + "Colorize": false, 194 + "Expression": "", 195 + "Flags": "none", 196 + "Level": "info", 197 + "Prefix": "", 198 + "StacktraceLevel": "none", 199 + "WriterOption": { 200 + "Compress": true, 201 + "CompressionLevel": -1, 202 + "DailyRotate": true, 203 + "FileName": "$FILENAME", 204 + "LogRotate": true, 205 + "MaxDays": 7, 206 + "MaxSize": 268435456 207 + }, 208 + "WriterType": "file" 209 + } 210 + } 211 + ` 212 + dump := manager.GetLogger(log.DEFAULT).DumpWriters() 213 + require.JSONEq(t, strings.ReplaceAll(writerDump, "$FILENAME", tempPath("gitea.log")), toJSON(dump)) 214 + 215 + dump = manager.GetLogger("access").DumpWriters() 216 + require.JSONEq(t, strings.ReplaceAll(writerDumpAccess, "$FILENAME", tempPath("access.log")), toJSON(dump)) 217 + 218 + dump = manager.GetLogger("router").DumpWriters() 219 + require.JSONEq(t, strings.ReplaceAll(writerDump, "$FILENAME", tempPath("gitea.log")), toJSON(dump)) 220 + } 221 + 222 + func TestLogConfigLegacyModeDisable(t *testing.T) { 223 + manager, managerClose := initLoggersByConfig(t, ` 224 + [log] 225 + ROUTER = file 226 + ACCESS = file 227 + DISABLE_ROUTER_LOG = true 228 + ENABLE_ACCESS_LOG = false 229 + `) 230 + defer managerClose() 231 + 232 + dump := manager.GetLogger("access").DumpWriters() 233 + require.JSONEq(t, "{}", toJSON(dump)) 234 + 235 + dump = manager.GetLogger("router").DumpWriters() 236 + require.JSONEq(t, "{}", toJSON(dump)) 237 + } 238 + 239 + func TestLogConfigNewConfig(t *testing.T) { 240 + manager, managerClose := initLoggersByConfig(t, ` 241 + [log] 242 + logger.access.MODE = console 243 + logger.xorm.MODE = console, console-1 244 + 245 + [log.console] 246 + LEVEL = warn 247 + 248 + [log.console-1] 249 + MODE = console 250 + LEVEL = error 251 + STDERR = true 252 + `) 253 + defer managerClose() 254 + 255 + writerDump := ` 256 + { 257 + "console": { 258 + "BufferLen": 10000, 259 + "Colorize": false, 260 + "Expression": "", 261 + "Flags": "stdflags", 262 + "Level": "warn", 263 + "Prefix": "", 264 + "StacktraceLevel": "none", 265 + "WriterOption": { 266 + "Stderr": false 267 + }, 268 + "WriterType": "console" 269 + }, 270 + "console-1": { 271 + "BufferLen": 10000, 272 + "Colorize": false, 273 + "Expression": "", 274 + "Flags": "stdflags", 275 + "Level": "error", 276 + "Prefix": "", 277 + "StacktraceLevel": "none", 278 + "WriterOption": { 279 + "Stderr": true 280 + }, 281 + "WriterType": "console" 282 + } 283 + } 284 + ` 285 + writerDumpAccess := ` 286 + { 287 + "console.access": { 288 + "BufferLen": 10000, 289 + "Colorize": false, 290 + "Expression": "", 291 + "Flags": "none", 292 + "Level": "warn", 293 + "Prefix": "", 294 + "StacktraceLevel": "none", 295 + "WriterOption": { 296 + "Stderr": false 297 + }, 298 + "WriterType": "console" 299 + } 300 + } 301 + ` 302 + dump := manager.GetLogger("xorm").DumpWriters() 303 + require.JSONEq(t, writerDump, toJSON(dump)) 304 + 305 + dump = manager.GetLogger("access").DumpWriters() 306 + require.JSONEq(t, writerDumpAccess, toJSON(dump)) 307 + } 308 + 309 + func TestLogConfigModeFile(t *testing.T) { 310 + tempDir := t.TempDir() 311 + 312 + tempPath := func(file string) string { 313 + return filepath.Join(tempDir, file) 314 + } 315 + 316 + manager, managerClose := initLoggersByConfig(t, ` 317 + [log] 318 + ROOT_PATH = `+tempDir+` 319 + BUFFER_LEN = 10 320 + MODE = file, file1 321 + 322 + [log.file1] 323 + MODE = file 324 + LEVEL = error 325 + STACKTRACE_LEVEL = fatal 326 + EXPRESSION = filter 327 + FLAGS = medfile 328 + PREFIX = "[Prefix] " 329 + FILE_NAME = file-xxx.log 330 + LOG_ROTATE = false 331 + MAX_SIZE_SHIFT = 1 332 + DAILY_ROTATE = false 333 + MAX_DAYS = 90 334 + COMPRESS = false 335 + COMPRESSION_LEVEL = 4 336 + `) 337 + defer managerClose() 338 + 339 + writerDump := ` 340 + { 341 + "file": { 342 + "BufferLen": 10, 343 + "Colorize": false, 344 + "Expression": "", 345 + "Flags": "stdflags", 346 + "Level": "info", 347 + "Prefix": "", 348 + "StacktraceLevel": "none", 349 + "WriterOption": { 350 + "Compress": true, 351 + "CompressionLevel": -1, 352 + "DailyRotate": true, 353 + "FileName": "$FILENAME-0", 354 + "LogRotate": true, 355 + "MaxDays": 7, 356 + "MaxSize": 268435456 357 + }, 358 + "WriterType": "file" 359 + }, 360 + "file1": { 361 + "BufferLen": 10, 362 + "Colorize": false, 363 + "Expression": "filter", 364 + "Flags": "medfile", 365 + "Level": "error", 366 + "Prefix": "[Prefix] ", 367 + "StacktraceLevel": "fatal", 368 + "WriterOption": { 369 + "Compress": false, 370 + "CompressionLevel": 4, 371 + "DailyRotate": false, 372 + "FileName": "$FILENAME-1", 373 + "LogRotate": false, 374 + "MaxDays": 90, 375 + "MaxSize": 2 376 + }, 377 + "WriterType": "file" 378 + } 379 + } 380 + ` 381 + 382 + dump := manager.GetLogger(log.DEFAULT).DumpWriters() 383 + expected := writerDump 384 + expected = strings.ReplaceAll(expected, "$FILENAME-0", tempPath("gitea.log")) 385 + expected = strings.ReplaceAll(expected, "$FILENAME-1", tempPath("file-xxx.log")) 386 + require.JSONEq(t, expected, toJSON(dump)) 387 + }
-1
modules/setting/repository.go
··· 278 278 Repository.MaxCreationLimit = sec.Key("MAX_CREATION_LIMIT").MustInt(-1) 279 279 Repository.DefaultBranch = sec.Key("DEFAULT_BRANCH").MustString(Repository.DefaultBranch) 280 280 RepoRootPath = sec.Key("ROOT").MustString(path.Join(AppDataPath, "gitea-repositories")) 281 - forcePathSeparator(RepoRootPath) 282 281 if !filepath.IsAbs(RepoRootPath) { 283 282 RepoRootPath = filepath.Join(AppWorkPath, RepoRootPath) 284 283 } else {
-1
modules/setting/server.go
··· 317 317 PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") 318 318 RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) 319 319 OfflineMode = sec.Key("OFFLINE_MODE").MustBool() 320 - Log.DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() 321 320 if len(StaticRootPath) == 0 { 322 321 StaticRootPath = AppWorkPath 323 322 }
+5 -10
modules/setting/setting.go
··· 115 115 116 116 // We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically 117 117 // By default set this logger at Info - we'll change it later, but we need to start with something. 118 - log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "info", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout)) 118 + log.SetConsoleLogger(log.DEFAULT, "console", log.INFO) 119 119 120 120 var err error 121 121 if AppPath, err = getAppPath(); err != nil { 122 122 log.Fatal("Failed to get app path: %v", err) 123 123 } 124 124 AppWorkPath = getWorkPath(AppPath) 125 - } 126 - 127 - func forcePathSeparator(path string) { 128 - if strings.Contains(path, "\\") { 129 - log.Fatal("Do not use '\\' or '\\\\' in paths, instead, please use '/' in all places") 130 - } 131 125 } 132 126 133 127 // IsRunUserMatchCurrentUser returns false if configured run user does not match ··· 218 212 219 213 // loadCommonSettingsFrom loads common configurations from a configuration provider. 220 214 func loadCommonSettingsFrom(cfg ConfigProvider) { 221 - // WARNNING: don't change the sequence except you know what you are doing. 215 + // WARNING: don't change the sequence except you know what you are doing. 222 216 loadRunModeFrom(cfg) 223 - loadLogFrom(cfg) 217 + loadLogGlobalFrom(cfg) 224 218 loadServerFrom(cfg) 225 219 loadSSHFrom(cfg) 226 220 ··· 282 276 283 277 // LoadSettings initializes the settings for normal start up 284 278 func LoadSettings() { 279 + initAllLoggers() 280 + 285 281 loadDBSetting(CfgProvider) 286 282 loadServiceFrom(CfgProvider) 287 283 loadOAuth2ClientFrom(CfgProvider) 288 - InitLogs(false) 289 284 loadCacheFrom(CfgProvider) 290 285 loadSessionFrom(CfgProvider) 291 286 loadCorsFrom(CfgProvider)
+5 -11
modules/ssh/ssh.go
··· 223 223 // validate the cert for this principal 224 224 if err := c.CheckCert(principal, cert); err != nil { 225 225 // User is presenting an invalid certificate - STOP any further processing 226 - if log.IsError() { 227 - log.Error("Invalid Certificate KeyID %s with Signature Fingerprint %s presented for Principal: %s from %s", cert.KeyId, gossh.FingerprintSHA256(cert.SignatureKey), principal, ctx.RemoteAddr()) 228 - } 226 + log.Error("Invalid Certificate KeyID %s with Signature Fingerprint %s presented for Principal: %s from %s", cert.KeyId, gossh.FingerprintSHA256(cert.SignatureKey), principal, ctx.RemoteAddr()) 229 227 log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) 230 228 231 229 return false ··· 239 237 return true 240 238 } 241 239 242 - if log.IsWarn() { 243 - log.Warn("From %s Fingerprint: %s is a certificate, but no valid principals found", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) 244 - log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) 245 - } 240 + log.Warn("From %s Fingerprint: %s is a certificate, but no valid principals found", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) 241 + log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) 246 242 return false 247 243 } 248 244 ··· 253 249 pkey, err := asymkey_model.SearchPublicKeyByContent(ctx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key)))) 254 250 if err != nil { 255 251 if asymkey_model.IsErrKeyNotExist(err) { 256 - if log.IsWarn() { 257 - log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) 258 - log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) 259 - } 252 + log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) 253 + log.Warn("Failed authentication attempt from %s", ctx.RemoteAddr()) 260 254 return false 261 255 } 262 256 log.Error("SearchPublicKeyByContent: %v", err)
+1 -1
modules/templates/htmlrenderer.go
··· 126 126 if msg == "" { 127 127 return 128 128 } 129 - log.FatalWithSkip(1, "Unable to compile templates, %s", msg) 129 + log.Fatal("Unable to compile templates, %s", msg) 130 130 } 131 131 132 132 type templateErrorPrettier struct {
+28 -39
modules/test/logchecker.go
··· 4 4 package test 5 5 6 6 import ( 7 - "strconv" 7 + "context" 8 + "fmt" 8 9 "strings" 9 10 "sync" 10 11 "sync/atomic" ··· 14 15 ) 15 16 16 17 type LogChecker struct { 17 - logger *log.MultiChannelledLogger 18 - loggerName string 19 - eventLoggerName string 18 + *log.EventWriterBaseImpl 20 19 21 20 filterMessages []string 22 21 filtered []bool ··· 27 26 mu sync.Mutex 28 27 } 29 28 30 - func (lc *LogChecker) LogEvent(event *log.Event) error { 29 + func (lc *LogChecker) Run(ctx context.Context) { 30 + for { 31 + select { 32 + case <-ctx.Done(): 33 + return 34 + case event, ok := <-lc.Queue: 35 + if !ok { 36 + return 37 + } 38 + lc.checkLogEvent(event) 39 + } 40 + } 41 + } 42 + 43 + func (lc *LogChecker) checkLogEvent(event *log.EventFormatted) { 31 44 lc.mu.Lock() 32 45 defer lc.mu.Unlock() 33 46 for i, msg := range lc.filterMessages { 34 - if strings.Contains(event.GetMsg(), msg) { 47 + if strings.Contains(event.Origin.MsgSimpleText, msg) { 35 48 lc.filtered[i] = true 36 49 } 37 50 } 38 - if strings.Contains(event.GetMsg(), lc.stopMark) { 51 + if strings.Contains(event.Origin.MsgSimpleText, lc.stopMark) { 39 52 lc.stopped = true 40 53 } 41 - return nil 42 - } 43 - 44 - func (lc *LogChecker) Close() {} 45 - 46 - func (lc *LogChecker) Flush() {} 47 - 48 - func (lc *LogChecker) GetLevel() log.Level { 49 - return log.TRACE 50 - } 51 - 52 - func (lc *LogChecker) GetStacktraceLevel() log.Level { 53 - return log.NONE 54 - } 55 - 56 - func (lc *LogChecker) GetName() string { 57 - return lc.eventLoggerName 58 - } 59 - 60 - func (lc *LogChecker) ReleaseReopen() error { 61 - return nil 62 54 } 63 55 64 56 var checkerIndex int64 65 57 66 - func NewLogChecker(loggerName string) (logChecker *LogChecker, cancel func()) { 67 - logger := log.GetLogger(loggerName) 58 + func NewLogChecker(namePrefix string) (logChecker *LogChecker, cancel func()) { 59 + logger := log.GetManager().GetLogger(namePrefix) 68 60 newCheckerIndex := atomic.AddInt64(&checkerIndex, 1) 69 - lc := &LogChecker{ 70 - logger: logger, 71 - loggerName: loggerName, 72 - eventLoggerName: "TestLogChecker-" + strconv.FormatInt(newCheckerIndex, 10), 73 - } 74 - if err := logger.AddLogger(lc); err != nil { 75 - panic(err) // it's impossible 76 - } 77 - return lc, func() { _, _ = logger.DelLogger(lc.GetName()) } 61 + writerName := namePrefix + "-" + fmt.Sprint(newCheckerIndex) 62 + 63 + lc := &LogChecker{} 64 + lc.EventWriterBaseImpl = log.NewEventWriterBase(writerName, "test-log-checker", log.WriterMode{}) 65 + logger.AddWriters(lc) 66 + return lc, func() { _ = logger.RemoveWriter(writerName) } 78 67 } 79 68 80 69 // Filter will make the `Check` function to check if these logs are outputted.
-2
modules/test/logchecker_test.go
··· 13 13 ) 14 14 15 15 func TestLogChecker(t *testing.T) { 16 - _ = log.NewLogger(1000, "console", "console", `{"level":"info","stacktracelevel":"NONE","stderr":true}`) 17 - 18 16 lc, cleanup := NewLogChecker(log.DEFAULT) 19 17 defer cleanup() 20 18
+26 -72
modules/testlogger/testlogger.go
··· 13 13 "testing" 14 14 "time" 15 15 16 - "code.gitea.io/gitea/modules/json" 17 16 "code.gitea.io/gitea/modules/log" 18 17 "code.gitea.io/gitea/modules/queue" 19 18 ) ··· 24 23 SlowFlush = 5 * time.Second 25 24 ) 26 25 27 - // TestLogger is a logger which will write to the testing log 28 - type TestLogger struct { 29 - log.WriterLogger 30 - } 31 - 32 26 var WriterCloser = &testLoggerWriterCloser{} 33 27 34 28 type testLoggerWriterCloser struct { 35 29 sync.RWMutex 36 - t []*testing.TB 30 + t []testing.TB 37 31 } 38 32 39 - func (w *testLoggerWriterCloser) pushT(t *testing.TB) { 33 + func (w *testLoggerWriterCloser) pushT(t testing.TB) { 40 34 w.Lock() 41 35 w.t = append(w.t, t) 42 36 w.Unlock() ··· 48 42 w.RLock() 49 43 defer w.RUnlock() 50 44 51 - var t *testing.TB 45 + var t testing.TB 52 46 if len(w.t) > 0 { 53 47 t = w.t[len(w.t)-1] 54 48 } ··· 57 51 p = p[:len(p)-1] 58 52 } 59 53 60 - if t == nil || *t == nil { 54 + if t == nil { 61 55 // if there is no running test, the log message should be outputted to console, to avoid losing important information. 62 56 // the "???" prefix is used to match the "===" and "+++" in PrintCurrentTest 63 57 return fmt.Fprintf(os.Stdout, "??? [TestLogger] %s\n", p) 64 58 } 65 59 66 - defer func() { 67 - err := recover() 68 - if err == nil { 69 - return 70 - } 71 - var errString string 72 - errErr, ok := err.(error) 73 - if ok { 74 - errString = errErr.Error() 75 - } else { 76 - errString, ok = err.(string) 77 - } 78 - if !ok { 79 - panic(err) 80 - } 81 - if !strings.HasPrefix(errString, "Log in goroutine after ") { 82 - panic(err) 83 - } 84 - }() 85 - 86 - (*t).Log(string(p)) 60 + t.Log(string(p)) 87 61 return len(p), nil 88 62 } 89 63 ··· 106 80 if t == nil { 107 81 continue 108 82 } 109 - fmt.Fprintf(os.Stdout, "Unclosed logger writer in test: %s", (*t).Name()) 110 - (*t).Errorf("Unclosed logger writer in test: %s", (*t).Name()) 83 + _, _ = fmt.Fprintf(os.Stdout, "Unclosed logger writer in test: %s", t.Name()) 84 + t.Errorf("Unclosed logger writer in test: %s", t.Name()) 111 85 } 112 86 w.t = nil 113 87 } ··· 124 98 _, filename, line, _ := runtime.Caller(actualSkip) 125 99 126 100 if log.CanColorStdout { 127 - fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", fmt.Formatter(log.NewColoredValue(t.Name())), strings.TrimPrefix(filename, prefix), line) 101 + _, _ = fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", fmt.Formatter(log.NewColoredValue(t.Name())), strings.TrimPrefix(filename, prefix), line) 128 102 } else { 129 - fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", t.Name(), strings.TrimPrefix(filename, prefix), line) 103 + _, _ = fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", t.Name(), strings.TrimPrefix(filename, prefix), line) 130 104 } 131 - WriterCloser.pushT(&t) 105 + WriterCloser.pushT(t) 132 106 return func() { 133 107 took := time.Since(start) 134 108 if took > SlowTest { 135 109 if log.CanColorStdout { 136 - fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgYellow)), fmt.Formatter(log.NewColoredValue(took, log.Bold, log.FgYellow))) 110 + _, _ = fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgYellow)), fmt.Formatter(log.NewColoredValue(took, log.Bold, log.FgYellow))) 137 111 } else { 138 - fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", t.Name(), took) 112 + _, _ = fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", t.Name(), took) 139 113 } 140 114 } 141 115 timer := time.AfterFunc(SlowFlush, func() { 142 116 if log.CanColorStdout { 143 - fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), SlowFlush) 117 + _, _ = fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), SlowFlush) 144 118 } else { 145 - fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", t.Name(), SlowFlush) 119 + _, _ = fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", t.Name(), SlowFlush) 146 120 } 147 121 }) 148 122 if err := queue.GetManager().FlushAll(context.Background(), time.Minute); err != nil { ··· 152 126 flushTook := time.Since(start) - took 153 127 if flushTook > SlowFlush { 154 128 if log.CanColorStdout { 155 - fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), fmt.Formatter(log.NewColoredValue(flushTook, log.Bold, log.FgRed))) 129 + _, _ = fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), fmt.Formatter(log.NewColoredValue(flushTook, log.Bold, log.FgRed))) 156 130 } else { 157 - fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook) 131 + _, _ = fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook) 158 132 } 159 133 } 160 134 WriterCloser.popT() ··· 168 142 args[i] = log.NewColoredValue(args[i]) 169 143 } 170 144 } 171 - fmt.Fprintf(os.Stdout, "\t"+format, args...) 145 + _, _ = fmt.Fprintf(os.Stdout, "\t"+format, args...) 172 146 } 173 147 174 - // NewTestLogger creates a TestLogger as a log.LoggerProvider 175 - func NewTestLogger() log.LoggerProvider { 176 - logger := &TestLogger{} 177 - logger.Colorize = log.CanColorStdout 178 - logger.Level = log.TRACE 179 - return logger 148 + // TestLogEventWriter is a logger which will write to the testing log 149 + type TestLogEventWriter struct { 150 + *log.EventWriterBaseImpl 180 151 } 181 152 182 - // Init inits connection writer with json config. 183 - // json config only need key "level". 184 - func (log *TestLogger) Init(config string) error { 185 - err := json.Unmarshal([]byte(config), log) 186 - if err != nil { 187 - return err 188 - } 189 - log.NewWriterLogger(WriterCloser) 190 - return nil 191 - } 192 - 193 - // Flush when log should be flushed 194 - func (log *TestLogger) Flush() { 195 - } 196 - 197 - // ReleaseReopen does nothing 198 - func (log *TestLogger) ReleaseReopen() error { 199 - return nil 200 - } 201 - 202 - // GetName returns the default name for this implementation 203 - func (log *TestLogger) GetName() string { 204 - return "test" 153 + // NewTestLoggerWriter creates a TestLogEventWriter as a log.LoggerProvider 154 + func NewTestLoggerWriter(name string, mode log.WriterMode) log.EventWriter { 155 + w := &TestLogEventWriter{} 156 + w.EventWriterBaseImpl = log.NewEventWriterBase(name, "test-log-writer", mode) 157 + w.OutputWriteCloser = WriterCloser 158 + return w 205 159 } 206 160 207 161 func init() {
+246
modules/util/rotatingfilewriter/writer.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package rotatingfilewriter 5 + 6 + import ( 7 + "bufio" 8 + "compress/gzip" 9 + "errors" 10 + "fmt" 11 + "os" 12 + "path/filepath" 13 + "strings" 14 + "sync" 15 + "time" 16 + 17 + "code.gitea.io/gitea/modules/graceful/releasereopen" 18 + "code.gitea.io/gitea/modules/util" 19 + ) 20 + 21 + type Options struct { 22 + Rotate bool 23 + MaximumSize int64 24 + RotateDaily bool 25 + KeepDays int 26 + Compress bool 27 + CompressionLevel int 28 + } 29 + 30 + type RotatingFileWriter struct { 31 + mu sync.Mutex 32 + fd *os.File 33 + 34 + currentSize int64 35 + openDate int 36 + 37 + options Options 38 + 39 + cancelReleaseReopen func() 40 + } 41 + 42 + var ErrorPrintf func(format string, args ...interface{}) 43 + 44 + // errorf tries to print error messages. Since this writer could be used by a logger system, this is the last chance to show the error in some cases 45 + func errorf(format string, args ...interface{}) { 46 + if ErrorPrintf != nil { 47 + ErrorPrintf("rotatingfilewriter: "+format+"\n", args...) 48 + } 49 + } 50 + 51 + // Open creates a new rotating file writer. 52 + // Notice: if a file is opened by two rotators, there will be conflicts when rotating. 53 + // In the future, there should be "rotating file manager" 54 + func Open(filename string, options *Options) (*RotatingFileWriter, error) { 55 + if options == nil { 56 + options = &Options{} 57 + } 58 + 59 + rfw := &RotatingFileWriter{ 60 + options: *options, 61 + } 62 + 63 + if err := rfw.open(filename); err != nil { 64 + return nil, err 65 + } 66 + 67 + rfw.cancelReleaseReopen = releasereopen.GetManager().Register(rfw) 68 + return rfw, nil 69 + } 70 + 71 + func (rfw *RotatingFileWriter) Write(b []byte) (int, error) { 72 + if rfw.options.Rotate && ((rfw.options.MaximumSize > 0 && rfw.currentSize >= rfw.options.MaximumSize) || (rfw.options.RotateDaily && time.Now().Day() != rfw.openDate)) { 73 + if err := rfw.DoRotate(); err != nil { 74 + // if this writer is used by a logger system, it's the logger system's responsibility to handle/show the error 75 + return 0, err 76 + } 77 + } 78 + 79 + n, err := rfw.fd.Write(b) 80 + if err == nil { 81 + rfw.currentSize += int64(n) 82 + } 83 + return n, err 84 + } 85 + 86 + func (rfw *RotatingFileWriter) Flush() error { 87 + return rfw.fd.Sync() 88 + } 89 + 90 + func (rfw *RotatingFileWriter) Close() error { 91 + rfw.mu.Lock() 92 + if rfw.cancelReleaseReopen != nil { 93 + rfw.cancelReleaseReopen() 94 + rfw.cancelReleaseReopen = nil 95 + } 96 + rfw.mu.Unlock() 97 + return rfw.fd.Close() 98 + } 99 + 100 + func (rfw *RotatingFileWriter) open(filename string) error { 101 + fd, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o660) 102 + if err != nil { 103 + return err 104 + } 105 + 106 + rfw.fd = fd 107 + 108 + finfo, err := fd.Stat() 109 + if err != nil { 110 + return err 111 + } 112 + rfw.currentSize = finfo.Size() 113 + rfw.openDate = finfo.ModTime().Day() 114 + 115 + return nil 116 + } 117 + 118 + func (rfw *RotatingFileWriter) ReleaseReopen() error { 119 + return errors.Join( 120 + rfw.fd.Close(), 121 + rfw.open(rfw.fd.Name()), 122 + ) 123 + } 124 + 125 + // DoRotate the log file creating a backup like xx.2013-01-01.2 126 + func (rfw *RotatingFileWriter) DoRotate() error { 127 + if !rfw.options.Rotate { 128 + return nil 129 + } 130 + 131 + rfw.mu.Lock() 132 + defer rfw.mu.Unlock() 133 + 134 + prefix := fmt.Sprintf("%s.%s.", rfw.fd.Name(), time.Now().Format("2006-01-02")) 135 + 136 + var err error 137 + fname := "" 138 + for i := 1; err == nil && i <= 999; i++ { 139 + fname = prefix + fmt.Sprintf("%03d", i) 140 + _, err = os.Lstat(fname) 141 + if rfw.options.Compress && err != nil { 142 + _, err = os.Lstat(fname + ".gz") 143 + } 144 + } 145 + // return error if the last file checked still existed 146 + if err == nil { 147 + return fmt.Errorf("cannot find free file to rename %s", rfw.fd.Name()) 148 + } 149 + 150 + fd := rfw.fd 151 + if err := fd.Close(); err != nil { // close file before rename 152 + return err 153 + } 154 + 155 + if err := util.Rename(fd.Name(), fname); err != nil { 156 + return err 157 + } 158 + 159 + if rfw.options.Compress { 160 + go func() { 161 + err := compressOldFile(fname, rfw.options.CompressionLevel) 162 + if err != nil { 163 + errorf("DoRotate: %v", err) 164 + } 165 + }() 166 + } 167 + 168 + if err := rfw.open(fd.Name()); err != nil { 169 + return err 170 + } 171 + 172 + go deleteOldFiles( 173 + filepath.Dir(fd.Name()), 174 + filepath.Base(fd.Name()), 175 + time.Now().AddDate(0, 0, -rfw.options.KeepDays), 176 + ) 177 + 178 + return nil 179 + } 180 + 181 + func compressOldFile(fname string, compressionLevel int) error { 182 + reader, err := os.Open(fname) 183 + if err != nil { 184 + return fmt.Errorf("compressOldFile: failed to open existing file %s: %w", fname, err) 185 + } 186 + defer reader.Close() 187 + 188 + buffer := bufio.NewReader(reader) 189 + fnameGz := fname + ".gz" 190 + fw, err := os.OpenFile(fnameGz, os.O_WRONLY|os.O_CREATE, 0o660) 191 + if err != nil { 192 + return fmt.Errorf("compressOldFile: failed to open new file %s: %w", fnameGz, err) 193 + } 194 + defer fw.Close() 195 + 196 + zw, err := gzip.NewWriterLevel(fw, compressionLevel) 197 + if err != nil { 198 + return fmt.Errorf("compressOldFile: failed to create gzip writer: %w", err) 199 + } 200 + defer zw.Close() 201 + 202 + _, err = buffer.WriteTo(zw) 203 + if err != nil { 204 + _ = zw.Close() 205 + _ = fw.Close() 206 + _ = util.Remove(fname + ".gz") 207 + return fmt.Errorf("compressOldFile: failed to write to gz file: %w", err) 208 + } 209 + _ = reader.Close() 210 + 211 + err = util.Remove(fname) 212 + if err != nil { 213 + return fmt.Errorf("compressOldFile: failed to delete old file: %w", err) 214 + } 215 + return nil 216 + } 217 + 218 + func deleteOldFiles(dir, prefix string, removeBefore time.Time) { 219 + err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) (returnErr error) { 220 + defer func() { 221 + if r := recover(); r != nil { 222 + returnErr = fmt.Errorf("unable to delete old file '%s', error: %+v", path, r) 223 + } 224 + }() 225 + 226 + if err != nil { 227 + return err 228 + } 229 + if d.IsDir() { 230 + return nil 231 + } 232 + info, err := d.Info() 233 + if err != nil { 234 + return err 235 + } 236 + if info.ModTime().Before(removeBefore) { 237 + if strings.HasPrefix(filepath.Base(path), prefix) { 238 + return util.Remove(path) 239 + } 240 + } 241 + return nil 242 + }) 243 + if err != nil { 244 + errorf("deleteOldFiles: failed to delete old file: %v", err) 245 + } 246 + }
+48
modules/util/rotatingfilewriter/writer_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package rotatingfilewriter 5 + 6 + import ( 7 + "compress/gzip" 8 + "io" 9 + "os" 10 + "path/filepath" 11 + "testing" 12 + 13 + "github.com/stretchr/testify/assert" 14 + ) 15 + 16 + func TestCompressOldFile(t *testing.T) { 17 + tmpDir := t.TempDir() 18 + fname := filepath.Join(tmpDir, "test") 19 + nonGzip := filepath.Join(tmpDir, "test-nonGzip") 20 + 21 + f, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY, 0o660) 22 + assert.NoError(t, err) 23 + ng, err := os.OpenFile(nonGzip, os.O_CREATE|os.O_WRONLY, 0o660) 24 + assert.NoError(t, err) 25 + 26 + for i := 0; i < 999; i++ { 27 + f.WriteString("This is a test file\n") 28 + ng.WriteString("This is a test file\n") 29 + } 30 + f.Close() 31 + ng.Close() 32 + 33 + err = compressOldFile(fname, gzip.DefaultCompression) 34 + assert.NoError(t, err) 35 + 36 + _, err = os.Lstat(fname + ".gz") 37 + assert.NoError(t, err) 38 + 39 + f, err = os.Open(fname + ".gz") 40 + assert.NoError(t, err) 41 + zr, err := gzip.NewReader(f) 42 + assert.NoError(t, err) 43 + data, err := io.ReadAll(zr) 44 + assert.NoError(t, err) 45 + original, err := os.ReadFile(nonGzip) 46 + assert.NoError(t, err) 47 + assert.Equal(t, original, data) 48 + }
+18 -14
modules/web/routing/logger.go
··· 5 5 6 6 import ( 7 7 "net/http" 8 + "strings" 8 9 "time" 9 10 10 11 "code.gitea.io/gitea/modules/context" ··· 25 26 } 26 27 27 28 var ( 28 - startMessage = log.NewColoredValueBytes("started ", log.DEBUG.Color()) 29 - slowMessage = log.NewColoredValueBytes("slow ", log.WARN.Color()) 30 - pollingMessage = log.NewColoredValueBytes("polling ", log.INFO.Color()) 31 - failedMessage = log.NewColoredValueBytes("failed ", log.WARN.Color()) 32 - completedMessage = log.NewColoredValueBytes("completed", log.INFO.Color()) 33 - unknownHandlerMessage = log.NewColoredValueBytes("completed", log.ERROR.Color()) 29 + startMessage = log.NewColoredValue("started ", log.DEBUG.ColorAttributes()...) 30 + slowMessage = log.NewColoredValue("slow ", log.WARN.ColorAttributes()...) 31 + pollingMessage = log.NewColoredValue("polling ", log.INFO.ColorAttributes()...) 32 + failedMessage = log.NewColoredValue("failed ", log.WARN.ColorAttributes()...) 33 + completedMessage = log.NewColoredValue("completed", log.INFO.ColorAttributes()...) 34 + unknownHandlerMessage = log.NewColoredValue("completed", log.ERROR.ColorAttributes()...) 34 35 ) 35 36 36 37 func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) { 37 38 return func(trigger Event, record *requestRecord) { 38 39 if trigger == StartEvent { 39 - if !logger.IsTrace() { 40 + if !logger.LevelEnabled(log.TRACE) { 40 41 // for performance, if the "started" message shouldn't be logged, we just return as early as possible 41 42 // developers can set the router log level to TRACE to get the "started" request messages. 42 43 return ··· 59 60 60 61 if trigger == StillExecutingEvent { 61 62 message := slowMessage 62 - level := log.WARN 63 + logf := logger.Warn 63 64 if isLongPolling { 64 - level = log.INFO 65 + logf = logger.Info 65 66 message = pollingMessage 66 67 } 67 - _ = logger.Log(0, level, "router: %s %v %s for %s, elapsed %v @ %s", 68 + logf("router: %s %v %s for %s, elapsed %v @ %s", 68 69 message, 69 70 log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr, 70 71 log.ColoredTime(time.Since(record.startTime)), ··· 74 75 } 75 76 76 77 if panicErr != nil { 77 - _ = logger.Log(0, log.WARN, "router: %s %v %s for %s, panic in %v @ %s, err=%v", 78 + logger.Warn("router: %s %v %s for %s, panic in %v @ %s, err=%v", 78 79 failedMessage, 79 80 log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr, 80 81 log.ColoredTime(time.Since(record.startTime)), ··· 88 89 if v, ok := record.responseWriter.(context.ResponseWriter); ok { 89 90 status = v.Status() 90 91 } 91 - level := log.INFO 92 + logf := log.Info 93 + if strings.HasPrefix(req.RequestURI, "/assets/") { 94 + logf = log.Trace 95 + } 92 96 message := completedMessage 93 97 if isUnknownHandler { 94 - level = log.ERROR 98 + logf = log.Error 95 99 message = unknownHandlerMessage 96 100 } 97 101 98 - _ = logger.Log(0, level, "router: %s %v %s for %s, %v %v in %v @ %s", 102 + logf("router: %s %v %s for %s, %v %v in %v @ %s", 99 103 message, 100 104 log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr, 101 105 log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(record.startTime)),
+2 -7
options/locale/locale_en-US.ini
··· 3036 3036 config.git_gc_timeout = GC Operation Timeout 3037 3037 3038 3038 config.log_config = Log Configuration 3039 - config.log_mode = Log Mode 3040 - config.own_named_logger = Named Logger 3041 - config.routes_to_default_logger = Routes To Default Logger 3042 - config.go_log = Uses Go Log (redirected to default) 3043 - config.router_log_mode = Router Log Mode 3039 + config.logger_name_fmt = Logger: %s 3044 3040 config.disabled_logger = Disabled 3045 3041 config.access_log_mode = Access Log Mode 3046 - config.access_log_template = Template 3047 - config.xorm_log_mode = XORM Log Mode 3042 + config.access_log_template = Access Log Template 3048 3043 config.xorm_log_sql = Log SQL 3049 3044 3050 3045 config.get_setting_failed = Get setting %s failed
+2 -2
routers/common/middleware.go
··· 60 60 handlers = append(handlers, proxy.ForwardedHeaders(opt)) 61 61 } 62 62 63 - if !setting.Log.DisableRouterLog { 63 + if setting.IsRouteLogEnabled() { 64 64 handlers = append(handlers, routing.NewLoggerHandler()) 65 65 } 66 66 67 - if setting.Log.EnableAccessLog { 67 + if setting.IsAccessLogEnabled() { 68 68 handlers = append(handlers, context.AccessLogger()) 69 69 } 70 70
+1 -1
routers/private/internal.go
··· 73 73 r.Post("/manager/release-and-reopen-logging", ReleaseReopenLogging) 74 74 r.Post("/manager/set-log-sql", SetLogSQL) 75 75 r.Post("/manager/add-logger", bind(private.LoggerOptions{}), AddLogger) 76 - r.Post("/manager/remove-logger/{group}/{name}", RemoveLogger) 76 + r.Post("/manager/remove-logger/{logger}/{writer}", RemoveLogger) 77 77 r.Get("/manager/processes", Processes) 78 78 r.Post("/mail/send", SendEmail) 79 79 r.Post("/restore_repo", RestoreRepo)
+72 -54
routers/private/manager.go
··· 10 10 "code.gitea.io/gitea/models/db" 11 11 "code.gitea.io/gitea/modules/context" 12 12 "code.gitea.io/gitea/modules/graceful" 13 - "code.gitea.io/gitea/modules/json" 13 + "code.gitea.io/gitea/modules/graceful/releasereopen" 14 14 "code.gitea.io/gitea/modules/log" 15 15 "code.gitea.io/gitea/modules/private" 16 16 "code.gitea.io/gitea/modules/queue" ··· 46 46 47 47 // PauseLogging pauses logging 48 48 func PauseLogging(ctx *context.PrivateContext) { 49 - log.Pause() 49 + log.GetManager().PauseAll() 50 50 ctx.PlainText(http.StatusOK, "success") 51 51 } 52 52 53 53 // ResumeLogging resumes logging 54 54 func ResumeLogging(ctx *context.PrivateContext) { 55 - log.Resume() 55 + log.GetManager().ResumeAll() 56 56 ctx.PlainText(http.StatusOK, "success") 57 57 } 58 58 59 59 // ReleaseReopenLogging releases and reopens logging files 60 60 func ReleaseReopenLogging(ctx *context.PrivateContext) { 61 - if err := log.ReleaseReopen(); err != nil { 61 + if err := releasereopen.GetManager().ReleaseReopen(); err != nil { 62 62 ctx.JSON(http.StatusInternalServerError, private.Response{ 63 63 Err: fmt.Sprintf("Error during release and reopen: %v", err), 64 64 }) ··· 75 75 76 76 // RemoveLogger removes a logger 77 77 func RemoveLogger(ctx *context.PrivateContext) { 78 - group := ctx.Params("group") 79 - name := ctx.Params("name") 80 - ok, err := log.GetLogger(group).DelLogger(name) 78 + logger := ctx.Params("logger") 79 + writer := ctx.Params("writer") 80 + err := log.GetManager().GetLogger(logger).RemoveWriter(writer) 81 81 if err != nil { 82 82 ctx.JSON(http.StatusInternalServerError, private.Response{ 83 - Err: fmt.Sprintf("Failed to remove logger: %s %s %v", group, name, err), 83 + Err: fmt.Sprintf("Failed to remove log writer: %s %s %v", logger, writer, err), 84 84 }) 85 85 return 86 86 } 87 - if ok { 88 - setting.RemoveSubLogDescription(group, name) 89 - } 90 - ctx.PlainText(http.StatusOK, fmt.Sprintf("Removed %s %s", group, name)) 87 + ctx.PlainText(http.StatusOK, fmt.Sprintf("Removed %s %s", logger, writer)) 91 88 } 92 89 93 90 // AddLogger adds a logger 94 91 func AddLogger(ctx *context.PrivateContext) { 95 92 opts := web.GetForm(ctx).(*private.LoggerOptions) 96 - if len(opts.Group) == 0 { 97 - opts.Group = log.DEFAULT 93 + 94 + if len(opts.Logger) == 0 { 95 + opts.Logger = log.DEFAULT 98 96 } 99 - if _, ok := opts.Config["flags"]; !ok { 100 - switch opts.Group { 97 + 98 + writerMode := log.WriterMode{} 99 + writerType := opts.Mode 100 + 101 + var flags string 102 + var ok bool 103 + if flags, ok = opts.Config["flags"].(string); !ok { 104 + switch opts.Logger { 101 105 case "access": 102 - opts.Config["flags"] = log.FlagsFromString("") 106 + flags = "" 103 107 case "router": 104 - opts.Config["flags"] = log.FlagsFromString("date,time") 108 + flags = "date,time" 105 109 default: 106 - opts.Config["flags"] = log.FlagsFromString("stdflags") 110 + flags = "stdflags" 107 111 } 108 112 } 113 + writerMode.Flags = log.FlagsFromString(flags) 109 114 110 - if _, ok := opts.Config["colorize"]; !ok && opts.Mode == "console" { 115 + if writerMode.Colorize, ok = opts.Config["colorize"].(bool); !ok && opts.Mode == "console" { 111 116 if _, ok := opts.Config["stderr"]; ok { 112 - opts.Config["colorize"] = log.CanColorStderr 117 + writerMode.Colorize = log.CanColorStderr 113 118 } else { 114 - opts.Config["colorize"] = log.CanColorStdout 119 + writerMode.Colorize = log.CanColorStdout 115 120 } 116 121 } 117 122 118 - if _, ok := opts.Config["level"]; !ok { 119 - opts.Config["level"] = setting.Log.Level 123 + writerMode.Level = setting.Log.Level 124 + if level, ok := opts.Config["level"].(string); ok { 125 + writerMode.Level = log.LevelFromString(level) 120 126 } 121 127 122 - if _, ok := opts.Config["stacktraceLevel"]; !ok { 123 - opts.Config["stacktraceLevel"] = setting.Log.StacktraceLogLevel 128 + writerMode.StacktraceLevel = setting.Log.StacktraceLogLevel 129 + if stacktraceLevel, ok := opts.Config["level"].(string); ok { 130 + writerMode.StacktraceLevel = log.LevelFromString(stacktraceLevel) 124 131 } 125 132 126 - if opts.Mode == "file" { 127 - if _, ok := opts.Config["maxsize"]; !ok { 128 - opts.Config["maxsize"] = 1 << 28 133 + writerMode.Prefix, _ = opts.Config["prefix"].(string) 134 + writerMode.Expression, _ = opts.Config["expression"].(string) 135 + 136 + switch writerType { 137 + case "console": 138 + writerOption := log.WriterConsoleOption{} 139 + writerOption.Stderr, _ = opts.Config["stderr"].(bool) 140 + writerMode.WriterOption = writerOption 141 + case "file": 142 + writerOption := log.WriterFileOption{} 143 + fileName, _ := opts.Config["filename"].(string) 144 + writerOption.FileName = setting.LogPrepareFilenameForWriter(fileName, opts.Writer+".log") 145 + writerOption.LogRotate = opts.Config["rotate"].(bool) 146 + maxSizeShift, _ := opts.Config["maxsize"].(int) 147 + if maxSizeShift == 0 { 148 + maxSizeShift = 28 129 149 } 130 - if _, ok := opts.Config["maxdays"]; !ok { 131 - opts.Config["maxdays"] = 7 150 + writerOption.MaxSize = 1 << maxSizeShift 151 + writerOption.DailyRotate, _ = opts.Config["daily"].(bool) 152 + writerOption.MaxDays, _ = opts.Config["maxdays"].(int) 153 + if writerOption.MaxDays == 0 { 154 + writerOption.MaxDays = 7 132 155 } 133 - if _, ok := opts.Config["compressionLevel"]; !ok { 134 - opts.Config["compressionLevel"] = -1 156 + writerOption.Compress, _ = opts.Config["compress"].(bool) 157 + writerOption.CompressionLevel, _ = opts.Config["compressionLevel"].(int) 158 + if writerOption.CompressionLevel == 0 { 159 + writerOption.CompressionLevel = -1 135 160 } 161 + writerMode.WriterOption = writerOption 162 + case "conn": 163 + writerOption := log.WriterConnOption{} 164 + writerOption.ReconnectOnMsg, _ = opts.Config["reconnectOnMsg"].(bool) 165 + writerOption.Reconnect, _ = opts.Config["reconnect"].(bool) 166 + writerOption.Protocol, _ = opts.Config["net"].(string) 167 + writerOption.Addr, _ = opts.Config["address"].(string) 168 + writerMode.WriterOption = writerOption 169 + default: 170 + panic(fmt.Sprintf("invalid log writer mode: %s", writerType)) 136 171 } 137 - 138 - bufferLen := setting.Log.BufferLength 139 - byteConfig, err := json.Marshal(opts.Config) 172 + writer, err := log.NewEventWriter(opts.Writer, writerType, writerMode) 140 173 if err != nil { 141 - log.Error("Failed to marshal log configuration: %v %v", opts.Config, err) 174 + log.Error("Failed to create new log writer: %v", err) 142 175 ctx.JSON(http.StatusInternalServerError, private.Response{ 143 - Err: fmt.Sprintf("Failed to marshal log configuration: %v %v", opts.Config, err), 176 + Err: fmt.Sprintf("Failed to create new log writer: %v", err), 144 177 }) 145 178 return 146 179 } 147 - config := string(byteConfig) 148 - 149 - if err := log.NewNamedLogger(opts.Group, bufferLen, opts.Name, opts.Mode, config); err != nil { 150 - log.Error("Failed to create new named logger: %s %v", config, err) 151 - ctx.JSON(http.StatusInternalServerError, private.Response{ 152 - Err: fmt.Sprintf("Failed to create new named logger: %s %v", config, err), 153 - }) 154 - return 155 - } 156 - 157 - setting.AddSubLogDescription(opts.Group, setting.SubLogDescription{ 158 - Name: opts.Name, 159 - Provider: opts.Mode, 160 - Config: config, 161 - }) 162 - 180 + log.GetManager().GetLogger(opts.Logger).AddWriters(writer) 163 181 ctx.PlainText(http.StatusOK, "success") 164 182 }
+2 -5
routers/web/admin/config.go
··· 117 117 ctx.Data["AppBuiltWith"] = setting.AppBuiltWith 118 118 ctx.Data["Domain"] = setting.Domain 119 119 ctx.Data["OfflineMode"] = setting.OfflineMode 120 - ctx.Data["DisableRouterLog"] = setting.Log.DisableRouterLog 121 120 ctx.Data["RunUser"] = setting.RunUser 122 121 ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode) 123 122 ctx.Data["GitVersion"] = git.VersionInfo() ··· 182 181 } 183 182 184 183 ctx.Data["EnvVars"] = envVars 185 - ctx.Data["Loggers"] = setting.GetLogDescriptions() 186 - ctx.Data["EnableAccessLog"] = setting.Log.EnableAccessLog 187 184 ctx.Data["AccessLogTemplate"] = setting.Log.AccessLogTemplate 188 - ctx.Data["DisableRouterLog"] = setting.Log.DisableRouterLog 189 - ctx.Data["EnableXORMLog"] = setting.Log.EnableXORMLog 190 185 ctx.Data["LogSQL"] = setting.Database.LogSQL 186 + 187 + ctx.Data["Loggers"] = log.GetManager().DumpLoggers() 191 188 192 189 ctx.HTML(http.StatusOK, tplConfig) 193 190 }
+3 -3
routers/web/repo/issue.go
··· 2742 2742 log.Trace("Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n"+ 2743 2743 "User in Repo has Permissions: %-+v", 2744 2744 ctx.Doer, 2745 - log.NewColoredIDValue(issue.PosterID), 2745 + issue.PosterID, 2746 2746 issueType, 2747 2747 ctx.Repo.Repository, 2748 2748 ctx.Repo.Permission) ··· 3020 3020 log.Trace("Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n"+ 3021 3021 "User in Repo has Permissions: %-+v", 3022 3022 ctx.Doer, 3023 - log.NewColoredIDValue(issue.PosterID), 3023 + issue.PosterID, 3024 3024 issueType, 3025 3025 ctx.Repo.Repository, 3026 3026 ctx.Repo.Permission) ··· 3122 3122 log.Trace("Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n"+ 3123 3123 "User in Repo has Permissions: %-+v", 3124 3124 ctx.Doer, 3125 - log.NewColoredIDValue(comment.Issue.PosterID), 3125 + comment.Issue.PosterID, 3126 3126 issueType, 3127 3127 ctx.Repo.Repository, 3128 3128 ctx.Repo.Permission)
+1 -1
routers/web/repo/issue_watch.go
··· 29 29 log.Trace("Permission Denied: User %-v not the Poster (ID: %d) and cannot read %s in Repo %-v.\n"+ 30 30 "User in Repo has Permissions: %-+v", 31 31 ctx.Doer, 32 - log.NewColoredIDValue(issue.PosterID), 32 + issue.PosterID, 33 33 issueType, 34 34 ctx.Repo.Repository, 35 35 ctx.Repo.Permission)
+3 -5
services/migrations/codebase.go
··· 115 115 return fmt.Sprintf("migration from codebase server %s %s/%s", d.baseURL, d.project, d.repoName) 116 116 } 117 117 118 - // ColorFormat provides a basic color format for a GogsDownloader 119 - func (d *CodebaseDownloader) ColorFormat(s fmt.State) { 118 + func (d *CodebaseDownloader) LogString() string { 120 119 if d == nil { 121 - log.ColorFprintf(s, "<nil: CodebaseDownloader>") 122 - return 120 + return "<CodebaseDownloader nil>" 123 121 } 124 - log.ColorFprintf(s, "migration from codebase server %s %s/%s", d.baseURL, d.project, d.repoName) 122 + return fmt.Sprintf("<CodebaseDownloader %s %s/%s>", d.baseURL, d.project, d.repoName) 125 123 } 126 124 127 125 // FormatCloneURL add authentication into remote URLs
+3 -5
services/migrations/gitbucket.go
··· 62 62 return fmt.Sprintf("migration from gitbucket server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) 63 63 } 64 64 65 - // ColorFormat provides a basic color format for a GitBucketDownloader 66 - func (g *GitBucketDownloader) ColorFormat(s fmt.State) { 65 + func (g *GitBucketDownloader) LogString() string { 67 66 if g == nil { 68 - log.ColorFprintf(s, "<nil: GitBucketDownloader>") 69 - return 67 + return "<GitBucketDownloader nil>" 70 68 } 71 - log.ColorFprintf(s, "migration from gitbucket server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) 69 + return fmt.Sprintf("<GitBucketDownloader %s %s/%s>", g.baseURL, g.repoOwner, g.repoName) 72 70 } 73 71 74 72 // NewGitBucketDownloader creates a GitBucket downloader
+3 -5
services/migrations/gitea_downloader.go
··· 134 134 return fmt.Sprintf("migration from gitea server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) 135 135 } 136 136 137 - // ColorFormat provides a basic color format for a GiteaDownloader 138 - func (g *GiteaDownloader) ColorFormat(s fmt.State) { 137 + func (g *GiteaDownloader) LogString() string { 139 138 if g == nil { 140 - log.ColorFprintf(s, "<nil: GiteaDownloader>") 141 - return 139 + return "<GiteaDownloader nil>" 142 140 } 143 - log.ColorFprintf(s, "migration from gitea server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) 141 + return fmt.Sprintf("<GiteaDownloader %s %s/%s>", g.baseURL, g.repoOwner, g.repoName) 144 142 } 145 143 146 144 // GetRepoInfo returns a repository information
+3 -5
services/migrations/github.go
··· 125 125 return fmt.Sprintf("migration from github server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) 126 126 } 127 127 128 - // ColorFormat provides a basic color format for a GithubDownloader 129 - func (g *GithubDownloaderV3) ColorFormat(s fmt.State) { 128 + func (g *GithubDownloaderV3) LogString() string { 130 129 if g == nil { 131 - log.ColorFprintf(s, "<nil: GithubDownloaderV3>") 132 - return 130 + return "<GithubDownloaderV3 nil>" 133 131 } 134 - log.ColorFprintf(s, "migration from github server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) 132 + return fmt.Sprintf("<GithubDownloaderV3 %s %s/%s>", g.baseURL, g.repoOwner, g.repoName) 135 133 } 136 134 137 135 func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) {
+3 -5
services/migrations/gitlab.go
··· 137 137 return fmt.Sprintf("migration from gitlab server %s [%d]/%s", g.baseURL, g.repoID, g.repoName) 138 138 } 139 139 140 - // ColorFormat provides a basic color format for a GitlabDownloader 141 - func (g *GitlabDownloader) ColorFormat(s fmt.State) { 140 + func (g *GitlabDownloader) LogString() string { 142 141 if g == nil { 143 - log.ColorFprintf(s, "<nil: GitlabDownloader>") 144 - return 142 + return "<GitlabDownloader nil>" 145 143 } 146 - log.ColorFprintf(s, "migration from gitlab server %s [%d]/%s", g.baseURL, g.repoID, g.repoName) 144 + return fmt.Sprintf("<GitlabDownloader %s [%d]/%s>", g.baseURL, g.repoID, g.repoName) 147 145 } 148 146 149 147 // SetContext set context
+3 -5
services/migrations/gogs.go
··· 77 77 return fmt.Sprintf("migration from gogs server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) 78 78 } 79 79 80 - // ColorFormat provides a basic color format for a GogsDownloader 81 - func (g *GogsDownloader) ColorFormat(s fmt.State) { 80 + func (g *GogsDownloader) LogString() string { 82 81 if g == nil { 83 - log.ColorFprintf(s, "<nil: GogsDownloader>") 84 - return 82 + return "<GogsDownloader nil>" 85 83 } 86 - log.ColorFprintf(s, "migration from gogs server %s %s/%s", g.baseURL, g.repoOwner, g.repoName) 84 + return fmt.Sprintf("<GogsDownloader %s %s/%s>", g.baseURL, g.repoOwner, g.repoName) 87 85 } 88 86 89 87 // SetContext set context
+3 -5
services/migrations/onedev.go
··· 114 114 return fmt.Sprintf("migration from oneDev server %s [%d]/%s", d.baseURL, d.repoID, d.repoName) 115 115 } 116 116 117 - // ColorFormat provides a basic color format for a OneDevDownloader 118 - func (d *OneDevDownloader) ColorFormat(s fmt.State) { 117 + func (d *OneDevDownloader) LogString() string { 119 118 if d == nil { 120 - log.ColorFprintf(s, "<nil: OneDevDownloader>") 121 - return 119 + return "<OneDevDownloader nil>" 122 120 } 123 - log.ColorFprintf(s, "migration from oneDev server %s [%d]/%s", d.baseURL, d.repoID, d.repoName) 121 + return fmt.Sprintf("<OneDevDownloader %s [%d]/%s>", d.baseURL, d.repoID, d.repoName) 124 122 } 125 123 126 124 func (d *OneDevDownloader) callAPI(endpoint string, parameter map[string]string, result interface{}) error {
+11 -54
templates/admin/config.tmpl
··· 361 361 </h4> 362 362 <div class="ui attached table segment"> 363 363 <dl class="dl-horizontal admin-dl-horizontal"> 364 - {{range .Loggers.default.SubLogDescriptions}} 365 - <dt>{{$.locale.Tr "admin.config.log_mode"}}</dt> 366 - <dd>{{.Name}} ({{.Provider}})</dd> 367 - <dt>{{$.locale.Tr "admin.config.log_config"}}</dt> 368 - <dd><pre>{{JsonUtils.PrettyIndent .Config}}</pre></dd> 364 + {{if .Loggers.xorm.IsEnabled}} 365 + <dt>{{$.locale.Tr "admin.config.xorm_log_sql"}}</dt> 366 + <dd>{{if $.LogSQL}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> 369 367 {{end}} 370 - <div class="ui divider"></div> 371 - <dt>{{$.locale.Tr "admin.config.router_log_mode"}}</dt> 372 - {{if .DisableRouterLog}} 373 - <dd>{{$.locale.Tr "admin.config.disabled_logger"}}</dd> 374 - {{else}} 375 - {{if .Loggers.router.SubLogDescriptions}} 376 - <dd>{{$.locale.Tr "admin.config.own_named_logger"}}</dd> 377 - {{range .Loggers.router.SubLogDescriptions}} 378 - <dt>{{$.locale.Tr "admin.config.log_mode"}}</dt> 379 - <dd>{{.Name}} ({{.Provider}})</dd> 380 - <dt>{{$.locale.Tr "admin.config.log_config"}}</dt> 381 - <dd><pre>{{JsonUtils.PrettyIndent .Config}}</pre></dd> 382 - {{end}} 383 - {{else}} 384 - <dd>{{$.locale.Tr "admin.config.routes_to_default_logger"}}</dd> 385 - {{end}} 386 - {{end}} 387 - <div class="ui divider"></div> 388 - <dt>{{$.locale.Tr "admin.config.access_log_mode"}}</dt> 389 - {{if .EnableAccessLog}} 390 - {{if .Loggers.access.SubLogDescriptions}} 391 - <dd>{{$.locale.Tr "admin.config.own_named_logger"}}</dd> 392 - {{range .Loggers.access.SubLogDescriptions}} 393 - <dt>{{$.locale.Tr "admin.config.log_mode"}}</dt> 394 - <dd>{{.Name}} ({{.Provider}})</dd> 395 - <dt>{{$.locale.Tr "admin.config.log_config"}}</dt> 396 - <dd><pre>{{JsonUtils.PrettyIndent .Config}}</pre></dd> 397 - {{end}} 398 - {{else}} 399 - <dd>{{$.locale.Tr "admin.config.routes_to_default_logger"}}</dd> 400 - {{end}} 368 + 369 + {{if .Loggers.access.IsEnabled}} 401 370 <dt>{{$.locale.Tr "admin.config.access_log_template"}}</dt> 402 371 <dd><code>{{$.AccessLogTemplate}}</code></dd> 403 - {{else}} 404 - <dd>{{$.locale.Tr "admin.config.disabled_logger"}}</dd> 405 372 {{end}} 406 - <div class="ui divider"></div> 407 - <dt>{{$.locale.Tr "admin.config.xorm_log_mode"}}</dt> 408 - {{if .EnableXORMLog}} 409 - {{if .Loggers.xorm.SubLogDescriptions}} 410 - <dd>{{$.locale.Tr "admin.config.own_named_logger"}}</dd> 411 - {{range .Loggers.xorm.SubLogDescriptions}} 412 - <dt>{{$.locale.Tr "admin.config.log_mode"}}</dt> 413 - <dd>{{.Name}} ({{.Provider}})</dd> 414 - <dt>{{$.locale.Tr "admin.config.log_config"}}</dt> 415 - <dd><pre>{{JsonUtils.PrettyIndent .Config}}</pre></dd> 416 - {{end}} 373 + 374 + {{range $loggerName, $loggerDetail := .Loggers}} 375 + <dt>{{$.locale.Tr "admin.config.logger_name_fmt" $loggerName}}</dt> 376 + {{if $loggerDetail.IsEnabled}} 377 + <dd><pre class="gt-m-0">{{$loggerDetail.EventWriters | JsonUtils.EncodeToString | JsonUtils.PrettyIndent}}</pre></dd> 417 378 {{else}} 418 - <dd>{{$.locale.Tr "admin.config.routes_to_default_logger"}}</dd> 379 + <dd>{{$.locale.Tr "admin.config.disabled_logger"}}</dd> 419 380 {{end}} 420 - <dt>{{$.locale.Tr "admin.config.xorm_log_sql"}}</dt> 421 - <dd>{{if $.LogSQL}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> 422 - {{else}} 423 - <dd>{{$.locale.Tr "admin.config.disabled_logger"}}</dd> 424 381 {{end}} 425 382 </dl> 426 383 </div>
+1 -1
tests/e2e/e2e_test.go
··· 31 31 var c *web.Route 32 32 33 33 func TestMain(m *testing.M) { 34 - defer log.Close() 34 + defer log.GetManager().Close() 35 35 36 36 managerCtx, cancel := context.WithCancel(context.Background()) 37 37 graceful.InitManager(managerCtx)
+1 -1
tests/integration/integration_test.go
··· 80 80 } 81 81 82 82 func TestMain(m *testing.M) { 83 - defer log.Close() 83 + defer log.GetManager().Close() 84 84 85 85 managerCtx, cancel := context.WithCancel(context.Background()) 86 86 graceful.InitManager(managerCtx)
+6 -2
tests/integration/migration-test/migration_test.go
··· 24 24 "code.gitea.io/gitea/modules/base" 25 25 "code.gitea.io/gitea/modules/charset" 26 26 "code.gitea.io/gitea/modules/git" 27 + "code.gitea.io/gitea/modules/log" 27 28 "code.gitea.io/gitea/modules/setting" 29 + "code.gitea.io/gitea/modules/testlogger" 28 30 "code.gitea.io/gitea/modules/util" 29 31 "code.gitea.io/gitea/tests" 30 32 ··· 35 37 var currentEngine *xorm.Engine 36 38 37 39 func initMigrationTest(t *testing.T) func() { 40 + log.RegisterEventWriter("test", testlogger.NewTestLoggerWriter) 41 + 38 42 deferFn := tests.PrintCurrentTest(t, 2) 39 43 giteaRoot := base.SetupGiteaRoot() 40 44 if giteaRoot == "" { ··· 84 88 85 89 assert.NoError(t, git.InitFull(context.Background())) 86 90 setting.LoadDBSetting() 87 - setting.InitLogs(true) 91 + setting.InitLoggersForTest() 88 92 return deferFn 89 93 } 90 94 ··· 292 296 return 293 297 } 294 298 295 - setting.InitSQLLog(false) 299 + setting.InitSQLLoggersForCli(log.INFO) 296 300 297 301 err := db.InitEngineWithMigration(context.Background(), wrappedMigrate) 298 302 assert.NoError(t, err)
+2 -4
tests/test_utils.go
··· 36 36 } 37 37 38 38 func InitTest(requireGitea bool) { 39 + log.RegisterEventWriter("test", testlogger.NewTestLoggerWriter) 40 + 39 41 giteaRoot := base.SetupGiteaRoot() 40 42 if giteaRoot == "" { 41 43 exitf("Environment variable $GITEA_ROOT not set") ··· 246 248 func Printf(format string, args ...interface{}) { 247 249 testlogger.Printf(format, args...) 248 250 } 249 - 250 - func init() { 251 - log.Register("test", testlogger.NewTestLogger) 252 - }