Skip to content

Commit

Permalink
Merge pull request #24 from dl1998/implement-logrecord
Browse files Browse the repository at this point in the history
Add LogRecord support
  • Loading branch information
dl1998 authored Mar 22, 2024
2 parents 0b00946 + c82e0e5 commit c71fb83
Show file tree
Hide file tree
Showing 25 changed files with 943 additions and 192 deletions.
113 changes: 101 additions & 12 deletions docs/architecture/diagrams/plantuml/class_diagram.plantuml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package pkg {
package common {
package formatter {
class "<<module>>" {
+ EvaluatePreset(loggerName : string, logLevel : level.Level, skipCaller : int) : map[string]interface{}
+ ParseKey(key : string, record : logrecord.Interface) : interface{}
}
}
package handler {
Expand Down Expand Up @@ -43,6 +43,7 @@ package pkg {
+ Previous() : Level
}
class "<<module>>" {
~ mapping : map[Level]string
+ All : level.Level
+ Trace : level.Level
+ Debug : level.Level
Expand All @@ -59,19 +60,48 @@ package pkg {
}
"<<module>>" ..> Level : uses
}
package logrecord {
interface Interface {
+ Name() : string
+ Time() : string
+ Timestamp() : int64
+ Level() : level.Level
+ FileName() : string
+ FileLine() : int
}
struct LogRecord implements Interface {
~ name : string
~ timeFormat : string
~ timestamp : time.Time
~ level : level.Level
~ fileName : string
~ fileLine : int
+ Name() : string
+ Time() : string
+ Timestamp() : int64
+ Level() : level.Level
+ FileName() : string
+ FileLine() : int
}
class "<<module>>" {
+ New(name : string, level : level.Level, timeFormat : string, skipCaller : int) : *LogRecord
}

"<<module>>" ..> LogRecord : uses
}
}
package logger {
package formatter {
interface Interface {
+ Template() : string
+ Format(message : string, loggerName : string, logLevel : level.Level, colored : bool) : string
+ Format(record : logrecord.Interface, colored : bool) : string
}

struct Formatter implements Interface {
~ template : string
+ IsEqual(anotherFormatter : *Formatter) : bool
+ Template() : string
+ Format(message : string, loggerName : string, logLevel : level.Level, colored : bool) : string
+ Format(record : logrecord.Interface, colored : bool) : string
}

class "<<module>>" {
Expand All @@ -90,7 +120,7 @@ package pkg {
+ ToLevel() level.Level
+ SetToLevel(toLevel level.Level)
+ Formatter() : formatter.Interface
+ Write(logName : string, logLevel : level.Level, message : string, parameters : ...any)
+ Write(record : logrecord.Interface)
}
struct Handler implements Interface {
~ *handler.Handler
Expand All @@ -101,7 +131,7 @@ package pkg {
+ ToLevel() : level.Level
+ SetToLevel(level : level.Level)
+ Formatter() : formatter.Interface
+ Write(logName : string, logLevel : level.Level, message : string, parameters : ...any)
+ Write(record : logrecord.Interface)
}
class "<<module>>" {
~ osOpenFile : os.OpenFile(name : string, flag : int, perm : FileMode) : (*File, error)
Expand All @@ -116,6 +146,33 @@ package pkg {

"<<module>>" ..> Handler : uses
}
package logrecord {
interface Interface {
+ Name() : string
+ Time() : string
+ Timestamp() : int64
+ Level() : level.Level
+ FileName() : string
+ FileLine() : int
+ Message() : string
}
struct LogRecord implements Interface {
~ *logrecord.LogRecord
~ message : string
+ Name() : string
+ Time() : string
+ Timestamp() : int64
+ Level() : level.Level
+ FileName() : string
+ FileLine() : int
+ Message() : string
}
class "<<module>>" {
+ New(name : string, level : level.Level, timeFormat : string, message : string, parameters : []any, skipCaller : int) : *LogRecord
}

"<<module>>" ..> LogRecord : uses
}
interface baseLoggerInterface {
+ Log(level : level.Level, message : string, parameters : ...any)
+ Name() : string
Expand Down Expand Up @@ -219,27 +276,27 @@ package pkg {
struct baseFormatter {
~ template : map[string]string
+ Template() : map[string]string
+ Format(loggerName : string, logLevel : level.Level, parameters : ...any) : map[string]interface{}
+ Format(record : logrecord.Interface) : map[string]interface{}
}

interface Interface {
+ Template() : string
+ Format(loggerName : string, logLevel : level.Level, colored : bool, parameters : ...any) : string
+ Format(record : logrecord.Interface, colored : bool) : string
}

struct JSONFormatter implements Interface {
~ baseFormatter : baseInterface
~ pretty : bool
+ Template() : string
+ Format(loggerName : string, logLevel : level.Level, colored : bool, parameters : ...any) : string
+ Format(record : logrecord.Interface, colored : bool) : string
}

struct KeyValueFormatter implements Interface {
~ baseFormatter : baseInterface
~ keyValueDelimiter : string
~ pairSeparator : string
+ Template() : string
+ Format(loggerName : string, logLevel : level.Level, colored : bool, parameters : ...any) : string
+ Format(record : logrecord.Interface, colored : bool) : string
}

class "<<module>>" {
Expand All @@ -262,7 +319,7 @@ package pkg {
+ ToLevel() level.Level
+ SetToLevel(toLevel level.Level)
+ Formatter() : formatter.Interface
+ Write(logName : string, logLevel : level.Level, parameters : ...any)
+ Write(record : logrecord.Interface)
}

struct Handler implements Interface {
Expand All @@ -274,7 +331,7 @@ package pkg {
+ ToLevel() : level.Level
+ SetToLevel(level : level.Level)
+ Formatter() : formatter.Interface
+ Write(logName : string, logLevel : level.Level, parameters : ...any)
+ Write(record : logrecord.Interface)
}

class "<<module>>" {
Expand All @@ -289,6 +346,33 @@ package pkg {

"<<module>>" ..> Handler : uses
}
package logrecord {
interface Interface {
+ Name() : string
+ Time() : string
+ Timestamp() : int64
+ Level() : level.Level
+ FileName() : string
+ FileLine() : int
+ Parameters() : map[string]interface{}
}
struct LogRecord implements Interface {
~ *logrecord.LogRecord
~ parameters : map[string]interface{}
+ Name() : string
+ Time() : string
+ Timestamp() : int64
+ Level() : level.Level
+ FileName() : string
+ FileLine() : int
+ Parameters() : map[string]interface{}
}
class "<<module>>" {
+ New(name : string, level : level.Level, timeFormat : string, parameters : map[string]interface{}, skipCaller : int) : *LogRecord
}

"<<module>>" ..> LogRecord : uses
}
interface baseLoggerInterface {
+ Log(level : level.Level, parameters : ...any)
+ Name() : string
Expand Down Expand Up @@ -358,7 +442,7 @@ package pkg {
~ rootLogger : *Logger
~ fromLevel : level.Level
~ toLevel : level.Level
~ template : string
~ template : map[string]string
~ init()
+ New(name : string) : *Logger
+ WithFromLevel(fromLevel : level.Level) : Option
Expand Down Expand Up @@ -397,6 +481,7 @@ package pkg {
}
}

"pkg.common.formatter.<<module>>" ..> "pkg.common.logrecord.Interface" : uses
pkg.common.handler.Handler *-- pkg.common.level.Level : contains
pkg.logger.handler.Handler *-- pkg.common.handler.Handler : contains
pkg.logger.handler.Handler *-- pkg.logger.formatter.Interface : contains
Expand All @@ -406,6 +491,10 @@ pkg.structuredlogger.handler.Handler *-- pkg.common.handler.Handler : contains
pkg.structuredlogger.handler.Handler *-- pkg.structuredlogger.formatter.Interface : contains
pkg.structuredlogger.baseLogger *-- "0..*" pkg.structuredlogger.handler.Interface : contains
pkg.structuredlogger.formatter.baseFormatter ..> "pkg.common.formatter.<<module>>" : uses
pkg.logger.logrecord.LogRecord *-- "pkg.common.logrecord.LogRecord" : contains
pkg.structuredlogger.logrecord.LogRecord *-- "pkg.common.logrecord.LogRecord" : contains
pkg.logger.handler.Handler ..> pkg.logger.logrecord.Interface : uses
pkg.structuredlogger.handler.Handler ..> pkg.structuredlogger.logrecord.Interface : uses


@enduml
Binary file modified docs/architecture/diagrams/png/class_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/architecture/diagrams/svg/class_diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions internal/testutils/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,19 @@ func AssertEquals[T any](t *testing.T, expected T, actual T) {
t.Fatalf("\nExpected: %v\nActual: %v", expected, actual)
}
}

// AssertNil checks if the value is nil.
func AssertNil(t *testing.T, value any) {
t.Helper()
if value != nil {
t.Fatalf("\nExpected: nil\nActual: %v", value)
}
}

// AssertNotNil checks if the value is not nil.
func AssertNotNil(t *testing.T, value any) {
t.Helper()
if value == nil {
t.Fatalf("\nExpected: not nil\nActual: %v", value)
}
}
42 changes: 24 additions & 18 deletions pkg/common/formatter/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,31 @@
package formatter

import (
"github.com/dl1998/go-logging/pkg/common/level"
"runtime"
"time"
"github.com/dl1998/go-logging/pkg/common/logrecord"
)

// EvaluatePreset evaluates pre-defined set of formatting options and returns map
// with mapping of the option to interpolated value.
func EvaluatePreset(loggerName string, logLevel level.Level, skipCaller int) map[string]interface{} {
_, functionName, functionLine, _ := runtime.Caller(skipCaller)
var presets = map[string]interface{}{
"%(name)": loggerName, // Logger name
"%(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)": time.Now().Unix(), // Current timestamp
"%(level)": logLevel.String(), // Logging log level name
"%(levelnr)": logLevel.DigitRepresentation(), // Logging log level number
"%(fname)": functionName, // Name of the function from which logger has been called
"%(fline)": functionLine, // Line number from which logger has been called
// ParseKey parses the key and returns the value.
func ParseKey(key string, record logrecord.Interface) interface{} {
var value interface{}

switch key {
case "%(name)":
value = record.Name()
case "%(level)":
value = record.Level().String()
case "%(levelnr)":
value = record.Level().DigitRepresentation()
case "%(datetime)":
value = record.Time()
case "%(timestamp)":
value = record.Timestamp()
case "%(fname)":
value = record.FileName()
case "%(fline)":
value = record.FileLine()
default:
value = key
}
return presets

return value
}
68 changes: 56 additions & 12 deletions pkg/common/formatter/formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,68 @@ package formatter
import (
"github.com/dl1998/go-logging/internal/testutils"
"github.com/dl1998/go-logging/pkg/common/level"
"github.com/dl1998/go-logging/pkg/common/logrecord"
"testing"
)

var loggerName = "test"
var loggingLevel = level.Debug
var (
loggerName = "test"
loggingLevel = level.Debug
timeFormat = ""
skipCaller = 1
)

// TestParseKey tests that ParseKey returns correct value for the key.
func TestParseKey(t *testing.T) {
record := logrecord.New(loggerName, loggingLevel, timeFormat, skipCaller)

// TestFormatter_EvaluatePreset tests that Formatter.EvaluatePreset correctly
// evaluates tags.
func TestEvaluatePreset(t *testing.T) {
preset := EvaluatePreset(loggerName, loggingLevel, 1)
tests := map[string]struct {
key string
expected interface{}
}{
"Name": {key: "%(name)", expected: loggerName},
"Level name": {key: "%(level)", expected: loggingLevel.String()},
"Level number": {key: "%(levelnr)", expected: loggingLevel.DigitRepresentation()},
"Date time": {key: "%(datetime)", expected: record.Time()},
"Timestamp": {key: "%(timestamp)", expected: record.Timestamp()},
"Function name": {key: "%(fname)", expected: record.FileName()},
"Function line": {key: "%(fline)", expected: record.FileLine()},
"Not a key": {key: "not a key", expected: "not a key"},
}

testutils.AssertEquals(t, loggerName, preset["%(name)"].(string))
testutils.AssertEquals(t, loggingLevel.String(), preset["%(level)"].(string))
for name, test := range tests {
t.Run(name, func(t *testing.T) {
value := ParseKey(test.key, record)

testutils.AssertEquals(t, test.expected, value)
})
}
}

// BenchmarkFormatter_EvaluatePreset performs benchmarking of the Formatter.EvaluatePreset().
func BenchmarkEvaluatePreset(b *testing.B) {
for index := 0; index < b.N; index++ {
EvaluatePreset(loggerName, loggingLevel, 1)
// BenchmarkParseKey performs benchmarking of the ParseKey().
func BenchmarkParseKey(b *testing.B) {
record := logrecord.New(loggerName, loggingLevel, timeFormat, skipCaller)

benchmarks := map[string]struct {
key string
}{
"Name": {key: "%(name)"},
"Level name": {key: "%(level)"},
"Level number": {key: "%(levelnr)"},
"Date time": {key: "%(datetime)"},
"Timestamp": {key: "%(timestamp)"},
"Function name": {key: "%(fname)"},
"Function line": {key: "%(fline)"},
"Not a key": {key: "not a key"},
}

for name, benchmark := range benchmarks {
b.Run(name, func(b *testing.B) {
b.ResetTimer()

for index := 0; index < b.N; index++ {
ParseKey(benchmark.key, record)
}
})
}
}
Loading

0 comments on commit c71fb83

Please sign in to comment.