mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-07-07 09:55:41 +02:00
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
This feature is intended to help reduce noisy logs generated by routine Kubernetes probes and Prometheus scraping. While logs are essential, these specific requests (e.g., to /metrics and /api/healthz) generally don't provide useful information and tend to clutter the output. The goal is to introduce functionality that effectively acts as the inverse of the existing EXPRESSION mode—allowing logging to be excluded based on a condition, rather than included. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8212 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: zokki <zokki.softwareschmiede@gmail.com> Co-committed-by: zokki <zokki.softwareschmiede@gmail.com>
184 lines
4.5 KiB
Go
184 lines
4.5 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package log
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"runtime/pprof"
|
|
"time"
|
|
)
|
|
|
|
// EventWriterBase is the base interface for most event writers
|
|
// It provides default implementations for most methods
|
|
type EventWriterBase interface {
|
|
Base() *EventWriterBaseImpl
|
|
GetWriterType() string
|
|
GetWriterName() string
|
|
GetLevel() Level
|
|
|
|
Run(ctx context.Context)
|
|
}
|
|
|
|
type EventWriterBaseImpl struct {
|
|
writerType string
|
|
|
|
Name string
|
|
Mode *WriterMode
|
|
Queue chan *EventFormatted
|
|
|
|
FormatMessage EventFormatter // format the Event to a message and write it to output
|
|
OutputWriteCloser io.WriteCloser // it will be closed when the event writer is stopped
|
|
GetPauseChan func() chan struct{}
|
|
|
|
shared bool
|
|
stopped chan struct{}
|
|
}
|
|
|
|
var _ EventWriterBase = (*EventWriterBaseImpl)(nil)
|
|
|
|
func (b *EventWriterBaseImpl) Base() *EventWriterBaseImpl {
|
|
return b
|
|
}
|
|
|
|
func (b *EventWriterBaseImpl) GetWriterType() string {
|
|
return b.writerType
|
|
}
|
|
|
|
func (b *EventWriterBaseImpl) GetWriterName() string {
|
|
return b.Name
|
|
}
|
|
|
|
func (b *EventWriterBaseImpl) GetLevel() Level {
|
|
return b.Mode.Level
|
|
}
|
|
|
|
// Run is the default implementation for EventWriter.Run
|
|
func (b *EventWriterBaseImpl) Run(ctx context.Context) {
|
|
defer b.OutputWriteCloser.Close()
|
|
|
|
var exprRegexp *regexp.Regexp
|
|
if b.Mode.Expression != "" {
|
|
var err error
|
|
if exprRegexp, err = regexp.Compile(b.Mode.Expression); err != nil {
|
|
FallbackErrorf("unable to compile expression %q for writer %q: %v", b.Mode.Expression, b.Name, err)
|
|
}
|
|
}
|
|
|
|
var exclusionRegexp *regexp.Regexp
|
|
if b.Mode.Exclusion != "" {
|
|
var err error
|
|
if exclusionRegexp, err = regexp.Compile(b.Mode.Exclusion); err != nil {
|
|
FallbackErrorf("unable to compile exclusion %q for writer %q: %v", b.Mode.Exclusion, b.Name, err)
|
|
}
|
|
}
|
|
|
|
handlePaused := func() {
|
|
if pause := b.GetPauseChan(); pause != nil {
|
|
select {
|
|
case <-pause:
|
|
case <-ctx.Done():
|
|
}
|
|
}
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case event, ok := <-b.Queue:
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
handlePaused()
|
|
|
|
if exprRegexp != nil {
|
|
fileLineCaller := fmt.Sprintf("%s:%d:%s", event.Origin.Filename, event.Origin.Line, event.Origin.Caller)
|
|
matched := exprRegexp.MatchString(fileLineCaller) || exprRegexp.MatchString(event.Origin.MsgSimpleText)
|
|
if !matched {
|
|
continue
|
|
}
|
|
}
|
|
if exclusionRegexp != nil {
|
|
fileLineCaller := fmt.Sprintf("%s:%d:%s", event.Origin.Filename, event.Origin.Line, event.Origin.Caller)
|
|
matched := exclusionRegexp.MatchString(fileLineCaller) || exclusionRegexp.MatchString(event.Origin.MsgSimpleText)
|
|
if matched {
|
|
continue
|
|
}
|
|
}
|
|
|
|
var err error
|
|
switch msg := event.Msg.(type) {
|
|
case string:
|
|
_, err = b.OutputWriteCloser.Write([]byte(msg))
|
|
case []byte:
|
|
_, err = b.OutputWriteCloser.Write(msg)
|
|
case io.WriterTo:
|
|
_, err = msg.WriteTo(b.OutputWriteCloser)
|
|
default:
|
|
_, err = fmt.Fprint(b.OutputWriteCloser, msg)
|
|
}
|
|
if err != nil {
|
|
FallbackErrorf("unable to write log message of %q (%v): %v", b.Name, err, event.Msg)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func NewEventWriterBase(name, writerType string, mode WriterMode) *EventWriterBaseImpl {
|
|
if mode.BufferLen == 0 {
|
|
mode.BufferLen = 1000
|
|
}
|
|
if mode.Level == UNDEFINED {
|
|
mode.Level = INFO
|
|
}
|
|
if mode.StacktraceLevel == UNDEFINED {
|
|
mode.StacktraceLevel = NONE
|
|
}
|
|
b := &EventWriterBaseImpl{
|
|
writerType: writerType,
|
|
|
|
Name: name,
|
|
Mode: &mode,
|
|
Queue: make(chan *EventFormatted, mode.BufferLen),
|
|
|
|
GetPauseChan: GetManager().GetPauseChan, // by default, use the global pause channel
|
|
FormatMessage: EventFormatTextMessage,
|
|
}
|
|
return b
|
|
}
|
|
|
|
// eventWriterStartGo use "go" to start an event worker's Run method
|
|
func eventWriterStartGo(ctx context.Context, w EventWriter, shared bool) {
|
|
if w.Base().stopped != nil {
|
|
return // already started
|
|
}
|
|
w.Base().shared = shared
|
|
w.Base().stopped = make(chan struct{})
|
|
|
|
ctxDesc := "Logger: EventWriter: " + w.GetWriterName()
|
|
if shared {
|
|
ctxDesc = "Logger: EventWriter (shared): " + w.GetWriterName()
|
|
}
|
|
writerCtx, writerCancel := newProcessTypedContext(ctx, ctxDesc)
|
|
go func() {
|
|
defer writerCancel()
|
|
defer close(w.Base().stopped)
|
|
pprof.SetGoroutineLabels(writerCtx)
|
|
w.Run(writerCtx)
|
|
}()
|
|
}
|
|
|
|
// eventWriterStopWait stops an event writer and waits for it to finish flushing (with a timeout)
|
|
func eventWriterStopWait(w EventWriter) {
|
|
close(w.Base().Queue)
|
|
select {
|
|
case <-w.Base().stopped:
|
|
case <-time.After(2 * time.Second):
|
|
FallbackErrorf("unable to stop log writer %q in time, skip", w.GetWriterName())
|
|
}
|
|
}
|