* Make LogDescriptions race safe * Add manager commands for pausing, resuming, adding and removing loggers Signed-off-by: Andrew Thornton <art27@cantab.net> * Placate lint * Ensure that file logger is run! * Add support for smtp and conn Signed-off-by: Andrew Thornton <art27@cantab.net> * Add release-and-reopen Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: Lauris BH <lauris@nix.lv>tags/v1.13.0-rc1
"os" | "os" | ||||
"time" | "time" | ||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/private" | "code.gitea.io/gitea/modules/private" | ||||
"github.com/urfave/cli" | "github.com/urfave/cli" | ||||
subcmdShutdown, | subcmdShutdown, | ||||
subcmdRestart, | subcmdRestart, | ||||
subcmdFlushQueues, | subcmdFlushQueues, | ||||
subcmdLogging, | |||||
}, | }, | ||||
} | } | ||||
subcmdShutdown = cli.Command{ | subcmdShutdown = cli.Command{ | ||||
Name: "shutdown", | |||||
Usage: "Gracefully shutdown the running process", | |||||
Name: "shutdown", | |||||
Usage: "Gracefully shutdown the running process", | |||||
Flags: []cli.Flag{ | |||||
cli.BoolFlag{ | |||||
Name: "debug", | |||||
}, | |||||
}, | |||||
Action: runShutdown, | Action: runShutdown, | ||||
} | } | ||||
subcmdRestart = cli.Command{ | subcmdRestart = cli.Command{ | ||||
Name: "restart", | |||||
Usage: "Gracefully restart the running process - (not implemented for windows servers)", | |||||
Name: "restart", | |||||
Usage: "Gracefully restart the running process - (not implemented for windows servers)", | |||||
Flags: []cli.Flag{ | |||||
cli.BoolFlag{ | |||||
Name: "debug", | |||||
}, | |||||
}, | |||||
Action: runRestart, | Action: runRestart, | ||||
} | } | ||||
subcmdFlushQueues = cli.Command{ | subcmdFlushQueues = cli.Command{ | ||||
Name: "timeout", | Name: "timeout", | ||||
Value: 60 * time.Second, | Value: 60 * time.Second, | ||||
Usage: "Timeout for the flushing process", | Usage: "Timeout for the flushing process", | ||||
}, | |||||
cli.BoolFlag{ | |||||
}, cli.BoolFlag{ | |||||
Name: "non-blocking", | Name: "non-blocking", | ||||
Usage: "Set to true to not wait for flush to complete before returning", | Usage: "Set to true to not wait for flush to complete before returning", | ||||
}, | }, | ||||
cli.BoolFlag{ | |||||
Name: "debug", | |||||
}, | |||||
}, | |||||
} | |||||
defaultLoggingFlags = []cli.Flag{ | |||||
cli.StringFlag{ | |||||
Name: "group, g", | |||||
Usage: "Group to add logger to - will default to \"default\"", | |||||
}, cli.StringFlag{ | |||||
Name: "name, n", | |||||
Usage: "Name of the new logger - will default to mode", | |||||
}, cli.StringFlag{ | |||||
Name: "level, l", | |||||
Usage: "Logging level for the new logger", | |||||
}, cli.StringFlag{ | |||||
Name: "stacktrace-level, L", | |||||
Usage: "Stacktrace logging level", | |||||
}, cli.StringFlag{ | |||||
Name: "flags, F", | |||||
Usage: "Flags for the logger", | |||||
}, cli.StringFlag{ | |||||
Name: "expression, e", | |||||
Usage: "Matching expression for the logger", | |||||
}, cli.StringFlag{ | |||||
Name: "prefix, p", | |||||
Usage: "Prefix for the logger", | |||||
}, cli.BoolFlag{ | |||||
Name: "color", | |||||
Usage: "Use color in the logs", | |||||
}, cli.BoolFlag{ | |||||
Name: "debug", | |||||
}, | |||||
} | |||||
subcmdLogging = cli.Command{ | |||||
Name: "logging", | |||||
Usage: "Adjust logging commands", | |||||
Subcommands: []cli.Command{ | |||||
{ | |||||
Name: "pause", | |||||
Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)", | |||||
Flags: []cli.Flag{ | |||||
cli.BoolFlag{ | |||||
Name: "debug", | |||||
}, | |||||
}, | |||||
Action: runPauseLogging, | |||||
}, { | |||||
Name: "resume", | |||||
Usage: "Resume logging", | |||||
Flags: []cli.Flag{ | |||||
cli.BoolFlag{ | |||||
Name: "debug", | |||||
}, | |||||
}, | |||||
Action: runResumeLogging, | |||||
}, { | |||||
Name: "release-and-reopen", | |||||
Usage: "Cause Gitea to release and re-open files used for logging", | |||||
Flags: []cli.Flag{ | |||||
cli.BoolFlag{ | |||||
Name: "debug", | |||||
}, | |||||
}, | |||||
Action: runReleaseReopenLogging, | |||||
}, { | |||||
Name: "remove", | |||||
Usage: "Remove a logger", | |||||
ArgsUsage: "[name] Name of logger to remove", | |||||
Flags: []cli.Flag{ | |||||
cli.BoolFlag{ | |||||
Name: "debug", | |||||
}, cli.StringFlag{ | |||||
Name: "group, g", | |||||
Usage: "Group to add logger to - will default to \"default\"", | |||||
}, | |||||
}, | |||||
Action: runRemoveLogger, | |||||
}, { | |||||
Name: "add", | |||||
Usage: "Add a logger", | |||||
Subcommands: []cli.Command{ | |||||
{ | |||||
Name: "console", | |||||
Usage: "Add a console logger", | |||||
Flags: append(defaultLoggingFlags, | |||||
cli.BoolFlag{ | |||||
Name: "stderr", | |||||
Usage: "Output console logs to stderr - only relevant for console", | |||||
}), | |||||
Action: runAddConsoleLogger, | |||||
}, { | |||||
Name: "file", | |||||
Usage: "Add a file logger", | |||||
Flags: append(defaultLoggingFlags, []cli.Flag{ | |||||
cli.StringFlag{ | |||||
Name: "filename, f", | |||||
Usage: "Filename for the logger - this must be set.", | |||||
}, cli.BoolTFlag{ | |||||
Name: "rotate, r", | |||||
Usage: "Rotate logs", | |||||
}, cli.Int64Flag{ | |||||
Name: "max-size, s", | |||||
Usage: "Maximum size in bytes before rotation", | |||||
}, cli.BoolTFlag{ | |||||
Name: "daily, d", | |||||
Usage: "Rotate logs daily", | |||||
}, cli.IntFlag{ | |||||
Name: "max-days, D", | |||||
Usage: "Maximum number of daily logs to keep", | |||||
}, cli.BoolTFlag{ | |||||
Name: "compress, z", | |||||
Usage: "Compress rotated logs", | |||||
}, cli.IntFlag{ | |||||
Name: "compression-level, Z", | |||||
Usage: "Compression level to use", | |||||
}, | |||||
}...), | |||||
Action: runAddFileLogger, | |||||
}, { | |||||
Name: "conn", | |||||
Usage: "Add a net conn logger", | |||||
Flags: append(defaultLoggingFlags, []cli.Flag{ | |||||
cli.BoolFlag{ | |||||
Name: "reconnect-on-message, R", | |||||
Usage: "Reconnect to host for every message", | |||||
}, cli.BoolFlag{ | |||||
Name: "reconnect, r", | |||||
Usage: "Reconnect to host when connection is dropped", | |||||
}, cli.StringFlag{ | |||||
Name: "protocol, P", | |||||
Usage: "Set protocol to use: tcp, unix, or udp (defaults to tcp)", | |||||
}, cli.StringFlag{ | |||||
Name: "address, a", | |||||
Usage: "Host address and port to connect to (defaults to :7020)", | |||||
}, | |||||
}...), | |||||
Action: runAddConnLogger, | |||||
}, { | |||||
Name: "smtp", | |||||
Usage: "Add an SMTP logger", | |||||
Flags: append(defaultLoggingFlags, []cli.Flag{ | |||||
cli.StringFlag{ | |||||
Name: "username, u", | |||||
Usage: "Mail server username", | |||||
}, cli.StringFlag{ | |||||
Name: "password, P", | |||||
Usage: "Mail server password", | |||||
}, cli.StringFlag{ | |||||
Name: "host, H", | |||||
Usage: "Mail server host (defaults to: 127.0.0.1:25)", | |||||
}, cli.StringSliceFlag{ | |||||
Name: "send-to, s", | |||||
Usage: "Email address(es) to send to", | |||||
}, cli.StringFlag{ | |||||
Name: "subject, S", | |||||
Usage: "Subject header of sent emails", | |||||
}, | |||||
}...), | |||||
Action: runAddSMTPLogger, | |||||
}, | |||||
}, | |||||
}, | |||||
}, | }, | ||||
} | } | ||||
) | ) | ||||
func runRemoveLogger(c *cli.Context) error { | |||||
setup("manager", c.Bool("debug")) | |||||
group := c.String("group") | |||||
if len(group) == 0 { | |||||
group = log.DEFAULT | |||||
} | |||||
name := c.Args().First() | |||||
statusCode, msg := private.RemoveLogger(group, name) | |||||
switch statusCode { | |||||
case http.StatusInternalServerError: | |||||
fail("InternalServerError", msg) | |||||
} | |||||
fmt.Fprintln(os.Stdout, msg) | |||||
return nil | |||||
} | |||||
func runAddSMTPLogger(c *cli.Context) error { | |||||
setup("manager", c.Bool("debug")) | |||||
vals := map[string]interface{}{} | |||||
mode := "smtp" | |||||
if c.IsSet("host") { | |||||
vals["host"] = c.String("host") | |||||
} else { | |||||
vals["host"] = "127.0.0.1:25" | |||||
} | |||||
if c.IsSet("username") { | |||||
vals["username"] = c.String("username") | |||||
} | |||||
if c.IsSet("password") { | |||||
vals["password"] = c.String("password") | |||||
} | |||||
if !c.IsSet("send-to") { | |||||
return fmt.Errorf("Some recipients must be provided") | |||||
} | |||||
vals["sendTos"] = c.StringSlice("send-to") | |||||
if c.IsSet("subject") { | |||||
vals["subject"] = c.String("subject") | |||||
} else { | |||||
vals["subject"] = "Diagnostic message from Gitea" | |||||
} | |||||
return commonAddLogger(c, mode, vals) | |||||
} | |||||
func runAddConnLogger(c *cli.Context) error { | |||||
setup("manager", c.Bool("debug")) | |||||
vals := map[string]interface{}{} | |||||
mode := "conn" | |||||
vals["net"] = "tcp" | |||||
if c.IsSet("protocol") { | |||||
switch c.String("protocol") { | |||||
case "udp": | |||||
vals["net"] = "udp" | |||||
case "unix": | |||||
vals["net"] = "unix" | |||||
} | |||||
} | |||||
if c.IsSet("address") { | |||||
vals["address"] = c.String("address") | |||||
} else { | |||||
vals["address"] = ":7020" | |||||
} | |||||
if c.IsSet("reconnect") { | |||||
vals["reconnect"] = c.Bool("reconnect") | |||||
} | |||||
if c.IsSet("reconnect-on-message") { | |||||
vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") | |||||
} | |||||
return commonAddLogger(c, mode, vals) | |||||
} | |||||
func runAddFileLogger(c *cli.Context) error { | |||||
setup("manager", c.Bool("debug")) | |||||
vals := map[string]interface{}{} | |||||
mode := "file" | |||||
if c.IsSet("filename") { | |||||
vals["filename"] = c.String("filename") | |||||
} else { | |||||
return fmt.Errorf("filename must be set when creating a file logger") | |||||
} | |||||
if c.IsSet("rotate") { | |||||
vals["rotate"] = c.Bool("rotate") | |||||
} | |||||
if c.IsSet("max-size") { | |||||
vals["maxsize"] = c.Int64("max-size") | |||||
} | |||||
if c.IsSet("daily") { | |||||
vals["daily"] = c.Bool("daily") | |||||
} | |||||
if c.IsSet("max-days") { | |||||
vals["maxdays"] = c.Int("max-days") | |||||
} | |||||
if c.IsSet("compress") { | |||||
vals["compress"] = c.Bool("compress") | |||||
} | |||||
if c.IsSet("compression-level") { | |||||
vals["compressionLevel"] = c.Int("compression-level") | |||||
} | |||||
return commonAddLogger(c, mode, vals) | |||||
} | |||||
func runAddConsoleLogger(c *cli.Context) error { | |||||
setup("manager", c.Bool("debug")) | |||||
vals := map[string]interface{}{} | |||||
mode := "console" | |||||
if c.IsSet("stderr") && c.Bool("stderr") { | |||||
vals["stderr"] = c.Bool("stderr") | |||||
} | |||||
return commonAddLogger(c, mode, vals) | |||||
} | |||||
func commonAddLogger(c *cli.Context, mode string, vals map[string]interface{}) error { | |||||
if len(c.String("level")) > 0 { | |||||
vals["level"] = log.FromString(c.String("level")).String() | |||||
} | |||||
if len(c.String("stacktrace-level")) > 0 { | |||||
vals["stacktraceLevel"] = log.FromString(c.String("stacktrace-level")).String() | |||||
} | |||||
if len(c.String("expression")) > 0 { | |||||
vals["expression"] = c.String("expression") | |||||
} | |||||
if len(c.String("prefix")) > 0 { | |||||
vals["prefix"] = c.String("prefix") | |||||
} | |||||
if len(c.String("flags")) > 0 { | |||||
vals["flags"] = log.FlagsFromString(c.String("flags")) | |||||
} | |||||
if c.IsSet("color") { | |||||
vals["colorize"] = c.Bool("color") | |||||
} | |||||
group := "default" | |||||
if c.IsSet("group") { | |||||
group = c.String("group") | |||||
} | |||||
name := mode | |||||
if c.IsSet("name") { | |||||
name = c.String("name") | |||||
} | |||||
statusCode, msg := private.AddLogger(group, name, mode, vals) | |||||
switch statusCode { | |||||
case http.StatusInternalServerError: | |||||
fail("InternalServerError", msg) | |||||
} | |||||
fmt.Fprintln(os.Stdout, msg) | |||||
return nil | |||||
} | |||||
func runShutdown(c *cli.Context) error { | func runShutdown(c *cli.Context) error { | ||||
setup("manager", false) | |||||
setup("manager", c.Bool("debug")) | |||||
statusCode, msg := private.Shutdown() | statusCode, msg := private.Shutdown() | ||||
switch statusCode { | switch statusCode { | ||||
case http.StatusInternalServerError: | case http.StatusInternalServerError: | ||||
} | } | ||||
func runRestart(c *cli.Context) error { | func runRestart(c *cli.Context) error { | ||||
setup("manager", false) | |||||
setup("manager", c.Bool("debug")) | |||||
statusCode, msg := private.Restart() | statusCode, msg := private.Restart() | ||||
switch statusCode { | switch statusCode { | ||||
case http.StatusInternalServerError: | case http.StatusInternalServerError: | ||||
} | } | ||||
func runFlushQueues(c *cli.Context) error { | func runFlushQueues(c *cli.Context) error { | ||||
setup("manager", false) | |||||
setup("manager", c.Bool("debug")) | |||||
statusCode, msg := private.FlushQueues(c.Duration("timeout"), c.Bool("non-blocking")) | statusCode, msg := private.FlushQueues(c.Duration("timeout"), c.Bool("non-blocking")) | ||||
switch statusCode { | switch statusCode { | ||||
case http.StatusInternalServerError: | case http.StatusInternalServerError: | ||||
fmt.Fprintln(os.Stdout, msg) | fmt.Fprintln(os.Stdout, msg) | ||||
return nil | return nil | ||||
} | } | ||||
func runPauseLogging(c *cli.Context) error { | |||||
setup("manager", c.Bool("debug")) | |||||
statusCode, msg := private.PauseLogging() | |||||
switch statusCode { | |||||
case http.StatusInternalServerError: | |||||
fail("InternalServerError", msg) | |||||
} | |||||
fmt.Fprintln(os.Stdout, msg) | |||||
return nil | |||||
} | |||||
func runResumeLogging(c *cli.Context) error { | |||||
setup("manager", c.Bool("debug")) | |||||
statusCode, msg := private.ResumeLogging() | |||||
switch statusCode { | |||||
case http.StatusInternalServerError: | |||||
fail("InternalServerError", msg) | |||||
} | |||||
fmt.Fprintln(os.Stdout, msg) | |||||
return nil | |||||
} | |||||
func runReleaseReopenLogging(c *cli.Context) error { | |||||
setup("manager", c.Bool("debug")) | |||||
statusCode, msg := private.ReleaseReopenLogging() | |||||
switch statusCode { | |||||
case http.StatusInternalServerError: | |||||
fail("InternalServerError", msg) | |||||
} | |||||
fmt.Fprintln(os.Stdout, msg) | |||||
return nil | |||||
} |
This is equivalent to sending all logs to the console, with default go log being sent to the console log too. | This is equivalent to sending all logs to the console, with default go log being sent to the console log too. | ||||
## Releasing-and-Reopening, Pausing and Resuming logging | |||||
If you are running on Unix you may wish to release-and-reopen logs in order to use `logrotate` or other tools. | |||||
It is possible force gitea to release and reopen it's logging files and connections by sending `SIGUSR1` to the | |||||
running process, or running `gitea manager logging release-and-reopen`. | |||||
Alternatively, you may wish to pause and resume logging - this can be accomplished through the use of the | |||||
`gitea manager logging pause` and `gitea manager logging resume` commands. Please note that whilst logging | |||||
is paused log events below INFO level will not be stored and only a limited number of events will be stored. | |||||
Logging may block, albeit temporarily, slowing gitea considerably whilst paused - therefore it is | |||||
recommended that pausing only done for a very short period of time. | |||||
## Adding and removing logging whilst Gitea is running | |||||
It is possible to add and remove logging whilst Gitea is running using the `gitea manager logging add` and `remove` subcommands. | |||||
This functionality can only adjust running log systems and cannot be used to start the access, macaron or router loggers if they | |||||
were not already initialised. If you wish to start these systems you are advised to adjust the app.ini and (gracefully) restart | |||||
the Gitea service. | |||||
The main intention of these commands is to easily add a temporary logger to investigate problems on running systems where a restart | |||||
may cause the issue to disappear. | |||||
## Log colorization | ## Log colorization | ||||
Logs to the console will be colorized by default when not running on | Logs to the console will be colorized by default when not running on |
``` | ``` | ||||
This function will receive a command line context and return a list of details about the problems or error. | This function will receive a command line context and return a list of details about the problems or error. | ||||
#### manager | |||||
Manage running server operations: | |||||
- Commands: | |||||
- `shutdown`: Gracefully shutdown the running process | |||||
- `restart`: Gracefully restart the running process - (not implemented for windows servers) | |||||
- `flush-queues`: Flush queues in the running process | |||||
- Options: | |||||
- `--timeout value`: Timeout for the flushing process (default: 1m0s) | |||||
- `--non-blocking`: Set to true to not wait for flush to complete before returning | |||||
- `logging`: Adjust logging commands | |||||
- Commands: | |||||
- `pause`: Pause logging | |||||
- Notes: | |||||
- The logging level will be raised to INFO temporarily if it is below this level. | |||||
- Gitea will buffer logs up to a certain point and will drop them after that point. | |||||
- `resume`: Resume logging | |||||
- `release-and-reopen`: Cause Gitea to release and re-open files and connections used for logging (Equivalent to sending SIGUSR1 to Gitea.) | |||||
- `remove name`: Remove the named logger | |||||
- Options: | |||||
- `--group group`, `-g group`: Set the group to remove the sublogger from. (defaults to `default`) | |||||
- `add`: Add a logger | |||||
- Commands: | |||||
- `console`: Add a console logger | |||||
- Options: | |||||
- `--group value`, `-g value`: Group to add logger to - will default to "default" | |||||
- `--name value`, `-n value`: Name of the new logger - will default to mode | |||||
- `--level value`, `-l value`: Logging level for the new logger | |||||
- `--stacktrace-level value`, `-L value`: Stacktrace logging level | |||||
- `--flags value`, `-F value`: Flags for the logger | |||||
- `--expression value`, `-e value`: Matching expression for the logger | |||||
- `--prefix value`, `-p value`: Prefix for the logger | |||||
- `--color`: Use color in the logs | |||||
- `--stderr`: Output console logs to stderr - only relevant for console | |||||
- `file`: Add a file logger | |||||
- Options: | |||||
- `--group value`, `-g value`: Group to add logger to - will default to "default" | |||||
- `--name value`, `-n value`: Name of the new logger - will default to mode | |||||
- `--level value`, `-l value`: Logging level for the new logger | |||||
- `--stacktrace-level value`, `-L value`: Stacktrace logging level | |||||
- `--flags value`, `-F value`: Flags for the logger | |||||
- `--expression value`, `-e value`: Matching expression for the logger | |||||
- `--prefix value`, `-p value`: Prefix for the logger | |||||
- `--color`: Use color in the logs | |||||
- `--filename value`, `-f value`: Filename for the logger - | |||||
- `--rotate`, `-r`: Rotate logs | |||||
- `--max-size value`, `-s value`: Maximum size in bytes before rotation | |||||
- `--daily`, `-d`: Rotate logs daily | |||||
- `--max-days value`, `-D value`: Maximum number of daily logs to keep | |||||
- `--compress`, `-z`: Compress rotated logs | |||||
- `--compression-level value`, `-Z value`: Compression level to use | |||||
- `conn`: Add a network connection logger | |||||
- Options: | |||||
- `--group value`, `-g value`: Group to add logger to - will default to "default" | |||||
- `--name value`, `-n value`: Name of the new logger - will default to mode | |||||
- `--level value`, `-l value`: Logging level for the new logger | |||||
- `--stacktrace-level value`, `-L value`: Stacktrace logging level | |||||
- `--flags value`, `-F value`: Flags for the logger | |||||
- `--expression value`, `-e value`: Matching expression for the logger | |||||
- `--prefix value`, `-p value`: Prefix for the logger | |||||
- `--color`: Use color in the logs | |||||
- `--reconnect-on-message`, `-R`: Reconnect to host for every message | |||||
- `--reconnect`, `-r`: Reconnect to host when connection is dropped | |||||
- `--protocol value`, `-P value`: Set protocol to use: tcp, unix, or udp (defaults to tcp) | |||||
- `--address value`, `-a value`: Host address and port to connect to (defaults to :7020) | |||||
- `smtp`: Add an SMTP logger | |||||
- Options: | |||||
- `--group value`, `-g value`: Group to add logger to - will default to "default" | |||||
- `--name value`, `-n value`: Name of the new logger - will default to mode | |||||
- `--level value`, `-l value`: Logging level for the new logger | |||||
- `--stacktrace-level value`, `-L value`: Stacktrace logging level | |||||
- `--flags value`, `-F value`: Flags for the logger | |||||
- `--expression value`, `-e value`: Matching expression for the logger | |||||
- `--prefix value`, `-p value`: Prefix for the logger | |||||
- `--color`: Use color in the logs | |||||
- `--username value`, `-u value`: Mail server username | |||||
- `--password value`, `-P value`: Mail server password | |||||
- `--host value`, `-H value`: Mail server host (defaults to: 127.0.0.1:25) | |||||
- `--send-to value`, `-s value`: Email address(es) to send to | |||||
- `--subject value`, `-S value`: Subject header of sent emails |
func (log *TestLogger) Flush() { | func (log *TestLogger) Flush() { | ||||
} | } | ||||
//ReleaseReopen does nothing | |||||
func (log *TestLogger) ReleaseReopen() error { | |||||
return nil | |||||
} | |||||
// GetName returns the default name for this implementation | // GetName returns the default name for this implementation | ||||
func (log *TestLogger) GetName() string { | func (log *TestLogger) GetName() string { | ||||
return "test" | return "test" |
log.Info("PID: %d. Received SIGHUP. Attempting GracefulRestart...", pid) | log.Info("PID: %d. Received SIGHUP. Attempting GracefulRestart...", pid) | ||||
g.DoGracefulRestart() | g.DoGracefulRestart() | ||||
case syscall.SIGUSR1: | case syscall.SIGUSR1: | ||||
log.Info("PID %d. Received SIGUSR1.", pid) | |||||
log.Warn("PID %d. Received SIGUSR1. Releasing and reopening logs", pid) | |||||
if err := log.ReleaseReopen(); err != nil { | |||||
log.Error("Error whilst releasing and reopening logs: %v", err) | |||||
} | |||||
case syscall.SIGUSR2: | case syscall.SIGUSR2: | ||||
log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) | log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) | ||||
g.DoImmediateHammer() | g.DoImmediateHammer() |
return nil | return nil | ||||
} | } | ||||
func (i *connWriter) releaseReopen() error { | |||||
if i.innerWriter != nil { | |||||
return i.connect() | |||||
} | |||||
return nil | |||||
} | |||||
// ConnLogger implements LoggerProvider. | // ConnLogger implements LoggerProvider. | ||||
// it writes messages in keep-live tcp connection. | // it writes messages in keep-live tcp connection. | ||||
type ConnLogger struct { | type ConnLogger struct { | ||||
return "conn" | return "conn" | ||||
} | } | ||||
// ReleaseReopen causes the ConnLogger to reconnect to the server | |||||
func (log *ConnLogger) ReleaseReopen() error { | |||||
return log.out.(*connWriter).releaseReopen() | |||||
} | |||||
func init() { | func init() { | ||||
Register("conn", NewConn) | Register("conn", NewConn) | ||||
} | } |
func (log *ConsoleLogger) Flush() { | func (log *ConsoleLogger) Flush() { | ||||
} | } | ||||
// ReleaseReopen causes the console logger to reconnect to os.Stdout | |||||
func (log *ConsoleLogger) ReleaseReopen() error { | |||||
if log.Stderr { | |||||
log.NewWriterLogger(&nopWriteCloser{ | |||||
w: os.Stderr, | |||||
}) | |||||
} else { | |||||
log.NewWriterLogger(&nopWriteCloser{ | |||||
w: os.Stdout, | |||||
}) | |||||
} | |||||
return nil | |||||
} | |||||
// GetName returns the default name for this implementation | // GetName returns the default name for this implementation | ||||
func (log *ConsoleLogger) GetName() string { | func (log *ConsoleLogger) GetName() string { | ||||
return "console" | return "console" |
GetLevel() Level | GetLevel() Level | ||||
GetStacktraceLevel() Level | GetStacktraceLevel() Level | ||||
GetName() string | GetName() string | ||||
ReleaseReopen() error | |||||
} | } | ||||
// ChannelledLog represents a cached channel to a LoggerProvider | // ChannelledLog represents a cached channel to a LoggerProvider | ||||
l.flush <- true | l.flush <- true | ||||
} | } | ||||
// ReleaseReopen this ChannelledLog | |||||
func (l *ChannelledLog) ReleaseReopen() error { | |||||
return l.loggerProvider.ReleaseReopen() | |||||
} | |||||
// GetLevel gets the level of this ChannelledLog | // GetLevel gets the level of this ChannelledLog | ||||
func (l *ChannelledLog) GetLevel() Level { | func (l *ChannelledLog) GetLevel() Level { | ||||
return l.loggerProvider.GetLevel() | return l.loggerProvider.GetLevel() | ||||
level Level | level Level | ||||
stacktraceLevel Level | stacktraceLevel Level | ||||
closed chan bool | closed chan bool | ||||
paused chan bool | |||||
} | } | ||||
// NewMultiChannelledLog a new logger instance with given logger provider and config. | // NewMultiChannelledLog a new logger instance with given logger provider and config. | ||||
stacktraceLevel: NONE, | stacktraceLevel: NONE, | ||||
close: make(chan bool), | close: make(chan bool), | ||||
closed: make(chan bool), | closed: make(chan bool), | ||||
paused: make(chan bool), | |||||
} | } | ||||
return m | return m | ||||
} | } | ||||
m.closed <- true | m.closed <- true | ||||
} | } | ||||
// Pause pauses this Logger | |||||
func (m *MultiChannelledLog) Pause() { | |||||
m.paused <- true | |||||
} | |||||
// Resume resumes this Logger | |||||
func (m *MultiChannelledLog) Resume() { | |||||
m.paused <- false | |||||
} | |||||
// ReleaseReopen causes this logger to tell its subloggers to release and reopen | |||||
func (m *MultiChannelledLog) ReleaseReopen() error { | |||||
m.mutex.Lock() | |||||
defer m.mutex.Unlock() | |||||
var accumulatedErr error | |||||
for _, logger := range m.loggers { | |||||
if err := logger.ReleaseReopen(); err != nil { | |||||
if accumulatedErr == nil { | |||||
accumulatedErr = fmt.Errorf("Error whilst reopening: %s Error: %v", logger.GetName(), err) | |||||
} else { | |||||
accumulatedErr = fmt.Errorf("Error whilst reopening: %s Error: %v & %v", logger.GetName(), err, accumulatedErr) | |||||
} | |||||
} | |||||
} | |||||
return accumulatedErr | |||||
} | |||||
// Start processing the MultiChannelledLog | // Start processing the MultiChannelledLog | ||||
func (m *MultiChannelledLog) Start() { | func (m *MultiChannelledLog) Start() { | ||||
m.mutex.Lock() | m.mutex.Lock() | ||||
} | } | ||||
m.started = true | m.started = true | ||||
m.mutex.Unlock() | m.mutex.Unlock() | ||||
paused := false | |||||
for { | for { | ||||
if paused { | |||||
select { | |||||
case paused = <-m.paused: | |||||
if !paused { | |||||
m.ResetLevel() | |||||
} | |||||
case _, ok := <-m.flush: | |||||
if !ok { | |||||
m.closeLoggers() | |||||
return | |||||
} | |||||
m.mutex.Lock() | |||||
for _, logger := range m.loggers { | |||||
logger.Flush() | |||||
} | |||||
m.mutex.Unlock() | |||||
case <-m.close: | |||||
m.closeLoggers() | |||||
return | |||||
} | |||||
continue | |||||
} | |||||
select { | select { | ||||
case paused = <-m.paused: | |||||
if paused && m.level < INFO { | |||||
m.level = INFO | |||||
} | |||||
case event, ok := <-m.queue: | case event, ok := <-m.queue: | ||||
if !ok { | if !ok { | ||||
m.closeLoggers() | m.closeLoggers() | ||||
select { | select { | ||||
case m.queue <- event: | case m.queue <- event: | ||||
return nil | return nil | ||||
case <-time.After(60 * time.Second): | |||||
case <-time.After(100 * time.Millisecond): | |||||
// We're blocked! | // We're blocked! | ||||
return ErrTimeout{ | return ErrTimeout{ | ||||
Name: m.name, | Name: m.name, |
_ = log.mw.fd.Sync() | _ = log.mw.fd.Sync() | ||||
} | } | ||||
// ReleaseReopen releases and reopens log files | |||||
func (log *FileLogger) ReleaseReopen() error { | |||||
closingErr := log.mw.fd.Close() | |||||
startingErr := log.StartLogger() | |||||
if startingErr != nil { | |||||
if closingErr != nil { | |||||
return fmt.Errorf("Error during closing: %v Error during starting: %v", closingErr, startingErr) | |||||
} | |||||
return startingErr | |||||
} | |||||
return closingErr | |||||
} | |||||
// GetName returns the default name for this implementation | // GetName returns the default name for this implementation | ||||
func (log *FileLogger) GetName() string { | func (log *FileLogger) GetName() string { | ||||
return "file" | return "file" |
package log | package log | ||||
import ( | import ( | ||||
"fmt" | |||||
"os" | "os" | ||||
"runtime" | "runtime" | ||||
"strings" | "strings" | ||||
return GetLevel() <= FATAL | return GetLevel() <= FATAL | ||||
} | } | ||||
// Pause pauses all the loggers | |||||
func Pause() { | |||||
NamedLoggers.Range(func(key, value interface{}) bool { | |||||
logger := value.(*Logger) | |||||
logger.Pause() | |||||
logger.Flush() | |||||
return true | |||||
}) | |||||
} | |||||
// Resume resumes all the loggers | |||||
func Resume() { | |||||
NamedLoggers.Range(func(key, value interface{}) bool { | |||||
logger := value.(*Logger) | |||||
logger.Resume() | |||||
return true | |||||
}) | |||||
} | |||||
// ReleaseReopen releases and reopens logging files | |||||
func ReleaseReopen() error { | |||||
var accumulatedErr error | |||||
NamedLoggers.Range(func(key, value interface{}) bool { | |||||
logger := value.(*Logger) | |||||
if err := logger.ReleaseReopen(); err != nil { | |||||
if accumulatedErr == nil { | |||||
accumulatedErr = fmt.Errorf("Error reopening %s: %v", key.(string), err) | |||||
} else { | |||||
accumulatedErr = fmt.Errorf("Error reopening %s: %v & %v", key.(string), err, accumulatedErr) | |||||
} | |||||
} | |||||
return true | |||||
}) | |||||
return accumulatedErr | |||||
} | |||||
// Close closes all the loggers | // Close closes all the loggers | ||||
func Close() { | func Close() { | ||||
l, ok := NamedLoggers.Load(DEFAULT) | l, ok := NamedLoggers.Load(DEFAULT) |
func (log *SMTPLogger) Flush() { | func (log *SMTPLogger) Flush() { | ||||
} | } | ||||
// ReleaseReopen does nothing | |||||
func (log *SMTPLogger) ReleaseReopen() error { | |||||
return nil | |||||
} | |||||
// GetName returns the default name for this implementation | // GetName returns the default name for this implementation | ||||
func (log *SMTPLogger) GetName() string { | func (log *SMTPLogger) GetName() string { | ||||
return "smtp" | return "smtp" |
"encoding/json" | "encoding/json" | ||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
"net/url" | |||||
"time" | "time" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
return http.StatusOK, "Flushed" | return http.StatusOK, "Flushed" | ||||
} | } | ||||
// PauseLogging pauses logging | |||||
func PauseLogging() (int, string) { | |||||
reqURL := setting.LocalURL + "api/internal/manager/pause-logging" | |||||
req := newInternalRequest(reqURL, "POST") | |||||
resp, err := req.Response() | |||||
if err != nil { | |||||
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) | |||||
} | |||||
defer resp.Body.Close() | |||||
if resp.StatusCode != http.StatusOK { | |||||
return resp.StatusCode, decodeJSONError(resp).Err | |||||
} | |||||
return http.StatusOK, "Logging Paused" | |||||
} | |||||
// ResumeLogging resumes logging | |||||
func ResumeLogging() (int, string) { | |||||
reqURL := setting.LocalURL + "api/internal/manager/resume-logging" | |||||
req := newInternalRequest(reqURL, "POST") | |||||
resp, err := req.Response() | |||||
if err != nil { | |||||
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) | |||||
} | |||||
defer resp.Body.Close() | |||||
if resp.StatusCode != http.StatusOK { | |||||
return resp.StatusCode, decodeJSONError(resp).Err | |||||
} | |||||
return http.StatusOK, "Logging Restarted" | |||||
} | |||||
// ReleaseReopenLogging releases and reopens logging files | |||||
func ReleaseReopenLogging() (int, string) { | |||||
reqURL := setting.LocalURL + "api/internal/manager/release-and-reopen-logging" | |||||
req := newInternalRequest(reqURL, "POST") | |||||
resp, err := req.Response() | |||||
if err != nil { | |||||
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) | |||||
} | |||||
defer resp.Body.Close() | |||||
if resp.StatusCode != http.StatusOK { | |||||
return resp.StatusCode, decodeJSONError(resp).Err | |||||
} | |||||
return http.StatusOK, "Logging Restarted" | |||||
} | |||||
// LoggerOptions represents the options for the add logger call | |||||
type LoggerOptions struct { | |||||
Group string | |||||
Name string | |||||
Mode string | |||||
Config map[string]interface{} | |||||
} | |||||
// AddLogger adds a logger | |||||
func AddLogger(group, name, mode string, config map[string]interface{}) (int, string) { | |||||
reqURL := setting.LocalURL + "api/internal/manager/add-logger" | |||||
req := newInternalRequest(reqURL, "POST") | |||||
req = req.Header("Content-Type", "application/json") | |||||
jsonBytes, _ := json.Marshal(LoggerOptions{ | |||||
Group: group, | |||||
Name: name, | |||||
Mode: mode, | |||||
Config: config, | |||||
}) | |||||
req.Body(jsonBytes) | |||||
resp, err := req.Response() | |||||
if err != nil { | |||||
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) | |||||
} | |||||
defer resp.Body.Close() | |||||
if resp.StatusCode != http.StatusOK { | |||||
return resp.StatusCode, decodeJSONError(resp).Err | |||||
} | |||||
return http.StatusOK, "Added" | |||||
} | |||||
// RemoveLogger removes a logger | |||||
func RemoveLogger(group, name string) (int, string) { | |||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/remove-logger/%s/%s", url.PathEscape(group), url.PathEscape(name)) | |||||
req := newInternalRequest(reqURL, "POST") | |||||
resp, err := req.Response() | |||||
if err != nil { | |||||
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error()) | |||||
} | |||||
defer resp.Body.Close() | |||||
if resp.StatusCode != http.StatusOK { | |||||
return resp.StatusCode, decodeJSONError(resp).Err | |||||
} | |||||
return http.StatusOK, "Removed" | |||||
} |
"path" | "path" | ||||
"path/filepath" | "path/filepath" | ||||
"strings" | "strings" | ||||
"sync" | |||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
var filenameSuffix = "" | var filenameSuffix = "" | ||||
var descriptionLock = sync.RWMutex{} | |||||
var logDescriptions = make(map[string]*LogDescription) | |||||
// GetLogDescriptions returns a race safe set of descriptions | |||||
func GetLogDescriptions() map[string]*LogDescription { | |||||
descriptionLock.RLock() | |||||
defer descriptionLock.RUnlock() | |||||
descs := make(map[string]*LogDescription, len(logDescriptions)) | |||||
for k, v := range logDescriptions { | |||||
subLogDescriptions := make([]SubLogDescription, len(v.SubLogDescriptions)) | |||||
for i, s := range v.SubLogDescriptions { | |||||
subLogDescriptions[i] = s | |||||
} | |||||
descs[k] = &LogDescription{ | |||||
Name: v.Name, | |||||
SubLogDescriptions: subLogDescriptions, | |||||
} | |||||
} | |||||
return descs | |||||
} | |||||
// AddLogDescription adds a set of descriptions to the complete description | |||||
func AddLogDescription(key string, description *LogDescription) { | |||||
descriptionLock.Lock() | |||||
defer descriptionLock.Unlock() | |||||
logDescriptions[key] = description | |||||
} | |||||
// AddSubLogDescription adds a sub log description | |||||
func AddSubLogDescription(key string, subLogDescription SubLogDescription) bool { | |||||
descriptionLock.Lock() | |||||
defer descriptionLock.Unlock() | |||||
desc, ok := logDescriptions[key] | |||||
if !ok { | |||||
return false | |||||
} | |||||
for i, sub := range desc.SubLogDescriptions { | |||||
if sub.Name == subLogDescription.Name { | |||||
desc.SubLogDescriptions[i] = subLogDescription | |||||
return true | |||||
} | |||||
} | |||||
desc.SubLogDescriptions = append(desc.SubLogDescriptions, subLogDescription) | |||||
return true | |||||
} | |||||
// RemoveSubLogDescription removes a sub log description | |||||
func RemoveSubLogDescription(key string, name string) bool { | |||||
descriptionLock.Lock() | |||||
defer descriptionLock.Unlock() | |||||
desc, ok := logDescriptions[key] | |||||
if !ok { | |||||
return false | |||||
} | |||||
for i, sub := range desc.SubLogDescriptions { | |||||
if sub.Name == name { | |||||
desc.SubLogDescriptions = append(desc.SubLogDescriptions[:i], desc.SubLogDescriptions[i+1:]...) | |||||
return true | |||||
} | |||||
} | |||||
return false | |||||
} | |||||
type defaultLogOptions struct { | type defaultLogOptions struct { | ||||
levelName string // LogLevel | levelName string // LogLevel | ||||
flags string | flags string | ||||
log.Info("%s Log: %s(%s:%s)", strings.Title(key), strings.Title(name), provider, levelName) | log.Info("%s Log: %s(%s:%s)", strings.Title(key), strings.Title(name), provider, levelName) | ||||
} | } | ||||
LogDescriptions[key] = &description | |||||
AddLogDescription(key, &description) | |||||
return &description | return &description | ||||
} | } | ||||
log.Info("Gitea Log Mode: %s(%s:%s)", strings.Title(name), strings.Title(provider), levelName) | log.Info("Gitea Log Mode: %s(%s:%s)", strings.Title(name), strings.Title(provider), levelName) | ||||
} | } | ||||
LogDescriptions[log.DEFAULT] = &description | |||||
AddLogDescription(log.DEFAULT, &description) | |||||
// Finally redirect the default golog to here | // Finally redirect the default golog to here | ||||
golog.SetFlags(0) | golog.SetFlags(0) |
LogLevel string | LogLevel string | ||||
StacktraceLogLevel string | StacktraceLogLevel string | ||||
LogRootPath string | LogRootPath string | ||||
LogDescriptions = make(map[string]*LogDescription) | |||||
RedirectMacaronLog bool | RedirectMacaronLog bool | ||||
DisableRouterLog bool | DisableRouterLog bool | ||||
RouterLogLevel log.Level | RouterLogLevel log.Level |
} | } | ||||
ctx.Data["EnvVars"] = envVars | ctx.Data["EnvVars"] = envVars | ||||
ctx.Data["Loggers"] = setting.LogDescriptions | |||||
ctx.Data["Loggers"] = setting.GetLogDescriptions() | |||||
ctx.Data["RedirectMacaronLog"] = setting.RedirectMacaronLog | ctx.Data["RedirectMacaronLog"] = setting.RedirectMacaronLog | ||||
ctx.Data["EnableAccessLog"] = setting.EnableAccessLog | ctx.Data["EnableAccessLog"] = setting.EnableAccessLog | ||||
ctx.Data["AccessLogTemplate"] = setting.AccessLogTemplate | ctx.Data["AccessLogTemplate"] = setting.AccessLogTemplate |
m.Post("/manager/shutdown", Shutdown) | m.Post("/manager/shutdown", Shutdown) | ||||
m.Post("/manager/restart", Restart) | m.Post("/manager/restart", Restart) | ||||
m.Post("/manager/flush-queues", bind(private.FlushOptions{}), FlushQueues) | m.Post("/manager/flush-queues", bind(private.FlushOptions{}), FlushQueues) | ||||
m.Post("/manager/pause-logging", PauseLogging) | |||||
m.Post("/manager/resume-logging", ResumeLogging) | |||||
m.Post("/manager/release-and-reopen-logging", ReleaseReopenLogging) | |||||
m.Post("/manager/add-logger", bind(private.LoggerOptions{}), AddLogger) | |||||
m.Post("/manager/remove-logger/:group/:name", RemoveLogger) | |||||
}, CheckInternalToken) | }, CheckInternalToken) | ||||
} | } |
package private | package private | ||||
import ( | import ( | ||||
"encoding/json" | |||||
"fmt" | |||||
"net/http" | "net/http" | ||||
"code.gitea.io/gitea/modules/graceful" | "code.gitea.io/gitea/modules/graceful" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"code.gitea.io/gitea/modules/private" | "code.gitea.io/gitea/modules/private" | ||||
"code.gitea.io/gitea/modules/queue" | "code.gitea.io/gitea/modules/queue" | ||||
"code.gitea.io/gitea/modules/setting" | |||||
"gitea.com/macaron/macaron" | "gitea.com/macaron/macaron" | ||||
) | ) | ||||
err := queue.GetManager().FlushAll(ctx.Req.Request.Context(), opts.Timeout) | err := queue.GetManager().FlushAll(ctx.Req.Request.Context(), opts.Timeout) | ||||
if err != nil { | if err != nil { | ||||
ctx.JSON(http.StatusRequestTimeout, map[string]interface{}{ | ctx.JSON(http.StatusRequestTimeout, map[string]interface{}{ | ||||
"err": err, | |||||
"err": fmt.Sprintf("%v", err), | |||||
}) | }) | ||||
} | } | ||||
ctx.PlainText(http.StatusOK, []byte("success")) | ctx.PlainText(http.StatusOK, []byte("success")) | ||||
} | } | ||||
// PauseLogging pauses logging | |||||
func PauseLogging(ctx *macaron.Context) { | |||||
log.Pause() | |||||
ctx.PlainText(http.StatusOK, []byte("success")) | |||||
} | |||||
// ResumeLogging resumes logging | |||||
func ResumeLogging(ctx *macaron.Context) { | |||||
log.Resume() | |||||
ctx.PlainText(http.StatusOK, []byte("success")) | |||||
} | |||||
// ReleaseReopenLogging releases and reopens logging files | |||||
func ReleaseReopenLogging(ctx *macaron.Context) { | |||||
if err := log.ReleaseReopen(); err != nil { | |||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||||
"err": fmt.Sprintf("Error during release and reopen: %v", err), | |||||
}) | |||||
return | |||||
} | |||||
ctx.PlainText(http.StatusOK, []byte("success")) | |||||
} | |||||
// RemoveLogger removes a logger | |||||
func RemoveLogger(ctx *macaron.Context) { | |||||
group := ctx.Params("group") | |||||
name := ctx.Params("name") | |||||
ok, err := log.GetLogger(group).DelLogger(name) | |||||
if err != nil { | |||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||||
"err": fmt.Sprintf("Failed to remove logger: %s %s %v", group, name, err), | |||||
}) | |||||
return | |||||
} | |||||
if ok { | |||||
setting.RemoveSubLogDescription(group, name) | |||||
} | |||||
ctx.PlainText(http.StatusOK, []byte(fmt.Sprintf("Removed %s %s", group, name))) | |||||
} | |||||
// AddLogger adds a logger | |||||
func AddLogger(ctx *macaron.Context, opts private.LoggerOptions) { | |||||
if len(opts.Group) == 0 { | |||||
opts.Group = log.DEFAULT | |||||
} | |||||
if _, ok := opts.Config["flags"]; !ok { | |||||
switch opts.Group { | |||||
case "access": | |||||
opts.Config["flags"] = log.FlagsFromString("") | |||||
case "router": | |||||
opts.Config["flags"] = log.FlagsFromString("date,time") | |||||
default: | |||||
opts.Config["flags"] = log.FlagsFromString("stdflags") | |||||
} | |||||
} | |||||
if _, ok := opts.Config["colorize"]; !ok && opts.Mode == "console" { | |||||
if _, ok := opts.Config["stderr"]; ok { | |||||
opts.Config["colorize"] = log.CanColorStderr | |||||
} else { | |||||
opts.Config["colorize"] = log.CanColorStdout | |||||
} | |||||
} | |||||
if _, ok := opts.Config["level"]; !ok { | |||||
opts.Config["level"] = setting.LogLevel | |||||
} | |||||
if _, ok := opts.Config["stacktraceLevel"]; !ok { | |||||
opts.Config["stacktraceLevel"] = setting.StacktraceLogLevel | |||||
} | |||||
if opts.Mode == "file" { | |||||
if _, ok := opts.Config["maxsize"]; !ok { | |||||
opts.Config["maxsize"] = 1 << 28 | |||||
} | |||||
if _, ok := opts.Config["maxdays"]; !ok { | |||||
opts.Config["maxdays"] = 7 | |||||
} | |||||
if _, ok := opts.Config["compressionLevel"]; !ok { | |||||
opts.Config["compressionLevel"] = -1 | |||||
} | |||||
} | |||||
bufferLen := setting.Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000) | |||||
byteConfig, err := json.Marshal(opts.Config) | |||||
if err != nil { | |||||
log.Error("Failed to marshal log configuration: %v %v", opts.Config, err) | |||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||||
"err": fmt.Sprintf("Failed to marshal log configuration: %v %v", opts.Config, err), | |||||
}) | |||||
return | |||||
} | |||||
config := string(byteConfig) | |||||
if err := log.NewNamedLogger(opts.Group, bufferLen, opts.Name, opts.Mode, config); err != nil { | |||||
log.Error("Failed to create new named logger: %s %v", config, err) | |||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ | |||||
"err": fmt.Sprintf("Failed to create new named logger: %s %v", config, err), | |||||
}) | |||||
return | |||||
} | |||||
setting.AddSubLogDescription(opts.Group, setting.SubLogDescription{ | |||||
Name: opts.Name, | |||||
Provider: opts.Mode, | |||||
Config: config, | |||||
}) | |||||
ctx.PlainText(http.StatusOK, []byte("success")) | |||||
} |