Compare commits

...

5 Commits

Author SHA1 Message Date
Jatinderjit Singh
ef88766b81 Merge branch 'main' into chore/ignore-context-canceled-logs 2026-01-20 10:44:33 +05:30
Jatinderjit Singh
16ef88521c chore: use logger with context 2026-01-08 02:21:06 +05:30
Jatinderjit Singh
feff1c1267 chore: update comments 2026-01-08 02:10:38 +05:30
Jatinderjit Singh
57db8b9e23 chore: add tests for filter wrapper 2026-01-08 02:04:08 +05:30
Jatinderjit Singh
9650d7ee24 chore: filter slog logs for context.Canceled errors 2026-01-08 02:03:54 +05:30
5 changed files with 126 additions and 2 deletions

View File

@@ -88,4 +88,4 @@ jobs:
- name: generate-openapi
run: |
go run cmd/enterprise/*.go generate openapi
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in openapi spec. Run go run cmd/enterprise/*.go generate openapi locally and commit."; exit 1)
git diff --compact-summary --exit-code || (echo; echo 'Unexpected difference in openapi spec. Run `go run cmd/enterprise/*.go generate openapi` locally and commit.'; exit 1)

View File

@@ -11,6 +11,8 @@ import (
type zapToSlogConverter struct{}
func NewLogger(config Config, wrappers ...loghandler.Wrapper) *slog.Logger {
wrappers = append([]loghandler.Wrapper{loghandler.NewFilter()}, wrappers...)
logger := slog.New(
loghandler.New(
slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: config.Logs.Level, AddSource: true, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {

View File

@@ -9,7 +9,7 @@ import (
type correlation struct{}
func NewCorrelation() *correlation {
func NewCorrelation() Wrapper {
return &correlation{}
}

View File

@@ -0,0 +1,54 @@
package loghandler
import (
"context"
"log/slog"
"github.com/SigNoz/signoz/pkg/errors"
)
// filter wraps a LogHandler to filter out log entries based on a custom logic
type filter struct{}
// NewFilter creates a new filtering wrapper
func NewFilter() Wrapper {
return &filter{}
}
// Wrap implements the Wrapper interface.
// It returns a LogHandler that filters log records based on a custom logic
func (f *filter) Wrap(next LogHandler) LogHandler {
return LogHandlerFunc(func(ctx context.Context, record slog.Record) error {
dropEntry := false
record.Attrs(func(attr slog.Attr) bool {
if shouldDropEntry(attr) {
dropEntry = true
return false // stop iteration
}
return true
})
// Skip logging this entry
if dropEntry {
return nil
}
return next.Handle(ctx, record)
})
}
// shouldDropEntry determines whether a log entry should be written based on
// its fields.
// Returns false if the entry should be suppressed, true otherwise.
//
// Current filters:
// - context.Canceled: These are expected errors from cancelled operations,
// and create noise in logs.
func shouldDropEntry(attr slog.Attr) bool {
if (attr.Key == "error" || attr.Key == "err") && attr.Value.Kind() == slog.KindAny {
if loggedErr, ok := attr.Value.Any().(error); ok && loggedErr != nil {
return errors.Is(loggedErr, context.Canceled)
}
}
return false
}

View File

@@ -0,0 +1,68 @@
package loghandler
import (
"bytes"
"context"
"encoding/json"
"log/slog"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFilter_ContextCanceled(t *testing.T) {
buf := bytes.NewBuffer(nil)
logger := slog.New(&handler{
base: slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}),
wrappers: []Wrapper{NewFilter()},
})
logger.WarnContext(context.Background(), "ignore_message", "error", context.Canceled)
// Buffer should be empty since the log should be filtered out
assert.Empty(t, buf.Bytes(), "context.Canceled error should be filtered out")
}
func TestFilter_OtherErrors(t *testing.T) {
buf := bytes.NewBuffer(nil)
logger := slog.New(&handler{
base: slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}),
wrappers: []Wrapper{NewFilter()},
})
// Log with different error - should NOT be filtered
logger.WarnContext(context.Background(), "log_message", "error", context.DeadlineExceeded)
// Buffer should contain the log entry
require.NotEmpty(t, buf.Bytes(), "other errors should be logged")
m := make(map[string]any)
err := json.Unmarshal(buf.Bytes(), &m)
require.NoError(t, err)
assert.Equal(t, "log_message", m["msg"])
assert.Equal(t, "WARN", m["level"])
assert.Equal(t, "context deadline exceeded", m["error"])
}
func TestFilter_NoError(t *testing.T) {
buf := bytes.NewBuffer(nil)
logger := slog.New(&handler{
base: slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}),
wrappers: []Wrapper{NewFilter()},
})
// Log without error - should be logged normally
logger.InfoContext(context.Background(), "normal_message", "key", "value")
// Buffer should contain the log entry
require.NotEmpty(t, buf.Bytes(), "logs without errors should be logged")
m := make(map[string]any)
err := json.Unmarshal(buf.Bytes(), &m)
require.NoError(t, err)
assert.Equal(t, "normal_message", m["msg"])
assert.Equal(t, "value", m["key"])
}