(chore): document and test new LogLevel APIs
Golang lint / lint (push) Successful in 47s

- add godoc for SameLevel and Equal
- update package docs and READMEs with new helpers
- refresh unreleased release notes
- add tests for Printf, HTTP method levels, and level constructors
This commit is contained in:
2026-04-27 17:04:41 +03:00
parent 812caed471
commit b7fa3f7606
6 changed files with 197 additions and 2 deletions
+18
View File
@@ -135,6 +135,23 @@ httpCache.SetForeground256Color(214)
Because `LogLevel` setters mutate the level in place, call them on a variable, not on a temporary value returned by `NewLogLevel(...)`.
Short forms such as `SetFgColor` and `SetBgColor` remain available for backward compatibility.
Common severities also have dedicated helpers:
```go
access := sneklog.NewInfoLogLevelWithColors("access", sneklog.FgCyan, sneklog.BgNone)
audit := sneklog.NewWarnLogLevel("audit")
```
HTTP method-specific predefined levels are available out of the box:
```go
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.
## Message replacement
`AddReplacer(old, new)` replaces matching text in every message before the
@@ -157,6 +174,7 @@ An empty `old` value is ignored.
- `Info`, `Warn`, `Error`, `Debug`, and `Fatal` accept a list of values.
- `Infof`, `Warnf`, `Errorf`, `Debugf`, and `Fatalf` use `fmt.Sprintf`.
- `Printf(level, format, args...)` formats a message for an explicit `LogLevel`.
- The `*ln` methods preserve newline semantics, which is useful for `stdout`, Docker, and line-based collectors.
- `Fatal`, `Fatalf`, and `Fatalln` call `os.Exit(1)` after writing the message.
- `AddReplacer` masks or rewrites message text before records are sent to writers.
+19
View File
@@ -135,6 +135,24 @@ httpCache.SetForeground256Color(214)
Так как setter'ы `LogLevel` изменяют уровень на месте, их нужно вызывать на переменной, а не на временном результате `NewLogLevel(...)`.
Короткие формы вроде `SetFgColor` и `SetBgColor` сохранены для обратной совместимости.
Для стандартных severity также есть отдельные helper'ы:
```go
access := sneklog.NewInfoLogLevelWithColors("access", sneklog.FgCyan, sneklog.BgNone)
audit := sneklog.NewWarnLogLevel("audit")
```
Для HTTP-методов доступны готовые уровни:
```go
level := sneklog.LogLevelForMethod(http.MethodPost)
logger.Print(level, "POST /users")
```
Если нужно сравнить только severity, используйте `SameLevel`. Если нужно
полное совпадение конфигурации уровня, включая цвета и атрибуты, используйте
`Equal`.
## Замена сообщений
`AddReplacer(old, new)` заменяет найденный текст в каждом сообщении до того,
@@ -156,6 +174,7 @@ logger.Infoln("login token:", "SOME_SECRET")
- `Info`, `Warn`, `Error`, `Debug`, `Fatal` принимают список значений.
- `Infof`, `Warnf`, `Errorf`, `Debugf`, `Fatalf` используют `fmt.Sprintf`.
- `Printf(level, format, args...)` форматирует сообщение для явного `LogLevel`.
- Методы `*ln` добавляют семантику перевода строки, что удобно для `stdout`, Docker и line-based collectors.
- `Fatal`, `Fatalf` и `Fatalln` вызывают `os.Exit(1)` после записи сообщения.
- `AddReplacer` маскирует или переписывает текст сообщений перед отправкой в writer'ы.
+5 -2
View File
@@ -1,6 +1,6 @@
# v2.2.0
# Unreleased (planned v2.2.0)
This release adds new public APIs for working with log levels, introduces HTTP method-specific log levels, and improves package documentation.
This release expands the public LogLevel API, adds HTTP method helpers, and improves package documentation and test coverage.
### Added
@@ -28,15 +28,18 @@ This release adds new public APIs for working with log levels, introduces HTTP m
- `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.
### 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.
### Documentation
- Improved GoDoc coverage for exported types, constructors, formatters, colors, HTTP helpers, and time layouts.
- Documented level comparison helpers and explicit-level formatted logging.
- Expanded inline documentation for formatter tokens and default formatter behavior.
### Compatibility
+8
View File
@@ -17,6 +17,11 @@
// passed to writers. Call SetFormatter on a writer to customize timestamps,
// traceback fields, colors, and message layout.
//
// The package also provides helper constructors for common severities such as
// NewInfoLogLevel and NewErrorLogLevel, plus predefined HTTP method-specific
// levels such as HTTPGetLevel and HTTPPostLevel. Use LogLevelForMethod to map
// an HTTP method to one of those predefined levels.
//
// Basic usage:
//
// logger := sneklog.CreateLogger().
@@ -41,4 +46,7 @@
// the logger and are closed by Logger.Close. Writers created from existing
// 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
// with Equal when the full level configuration must match.
package sneklog
+47
View File
@@ -196,3 +196,50 @@ func (l *LogLevel) GetAttributes() []Attribute {
copy(attrs, l.attrs)
return attrs
}
// SameLevel reports whether two log levels have the same severity index.
func (l *LogLevel) SameLevel(other LogLevel) bool {
return l.n == other.n
}
// 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 {
return false
}
if l.fg != other.fg || l.bg != other.bg {
return false
}
if l.fg256 != other.fg256 || l.bg256 != other.bg256 {
return false
}
if len(l.attrs) != len(other.attrs) {
return false
}
for i := range l.attrs {
if l.attrs[i] != other.attrs[i] {
return false
}
}
if len(l.fgRgb) != len(other.fgRgb) {
return false
}
for i := range l.fgRgb {
if l.fgRgb[i] != other.fgRgb[i] {
return false
}
}
if len(l.bgRgb) != len(other.bgRgb) {
return false
}
for i := range l.bgRgb {
if l.bgRgb[i] != other.bgRgb[i] {
return false
}
}
return true
}
+100
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"errors"
"net/http"
"os"
"strings"
"testing"
@@ -100,6 +101,105 @@ func TestLoggerPreservesMessageTypesWithoutReplacers(t *testing.T) {
}
}
func TestLoggerPrintfFormatsMessage(t *testing.T) {
writer := &stubLoggerWriter{}
logger := CreateLogger().
SetLevel(DEBUG).
AddWriter(writer)
logger.Printf(INFO, "status=%d %s", 200, "ok")
if writer.printCalls != 1 {
t.Fatalf("Printf() should write once, got %d calls", writer.printCalls)
}
if len(writer.messages) != 1 {
t.Fatalf("Printf() should produce one formatted message, got %d parts", len(writer.messages))
}
if got, ok := writer.messages[0].(string); !ok || got != "status=200 ok" {
t.Fatalf("Printf() should format message, got %#v", writer.messages)
}
}
func TestLogLevelForMethodReturnsExpectedLevels(t *testing.T) {
tests := []struct {
name string
method string
want LogLevel
}{
{name: "get", method: http.MethodGet, want: HTTPGetLevel},
{name: "head", method: http.MethodHead, want: HTTPHeadLevel},
{name: "post", method: http.MethodPost, want: HTTPPostLevel},
{name: "put", method: http.MethodPut, want: HTTPPutLevel},
{name: "patch", method: http.MethodPatch, want: HTTPPatchLevel},
{name: "delete", method: http.MethodDelete, want: HTTPDeleteLevel},
{name: "options", method: http.MethodOptions, want: HTTPOptionsLevel},
{name: "connect", method: http.MethodConnect, want: HTTPConnectLevel},
{name: "trace", method: http.MethodTrace, want: HTTPTraceLevel},
{name: "unknown", method: "PROPFIND", want: HTTPUnknownLevel},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := LogLevelForMethod(tt.method)
if got.n != tt.want.n || got.t != tt.want.t || got.fg != tt.want.fg || got.bg != tt.want.bg {
t.Fatalf("LogLevelForMethod(%q) = %#v, want %#v", tt.method, got, tt.want)
}
})
}
}
func TestNewLevelConstructorsSetExpectedSeverity(t *testing.T) {
tests := []struct {
name string
got LogLevel
want LogLevel
}{
{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},
}
for _, tt := range tests {
t.Run(tt.name, func(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.GetName() != "custom" {
t.Fatalf("%s constructor should preserve name, got %q", tt.name, tt.got.GetName())
}
})
}
}
func TestNewLevelConstructorsWithColorsSetExpectedFields(t *testing.T) {
info := NewInfoLogLevelWithColors("info-custom", FgBlue, BgYellow)
if info.n != INFO.n || info.GetName() != "info-custom" || info.fg != FgBlue || info.bg != BgYellow {
t.Fatalf("NewInfoLogLevelWithColors() = %#v", info)
}
warn := NewWarnLogLevelWithColors("warn-custom", FgMagenta, BgCyan)
if warn.n != WARN.n || warn.GetName() != "warn-custom" || warn.fg != FgMagenta || warn.bg != BgCyan {
t.Fatalf("NewWarnLogLevelWithColors() = %#v", warn)
}
errLevel := NewErrorLogLevelWithColors("error-custom", FgRed, BgWhite)
if errLevel.n != ERROR.n || errLevel.GetName() != "error-custom" || errLevel.fg != FgRed || errLevel.bg != BgWhite {
t.Fatalf("NewErrorLogLevelWithColors() = %#v", errLevel)
}
fatal := NewFatalLogLevelWithColors("fatal-custom", FgHiRed, BgBlack)
if fatal.n != FATAL.n || fatal.GetName() != "fatal-custom" || fatal.fg != FgHiRed || fatal.bg != BgBlack {
t.Fatalf("NewFatalLogLevelWithColors() = %#v", fatal)
}
debug := NewDebugLogLevelWithColors("debug-custom", FgGreen, BgDefault)
if debug.n != DEBUG.n || debug.GetName() != "debug-custom" || debug.fg != FgGreen || debug.bg != BgDefault {
t.Fatalf("NewDebugLogLevelWithColors() = %#v", debug)
}
}
func TestCreateTextWriterCloseOnNonCloserIsNoOp(t *testing.T) {
writer := CreateTextWriter(&bytes.Buffer{})
if err := writer.Close(); err != nil {