diff options
author | zeripath <art27@cantab.net> | 2020-07-06 01:07:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-05 20:07:07 -0400 |
commit | c5b08f6d5a73e6ba84da84e804d05a8dd3d651be (patch) | |
tree | 7e0f72b9c62b8764d000614714c91a01ebbee223 /modules | |
parent | 38fb087d1983ca8320fb1a8c90150ae7956b358d (diff) | |
download | gitea-c5b08f6d5a73e6ba84da84e804d05a8dd3d651be.tar.gz gitea-c5b08f6d5a73e6ba84da84e804d05a8dd3d651be.zip |
Pause, Resume, Release&Reopen, Add and Remove Logging from command line (#11777)
* 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>
Diffstat (limited to 'modules')
-rw-r--r-- | modules/graceful/manager_unix.go | 5 | ||||
-rw-r--r-- | modules/log/conn.go | 12 | ||||
-rw-r--r-- | modules/log/console.go | 14 | ||||
-rw-r--r-- | modules/log/event.go | 64 | ||||
-rw-r--r-- | modules/log/file.go | 13 | ||||
-rw-r--r-- | modules/log/log.go | 37 | ||||
-rw-r--r-- | modules/log/smtp.go | 5 | ||||
-rw-r--r-- | modules/private/manager.go | 108 | ||||
-rw-r--r-- | modules/setting/log.go | 68 | ||||
-rw-r--r-- | modules/setting/setting.go | 1 |
10 files changed, 322 insertions, 5 deletions
diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index d56a4558b7..540974454c 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -113,7 +113,10 @@ func (g *Manager) handleSignals(ctx context.Context) { log.Info("PID: %d. Received SIGHUP. Attempting GracefulRestart...", pid) g.DoGracefulRestart() 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: log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) g.DoImmediateHammer() diff --git a/modules/log/conn.go b/modules/log/conn.go index 8816664526..1abe44c1d4 100644 --- a/modules/log/conn.go +++ b/modules/log/conn.go @@ -77,6 +77,13 @@ func (i *connWriter) connect() error { return nil } +func (i *connWriter) releaseReopen() error { + if i.innerWriter != nil { + return i.connect() + } + return nil +} + // ConnLogger implements LoggerProvider. // it writes messages in keep-live tcp connection. type ConnLogger struct { @@ -119,6 +126,11 @@ func (log *ConnLogger) GetName() string { return "conn" } +// ReleaseReopen causes the ConnLogger to reconnect to the server +func (log *ConnLogger) ReleaseReopen() error { + return log.out.(*connWriter).releaseReopen() +} + func init() { Register("conn", NewConn) } diff --git a/modules/log/console.go b/modules/log/console.go index 6cfca8a733..a805021f0b 100644 --- a/modules/log/console.go +++ b/modules/log/console.go @@ -68,6 +68,20 @@ func (log *ConsoleLogger) Init(config string) error { 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 func (log *ConsoleLogger) GetName() string { return "console" diff --git a/modules/log/event.go b/modules/log/event.go index 37efa3c230..6975bf749d 100644 --- a/modules/log/event.go +++ b/modules/log/event.go @@ -29,6 +29,7 @@ type EventLogger interface { GetLevel() Level GetStacktraceLevel() Level GetName() string + ReleaseReopen() error } // ChannelledLog represents a cached channel to a LoggerProvider @@ -117,6 +118,11 @@ func (l *ChannelledLog) Flush() { l.flush <- true } +// ReleaseReopen this ChannelledLog +func (l *ChannelledLog) ReleaseReopen() error { + return l.loggerProvider.ReleaseReopen() +} + // GetLevel gets the level of this ChannelledLog func (l *ChannelledLog) GetLevel() Level { return l.loggerProvider.GetLevel() @@ -145,6 +151,7 @@ type MultiChannelledLog struct { level Level stacktraceLevel Level closed chan bool + paused chan bool } // NewMultiChannelledLog a new logger instance with given logger provider and config. @@ -159,6 +166,7 @@ func NewMultiChannelledLog(name string, bufferLength int64) *MultiChannelledLog stacktraceLevel: NONE, close: make(chan bool), closed: make(chan bool), + paused: make(chan bool), } return m } @@ -229,6 +237,33 @@ func (m *MultiChannelledLog) closeLoggers() { 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 func (m *MultiChannelledLog) Start() { m.mutex.Lock() @@ -238,8 +273,35 @@ func (m *MultiChannelledLog) Start() { } m.started = true m.mutex.Unlock() + paused := false 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 { + case paused = <-m.paused: + if paused && m.level < INFO { + m.level = INFO + } case event, ok := <-m.queue: if !ok { m.closeLoggers() @@ -275,7 +337,7 @@ func (m *MultiChannelledLog) LogEvent(event *Event) error { select { case m.queue <- event: return nil - case <-time.After(60 * time.Second): + case <-time.After(100 * time.Millisecond): // We're blocked! return ErrTimeout{ Name: m.name, diff --git a/modules/log/file.go b/modules/log/file.go index 877820b8be..925d83f2b7 100644 --- a/modules/log/file.go +++ b/modules/log/file.go @@ -249,6 +249,19 @@ func (log *FileLogger) Flush() { _ = 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 func (log *FileLogger) GetName() string { return "file" diff --git a/modules/log/log.go b/modules/log/log.go index 71e88491f1..2a35b5752c 100644 --- a/modules/log/log.go +++ b/modules/log/log.go @@ -5,6 +5,7 @@ package log import ( + "fmt" "os" "runtime" "strings" @@ -192,6 +193,42 @@ func IsFatal() bool { 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 func Close() { l, ok := NamedLoggers.Load(DEFAULT) diff --git a/modules/log/smtp.go b/modules/log/smtp.go index f912299a73..edf4943619 100644 --- a/modules/log/smtp.go +++ b/modules/log/smtp.go @@ -97,6 +97,11 @@ func (log *SMTPLogger) sendMail(p []byte) (int, error) { func (log *SMTPLogger) Flush() { } +// ReleaseReopen does nothing +func (log *SMTPLogger) ReleaseReopen() error { + return nil +} + // GetName returns the default name for this implementation func (log *SMTPLogger) GetName() string { return "smtp" diff --git a/modules/private/manager.go b/modules/private/manager.go index 503acf17d6..6c9ec920bb 100644 --- a/modules/private/manager.go +++ b/modules/private/manager.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "time" "code.gitea.io/gitea/modules/setting" @@ -81,3 +82,110 @@ func FlushQueues(timeout time.Duration, nonBlocking bool) (int, string) { 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" +} diff --git a/modules/setting/log.go b/modules/setting/log.go index 5ffb2479dd..35bf021ac2 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -12,6 +12,7 @@ import ( "path" "path/filepath" "strings" + "sync" "code.gitea.io/gitea/modules/log" @@ -20,6 +21,69 @@ import ( 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 { levelName string // LogLevel flags string @@ -185,7 +249,7 @@ func generateNamedLogger(key string, options defaultLogOptions) *LogDescription log.Info("%s Log: %s(%s:%s)", strings.Title(key), strings.Title(name), provider, levelName) } - LogDescriptions[key] = &description + AddLogDescription(key, &description) return &description } @@ -279,7 +343,7 @@ func newLogService() { 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 golog.SetFlags(0) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 8efc832f9d..4c2fba8048 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -289,7 +289,6 @@ var ( LogLevel string StacktraceLogLevel string LogRootPath string - LogDescriptions = make(map[string]*LogDescription) RedirectMacaronLog bool DisableRouterLog bool RouterLogLevel log.Level |