Skip to content

Commit

Permalink
Add human readable logger (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
lerenn committed Sep 14, 2023
1 parent 6b6d875 commit 1485c64
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 56 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ Generate Go application and user boilerplate from AsyncAPI specifications.
* Formats:
* JSON
* Logging:
* JSON (ECS compatible)
* Elastic Common Schema (JSON)
* Text (Humand readable)
* Custom

## Usage

Expand Down Expand Up @@ -512,7 +514,7 @@ import(
/* ... */
)

// Create a new app controller with an Elastic Common Schema compatible logger
// Create a new app controller with an Elastic Common Schema JSON compatible logger
ctrl, _ := NewAppController(/* Broker of your choice */, WithLogger(log.NewECS()))
```

Expand Down
2 changes: 1 addition & 1 deletion examples/ping/kafka/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (s Subscriber) Ping(ctx context.Context, req PingMessage, _ bool) {

func main() {
// Instanciate a Kafka controller with a logger
logger := loggers.NewECS()
logger := loggers.NewText()
broker := kafka.NewController(
[]string{"kafka:9092"}, // List of hosts
kafka.WithLogger(logger), // Attach an internal logger
Expand Down
2 changes: 1 addition & 1 deletion examples/ping/kafka/user/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func main() {
// Instanciate a Kafka controller with a logger
logger := loggers.NewECS()
logger := loggers.NewText()
broker := kafka.NewController(
[]string{"kafka:9092"}, // List of hosts
kafka.WithLogger(logger), // Attach an internal logger
Expand Down
2 changes: 1 addition & 1 deletion examples/ping/nats/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (s ServerSubscriber) Ping(ctx context.Context, req PingMessage, _ bool) {

func main() {
// Instanciate a NATS controller with a logger
logger := loggers.NewECS()
logger := loggers.NewText()
broker := nats.NewController("nats://nats:4222", nats.WithLogger(logger))

// Create a new app controller
Expand Down
2 changes: 1 addition & 1 deletion examples/ping/nats/user/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func main() {
// Instanciate a NATS controller with a logger
logger := loggers.NewECS()
logger := loggers.NewText()
broker := nats.NewController("nats://nats:4222", nats.WithLogger(logger))

// Create a new user controller
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/klauspost/compress v1.16.5 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/nats-io/nats-server/v2 v2.9.11 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
Expand All @@ -14,6 +16,11 @@ github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/d
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
Expand Down Expand Up @@ -68,6 +75,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
47 changes: 47 additions & 0 deletions pkg/extensions/loggers/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package loggers

import (
"fmt"
"strings"

"github.com/lerenn/asyncapi-codegen/pkg/extensions"
)

func insertLogIntoStruct(key, value string, m map[string]any) map[string]any {
// Split key
l := strings.Split(key, ".")

// Check if there is no depth, just add it to the map
if len(l) == 1 {
m[key] = value
return m
}

// Check if the submap exists, otherwise create it
var subm map[string]any
if v, ok := m[l[0]]; !ok {
subm = make(map[string]any)
} else {
subm, ok = v.(map[string]any)
if !ok {
// Explicitly drop the old value
subm = make(map[string]any)
}
}

// Insert the log into the submap
subm = insertLogIntoStruct(strings.Join(l[1:], "."), value, subm)

// Insert the submap into the map
m[l[0]] = subm

return m
}

func structureLogs(info []extensions.LogInfo) map[string]any {
structuredLog := make(map[string]any)
for _, logInfo := range info {
structuredLog = insertLogIntoStruct(logInfo.Key, fmt.Sprintf("%+v", logInfo.Value), structuredLog)
}
return structuredLog
}
68 changes: 18 additions & 50 deletions pkg/extensions/loggers/ecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,20 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"time"

"github.com/lerenn/asyncapi-codegen/pkg/extensions"
)

// ECS is a logger that will print logs in Elastic Common Schema format.
// ECS is a logger that will print logs in Elastic Common Schema ECS format.
type ECS struct{}

// NewECS creates a new ECS logger.
func NewECS() ECS {
return ECS{}
}

func insertLogIntoStruct(key, value string, m map[string]any) map[string]any {
// Split key
l := strings.Split(key, ".")

// Check if there is no depth, just add it to the map
if len(l) == 1 {
m[key] = value
return m
}

// Check if the submap exists, otherwise create it
var subm map[string]any
if v, ok := m[l[0]]; !ok {
subm = make(map[string]any)
} else {
subm, ok = v.(map[string]any)
if !ok {
// Explicitly drop the old value
subm = make(map[string]any)
}
}

// Insert the log into the submap
subm = insertLogIntoStruct(strings.Join(l[1:], "."), value, subm)

// Insert the submap into the map
m[l[0]] = subm

return m
}

func structureLogs(info []extensions.LogInfo) map[string]any {
structuredLog := make(map[string]any)
for _, logInfo := range info {
structuredLog = insertLogIntoStruct(logInfo.Key, fmt.Sprintf("%+v", logInfo.Value), structuredLog)
}
return structuredLog
}

func (logger ECS) formatLog(ctx context.Context, msg string, info ...extensions.LogInfo) string {
func (ecs ECS) setInfoFromContext(ctx context.Context, msg string, info ...extensions.LogInfo) []extensions.LogInfo {
// Add additional keys from context
extensions.IfContextSetWith(ctx, extensions.ContextKeyIsProvider, func(value any) {
info = append(info, extensions.LogInfo{Key: "asyncapi.provider", Value: value})
Expand Down Expand Up @@ -93,6 +53,14 @@ func (logger ECS) formatLog(ctx context.Context, msg string, info ...extensions.
Value: "github.com/lerenn/asyncapi-codegen/pkg/extensions/loggers/ecs.go",
})

// Return info
return info
}

func (ecs ECS) formatLog(ctx context.Context, msg string, info ...extensions.LogInfo) string {
// Set additional fields
info = ecs.setInfoFromContext(ctx, msg, info...)

// Structure log
sl := structureLogs(info)

Expand All @@ -105,25 +73,25 @@ func (logger ECS) formatLog(ctx context.Context, msg string, info ...extensions.
return string(b)
}

func (logger ECS) logWithLevel(ctx context.Context, level string, msg string, info ...extensions.LogInfo) {
func (ecs ECS) logWithLevel(ctx context.Context, level string, msg string, info ...extensions.LogInfo) {
// Add additional keys
info = append(info, extensions.LogInfo{Key: "log.level", Value: level})

// Print log
fmt.Println(logger.formatLog(ctx, msg, info...))
fmt.Println(ecs.formatLog(ctx, msg, info...))
}

// Info logs a message at info level with context and additional info.
func (logger ECS) Info(ctx context.Context, msg string, info ...extensions.LogInfo) {
logger.logWithLevel(ctx, "info", msg, info...)
func (ecs ECS) Info(ctx context.Context, msg string, info ...extensions.LogInfo) {
ecs.logWithLevel(ctx, "info", msg, info...)
}

// Warning logs a message at warning level with context and additional info.
func (logger ECS) Warning(ctx context.Context, msg string, info ...extensions.LogInfo) {
logger.logWithLevel(ctx, "warning", msg, info...)
func (ecs ECS) Warning(ctx context.Context, msg string, info ...extensions.LogInfo) {
ecs.logWithLevel(ctx, "warning", msg, info...)
}

// Error logs a message at error level with context and additional info.
func (logger ECS) Error(ctx context.Context, msg string, info ...extensions.LogInfo) {
logger.logWithLevel(ctx, "error", msg, info...)
func (ecs ECS) Error(ctx context.Context, msg string, info ...extensions.LogInfo) {
ecs.logWithLevel(ctx, "error", msg, info...)
}
120 changes: 120 additions & 0 deletions pkg/extensions/loggers/text.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package loggers

import (
"context"
"fmt"
"strings"
"time"

"github.com/fatih/color"
"github.com/lerenn/asyncapi-codegen/pkg/extensions"
)

// Text is a logger that will print logs in Elastic Common Schema format.
type Text struct {
boldRedPrinter *color.Color
boldOrangePrinter *color.Color
boldWhitePrinter *color.Color
greyPrinter *color.Color
}

// NewText creates a new Human logger.
func NewText() Text {
// Create red color
red := color.New(color.FgHiRed)
boldRed := red.Add(color.Bold)

// Create orange color
orange := color.New(color.FgHiYellow)
boldOrange := orange.Add(color.Bold)

// Create white color
white := color.New(color.FgWhite)
boldWhite := white.Add(color.Bold)

return Text{
boldRedPrinter: boldRed,
boldOrangePrinter: boldOrange,
boldWhitePrinter: boldWhite,
greyPrinter: color.New(color.FgHiBlack),
}
}

func (tl Text) humanizeStructuredLogs(sl map[string]any, msgFmt *color.Color, prefixes ...string) string {
var s string
joinedPrefixes := strings.Join(prefixes, "")

// Put timestamp and message first if it tsExists
ts, tsExists := sl["@Timestamp"]
msg, msgExists := sl["Message"]
if tsExists && msgExists {
s += msgFmt.Sprintf("> %s%s: %s\n", joinedPrefixes, ts, msg)
delete(sl, "@Timestamp")
delete(sl, "Message")
return s + tl.humanizeStructuredLogs(sl, msgFmt, append(prefixes, " ")...)
}

// Generate other keys
for k, v := range sl {
switch tv := v.(type) {
case map[string]any:
children := tl.humanizeStructuredLogs(tv, msgFmt, append(prefixes, " ")...)
s += tl.greyPrinter.Sprintf("%s%s:\n%s", joinedPrefixes, k, children)
default:
s += tl.greyPrinter.Sprintf("%s%s: %v\n", joinedPrefixes, k, tv)
}
}
return s
}

func (tl Text) setInfoFromContext(ctx context.Context, msg string, info ...extensions.LogInfo) []extensions.LogInfo {
// Add additional keys from context
extensions.IfContextSetWith(ctx, extensions.ContextKeyIsChannel, func(value any) {
info = append(info, extensions.LogInfo{Key: "Channel", Value: value})
})
extensions.IfContextSetWith(ctx, extensions.ContextKeyIsCorrelationID, func(value any) {
info = append(info, extensions.LogInfo{Key: "CorrelationID", Value: value})
})
extensions.IfContextSetWith(ctx, extensions.ContextKeyIsMessage, func(value any) {
info = append(info, extensions.LogInfo{Key: "Content", Value: value})
})

// Add additional keys
info = append(info, extensions.LogInfo{
Key: "Message",
Value: msg,
})
info = append(info, extensions.LogInfo{
Key: "@Timestamp",
Value: time.Now().UTC().Format(time.RFC3339Nano),
})

// Return info
return info
}

func (tl Text) formatLog(ctx context.Context, msgFmt *color.Color, msg string, info ...extensions.LogInfo) string {
// Set additional fields
info = tl.setInfoFromContext(ctx, msg, info...)

// Structure log
sl := structureLogs(info)

// Humanize structured logs
return tl.humanizeStructuredLogs(sl, msgFmt)
}

// Info logs a message at info level with context and additional info.
func (tl Text) Info(ctx context.Context, msg string, info ...extensions.LogInfo) {
fmt.Println(tl.formatLog(ctx, tl.boldWhitePrinter, msg, info...))
}

// Warning logs a message at warning level with context and additional info.
func (tl Text) Warning(ctx context.Context, msg string, info ...extensions.LogInfo) {
fmt.Println(tl.formatLog(ctx, tl.boldOrangePrinter, msg, info...))
}

// Error logs a message at error level with context and additional info.
func (tl Text) Error(ctx context.Context, msg string, info ...extensions.LogInfo) {
fmt.Println(tl.formatLog(ctx, tl.boldRedPrinter, msg, info...))
}

0 comments on commit 1485c64

Please sign in to comment.