REPOSITORY / ScuroNeko/SNekLog
Compare commits
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6d15b530f
|
||
|
|
b7fa3f7606
|
||
|
|
812caed471
|
@@ -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.
|
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
|
## Writers and ownership
|
||||||
|
|
||||||
`Logger.Close()` only closes writers created by the logger itself:
|
`Logger.Close()` only closes writers created by the logger itself:
|
||||||
@@ -135,6 +139,49 @@ httpCache.SetForeground256Color(214)
|
|||||||
Because `LogLevel` setters mutate the level in place, call them on a variable, not on a temporary value returned by `NewLogLevel(...)`.
|
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.
|
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")
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
level := sneklog.LogLevelForMethod(http.MethodPost)
|
||||||
|
logger.Print(level, "POST /users")
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
## Message replacement
|
||||||
|
|
||||||
`AddReplacer(old, new)` replaces matching text in every message before the
|
`AddReplacer(old, new)` replaces matching text in every message before the
|
||||||
@@ -157,6 +204,7 @@ An empty `old` value is ignored.
|
|||||||
|
|
||||||
- `Info`, `Warn`, `Error`, `Debug`, and `Fatal` accept a list of values.
|
- `Info`, `Warn`, `Error`, `Debug`, and `Fatal` accept a list of values.
|
||||||
- `Infof`, `Warnf`, `Errorf`, `Debugf`, and `Fatalf` use `fmt.Sprintf`.
|
- `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.
|
- 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.
|
- `Fatal`, `Fatalf`, and `Fatalln` call `os.Exit(1)` after writing the message.
|
||||||
- `AddReplacer` masks or rewrites message text before records are sent to writers.
|
- `AddReplacer` masks or rewrites message text before records are sent to writers.
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ func main() {
|
|||||||
|
|
||||||
Важно: в текущей модели уровней `Level(sneklog.FATAL)` пропускает `INFO`, `WARN`, `ERROR` и `FATAL`, но не `DEBUG`. Чтобы включить все сообщения, используйте `Level(sneklog.DEBUG)`.
|
Важно: в текущей модели уровней `Level(sneklog.FATAL)` пропускает `INFO`, `WARN`, `ERROR` и `FATAL`, но не `DEBUG`. Чтобы включить все сообщения, используйте `Level(sneklog.DEBUG)`.
|
||||||
|
|
||||||
|
Если нужна классическая threshold-фильтрация, включите `SetThresholdMode(true)` и
|
||||||
|
задавайте уровни через `NewThresholdLogLevel(...)` или
|
||||||
|
`NewThresholdLogLevelWithColors(...)`.
|
||||||
|
|
||||||
## Writer'ы и владение
|
## Writer'ы и владение
|
||||||
|
|
||||||
`Logger.Close()` закрывает только writer'ы, которые логгер создал сам:
|
`Logger.Close()` закрывает только writer'ы, которые логгер создал сам:
|
||||||
@@ -135,6 +139,49 @@ httpCache.SetForeground256Color(214)
|
|||||||
Так как setter'ы `LogLevel` изменяют уровень на месте, их нужно вызывать на переменной, а не на временном результате `NewLogLevel(...)`.
|
Так как setter'ы `LogLevel` изменяют уровень на месте, их нужно вызывать на переменной, а не на временном результате `NewLogLevel(...)`.
|
||||||
Короткие формы вроде `SetFgColor` и `SetBgColor` сохранены для обратной совместимости.
|
Короткие формы вроде `SetFgColor` и `SetBgColor` сохранены для обратной совместимости.
|
||||||
|
|
||||||
|
Для стандартных severity также есть отдельные helper'ы:
|
||||||
|
|
||||||
|
```go
|
||||||
|
access := sneklog.NewInfoLogLevelWithColors("access", sneklog.FgCyan, sneklog.BgNone)
|
||||||
|
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
|
||||||
|
level := sneklog.LogLevelForMethod(http.MethodPost)
|
||||||
|
logger.Print(level, "POST /users")
|
||||||
|
```
|
||||||
|
|
||||||
|
При сравнении уровней:
|
||||||
|
|
||||||
|
- используйте `SameLevel`, если важен только legacy severity index;
|
||||||
|
- используйте `SameThreshold`, если важна только threshold-фильтрация;
|
||||||
|
- используйте `Equal`, если нужно полное совпадение конфигурации, включая threshold, цвета и атрибуты.
|
||||||
|
|
||||||
## Замена сообщений
|
## Замена сообщений
|
||||||
|
|
||||||
`AddReplacer(old, new)` заменяет найденный текст в каждом сообщении до того,
|
`AddReplacer(old, new)` заменяет найденный текст в каждом сообщении до того,
|
||||||
@@ -156,6 +203,7 @@ logger.Infoln("login token:", "SOME_SECRET")
|
|||||||
|
|
||||||
- `Info`, `Warn`, `Error`, `Debug`, `Fatal` принимают список значений.
|
- `Info`, `Warn`, `Error`, `Debug`, `Fatal` принимают список значений.
|
||||||
- `Infof`, `Warnf`, `Errorf`, `Debugf`, `Fatalf` используют `fmt.Sprintf`.
|
- `Infof`, `Warnf`, `Errorf`, `Debugf`, `Fatalf` используют `fmt.Sprintf`.
|
||||||
|
- `Printf(level, format, args...)` форматирует сообщение для явного `LogLevel`.
|
||||||
- Методы `*ln` добавляют семантику перевода строки, что удобно для `stdout`, Docker и line-based collectors.
|
- Методы `*ln` добавляют семантику перевода строки, что удобно для `stdout`, Docker и line-based collectors.
|
||||||
- `Fatal`, `Fatalf` и `Fatalln` вызывают `os.Exit(1)` после записи сообщения.
|
- `Fatal`, `Fatalf` и `Fatalln` вызывают `os.Exit(1)` после записи сообщения.
|
||||||
- `AddReplacer` маскирует или переписывает текст сообщений перед отправкой в writer'ы.
|
- `AddReplacer` маскирует или переписывает текст сообщений перед отправкой в writer'ы.
|
||||||
|
|||||||
@@ -1,3 +1,63 @@
|
|||||||
|
# Unreleased (planned v2.2.0)
|
||||||
|
|
||||||
|
This release expands the public LogLevel API, adds optional threshold-based
|
||||||
|
filtering alongside the legacy severity ordering, and improves documentation
|
||||||
|
and test coverage.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added dedicated log level constructors:
|
||||||
|
- `NewInfoLogLevel`
|
||||||
|
- `NewInfoLogLevelWithColors`
|
||||||
|
- `NewWarnLogLevel`
|
||||||
|
- `NewWarnLogLevelWithColors`
|
||||||
|
- `NewErrorLogLevel`
|
||||||
|
- `NewErrorLogLevelWithColors`
|
||||||
|
- `NewFatalLogLevel`
|
||||||
|
- `NewFatalLogLevelWithColors`
|
||||||
|
- `NewDebugLogLevel`
|
||||||
|
- `NewDebugLogLevelWithColors`
|
||||||
|
- Added threshold-aware log level constructors:
|
||||||
|
- `NewThresholdLogLevel`
|
||||||
|
- `NewThresholdLogLevelWithColors`
|
||||||
|
- Added HTTP method-specific predefined log levels:
|
||||||
|
- `HTTPGetLevel`
|
||||||
|
- `HTTPHeadLevel`
|
||||||
|
- `HTTPPostLevel`
|
||||||
|
- `HTTPPutLevel`
|
||||||
|
- `HTTPPatchLevel`
|
||||||
|
- `HTTPDeleteLevel`
|
||||||
|
- `HTTPOptionsLevel`
|
||||||
|
- `HTTPConnectLevel`
|
||||||
|
- `HTTPTraceLevel`
|
||||||
|
- `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 `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.
|
||||||
|
- 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 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, `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
|
# v2.1.0
|
||||||
|
|
||||||
This release expands sneklog color customization while preserving compatibility with the v2.0.1 API.
|
This release expands sneklog color customization while preserving compatibility with the v2.0.1 API.
|
||||||
|
|||||||
@@ -5,12 +5,25 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FgColor is a basic ANSI foreground color code.
|
||||||
type FgColor uint8
|
type FgColor uint8
|
||||||
|
|
||||||
|
// BgColor is a basic ANSI background color code.
|
||||||
type BgColor uint8
|
type BgColor uint8
|
||||||
|
|
||||||
|
// FgColor256 is an ANSI 256-color foreground value.
|
||||||
type FgColor256 uint8
|
type FgColor256 uint8
|
||||||
|
|
||||||
|
// BgColor256 is an ANSI 256-color background value.
|
||||||
type BgColor256 uint8
|
type BgColor256 uint8
|
||||||
|
|
||||||
|
// BgColorRGB is an ANSI truecolor background value encoded as RGB bytes.
|
||||||
type BgColorRGB []uint8
|
type BgColorRGB []uint8
|
||||||
|
|
||||||
|
// FgColorRGB is an ANSI truecolor foreground value encoded as RGB bytes.
|
||||||
type FgColorRGB []uint8
|
type FgColorRGB []uint8
|
||||||
|
|
||||||
|
// Attribute is an ANSI text attribute code.
|
||||||
type Attribute uint8
|
type Attribute uint8
|
||||||
|
|
||||||
// NewBgColorRGB builds an RGB background color value.
|
// NewBgColorRGB builds an RGB background color value.
|
||||||
@@ -23,6 +36,7 @@ func NewFgColorRGB(r, g, b uint8) FgColorRGB {
|
|||||||
return FgColorRGB{r, g, b}
|
return FgColorRGB{r, g, b}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text attribute codes.
|
||||||
const (
|
const (
|
||||||
Reset Attribute = iota
|
Reset Attribute = iota
|
||||||
Bold
|
Bold
|
||||||
@@ -35,6 +49,8 @@ const (
|
|||||||
Conceal
|
Conceal
|
||||||
CrossedOut
|
CrossedOut
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Additional text attribute codes.
|
||||||
const (
|
const (
|
||||||
DoubleUnderline Attribute = iota + 21 // Can be disable bold on some terminals
|
DoubleUnderline Attribute = iota + 21 // Can be disable bold on some terminals
|
||||||
DisableBold
|
DisableBold
|
||||||
@@ -45,6 +61,8 @@ const (
|
|||||||
DisableConceal
|
DisableConceal
|
||||||
DisableCrossedOut
|
DisableCrossedOut
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Basic foreground color codes.
|
||||||
const (
|
const (
|
||||||
FgNone = 0
|
FgNone = 0
|
||||||
FgBlack FgColor = iota + 30
|
FgBlack FgColor = iota + 30
|
||||||
@@ -57,6 +75,8 @@ const (
|
|||||||
FgWhite
|
FgWhite
|
||||||
FgDefault FgColor = 39
|
FgDefault FgColor = 39
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Basic background color codes.
|
||||||
const (
|
const (
|
||||||
BgNone = 0
|
BgNone = 0
|
||||||
BgBlack BgColor = iota + 40
|
BgBlack BgColor = iota + 40
|
||||||
@@ -69,6 +89,8 @@ const (
|
|||||||
BgWhite
|
BgWhite
|
||||||
BgDefault BgColor = 49
|
BgDefault BgColor = 49
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// High-intensity foreground color codes.
|
||||||
const (
|
const (
|
||||||
FgHiBlack FgColor = iota + 90
|
FgHiBlack FgColor = iota + 90
|
||||||
FgHiRed
|
FgHiRed
|
||||||
@@ -79,6 +101,8 @@ const (
|
|||||||
FgHiCyan
|
FgHiCyan
|
||||||
FgHiWhite
|
FgHiWhite
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// High-intensity background color codes.
|
||||||
const (
|
const (
|
||||||
BgHiBlack BgColor = iota + 100
|
BgHiBlack BgColor = iota + 100
|
||||||
BgHiRed
|
BgHiRed
|
||||||
@@ -90,7 +114,7 @@ const (
|
|||||||
BgHiWhite
|
BgHiWhite
|
||||||
)
|
)
|
||||||
|
|
||||||
// This attributes rarely used and not supported by all terminals.
|
// Rarely used text attribute codes that are not supported by all terminals.
|
||||||
const (
|
const (
|
||||||
Border Attribute = iota + 51
|
Border Attribute = iota + 51
|
||||||
Outline
|
Outline
|
||||||
@@ -99,6 +123,7 @@ const (
|
|||||||
DisableUpperline
|
DisableUpperline
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ColorStringBuilder incrementally builds ANSI-colored strings.
|
||||||
type ColorStringBuilder struct {
|
type ColorStringBuilder struct {
|
||||||
str string
|
str string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@
|
|||||||
// passed to writers. Call SetFormatter on a writer to customize timestamps,
|
// passed to writers. Call SetFormatter on a writer to customize timestamps,
|
||||||
// traceback fields, colors, and message layout.
|
// 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:
|
// Basic usage:
|
||||||
//
|
//
|
||||||
// logger := sneklog.CreateLogger().
|
// logger := sneklog.CreateLogger().
|
||||||
@@ -41,4 +46,8 @@
|
|||||||
// the logger and are closed by Logger.Close. Writers created from existing
|
// the logger and are closed by Logger.Close. Writers created from existing
|
||||||
// io.Writer values through CreateTextWriter or CreateJsonWriter remain owned by
|
// io.Writer values through CreateTextWriter or CreateJsonWriter remain owned by
|
||||||
// the caller and are not closed by the logger.
|
// the caller and are not closed by the logger.
|
||||||
|
//
|
||||||
|
// 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
|
package sneklog
|
||||||
|
|||||||
+23
-8
@@ -6,16 +6,28 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Formatter
|
// Formatter configures how log records are rendered.
|
||||||
// Format: %t - time, %N - name, %l - level, %L - level uppercase, %b - traceback, %B - full traceback, %m - message,
|
|
||||||
// %M - method, %f - filename, %n - line number, %s - method signature, %p - full path
|
|
||||||
// Example: "%t [%l] %N: %m (%f:%n %s)"
|
|
||||||
//
|
//
|
||||||
// TimeStampFormat: %Y - year, %m - month, %d - day, %H - hour, %M - minute, %S - second,
|
// Format tokens:
|
||||||
// %z - timezone(i.e. 0300), %Z - timezone(i.e. 03:00)
|
// - %t: time
|
||||||
// Example: "%d.%m.%Y %H:%M:%S"
|
// - %N: name
|
||||||
|
// - %l: level
|
||||||
|
// - %L: uppercase level
|
||||||
|
// - %b: first traceback frame
|
||||||
|
// - %B: full traceback
|
||||||
|
// - %m: message
|
||||||
|
// - %M: method
|
||||||
|
// - %f: filename
|
||||||
|
// - %n: line number
|
||||||
|
// - %s: method signature
|
||||||
|
// - %p: full path
|
||||||
//
|
//
|
||||||
// TraceBackFormat: %M - method, %f - filename, %n - line number, %s - method signature, %p - full path
|
// Example format: "%t [%l] %N: %m (%f:%n %s)"
|
||||||
|
//
|
||||||
|
// TimeStampFormat uses strftime-like directives such as %Y, %m, %d, %H, %M,
|
||||||
|
// %S, %z, and %Z.
|
||||||
|
//
|
||||||
|
// TraceBackFormat supports the traceback-related tokens %M, %f, %n, %s, and %p.
|
||||||
type Formatter struct {
|
type Formatter struct {
|
||||||
Format string
|
Format string
|
||||||
MessageSeparator string
|
MessageSeparator string
|
||||||
@@ -31,6 +43,7 @@ type Formatter struct {
|
|||||||
ColorOnlyStdout bool
|
ColorOnlyStdout bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultTextFormatter is the default formatter used by text writers.
|
||||||
var DefaultTextFormatter = &Formatter{
|
var DefaultTextFormatter = &Formatter{
|
||||||
Format: "%t %l %N: %m",
|
Format: "%t %l %N: %m",
|
||||||
MessageSeparator: " ",
|
MessageSeparator: " ",
|
||||||
@@ -40,6 +53,8 @@ var DefaultTextFormatter = &Formatter{
|
|||||||
ColorOutput: true,
|
ColorOutput: true,
|
||||||
ColorOnlyStdout: true,
|
ColorOnlyStdout: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultJsonFormatter is the default formatter used by JSON writers.
|
||||||
var DefaultJsonFormatter = &Formatter{
|
var DefaultJsonFormatter = &Formatter{
|
||||||
Format: "%m",
|
Format: "%m",
|
||||||
MessageSeparator: " ",
|
MessageSeparator: " ",
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package sneklog
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// HTTP method-specific log levels.
|
||||||
|
var (
|
||||||
|
HTTPGetLevel = NewInfoLogLevelWithColors("get", FgGreen, BgNone)
|
||||||
|
HTTPHeadLevel = NewInfoLogLevelWithColors("head", FgCyan, BgNone)
|
||||||
|
HTTPPostLevel = NewInfoLogLevelWithColors("post", FgBlue, BgNone)
|
||||||
|
HTTPPutLevel = NewInfoLogLevelWithColors("put", FgYellow, BgNone)
|
||||||
|
HTTPPatchLevel = NewInfoLogLevelWithColors("patch", FgMagenta, BgNone)
|
||||||
|
HTTPDeleteLevel = NewInfoLogLevelWithColors("delete", FgRed, BgNone)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Additional HTTP method-specific log levels.
|
||||||
|
var (
|
||||||
|
HTTPOptionsLevel = NewDebugLogLevelWithColors("options", FgWhite, BgNone)
|
||||||
|
HTTPConnectLevel = NewDebugLogLevelWithColors("connect", FgCyan, BgNone)
|
||||||
|
HTTPTraceLevel = NewDebugLogLevelWithColors("trace", FgWhite, BgNone)
|
||||||
|
HTTPUnknownLevel = NewDebugLogLevelWithColors("http", FgWhite, BgNone)
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogLevelForMethod returns the predefined log level associated with an HTTP method.
|
||||||
|
func LogLevelForMethod(method string) LogLevel {
|
||||||
|
switch method {
|
||||||
|
case http.MethodGet:
|
||||||
|
return HTTPGetLevel
|
||||||
|
case http.MethodHead:
|
||||||
|
return HTTPHeadLevel
|
||||||
|
case http.MethodPost:
|
||||||
|
return HTTPPostLevel
|
||||||
|
case http.MethodPut:
|
||||||
|
return HTTPPutLevel
|
||||||
|
case http.MethodPatch:
|
||||||
|
return HTTPPatchLevel
|
||||||
|
case http.MethodDelete:
|
||||||
|
return HTTPDeleteLevel
|
||||||
|
case http.MethodOptions:
|
||||||
|
return HTTPOptionsLevel
|
||||||
|
case http.MethodConnect:
|
||||||
|
return HTTPConnectLevel
|
||||||
|
case http.MethodTrace:
|
||||||
|
return HTTPTraceLevel
|
||||||
|
default:
|
||||||
|
return HTTPUnknownLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,10 +83,10 @@ func (l *Logger) Debugln(m ...any) {
|
|||||||
l.Println(DEBUG, m...)
|
l.Println(DEBUG, m...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print write message without trailing "\n"
|
func (l *Logger) print(level LogLevel, newline bool, m ...any) {
|
||||||
// Good for database
|
if l.threshold && l.level.th < level.th {
|
||||||
func (l *Logger) Print(level LogLevel, m ...any) {
|
return
|
||||||
if l.level.n < level.n {
|
} else if l.level.n < level.n {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(l.writers) == 0 {
|
if len(l.writers) == 0 {
|
||||||
@@ -94,30 +94,10 @@ func (l *Logger) Print(level LogLevel, m ...any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tb := getFullTraceback(1)
|
tb := getFullTraceback(1)
|
||||||
for _, writer := range l.writers {
|
messages := l.replaceAll(m...)
|
||||||
if writer == nil {
|
if newline {
|
||||||
continue
|
messages = append(messages, any("\n"))
|
||||||
}
|
|
||||||
err := writer.Print(level, l.prefix, tb, l.replaceAll(m...)...)
|
|
||||||
if err != nil {
|
|
||||||
l.reportWriterError(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Println
|
|
||||||
// Docker requires "\n" at end to write to log.
|
|
||||||
// print not work for docker, otherwise it will work and write into stdout
|
|
||||||
func (l *Logger) Println(level LogLevel, m ...any) {
|
|
||||||
if l.level.n < level.n {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(l.writers) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tb := getFullTraceback(1)
|
|
||||||
messages := append(append(make([]any, 0, len(m)+1), l.replaceAll(m...)...), "\n")
|
|
||||||
for _, writer := range l.writers {
|
for _, writer := range l.writers {
|
||||||
if writer == nil {
|
if writer == nil {
|
||||||
continue
|
continue
|
||||||
@@ -129,6 +109,17 @@ func (l *Logger) Println(level LogLevel, m ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print logs a message without appending a trailing newline semantic.
|
||||||
|
func (l *Logger) Print(level LogLevel, m ...any) { l.print(level, false, m...) }
|
||||||
|
|
||||||
|
// Println logs a message and appends a trailing newline semantic for writers that need it.
|
||||||
|
func (l *Logger) Println(level LogLevel, m ...any) { l.print(level, true, m...) }
|
||||||
|
|
||||||
|
// Printf formats according to a format specifier and logs the resulting message.
|
||||||
|
func (l *Logger) Printf(level LogLevel, format string, args ...any) {
|
||||||
|
l.print(level, false, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
// reportWriterError writes internal writer failures directly to stderr to avoid
|
// reportWriterError writes internal writer failures directly to stderr to avoid
|
||||||
// re-entering the same logger path that just failed.
|
// re-entering the same logger path that just failed.
|
||||||
func (l *Logger) reportWriterError(err error) {
|
func (l *Logger) reportWriterError(err error) {
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
package sneklog
|
||||||
|
|
||||||
|
// Predefined log levels.
|
||||||
|
var (
|
||||||
|
INFO = NewInfoLogLevelWithColors("info", FgWhite, BgNone)
|
||||||
|
WARN = NewWarnLogLevelWithColors("warn", FgHiYellow, BgNone)
|
||||||
|
ERROR = NewErrorLogLevelWithColors("error", FgHiRed, BgNone)
|
||||||
|
FATAL = NewFatalLogLevelWithColors("fatal", FgRed, BgNone)
|
||||||
|
DEBUG = NewDebugLogLevelWithColors("debug", FgGreen, BgNone)
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogLevel describes a logging severity.
|
||||||
|
type LogLevel struct {
|
||||||
|
n uint8
|
||||||
|
th uint8
|
||||||
|
t string
|
||||||
|
|
||||||
|
fg FgColor
|
||||||
|
fg256 FgColor256
|
||||||
|
fgRgb FgColorRGB
|
||||||
|
|
||||||
|
bg BgColor
|
||||||
|
bg256 BgColor256
|
||||||
|
bgRgb BgColorRGB
|
||||||
|
|
||||||
|
attrs []Attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 NewThresholdLogLevel(0, 10, name) }
|
||||||
|
|
||||||
|
// NewInfoLogLevelWithColors creates an info-level log level with ANSI colors.
|
||||||
|
func NewInfoLogLevelWithColors(name string, fg FgColor, bg BgColor) LogLevel {
|
||||||
|
return NewThresholdLogLevelWithColors(0, 10, name, fg, bg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWarnLogLevel creates a warn-level log level without predefined colors.
|
||||||
|
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 NewThresholdLogLevelWithColors(1, 20, name, fg, bg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorLogLevel creates an error-level log level without predefined colors.
|
||||||
|
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 NewThresholdLogLevelWithColors(2, 30, name, fg, bg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFatalLogLevel creates a fatal-level log level without predefined colors.
|
||||||
|
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 NewThresholdLogLevelWithColors(3, 40, name, fg, bg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDebugLogLevel creates a debug-level log level without predefined colors.
|
||||||
|
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 NewThresholdLogLevelWithColors(4, 0, name, fg, bg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the lowercase textual representation of the level.
|
||||||
|
func (l *LogLevel) GetName() string { return l.t }
|
||||||
|
|
||||||
|
// SetFgColor sets the ANSI foreground color and clears other foreground color modes.
|
||||||
|
// Deprecated: use SetForegroundColor. This method will be removed in v3.
|
||||||
|
func (l *LogLevel) SetFgColor(color FgColor) *LogLevel {
|
||||||
|
return l.SetForegroundColor(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFgColor returns the ANSI foreground color for the level.
|
||||||
|
// Deprecated: use GetForegroundColor. This method will be removed in v3.
|
||||||
|
func (l *LogLevel) GetFgColor() FgColor { return l.GetForegroundColor() }
|
||||||
|
|
||||||
|
// SetForegroundColor sets the ANSI foreground color and clears other foreground color modes.
|
||||||
|
func (l *LogLevel) SetForegroundColor(color FgColor) *LogLevel {
|
||||||
|
l.fg = color
|
||||||
|
l.fg256 = 0
|
||||||
|
l.fgRgb = nil
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetForegroundColor returns the ANSI foreground color for the level.
|
||||||
|
func (l *LogLevel) GetForegroundColor() FgColor { return l.fg }
|
||||||
|
|
||||||
|
// SetForeground256Color sets the 256-color foreground and clears other foreground color modes.
|
||||||
|
func (l *LogLevel) SetForeground256Color(color FgColor256) *LogLevel {
|
||||||
|
l.fg = 0
|
||||||
|
l.fg256 = color
|
||||||
|
l.fgRgb = nil
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetForeground256Color returns the configured 256-color foreground value.
|
||||||
|
func (l *LogLevel) GetForeground256Color() FgColor256 { return l.fg256 }
|
||||||
|
|
||||||
|
// SetForegroundRGB sets the RGB foreground color and clears other foreground color modes.
|
||||||
|
func (l *LogLevel) SetForegroundRGB(r, g, b uint8) *LogLevel {
|
||||||
|
l.fg = 0
|
||||||
|
l.fg256 = 0
|
||||||
|
l.fgRgb = FgColorRGB{r, g, b}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetForegroundRGB returns the configured RGB foreground value.
|
||||||
|
func (l *LogLevel) GetForegroundRGB() FgColorRGB { return l.fgRgb }
|
||||||
|
|
||||||
|
// SetBgColor sets the ANSI background color and clears other background color modes.
|
||||||
|
// Deprecated: use SetBackgroundColor. This method will be removed in v3.
|
||||||
|
func (l *LogLevel) SetBgColor(color BgColor) *LogLevel {
|
||||||
|
return l.SetBackgroundColor(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBgColor returns the ANSI background color for the level.
|
||||||
|
// Deprecated: use GetBackgroundColor. This method will be removed in v3.
|
||||||
|
func (l *LogLevel) GetBgColor() BgColor { return l.GetBackgroundColor() }
|
||||||
|
|
||||||
|
// SetBackgroundColor sets the ANSI background color and clears other background color modes.
|
||||||
|
func (l *LogLevel) SetBackgroundColor(color BgColor) *LogLevel {
|
||||||
|
l.bg = color
|
||||||
|
l.bg256 = 0
|
||||||
|
l.bgRgb = nil
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackgroundColor returns the ANSI background color for the level.
|
||||||
|
func (l *LogLevel) GetBackgroundColor() BgColor { return l.bg }
|
||||||
|
|
||||||
|
// SetBackground256Color sets the 256-color background and clears other background color modes.
|
||||||
|
func (l *LogLevel) SetBackground256Color(color BgColor256) *LogLevel {
|
||||||
|
l.bg = 0
|
||||||
|
l.bg256 = color
|
||||||
|
l.bgRgb = nil
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackground256Color returns the configured 256-color background value.
|
||||||
|
func (l *LogLevel) GetBackground256Color() BgColor256 { return l.bg256 }
|
||||||
|
|
||||||
|
// SetBackgroundRGB sets the RGB background color and clears other background color modes.
|
||||||
|
func (l *LogLevel) SetBackgroundRGB(r, g, b uint8) *LogLevel {
|
||||||
|
l.bg = 0
|
||||||
|
l.bg256 = 0
|
||||||
|
l.bgRgb = BgColorRGB{r, g, b}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackgroundRGB returns the configured RGB background value.
|
||||||
|
func (l *LogLevel) GetBackgroundRGB() BgColorRGB { return l.bgRgb }
|
||||||
|
|
||||||
|
// AddAttribute appends an ANSI text attribute to the level.
|
||||||
|
func (l *LogLevel) AddAttribute(a Attribute) *LogLevel {
|
||||||
|
l.attrs = append(l.attrs, a)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAttribute removes all matching ANSI text attributes from the level.
|
||||||
|
func (l *LogLevel) RemoveAttribute(a Attribute) *LogLevel {
|
||||||
|
attrs := make([]Attribute, 0)
|
||||||
|
for _, attr := range l.attrs {
|
||||||
|
if attr != a {
|
||||||
|
attrs = append(attrs, attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.attrs = attrs
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAttributes replaces the level attributes with a copy of the provided slice.
|
||||||
|
func (l *LogLevel) SetAttributes(a []Attribute) *LogLevel {
|
||||||
|
attrs := make([]Attribute, len(a))
|
||||||
|
copy(attrs, a)
|
||||||
|
l.attrs = attrs
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAttributes returns a copy of the configured ANSI text attributes.
|
||||||
|
func (l *LogLevel) GetAttributes() []Attribute {
|
||||||
|
attrs := make([]Attribute, len(l.attrs))
|
||||||
|
copy(attrs, l.attrs)
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 || l.th != other.th {
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -7,154 +7,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogLevel describes a logging severity.
|
|
||||||
type LogLevel struct {
|
|
||||||
n uint8
|
|
||||||
t string
|
|
||||||
|
|
||||||
fg FgColor
|
|
||||||
fg256 FgColor256
|
|
||||||
fgRgb FgColorRGB
|
|
||||||
|
|
||||||
bg BgColor
|
|
||||||
bg256 BgColor256
|
|
||||||
bgRgb BgColorRGB
|
|
||||||
|
|
||||||
attrs []Attribute
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLogLevel creates a log level without predefined colors.
|
|
||||||
func NewLogLevel(index uint8, name string) LogLevel {
|
|
||||||
return LogLevel{n: index, t: name, attrs: []Attribute{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLogLevelWithColors creates a log level with ANSI foreground and background colors.
|
|
||||||
func NewLogLevelWithColors(index uint8, name string, fg FgColor, bg BgColor) LogLevel {
|
|
||||||
return LogLevel{n: index, t: name, fg: fg, bg: bg, attrs: []Attribute{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the lowercase textual representation of the level.
|
|
||||||
func (l *LogLevel) GetName() string { return l.t }
|
|
||||||
|
|
||||||
// SetFgColor sets the ANSI foreground color and clears other foreground color modes.
|
|
||||||
// Deprecated: use SetForegroundColor. This method will be removed in v3.
|
|
||||||
func (l *LogLevel) SetFgColor(color FgColor) *LogLevel {
|
|
||||||
return l.SetForegroundColor(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFgColor returns the ANSI foreground color for the level.
|
|
||||||
// Deprecated: use GetForegroundColor. This method will be removed in v3.
|
|
||||||
func (l *LogLevel) GetFgColor() FgColor { return l.GetForegroundColor() }
|
|
||||||
|
|
||||||
// SetForegroundColor sets the ANSI foreground color and clears other foreground color modes.
|
|
||||||
func (l *LogLevel) SetForegroundColor(color FgColor) *LogLevel {
|
|
||||||
l.fg = color
|
|
||||||
l.fg256 = 0
|
|
||||||
l.fgRgb = nil
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetForegroundColor returns the ANSI foreground color for the level.
|
|
||||||
func (l *LogLevel) GetForegroundColor() FgColor { return l.fg }
|
|
||||||
|
|
||||||
// SetForeground256Color sets the 256-color foreground and clears other foreground color modes.
|
|
||||||
func (l *LogLevel) SetForeground256Color(color FgColor256) *LogLevel {
|
|
||||||
l.fg = 0
|
|
||||||
l.fg256 = color
|
|
||||||
l.fgRgb = nil
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetForeground256Color returns the configured 256-color foreground value.
|
|
||||||
func (l *LogLevel) GetForeground256Color() FgColor256 { return l.fg256 }
|
|
||||||
|
|
||||||
// SetForegroundRGB sets the RGB foreground color and clears other foreground color modes.
|
|
||||||
func (l *LogLevel) SetForegroundRGB(r, g, b uint8) *LogLevel {
|
|
||||||
l.fg = 0
|
|
||||||
l.fg256 = 0
|
|
||||||
l.fgRgb = FgColorRGB{r, g, b}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetForegroundRGB returns the configured RGB foreground value.
|
|
||||||
func (l *LogLevel) GetForegroundRGB() FgColorRGB { return l.fgRgb }
|
|
||||||
|
|
||||||
// SetBgColor sets the ANSI background color and clears other background color modes.
|
|
||||||
// Deprecated: use SetBackgroundColor. This method will be removed in v3.
|
|
||||||
func (l *LogLevel) SetBgColor(color BgColor) *LogLevel {
|
|
||||||
return l.SetBackgroundColor(color)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBgColor returns the ANSI background color for the level.
|
|
||||||
// Deprecated: use GetBackgroundColor. This method will be removed in v3.
|
|
||||||
func (l *LogLevel) GetBgColor() BgColor { return l.GetBackgroundColor() }
|
|
||||||
|
|
||||||
// SetBackgroundColor sets the ANSI background color and clears other background color modes.
|
|
||||||
func (l *LogLevel) SetBackgroundColor(color BgColor) *LogLevel {
|
|
||||||
l.bg = color
|
|
||||||
l.bg256 = 0
|
|
||||||
l.bgRgb = nil
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBackgroundColor returns the ANSI background color for the level.
|
|
||||||
func (l *LogLevel) GetBackgroundColor() BgColor { return l.bg }
|
|
||||||
|
|
||||||
// SetBackground256Color sets the 256-color background and clears other background color modes.
|
|
||||||
func (l *LogLevel) SetBackground256Color(color BgColor256) *LogLevel {
|
|
||||||
l.bg = 0
|
|
||||||
l.bg256 = color
|
|
||||||
l.bgRgb = nil
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBackground256Color returns the configured 256-color background value.
|
|
||||||
func (l *LogLevel) GetBackground256Color() BgColor256 { return l.bg256 }
|
|
||||||
|
|
||||||
// SetBackgroundRGB sets the RGB background color and clears other background color modes.
|
|
||||||
func (l *LogLevel) SetBackgroundRGB(r, g, b uint8) *LogLevel {
|
|
||||||
l.bg = 0
|
|
||||||
l.bg256 = 0
|
|
||||||
l.bgRgb = BgColorRGB{r, g, b}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBackgroundRGB returns the configured RGB background value.
|
|
||||||
func (l *LogLevel) GetBackgroundRGB() BgColorRGB { return l.bgRgb }
|
|
||||||
|
|
||||||
// AddAttribute appends an ANSI text attribute to the level.
|
|
||||||
func (l *LogLevel) AddAttribute(a Attribute) *LogLevel {
|
|
||||||
l.attrs = append(l.attrs, a)
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAttribute removes all matching ANSI text attributes from the level.
|
|
||||||
func (l *LogLevel) RemoveAttribute(a Attribute) *LogLevel {
|
|
||||||
attrs := make([]Attribute, 0)
|
|
||||||
for _, attr := range l.attrs {
|
|
||||||
if attr != a {
|
|
||||||
attrs = append(attrs, attr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l.attrs = attrs
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAttributes replaces the level attributes with a copy of the provided slice.
|
|
||||||
func (l *LogLevel) SetAttributes(a []Attribute) *LogLevel {
|
|
||||||
attrs := make([]Attribute, len(a))
|
|
||||||
copy(attrs, a)
|
|
||||||
l.attrs = attrs
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAttributes returns a copy of the configured ANSI text attributes.
|
|
||||||
func (l *LogLevel) GetAttributes() []Attribute {
|
|
||||||
attrs := make([]Attribute, len(l.attrs))
|
|
||||||
copy(attrs, l.attrs)
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
// MethodTraceback describes a single stack frame attached to a log entry.
|
// MethodTraceback describes a single stack frame attached to a log entry.
|
||||||
type MethodTraceback struct {
|
type MethodTraceback struct {
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
@@ -164,23 +16,16 @@ type MethodTraceback struct {
|
|||||||
FullPath string `json:"fullPath"`
|
FullPath string `json:"fullPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Predefined log levels.
|
|
||||||
var (
|
|
||||||
INFO = NewLogLevelWithColors(0, "info", FgWhite, BgNone)
|
|
||||||
WARN = NewLogLevelWithColors(1, "warn", FgHiYellow, BgNone)
|
|
||||||
ERROR = NewLogLevelWithColors(2, "error", FgHiRed, BgNone)
|
|
||||||
FATAL = NewLogLevelWithColors(3, "fatal", FgRed, BgNone)
|
|
||||||
DEBUG = NewLogLevelWithColors(4, "debug", FgGreen, BgNone)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logger routes log records to one or more configured writers.
|
// Logger routes log records to one or more configured writers.
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
prefix string
|
prefix string
|
||||||
level LogLevel
|
level LogLevel
|
||||||
|
|
||||||
writers []LoggerWriter
|
writers []LoggerWriter
|
||||||
replacers []replacer
|
replacers []replacer
|
||||||
|
|
||||||
jsonPretty bool
|
jsonPretty bool
|
||||||
|
threshold bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLogger creates a logger with default settings.
|
// CreateLogger creates a logger with default settings.
|
||||||
@@ -239,6 +84,13 @@ func (l *Logger) SetJSONPretty(b bool) *Logger {
|
|||||||
return l
|
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.
|
// AddWriters appends multiple writers to the logger.
|
||||||
func (l *Logger) AddWriters(writers ...LoggerWriter) *Logger {
|
func (l *Logger) AddWriters(writers ...LoggerWriter) *Logger {
|
||||||
l.writers = append(l.writers, writers...)
|
l.writers = append(l.writers, writers...)
|
||||||
|
|||||||
+175
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -100,6 +101,180 @@ 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 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
|
||||||
|
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
|
||||||
|
wantTh uint8
|
||||||
|
}{
|
||||||
|
{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 {
|
||||||
|
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.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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestCreateTextWriterCloseOnNonCloserIsNoOp(t *testing.T) {
|
||||||
writer := CreateTextWriter(&bytes.Buffer{})
|
writer := CreateTextWriter(&bytes.Buffer{})
|
||||||
if err := writer.Close(); err != nil {
|
if err := writer.Close(); err != nil {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sneklog
|
|||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
|
// Predefined strftime-like time layouts.
|
||||||
const (
|
const (
|
||||||
Layout = "%m/%d %I:%M:%S%p '%y %z"
|
Layout = "%m/%d %I:%M:%S%p '%y %z"
|
||||||
ANSIC = "%a %b %e %H:%M:%S %Y"
|
ANSIC = "%a %b %e %H:%M:%S %Y"
|
||||||
|
|||||||
Reference in New Issue
Block a user