From 93a9a63b70c3a9243be43dc20c5f15d8fef05c50 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 17 May 2025 05:58:17 +0200 Subject: [PATCH] Adding upstream version 0.3.1. Signed-off-by: Daniel Baumann --- .gitattributes | 1 + .github/dependabot.yml | 6 ++ .github/workflows/test.yml | 21 +++++ .gitignore | 13 +++ LICENSE | 21 +++++ README.md | 27 ++++++ go.mod | 3 + levels.go | 58 ++++++++++++ levels_test.go | 35 ++++++++ logr.go | 178 +++++++++++++++++++++++++++++++++++++ logr_test.go | 75 ++++++++++++++++ 11 files changed, 438 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 go.mod create mode 100644 levels.go create mode 100644 levels_test.go create mode 100644 logr.go create mode 100644 logr_test.go diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1230149 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ae7e3b1 --- /dev/null +++ b/.github/workflows/test.yml @@ -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 ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..175bc2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# IDE +*.iml +.idea +.vscode + +# OS +.DS_Store + +# JS +node_modules + +# Go +/vendor diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..49a3d72 --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fdda348 --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a1cef32 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/TwiN/logr + +go 1.23.3 diff --git a/levels.go b/levels.go new file mode 100644 index 0000000..0cdf383 --- /dev/null +++ b/levels.go @@ -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 + } +} diff --git a/levels_test.go b/levels_test.go new file mode 100644 index 0000000..ece1302 --- /dev/null +++ b/levels_test.go @@ -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) + } + }) + } +} diff --git a/logr.go b/logr.go new file mode 100644 index 0000000..7e43b6a --- /dev/null +++ b/logr.go @@ -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 " DEBUG " + // + // 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...) +} diff --git a/logr_test.go b/logr_test.go new file mode 100644 index 0000000..d88781d --- /dev/null +++ b/logr_test.go @@ -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) + } +}