(new): add threshold-based log level filtering and docs
Golang lint / lint (push) Successful in 1m36s
Golang lint / lint (push) Successful in 1m36s
- add threshold-aware LogLevel constructors - add Logger.SetThresholdMode and SameThreshold - preserve legacy SameLevel behavior for compatibility - extend tests for threshold mode and compatibility - update README and release notes for v2.2.0
This commit is contained in:
@@ -71,6 +71,10 @@ func main() {
|
||||
|
||||
Important: with the current level ordering, `Level(sneklog.FATAL)` allows `INFO`, `WARN`, `ERROR`, and `FATAL`, but not `DEBUG`. Use `Level(sneklog.DEBUG)` to enable every level.
|
||||
|
||||
If you want classic threshold filtering instead, call `SetThresholdMode(true)` and
|
||||
configure levels with `NewThresholdLogLevel(...)` or
|
||||
`NewThresholdLogLevelWithColors(...)`.
|
||||
|
||||
## Writers and ownership
|
||||
|
||||
`Logger.Close()` only closes writers created by the logger itself:
|
||||
@@ -142,6 +146,29 @@ access := sneklog.NewInfoLogLevelWithColors("access", sneklog.FgCyan, sneklog.Bg
|
||||
audit := sneklog.NewWarnLogLevel("audit")
|
||||
```
|
||||
|
||||
These helpers keep the legacy severity index for backward compatibility and also
|
||||
assign default threshold values:
|
||||
|
||||
- `INFO`: `th=10`
|
||||
- `WARN`: `th=20`
|
||||
- `ERROR`: `th=30`
|
||||
- `FATAL`: `th=40`
|
||||
- `DEBUG`: `th=0`
|
||||
|
||||
To define custom threshold-based levels explicitly:
|
||||
|
||||
```go
|
||||
trace := sneklog.NewThresholdLogLevelWithColors(4, 5, "trace", sneklog.FgHiBlack, sneklog.BgNone)
|
||||
audit := sneklog.NewThresholdLogLevel(1, 25, "audit")
|
||||
|
||||
logger := sneklog.NewLogger().
|
||||
SetLevel(audit).
|
||||
SetThresholdMode(true)
|
||||
|
||||
logger.Print(trace, "verbose trace") // filtered out
|
||||
logger.Print(audit, "audit event") // allowed
|
||||
```
|
||||
|
||||
HTTP method-specific predefined levels are available out of the box:
|
||||
|
||||
```go
|
||||
@@ -149,8 +176,11 @@ level := sneklog.LogLevelForMethod(http.MethodPost)
|
||||
logger.Print(level, "POST /users")
|
||||
```
|
||||
|
||||
When comparing levels, use `SameLevel` if only the severity matters and `Equal`
|
||||
if the full configuration, including colors and attributes, must match.
|
||||
When comparing levels:
|
||||
|
||||
- use `SameLevel` if only the legacy severity index matters;
|
||||
- use `SameThreshold` if only threshold filtering matters;
|
||||
- use `Equal` if the full configuration, including threshold, colors, and attributes, must match.
|
||||
|
||||
## Message replacement
|
||||
|
||||
|
||||
+32
-3
@@ -71,6 +71,10 @@ func main() {
|
||||
|
||||
Важно: в текущей модели уровней `Level(sneklog.FATAL)` пропускает `INFO`, `WARN`, `ERROR` и `FATAL`, но не `DEBUG`. Чтобы включить все сообщения, используйте `Level(sneklog.DEBUG)`.
|
||||
|
||||
Если нужна классическая threshold-фильтрация, включите `SetThresholdMode(true)` и
|
||||
задавайте уровни через `NewThresholdLogLevel(...)` или
|
||||
`NewThresholdLogLevelWithColors(...)`.
|
||||
|
||||
## Writer'ы и владение
|
||||
|
||||
`Logger.Close()` закрывает только writer'ы, которые логгер создал сам:
|
||||
@@ -142,6 +146,29 @@ access := sneklog.NewInfoLogLevelWithColors("access", sneklog.FgCyan, sneklog.Bg
|
||||
audit := sneklog.NewWarnLogLevel("audit")
|
||||
```
|
||||
|
||||
Эти helper'ы сохраняют legacy severity index для обратной совместимости и
|
||||
одновременно выставляют значения threshold по умолчанию:
|
||||
|
||||
- `INFO`: `th=10`
|
||||
- `WARN`: `th=20`
|
||||
- `ERROR`: `th=30`
|
||||
- `FATAL`: `th=40`
|
||||
- `DEBUG`: `th=0`
|
||||
|
||||
Чтобы явно создавать уровни для threshold-режима:
|
||||
|
||||
```go
|
||||
trace := sneklog.NewThresholdLogLevelWithColors(4, 5, "trace", sneklog.FgHiBlack, sneklog.BgNone)
|
||||
audit := sneklog.NewThresholdLogLevel(1, 25, "audit")
|
||||
|
||||
logger := sneklog.NewLogger().
|
||||
SetLevel(audit).
|
||||
SetThresholdMode(true)
|
||||
|
||||
logger.Print(trace, "verbose trace") // будет отфильтровано
|
||||
logger.Print(audit, "audit event") // будет записано
|
||||
```
|
||||
|
||||
Для HTTP-методов доступны готовые уровни:
|
||||
|
||||
```go
|
||||
@@ -149,9 +176,11 @@ level := sneklog.LogLevelForMethod(http.MethodPost)
|
||||
logger.Print(level, "POST /users")
|
||||
```
|
||||
|
||||
Если нужно сравнить только severity, используйте `SameLevel`. Если нужно
|
||||
полное совпадение конфигурации уровня, включая цвета и атрибуты, используйте
|
||||
`Equal`.
|
||||
При сравнении уровней:
|
||||
|
||||
- используйте `SameLevel`, если важен только legacy severity index;
|
||||
- используйте `SameThreshold`, если важна только threshold-фильтрация;
|
||||
- используйте `Equal`, если нужно полное совпадение конфигурации, включая threshold, цвета и атрибуты.
|
||||
|
||||
## Замена сообщений
|
||||
|
||||
|
||||
+16
-5
@@ -1,6 +1,8 @@
|
||||
# Unreleased (planned v2.2.0)
|
||||
|
||||
This release expands the public LogLevel API, adds HTTP method helpers, and improves package documentation and test coverage.
|
||||
This release expands the public LogLevel API, adds optional threshold-based
|
||||
filtering alongside the legacy severity ordering, and improves documentation
|
||||
and test coverage.
|
||||
|
||||
### Added
|
||||
|
||||
@@ -15,6 +17,9 @@ This release expands the public LogLevel API, adds HTTP method helpers, and impr
|
||||
- `NewFatalLogLevelWithColors`
|
||||
- `NewDebugLogLevel`
|
||||
- `NewDebugLogLevelWithColors`
|
||||
- Added threshold-aware log level constructors:
|
||||
- `NewThresholdLogLevel`
|
||||
- `NewThresholdLogLevelWithColors`
|
||||
- Added HTTP method-specific predefined log levels:
|
||||
- `HTTPGetLevel`
|
||||
- `HTTPHeadLevel`
|
||||
@@ -28,24 +33,30 @@ This release expands the public LogLevel API, adds HTTP method helpers, and impr
|
||||
- `HTTPUnknownLevel`
|
||||
- Added `LogLevelForMethod(method string) LogLevel` to map HTTP methods to predefined log levels.
|
||||
- Added `Logger.Printf(level, format, args...)` for formatted logging with an explicit log level.
|
||||
- Added `LogLevel.SameLevel` and `LogLevel.Equal` for severity-only and full level comparisons.
|
||||
- Added `Logger.SetThresholdMode(bool)` to switch filtering from legacy severity-index ordering to threshold-based ordering.
|
||||
- Added `LogLevel.SameLevel`, `LogLevel.SameThreshold`, and `LogLevel.Equal` for severity-only, threshold-only, and full level comparisons.
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactored log level definitions into a dedicated `levels.go` file.
|
||||
- Unified `Logger.Print` and `Logger.Println` through a shared internal implementation without changing their behavior.
|
||||
- Expanded test coverage for `Logger.Printf`, HTTP level mapping, and new level constructors.
|
||||
- Common helper constructors such as `NewInfoLogLevel` now also assign threshold values while preserving legacy severity indexes.
|
||||
- `LogLevel.Equal` now includes threshold configuration in full equality checks.
|
||||
- Expanded test coverage for `Logger.Printf`, HTTP level mapping, constructor behavior, threshold filtering, and backward compatibility.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Improved GoDoc coverage for exported types, constructors, formatters, colors, HTTP helpers, and time layouts.
|
||||
- Documented level comparison helpers and explicit-level formatted logging.
|
||||
- Documented threshold mode, threshold-aware constructors, level comparison helpers, and explicit-level formatted logging.
|
||||
- Expanded inline documentation for formatter tokens and default formatter behavior.
|
||||
- Updated English and Russian README files with threshold-mode examples and comparison guidance.
|
||||
|
||||
### Compatibility
|
||||
|
||||
- No breaking API changes.
|
||||
- Existing `Print`, `Println`, predefined levels, and color APIs remain compatible.
|
||||
- Existing `Print`, `Println`, predefined levels, `Level/SetLevel`, and color APIs remain compatible.
|
||||
- Legacy severity ordering remains the default; threshold filtering is enabled only when `SetThresholdMode(true)` is called.
|
||||
- `SameLevel` remains severity-only for backward compatibility, even when levels carry threshold metadata.
|
||||
|
||||
# v2.1.0
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
// io.Writer values through CreateTextWriter or CreateJsonWriter remain owned by
|
||||
// the caller and are not closed by the logger.
|
||||
//
|
||||
// LogLevel values can be compared with SameLevel when only severity matters or
|
||||
// LogLevel values can be compared with SameLevel when only the legacy severity
|
||||
// index matters, with SameThreshold when only threshold filtering matters, or
|
||||
// with Equal when the full level configuration must match.
|
||||
package sneklog
|
||||
|
||||
@@ -84,7 +84,9 @@ func (l *Logger) Debugln(m ...any) {
|
||||
}
|
||||
|
||||
func (l *Logger) print(level LogLevel, newline bool, m ...any) {
|
||||
if l.level.n < level.n {
|
||||
if l.threshold && l.level.th < level.th {
|
||||
return
|
||||
} else if l.level.n < level.n {
|
||||
return
|
||||
}
|
||||
if len(l.writers) == 0 {
|
||||
|
||||
@@ -11,8 +11,9 @@ var (
|
||||
|
||||
// LogLevel describes a logging severity.
|
||||
type LogLevel struct {
|
||||
n uint8
|
||||
t string
|
||||
n uint8
|
||||
th uint8
|
||||
t string
|
||||
|
||||
fg FgColor
|
||||
fg256 FgColor256
|
||||
@@ -26,53 +27,68 @@ type LogLevel struct {
|
||||
}
|
||||
|
||||
// NewLogLevel creates a log level without predefined colors.
|
||||
// Deprecated: use NewThresholdLogLevel. This function will be removed in v3.
|
||||
func NewLogLevel(index uint8, name string) LogLevel {
|
||||
return NewLogLevelWithColors(index, name, 0, 0)
|
||||
}
|
||||
|
||||
// NewLogLevelWithColors creates a log level with ANSI foreground and background colors.
|
||||
// Deprecated: use NewThresholdLogLevelWithColors. This function will be removed in v3.
|
||||
func NewLogLevelWithColors(index uint8, name string, fg FgColor, bg BgColor) LogLevel {
|
||||
return LogLevel{n: index, t: name, fg: fg, bg: bg, attrs: []Attribute{}}
|
||||
}
|
||||
|
||||
// NewThresholdLogLevel creates a log level with both a legacy severity index and
|
||||
// a threshold value used when Logger.SetThresholdMode(true) is enabled.
|
||||
func NewThresholdLogLevel(index, th uint8, name string) LogLevel {
|
||||
return NewThresholdLogLevelWithColors(index, th, name, FgNone, BgNone)
|
||||
}
|
||||
|
||||
// NewThresholdLogLevelWithColors creates a log level with both a legacy
|
||||
// severity index and a threshold value, plus ANSI foreground and background
|
||||
// colors.
|
||||
func NewThresholdLogLevelWithColors(index, th uint8, name string, fg FgColor, bg BgColor) LogLevel {
|
||||
return LogLevel{n: index, th: th, t: name, fg: fg, bg: bg, attrs: []Attribute{}}
|
||||
}
|
||||
|
||||
// NewInfoLogLevel creates an info-level log level without predefined colors.
|
||||
func NewInfoLogLevel(name string) LogLevel { return NewLogLevel(0, name) }
|
||||
func NewInfoLogLevel(name string) LogLevel { return NewThresholdLogLevel(0, 10, name) }
|
||||
|
||||
// NewInfoLogLevelWithColors creates an info-level log level with ANSI colors.
|
||||
func NewInfoLogLevelWithColors(name string, fg FgColor, bg BgColor) LogLevel {
|
||||
return NewLogLevelWithColors(0, name, fg, bg)
|
||||
return NewThresholdLogLevelWithColors(0, 10, name, fg, bg)
|
||||
}
|
||||
|
||||
// NewWarnLogLevel creates a warn-level log level without predefined colors.
|
||||
func NewWarnLogLevel(name string) LogLevel { return NewLogLevel(1, name) }
|
||||
func NewWarnLogLevel(name string) LogLevel { return NewThresholdLogLevel(1, 20, name) }
|
||||
|
||||
// NewWarnLogLevelWithColors creates a warn-level log level with ANSI colors.
|
||||
func NewWarnLogLevelWithColors(name string, fg FgColor, bg BgColor) LogLevel {
|
||||
return NewLogLevelWithColors(1, name, fg, bg)
|
||||
return NewThresholdLogLevelWithColors(1, 20, name, fg, bg)
|
||||
}
|
||||
|
||||
// NewErrorLogLevel creates an error-level log level without predefined colors.
|
||||
func NewErrorLogLevel(name string) LogLevel { return NewLogLevel(2, name) }
|
||||
func NewErrorLogLevel(name string) LogLevel { return NewThresholdLogLevel(2, 30, name) }
|
||||
|
||||
// NewErrorLogLevelWithColors creates an error-level log level with ANSI colors.
|
||||
func NewErrorLogLevelWithColors(name string, fg FgColor, bg BgColor) LogLevel {
|
||||
return NewLogLevelWithColors(2, name, fg, bg)
|
||||
return NewThresholdLogLevelWithColors(2, 30, name, fg, bg)
|
||||
}
|
||||
|
||||
// NewFatalLogLevel creates a fatal-level log level without predefined colors.
|
||||
func NewFatalLogLevel(name string) LogLevel { return NewLogLevel(3, name) }
|
||||
func NewFatalLogLevel(name string) LogLevel { return NewThresholdLogLevel(3, 40, name) }
|
||||
|
||||
// NewFatalLogLevelWithColors creates a fatal-level log level with ANSI colors.
|
||||
func NewFatalLogLevelWithColors(name string, fg FgColor, bg BgColor) LogLevel {
|
||||
return NewLogLevelWithColors(3, name, fg, bg)
|
||||
return NewThresholdLogLevelWithColors(3, 40, name, fg, bg)
|
||||
}
|
||||
|
||||
// NewDebugLogLevel creates a debug-level log level without predefined colors.
|
||||
func NewDebugLogLevel(name string) LogLevel { return NewLogLevel(4, name) }
|
||||
func NewDebugLogLevel(name string) LogLevel { return NewThresholdLogLevel(4, 0, name) }
|
||||
|
||||
// NewDebugLogLevelWithColors creates a debug-level log level with ANSI colors.
|
||||
func NewDebugLogLevelWithColors(name string, fg FgColor, bg BgColor) LogLevel {
|
||||
return NewLogLevelWithColors(4, name, fg, bg)
|
||||
return NewThresholdLogLevelWithColors(4, 0, name, fg, bg)
|
||||
}
|
||||
|
||||
// GetName returns the lowercase textual representation of the level.
|
||||
@@ -198,13 +214,21 @@ func (l *LogLevel) GetAttributes() []Attribute {
|
||||
}
|
||||
|
||||
// SameLevel reports whether two log levels have the same severity index.
|
||||
// Deprecated: use SameThreshold for threshold-only comparisons or Equal for the
|
||||
// full level configuration. SameLevel remains severity-only for backward
|
||||
// compatibility and ignores threshold values.
|
||||
func (l *LogLevel) SameLevel(other LogLevel) bool {
|
||||
return l.n == other.n
|
||||
}
|
||||
|
||||
// SameThreshold reports whether two log levels have the same threshold value.
|
||||
func (l *LogLevel) SameThreshold(other LogLevel) bool {
|
||||
return l.th == other.th
|
||||
}
|
||||
|
||||
// Equal reports whether two log levels have identical configuration.
|
||||
func (l *LogLevel) Equal(other LogLevel) bool {
|
||||
if l.t != other.t || l.n != other.n {
|
||||
if l.t != other.t || l.n != other.n || l.th != other.th {
|
||||
return false
|
||||
}
|
||||
if l.fg != other.fg || l.bg != other.bg {
|
||||
|
||||
@@ -18,12 +18,14 @@ type MethodTraceback struct {
|
||||
|
||||
// Logger routes log records to one or more configured writers.
|
||||
type Logger struct {
|
||||
prefix string
|
||||
level LogLevel
|
||||
prefix string
|
||||
level LogLevel
|
||||
|
||||
writers []LoggerWriter
|
||||
replacers []replacer
|
||||
|
||||
jsonPretty bool
|
||||
threshold bool
|
||||
}
|
||||
|
||||
// CreateLogger creates a logger with default settings.
|
||||
@@ -82,6 +84,13 @@ func (l *Logger) SetJSONPretty(b bool) *Logger {
|
||||
return l
|
||||
}
|
||||
|
||||
// SetThresholdMode switches Logger.Print filtering from legacy severity-index
|
||||
// ordering to LogLevel threshold ordering.
|
||||
func (l *Logger) SetThresholdMode(b bool) *Logger {
|
||||
l.threshold = b
|
||||
return l
|
||||
}
|
||||
|
||||
// AddWriters appends multiple writers to the logger.
|
||||
func (l *Logger) AddWriters(writers ...LoggerWriter) *Logger {
|
||||
l.writers = append(l.writers, writers...)
|
||||
|
||||
+83
-8
@@ -120,6 +120,77 @@ func TestLoggerPrintfFormatsMessage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerUsesSeverityOrderingWhenThresholdModeIsDisabled(t *testing.T) {
|
||||
writer := &stubLoggerWriter{}
|
||||
logger := CreateLogger().
|
||||
SetLevel(NewThresholdLogLevel(1, 0, "warn")).
|
||||
AddWriter(writer)
|
||||
|
||||
logger.Print(NewThresholdLogLevel(0, 100, "info"))
|
||||
logger.Print(NewThresholdLogLevel(2, 0, "error"))
|
||||
|
||||
if writer.printCalls != 1 {
|
||||
t.Fatalf("severity mode should allow only lower-or-equal severity levels, got %d writes", writer.printCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerUsesThresholdOrderingWhenThresholdModeIsEnabled(t *testing.T) {
|
||||
writer := &stubLoggerWriter{}
|
||||
logger := CreateLogger().
|
||||
SetLevel(NewThresholdLogLevel(4, 20, "custom")).
|
||||
SetThresholdMode(true).
|
||||
AddWriter(writer)
|
||||
|
||||
logger.Print(NewThresholdLogLevel(4, 10, "debug"))
|
||||
logger.Print(NewThresholdLogLevel(0, 30, "info"))
|
||||
|
||||
if writer.printCalls != 1 {
|
||||
t.Fatalf("threshold mode should allow only entries at or below the configured threshold, got %d writes", writer.printCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeprecatedLevelMethodKeepsLegacySeverityBehavior(t *testing.T) {
|
||||
writer := &stubLoggerWriter{}
|
||||
logger := CreateLogger().
|
||||
Level(FATAL).
|
||||
AddWriter(writer)
|
||||
|
||||
logger.Print(INFO, "info")
|
||||
logger.Print(WARN, "warn")
|
||||
logger.Print(ERROR, "error")
|
||||
logger.Print(FATAL, "fatal")
|
||||
logger.Print(DEBUG, "debug")
|
||||
|
||||
if writer.printCalls != 4 {
|
||||
t.Fatalf("Level(FATAL) should keep legacy behavior and allow INFO/WARN/ERROR/FATAL only, got %d writes", writer.printCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameLevelIgnoresThresholdDifferencesForCompatibility(t *testing.T) {
|
||||
legacy := NewLogLevel(1, "warn")
|
||||
threshold := NewThresholdLogLevel(1, 20, "warn")
|
||||
|
||||
if !legacy.SameLevel(threshold) {
|
||||
t.Fatalf("SameLevel() should continue comparing only severity index")
|
||||
}
|
||||
if legacy.Equal(threshold) {
|
||||
t.Fatalf("Equal() should distinguish levels with different threshold configuration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameThresholdComparesOnlyThresholdValue(t *testing.T) {
|
||||
first := NewThresholdLogLevel(1, 20, "warn")
|
||||
second := NewThresholdLogLevel(9, 20, "custom")
|
||||
third := NewThresholdLogLevel(1, 30, "warn")
|
||||
|
||||
if !first.SameThreshold(second) {
|
||||
t.Fatalf("SameThreshold() should ignore severity and compare threshold only")
|
||||
}
|
||||
if first.SameThreshold(third) {
|
||||
t.Fatalf("SameThreshold() should detect different threshold values")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogLevelForMethodReturnsExpectedLevels(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -150,15 +221,16 @@ func TestLogLevelForMethodReturnsExpectedLevels(t *testing.T) {
|
||||
|
||||
func TestNewLevelConstructorsSetExpectedSeverity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
got LogLevel
|
||||
want LogLevel
|
||||
name string
|
||||
got LogLevel
|
||||
want LogLevel
|
||||
wantTh uint8
|
||||
}{
|
||||
{name: "info", got: NewInfoLogLevel("custom"), want: INFO},
|
||||
{name: "warn", got: NewWarnLogLevel("custom"), want: WARN},
|
||||
{name: "error", got: NewErrorLogLevel("custom"), want: ERROR},
|
||||
{name: "fatal", got: NewFatalLogLevel("custom"), want: FATAL},
|
||||
{name: "debug", got: NewDebugLogLevel("custom"), want: DEBUG},
|
||||
{name: "info", got: NewInfoLogLevel("custom"), want: INFO, wantTh: 10},
|
||||
{name: "warn", got: NewWarnLogLevel("custom"), want: WARN, wantTh: 20},
|
||||
{name: "error", got: NewErrorLogLevel("custom"), want: ERROR, wantTh: 30},
|
||||
{name: "fatal", got: NewFatalLogLevel("custom"), want: FATAL, wantTh: 40},
|
||||
{name: "debug", got: NewDebugLogLevel("custom"), want: DEBUG, wantTh: 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -166,6 +238,9 @@ func TestNewLevelConstructorsSetExpectedSeverity(t *testing.T) {
|
||||
if tt.got.n != tt.want.n {
|
||||
t.Fatalf("%s severity = %d, want %d", tt.name, tt.got.n, tt.want.n)
|
||||
}
|
||||
if tt.got.th != tt.wantTh {
|
||||
t.Fatalf("%s threshold = %d, want %d", tt.name, tt.got.th, tt.wantTh)
|
||||
}
|
||||
if tt.got.GetName() != "custom" {
|
||||
t.Fatalf("%s constructor should preserve name, got %q", tt.name, tt.got.GetName())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user