Skip to content

Commit

Permalink
Merge pull request #1 from dl1998/add-tests
Browse files Browse the repository at this point in the history
Add missing tests and docstrings
  • Loading branch information
dl1998 authored Mar 7, 2024
2 parents 1967cc0 + c083693 commit e666af2
Show file tree
Hide file tree
Showing 9 changed files with 1,617 additions and 59 deletions.
13 changes: 13 additions & 0 deletions internal/testutils/testutils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package testutils

import (
"reflect"
"testing"
)

func AssertEquals[T any](t *testing.T, expected T, actual T) {
t.Helper()
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("\nExpected: %v\nActual: %v", expected, actual)
}
}
52 changes: 38 additions & 14 deletions pkg/logger/formatter/formatter.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Package formatter contains formatter that interpolates template strings and
// formats them.
package formatter

import (
Expand Down Expand Up @@ -26,35 +28,57 @@ var logLevelColors = map[loglevel.LogLevel]string{
// Reset color
const resetColor = "\033[0m"

// Interface represents interface that shall be satisfied by Formatter.
type Interface interface {
Template() string
Format(message string, loggerName string, level loglevel.LogLevel, colored bool) string
}

// Formatter struct that contains necessary for the formatting fields.
type Formatter struct {
format string
template string
}

// New create a new instance of the Formatter.
func New(template string) *Formatter {
return &Formatter{template: template}
}

func New(format string) *Formatter {
return &Formatter{format: format}
// IsEqual checks that two formatters are the same and returns result of the
// comparison.
func (formatter *Formatter) IsEqual(anotherFormatter *Formatter) bool {
return formatter.template == anotherFormatter.template
}

// EvaluatePreset evaluates pre-defined set of formatting options and returns map
// with mapping of the option to interpolated value.
func (formatter *Formatter) EvaluatePreset(message string, loggerName string, level loglevel.LogLevel) map[string]string {
_, functionName, functionLine, _ := runtime.Caller(2)
var presets = map[string]string{
"%(name)": loggerName,
"%(message)": message,
"%(time)": time.Now().Format(time.TimeOnly),
"%(date)": time.Now().Format(time.DateOnly),
"%(isotime)": time.Now().Format(time.RFC3339),
"%(timestamp)": strconv.FormatInt(time.Now().Unix(), 10),
"%(level)": level.String(),
"%(levelnr)": strconv.Itoa(level.DigitRepresentation()),
"%(fname)": functionName,
"%(fline)": strconv.Itoa(functionLine),
"%(name)": loggerName, // Logger name
"%(message)": message, // Logged message
"%(time)": time.Now().Format(time.TimeOnly), // Current time (format: HH:MM:ss)
"%(date)": time.Now().Format(time.DateOnly), // Current date (format: yyyy-mm-dd)
"%(isotime)": time.Now().Format(time.RFC3339), // Current date and time (format: yyyy-mm-ddTHH:MM:ssGMT)
"%(timestamp)": strconv.FormatInt(time.Now().Unix(), 10), // Current timestamp
"%(level)": level.String(), // Logging level name
"%(levelnr)": strconv.Itoa(level.DigitRepresentation()), // Logging level number
"%(fname)": functionName, // Name of the function from which logger has been called
"%(fline)": strconv.Itoa(functionLine), // Line number from which logger has been called
}
return presets
}

// Template returns template string used by formatter.
func (formatter *Formatter) Template() string {
return formatter.template
}

// Format formats provided message template to the interpolated string.
func (formatter *Formatter) Format(message string, loggerName string, level loglevel.LogLevel, colored bool) string {
var presets = formatter.EvaluatePreset(message, loggerName, level)

format := formatter.format
format := formatter.template

for key, value := range presets {
format = strings.ReplaceAll(format, key, value)
Expand Down
114 changes: 114 additions & 0 deletions pkg/logger/formatter/formatter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Package formatter_test has tests for formatter package.
package formatter

import (
"fmt"
"github.com/dl1998/go-logging/internal/testutils"
"github.com/dl1998/go-logging/pkg/logger/loglevel"
"testing"
)

var template = "%(level):%(name):%(message)"
var message = "Test message."
var loggerName = "test"
var loggingLevel = loglevel.LogLevel(loglevel.Debug)

// TestNew tests that New create correct Formatter instance.
func TestNew(t *testing.T) {
newFormatter := New(template)

testutils.AssertEquals(t, template, newFormatter.template)
}

// BenchmarkNew performs benchmarking of the New().
func BenchmarkNew(b *testing.B) {
for index := 0; index < b.N; index++ {
New(template)
}
}

// TestFormatter_IsEqual tests that Formatter.IsEqual returns true, if two
// Formatter(s) are the same.
func TestFormatter_IsEqual(t *testing.T) {
newFormatter := New(template)

isEqual := newFormatter.IsEqual(newFormatter)

if !isEqual {
t.Fatalf("expected: %t, actual: %t", true, isEqual)
}
}

// BenchmarkFormatter_IsEqual performs benchmarking of the Formatter.IsEqual().
func BenchmarkFormatter_IsEqual(b *testing.B) {
newFormatter := New(template)

for index := 0; index < b.N; index++ {
newFormatter.IsEqual(newFormatter)
}
}

// TestFormatter_EvaluatePreset tests that Formatter.EvaluatePreset correctly
// evaluates tags.
func TestFormatter_EvaluatePreset(t *testing.T) {
newFormatter := New(template)

preset := newFormatter.EvaluatePreset(message, loggerName, loggingLevel)

testutils.AssertEquals(t, message, preset["%(message)"])
testutils.AssertEquals(t, loggerName, preset["%(name)"])
testutils.AssertEquals(t, loggingLevel.String(), preset["%(level)"])
}

// BenchmarkFormatter_EvaluatePreset performs benchmarking of the Formatter.EvaluatePreset().
func BenchmarkFormatter_EvaluatePreset(b *testing.B) {
newFormatter := New(template)

for index := 0; index < b.N; index++ {
newFormatter.EvaluatePreset(message, loggerName, loggingLevel)
}
}

// TestFormatter_Template tests that Formatter.Template return assigned template.
func TestFormatter_Template(t *testing.T) {
newFormatter := New(template)

testutils.AssertEquals(t, template, newFormatter.Template())
}

// BenchmarkFormatter_Template performs benchmarking of the Formatter.Template().
func BenchmarkFormatter_Template(b *testing.B) {
newFormatter := New(template)

for index := 0; index < b.N; index++ {
newFormatter.Template()
}
}

// TestFormatter_Format tests that Formatter.Format correctly formats string.
func TestFormatter_Format(t *testing.T) {
newFormatter := New(template)

parameters := []struct {
colored bool
expected string
}{
{false, fmt.Sprintf("%s:%s:%s\n", loggingLevel.String(), loggerName, message)},
{true, fmt.Sprintf("\033[36m%s:%s:%s\033[0m\n", loggingLevel.String(), loggerName, message)},
}

for index := range parameters {
actual := newFormatter.Format(message, loggerName, loggingLevel, parameters[index].colored)

testutils.AssertEquals(t, parameters[index].expected, actual)
}
}

// BenchmarkFormatter_Format performs benchmarking of the Formatter.Format().
func BenchmarkFormatter_Format(b *testing.B) {
newFormatter := New(template)

for index := 0; index < b.N; index++ {
newFormatter.Format(message, loggerName, loggingLevel, true)
}
}
60 changes: 44 additions & 16 deletions pkg/logger/handler/handler.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Package handler provides handlers for the logger, it contains logic for the
// logging messages.
package handler

import (
Expand All @@ -9,14 +11,29 @@ import (
"strings"
)

var osOpenFile = os.OpenFile
var osStdout = os.Stdout
var osStderr = os.Stderr

// Interface represents interface that shall be satisfied by Handler.
type Interface interface {
Level() loglevel.LogLevel
SetLevel(level loglevel.LogLevel)
Formatter() formatter.Interface
Write(logName string, level loglevel.LogLevel, message string, parameters ...any)
}

// Handler struct contains information where it shall write log message, how to
// format them and their log level.
type Handler struct {
level loglevel.LogLevel
formatter formatter.Formatter
formatter formatter.Interface
writer io.Writer
errorWriter io.Writer
}

func New(level loglevel.LogLevel, newFormatter formatter.Formatter, writer io.Writer, errorWriter io.Writer) *Handler {
// New create a new instance of the Handler.
func New(level loglevel.LogLevel, newFormatter formatter.Interface, writer io.Writer, errorWriter io.Writer) *Handler {
return &Handler{
level: level,
formatter: newFormatter,
Expand All @@ -25,15 +42,16 @@ func New(level loglevel.LogLevel, newFormatter formatter.Formatter, writer io.Wr
}
}

func NewConsoleHandler(level loglevel.LogLevel, newFormatter formatter.Formatter) *Handler {
writer := os.Stdout
errorWriter := os.Stderr

return New(level, newFormatter, writer, errorWriter)
// NewConsoleHandler create a new instance of the Handler that writes log
// messages to the os.Stdout and os.Stderr respectively.
func NewConsoleHandler(level loglevel.LogLevel, newFormatter formatter.Interface) *Handler {
return New(level, newFormatter, osStdout, osStderr)
}

func NewFileHandler(level loglevel.LogLevel, newFormatter formatter.Formatter, file string) *Handler {
writer, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// NewFileHandler creates a new instance of the Handler that writes log message
// to the log file.
func NewFileHandler(level loglevel.LogLevel, newFormatter formatter.Interface, file string) *Handler {
writer, err := osOpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)

if err != nil {
fmt.Println(err)
Expand All @@ -42,36 +60,46 @@ func NewFileHandler(level loglevel.LogLevel, newFormatter formatter.Formatter, f
return New(level, newFormatter, writer, writer)
}

// Level returns log level of the Handler.
func (handler *Handler) Level() loglevel.LogLevel {
return handler.level
}

// SetLevel sets a new log level for the Handler.
func (handler *Handler) SetLevel(level loglevel.LogLevel) {
handler.level = level
}

// Formatter returns formatter.Interface used by the Handler.
func (handler *Handler) Formatter() formatter.Interface {
return handler.formatter
}

// Write writes log message to the defined by the Handler writer.
func (handler *Handler) Write(logName string, level loglevel.LogLevel, message string, parameters ...any) {
formattedMessage := fmt.Sprintf(message, parameters...)

writer := handler.writer

if level >= loglevel.Error {
writer = handler.errorWriter
}

var colored = false

if consoleSupportsANSIColors() && handler.writer == os.Stdout {
if consoleSupportsANSIColors() && (writer == osStdout || writer == osStderr) {
colored = true
}

log := handler.formatter.Format(formattedMessage, logName, level, colored)

writer := handler.writer

if level >= loglevel.Error {
writer = handler.errorWriter
}

if _, err := writer.Write([]byte(log)); err != nil {
fmt.Println(err)
}
}

// consoleSupportsANSIColors returns true, if current terminal supports ANSI
// colors, otherwise returns False.
func consoleSupportsANSIColors() bool {
term := os.Getenv("TERM")
return strings.Contains(term, "xterm") || strings.Contains(term, "color")
Expand Down
Loading

0 comments on commit e666af2

Please sign in to comment.