Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,18 @@ Formatters:
- `DefaultFormatter`
- `ColouredFormatter`

Example usage. Create a new package `log` in your app such that:
## Configuration

The logger can be configured using optional configuration functions:

- `WithLogLevel(level Level)` - Sets the minimum log level to output (default: `INFO`)
- `WithFormatter(formatter Formatter)` - Sets the log formatter (default: `DefaultFormatter`)

## Example Usage

### Basic Usage

Create a new package `log` in your app:

```go
package log
Expand All @@ -29,7 +40,8 @@ import (
)

var (
logger = logging.New(nil, nil, new(logging.ColouredFormatter))
// Create logger with default configuration (INFO level, DefaultFormatter)
logger = logging.New(nil, nil)

// INFO ...
INFO = logger[logging.INFO]
Expand All @@ -42,6 +54,41 @@ var (
)
```

### Custom Configuration

You can customize the logger using configuration options:

```go
package log

import (
"github.com/RichardKnop/logging"
)

var (
// Create logger with coloured formatter and DEBUG level
logger = logging.New(
nil,
nil,
logging.WithLogLevel(logging.DEBUG),
logging.WithFormatter(new(logging.ColouredFormatter)),
)

// DEBUG ...
DEBUG = logger[logging.DEBUG]
// INFO ...
INFO = logger[logging.INFO]
// WARNING ...
WARNING = logger[logging.WARNING]
// ERROR ...
ERROR = logger[logging.ERROR]
// FATAL ...
FATAL = logger[logging.FATAL]
)
```

### Using the Logger

Then from your app you could do:

```go
Expand All @@ -53,5 +100,7 @@ import (

func main() {
log.INFO.Print("log message")
log.WARNING.Printf("formatted %s", "message")
log.ERROR.Println("error message")
}
```
8 changes: 4 additions & 4 deletions coloured_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const (
)

// Colour map
var colour = map[level]string{
var colour = map[Level]string{
INFO: fmt.Sprintf(colourSeq, 94), // blue
WARNING: fmt.Sprintf(colourSeq, 95), // pink
ERROR: fmt.Sprintf(colourSeq, 91), // red
Expand All @@ -25,16 +25,16 @@ type ColouredFormatter struct {
}

// GetPrefix returns colour escape code
func (f *ColouredFormatter) GetPrefix(lvl level) string {
func (f *ColouredFormatter) GetPrefix(lvl Level) string {
return colour[lvl]
}

// GetSuffix returns reset sequence code
func (f *ColouredFormatter) GetSuffix(lvl level) string {
func (f *ColouredFormatter) GetSuffix(lvl Level) string {
return resetSeq
}

// Format adds filename and line number before the log message
func (f *ColouredFormatter) Format(lvl level, v ...interface{}) []interface{} {
func (f *ColouredFormatter) Format(lvl Level, v ...interface{}) []interface{} {
return append([]interface{}{header()}, v...)
}
2 changes: 1 addition & 1 deletion coloured_formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestColouredFormatter(t *testing.T) {

var (
out, errOut = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
logger = logging.New(out, errOut, new(logging.ColouredFormatter))
logger = logging.New(out, errOut, logging.WithFormatter(new(logging.ColouredFormatter)))
now time.Time
actual []byte
expected string
Expand Down
34 changes: 34 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package logging

// Config holds configuration options for the logger.
type Config struct {
// LogLevel sets the minimum log level to output. If not set, INFO level is used.
LogLevel Level

// Formatter sets the log formatter to use. If not set, DefaultFormatter is used.
Formatter Formatter
}

func defaultConfig() *Config {
return &Config{
LogLevel: INFO,
Formatter: new(DefaultFormatter),
}
}

// ConfigOption defines a function type for configuring the logger.
type ConfigOption func(*Config)

// WithLogLevel sets the log level in the logger configuration.
func WithLogLevel(lvl Level) ConfigOption {
return func(c *Config) {
c.LogLevel = lvl
}
}

// WithFormatter sets the log formatter in the logger configuration.
func WithFormatter(f Formatter) ConfigOption {
return func(c *Config) {
c.Formatter = f
}
}
6 changes: 3 additions & 3 deletions default_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ type DefaultFormatter struct {
}

// GetPrefix returns ""
func (f *DefaultFormatter) GetPrefix(lvl level) string {
func (f *DefaultFormatter) GetPrefix(lvl Level) string {
return ""
}

// GetSuffix returns ""
func (f *DefaultFormatter) GetSuffix(lvl level) string {
func (f *DefaultFormatter) GetSuffix(lvl Level) string {
return ""
}

// Format adds filename and line number before the log message
func (f *DefaultFormatter) Format(lvl level, v ...interface{}) []interface{} {
func (f *DefaultFormatter) Format(lvl Level, v ...interface{}) []interface{} {
return append([]interface{}{header()}, v...)
}
6 changes: 3 additions & 3 deletions formatter_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ const (

// Formatter interface
type Formatter interface {
GetPrefix(lvl level) string
Format(lvl level, v ...interface{}) []interface{}
GetSuffix(lvl level) string
GetPrefix(lvl Level) string
Format(lvl Level, v ...interface{}) []interface{}
GetSuffix(lvl Level) string
}

// Returns header including filename and line number
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
module github.com/RichardKnop/logging

go 1.22

require github.com/stretchr/testify v1.11.1

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
gopkg.in/yaml.v3 v3.0.1 // indirect
)
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
65 changes: 41 additions & 24 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@ import (
)

// Level type
type level int
type Level int

const (
// DEBUG level
DEBUG level = iota
// INFO level
// DEBUG Level
DEBUG Level = iota
// INFO Level
INFO
// WARNING level
// WARNING Level
WARNING
// ERROR level
// ERROR Level
ERROR
// FATAL level
// FATAL Level
FATAL

flag = log.Ldate | log.Ltime
)

// Log level prefix map
var prefix = map[level]string{
// Log Level prefix map
var prefix = map[Level]string{
DEBUG: "DEBUG: ",
INFO: "INFO: ",
WARNING: "WARNING: ",
Expand All @@ -34,10 +34,25 @@ var prefix = map[level]string{
}

// Logger ...
type Logger map[level]LoggerInterface
type Logger map[Level]LoggerInterface

// New returns instance of Logger.
func New(out, errOut io.Writer, ops ...ConfigOption) Logger {
config := defaultConfig()

for _, op := range ops {
op(config)
}

// If log level is out of bounds, set to nearest valid level.
if config.LogLevel < DEBUG {
config.LogLevel = DEBUG
}

if config.LogLevel > FATAL {
config.LogLevel = FATAL
}

// New returns instance of Logger
func New(out, errOut io.Writer, f Formatter) Logger {
// Fall back to stdout if out not set
if out == nil {
out = os.Stdout
Expand All @@ -48,24 +63,26 @@ func New(out, errOut io.Writer, f Formatter) Logger {
errOut = os.Stderr
}

// Fall back to DefaultFormatter if f not set
if f == nil {
f = new(DefaultFormatter)
}
l := make(map[Level]LoggerInterface, 5)

l := make(map[level]LoggerInterface, 5)
l[DEBUG] = &Wrapper{lvl: DEBUG, formatter: f, logger: log.New(out, f.GetPrefix(DEBUG)+prefix[DEBUG], flag)}
l[INFO] = &Wrapper{lvl: INFO, formatter: f, logger: log.New(out, f.GetPrefix(INFO)+prefix[INFO], flag)}
l[WARNING] = &Wrapper{lvl: INFO, formatter: f, logger: log.New(out, f.GetPrefix(WARNING)+prefix[WARNING], flag)}
l[ERROR] = &Wrapper{lvl: INFO, formatter: f, logger: log.New(errOut, f.GetPrefix(ERROR)+prefix[ERROR], flag)}
l[FATAL] = &Wrapper{lvl: INFO, formatter: f, logger: log.New(errOut, f.GetPrefix(FATAL)+prefix[FATAL], flag)}
for level := DEBUG; level <= FATAL; level++ {
l[level] = NewNoOp()

if level >= config.LogLevel {
l[level] = &Wrapper{
lvl: level,
formatter: config.Formatter,
logger: log.New(out, config.Formatter.GetPrefix(level)+prefix[level], flag),
}
}
}

return Logger(l)
return l
}

// Wrapper ...
type Wrapper struct {
lvl level
lvl Level
formatter Formatter
logger LoggerInterface
}
Expand Down
51 changes: 51 additions & 0 deletions logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package logging_test

import (
"testing"

"github.com/RichardKnop/logging"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNew(t *testing.T) {
tests := []struct {
name string
logLevel logging.Level
expectedLogs int
}{
{"DEBUG", logging.DEBUG, 5},
{"INFO", logging.INFO, 4},
{"WARNING", logging.WARNING, 3},
{"ERROR", logging.ERROR, 2},
{"FATAL", logging.FATAL, 1},
}

for _, tt := range tests {
out := &stubWriter{}

t.Run("GIVEN a logging constructor configured with min log level "+tt.name, func(t *testing.T) {
logger := logging.New(out, nil, logging.WithLogLevel(tt.logLevel))

t.Run("WHEN logging with all the log levels", func(t *testing.T) {
for level := logging.DEBUG; level <= logging.FATAL; level++ {
logger[level].Print("This is a test log")
}

t.Run("THEN only those allowed logs are performed", func(t *testing.T) {
require.Len(t, out.loggedMessages, tt.expectedLogs)
assert.Contains(t, out.loggedMessages[0], "This is a test log")
})
})
})
}
}

type stubWriter struct {
loggedMessages []string
}

func (s *stubWriter) Write(p []byte) (n int, err error) {
s.loggedMessages = append(s.loggedMessages, string(p))
return len(p), nil
}
28 changes: 28 additions & 0 deletions noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package logging

type noOp struct {
}

// NewNoOp creates a no-op logger that implements LoggerInterface but does nothing.
// Useful for disabling logging. Also used with Config MinLogLevel on disabled levels.
func NewNoOp() LoggerInterface {
return &noOp{}
}

func (n noOp) Print(i ...interface{}) {}

func (n noOp) Printf(s string, i ...interface{}) {}

func (n noOp) Println(i ...interface{}) {}

func (n noOp) Fatal(i ...interface{}) {}

func (n noOp) Fatalf(s string, i ...interface{}) {}

func (n noOp) Fatalln(i ...interface{}) {}

func (n noOp) Panic(i ...interface{}) {}

func (n noOp) Panicf(s string, i ...interface{}) {}

func (n noOp) Panicln(i ...interface{}) {}