1
0
Fork 0

Adding upstream version 0.3.1.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-17 05:58:17 +02:00
parent 4da56737a9
commit 93a9a63b70
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
11 changed files with 438 additions and 0 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

21
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: test
on:
pull_request:
paths-ignore:
- '*.md'
push:
branches:
- master
paths-ignore:
- '*.md'
jobs:
test:
name: test
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- uses: actions/setup-go@v5
with:
go-version: 1.23.3
- uses: actions/checkout@v4
- run: go test -mod vendor ./...

13
.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
# IDE
*.iml
.idea
.vscode
# OS
.DS_Store
# JS
node_modules
# Go
/vendor

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 TwiN
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
README.md Normal file
View file

@ -0,0 +1,27 @@
# logr
Go logging library with levels.
## Usage
```console
go get -u github.com/TwiN/logr
```
```go
import "github.com/TwiN/logr"
func main() {
logr.Debug("This is a debug message")
logr.Infof("This is an %s message", "info")
logr.Warn("This is a warn message")
logr.Error("This is an error message")
logr.Fatal("This is a fatal message") // Exits with code 1
}
```
You can set the default logger's threshold like so:
```go
logr.SetThreshold(logr.LevelWarn)
```
The above would make it so only `WARN`, `ERROR` and `FATAL` messages are logged, while `DEBUG` and `INFO` messages are ignored.
TODO: Finish documentation

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module github.com/TwiN/logr
go 1.23.3

58
levels.go Normal file
View file

@ -0,0 +1,58 @@
package logr
import (
"errors"
"strings"
)
type Level string
const (
LevelDebug Level = "DEBUG"
LevelInfo Level = "INFO"
LevelWarn Level = "WARN"
LevelError Level = "ERROR"
LevelFatal Level = "FATAL"
)
var (
ErrInvalidLevelString = errors.New("invalid level, must be one of DEBUG, INFO, WARN, ERROR or FATAL")
)
func (level Level) Value() int {
switch level {
case LevelDebug:
return 0
case LevelInfo:
return 1
case LevelWarn:
return 2
case LevelError:
return 3
case LevelFatal:
return 4
default:
return -1
}
}
func (level Level) IsValid() bool {
return level.Value() != -1
}
func LevelFromString(level string) (Level, error) {
switch strings.ToUpper(level) {
case "DEBUG":
return LevelDebug, nil
case "INFO":
return LevelInfo, nil
case "WARN":
return LevelWarn, nil
case "ERROR":
return LevelError, nil
case "FATAL":
return LevelFatal, nil
default:
return LevelDebug, ErrInvalidLevelString
}
}

35
levels_test.go Normal file
View file

@ -0,0 +1,35 @@
package logr
import (
"testing"
)
func TestLevelFromString(t *testing.T) {
scenarios := []struct {
input string
expected Level
expectedErr error
}{
{"DEBUG", LevelDebug, nil},
{"debug", LevelDebug, nil},
{"INFO", LevelInfo, nil},
{"info", LevelInfo, nil},
{"WARN", LevelWarn, nil},
{"warn", LevelWarn, nil},
{"ERROR", LevelError, nil},
{"error", LevelError, nil},
{"", LevelDebug, ErrInvalidLevelString},
{"invalid", LevelDebug, ErrInvalidLevelString},
}
for _, scenario := range scenarios {
t.Run(scenario.input, func(t *testing.T) {
actual, err := LevelFromString(scenario.input)
if actual != scenario.expected {
t.Errorf("expected %s, got %s", scenario.expected, actual)
}
if err != scenario.expectedErr {
t.Errorf("expected %v, got %v", scenario.expectedErr, err)
}
})
}
}

178
logr.go Normal file
View file

@ -0,0 +1,178 @@
package logr
import (
"io"
"log"
"os"
)
var defaultLogger = New(LevelInfo, false, os.Stdout)
type Logger struct {
// threshold is the minimum level output by this logger.
//
// For example, if the threshold is set to LevelWarn, then only logs of LevelWarn and LevelError will be output
// while logs of LevelDebug and LevelInfo will be ignored.
//
// Defaults to LevelInfo.
threshold Level
// shouldPrefixMessageWithLevel is whether to include the log level prefix in each log.
//
// For example, if this is set to true, then a debug log would output "<date> DEBUG <message>"
//
// Defaults to false.
shouldPrefixMessageWithLevel bool
stdLogger *log.Logger
}
// New creates a new logger with the given threshold and output.
func New(threshold Level, shouldPrefixMessageWithLevel bool, output io.Writer) *Logger {
if !threshold.IsValid() {
threshold = LevelInfo // Default to LevelInfo if the threshold is invalid
}
return &Logger{
threshold: threshold,
shouldPrefixMessageWithLevel: shouldPrefixMessageWithLevel,
stdLogger: log.New(output, "", log.LstdFlags),
}
}
func (logger *Logger) SetOutput(output io.Writer) {
// Because we're using the standard log package under the hood, we can just directly pass this
// to the logger and call it a day. It already takes care of locking and discarding the previous writer.
logger.stdLogger.SetOutput(output)
}
func (logger *Logger) SetThreshold(threshold Level) {
if !threshold.IsValid() {
threshold = LevelInfo // Default to LevelInfo if the threshold is invalid
} else {
logger.threshold = threshold
}
}
func (logger *Logger) GetThreshold() Level {
return logger.threshold
}
func (logger *Logger) Log(level Level, message string) {
logger.Logf(level, message)
}
func (logger *Logger) Logf(level Level, format string, args ...any) {
if level.Value() < logger.threshold.Value() {
// The log level is below the threshold, so ignore the log
return
}
if logger.shouldPrefixMessageWithLevel {
format = "- " + string(level) + " - " + format
}
logger.stdLogger.Printf(format, args...)
if level == LevelFatal {
os.Exit(1)
}
}
func (logger *Logger) Debug(message string) {
logger.Log(LevelDebug, message)
}
func (logger *Logger) Debugf(format string, args ...any) {
logger.Logf(LevelDebug, format, args...)
}
func (logger *Logger) Info(message string) {
logger.Log(LevelInfo, message)
}
func (logger *Logger) Infof(format string, args ...any) {
logger.Logf(LevelInfo, format, args...)
}
func (logger *Logger) Warn(message string) {
logger.Log(LevelWarn, message)
}
func (logger *Logger) Warnf(format string, args ...any) {
logger.Logf(LevelWarn, format, args...)
}
func (logger *Logger) Error(message string) {
logger.Log(LevelError, message)
}
func (logger *Logger) Errorf(format string, args ...any) {
logger.Logf(LevelError, format, args...)
}
func (logger *Logger) Fatal(message string) {
logger.Log(LevelFatal, message)
}
func (logger *Logger) Fatalf(format string, args ...any) {
logger.Logf(LevelFatal, format, args...)
}
// SetOutput sets the output of the default logger
func SetOutput(output io.Writer) {
defaultLogger.SetOutput(output)
}
// SetThreshold sets the minimum level output by the default logger
func SetThreshold(threshold Level) {
defaultLogger.SetThreshold(threshold)
}
func GetThreshold() Level {
return defaultLogger.GetThreshold()
}
func Log(level Level, message string) {
defaultLogger.Log(level, message)
}
func Logf(level Level, message string, args ...any) {
defaultLogger.Logf(level, message, args)
}
func Debug(message string) {
defaultLogger.Debug(message)
}
func Debugf(format string, args ...any) {
defaultLogger.Debugf(format, args...)
}
func Info(message string) {
defaultLogger.Info(message)
}
func Infof(format string, args ...any) {
defaultLogger.Infof(format, args...)
}
func Warn(message string) {
defaultLogger.Warn(message)
}
func Warnf(format string, args ...any) {
defaultLogger.Warnf(format, args...)
}
func Error(message string) {
defaultLogger.Error(message)
}
func Errorf(format string, args ...any) {
defaultLogger.Errorf(format, args...)
}
func Fatal(message string) {
defaultLogger.Fatal(message)
}
func Fatalf(format string, args ...any) {
defaultLogger.Fatalf(format, args...)
}

75
logr_test.go Normal file
View file

@ -0,0 +1,75 @@
package logr
import (
"bytes"
"strings"
"testing"
)
func TestNew(t *testing.T) {
logger := New("what", false, nil)
if logger.threshold != LevelInfo {
t.Error("expected invalid threshold to be silently set to INFO, got", logger.threshold)
}
}
func TestLogger(t *testing.T) {
var writer bytes.Buffer
logger := New(LevelWarn, false, &writer)
logger.Debug("this is debug")
logger.Info("this is info")
logger.Warn("this is warn")
logger.Error("this is error")
output := writer.String()
if numberOfNewLines := strings.Count(output, "\n"); numberOfNewLines != 2 {
t.Error("expected 2 newlines, got", numberOfNewLines)
}
if strings.Contains(output, "this is debug") {
t.Error("expected no debug message, got", output)
}
if strings.Contains(output, "this is info") {
t.Error("expected no info message, got", output)
}
if !strings.Contains(output, "this is warn") {
t.Error("expected warn message, got", output)
}
if !strings.Contains(output, "this is error") {
t.Error("expected error message, got", output)
}
}
func TestLogger_SetThreshold(t *testing.T) {
var writer bytes.Buffer
logger := New(LevelDebug, false, &writer)
logger.SetThreshold(LevelError)
logger.Debug("this is debug")
logger.Info("this is info")
logger.Warn("this is warn")
logger.Error("this is error")
output := writer.String()
if numberOfNewLines := strings.Count(output, "\n"); numberOfNewLines != 1 {
t.Error("expected 1 newline, got", numberOfNewLines)
}
}
func TestLoggerFormatting(t *testing.T) {
var writer bytes.Buffer
logger := New(LevelDebug, true, &writer)
logger.Debugf("hello, %s", "world")
logger.Infof("%d", 11111)
logger.Warnf("%s got %d%% in math", "John Doe", 87)
logger.Errorf("%s sent %s$%.02f to %s", "John", "CAD", 69.1, "Jane")
output := writer.String()
if !strings.Contains(output, "- DEBUG - hello, world") {
t.Error("expected '- DEBUG - hello, world.', got", output)
}
if !strings.Contains(output, "- INFO - 11111") {
t.Error("expected '- INFO - John Doe got 87% in math', got", output)
}
if !strings.Contains(output, "- WARN - John Doe got 87% in math") {
t.Error("expected '- WARN - John Doe got 87% in math', got", output)
}
if !strings.Contains(output, "- ERROR - John sent CAD$69.10 to Jane") {
t.Error("expected '- ERROR - John sent USD$123.40 to Jane', got", output)
}
}