Compare commits
No commits in common. "34c00624389cfaf3e5ceb371712ca11e560f8ead" and "da98f624381ffee6df101fae82075f266a4018c7" have entirely different histories.
34c0062438
...
da98f62438
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2015 Charles Iliya Krempeaux :: http://changelog.ca/
|
Copyright (c) 2015 Charles Iliya Krempeaux <charles@reptile.ca> :: http://changelog.ca/
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
113
README.md
113
README.md
|
@ -1,115 +1,18 @@
|
||||||
# go-log
|
# go-flog
|
||||||
|
|
||||||
A library that provides structured and formatted logging for the Go programming language.
|
A library that provides structured and formatted logging for the Go programming language.
|
||||||
|
|
||||||
(This Go package was originally named `flog`, but in version 2 was renamed to `log`.)
|
|
||||||
|
|
||||||
## Online Documention
|
## Documention
|
||||||
|
|
||||||
Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-log
|
Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-flog
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/reiver/go-log?status.svg)](https://godoc.org/github.com/reiver/go-log)
|
[![GoDoc](https://godoc.org/github.com/reiver/go-flog?status.svg)](https://godoc.org/github.com/reiver/go-flog)
|
||||||
|
|
||||||
## Basic Usage
|
|
||||||
|
|
||||||
Basic usage of this logger looks like this:
|
## More Routers
|
||||||
|
|
||||||
```golang
|
In addition to the builtin routers that go-flog comes with, other external
|
||||||
router := log.NewPrettyWritingRouter(os.Stdout)
|
routers are also available. These include:
|
||||||
|
|
||||||
logger := log.New(router)
|
* [go-slackchannelrouter](https://github.com/reiver/go-slackchannelrouter) Makes it so log messages get posted to a [Slack](https://slack.com/) channel.
|
||||||
```
|
|
||||||
Once you have the logger, you can do things such as:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
logger.Print("Hello world!")
|
|
||||||
logger.Println("Hello world!")
|
|
||||||
logger.Printf("Hello %s!", name)
|
|
||||||
|
|
||||||
logger.Panic("Uh oh!")
|
|
||||||
logger.Panicln("Uh oh!")
|
|
||||||
logger.Panicf("Uh oh, had a problem happen: %s.", problemDescription)
|
|
||||||
|
|
||||||
logger.Fatal("Something really bad happened!")
|
|
||||||
logger.Fatalln("Something really bad happened!")
|
|
||||||
logger.Fatalf("Something really bad happened: %s.", problemDescription)
|
|
||||||
```
|
|
||||||
|
|
||||||
BTW, if the PrettyWritingRouter was being used, then this:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
logger.Print("Hello world!")
|
|
||||||
```
|
|
||||||
|
|
||||||
Would generate output like the following:
|
|
||||||
|
|
||||||
```
|
|
||||||
Hello world! (2015-10-10 17:28:49.397356044 -0700 PDT)
|
|
||||||
```
|
|
||||||
|
|
||||||
(Although note that in actual usage this would have color.)
|
|
||||||
|
|
||||||
(Note that for for other routers the actual output would look very different!
|
|
||||||
What the output looks like is router dependent.)
|
|
||||||
|
|
||||||
## Structured Logging
|
|
||||||
|
|
||||||
But those method calls all generated unstructure data.
|
|
||||||
|
|
||||||
To include structured data the logger's With method needs to be used.
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
newLogger := logger.With(map[string]interface{}{
|
|
||||||
"method":"Toil",
|
|
||||||
"secret_note":"Hi there! How are you?",
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Then if the PrettyWritingRouter was being used, then this:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
newLogger.Print("Hello world!")
|
|
||||||
```
|
|
||||||
|
|
||||||
Would generate output like the following:
|
|
||||||
|
|
||||||
```
|
|
||||||
Hello world! (2015-10-10 17:28:49.397356044 -0700 PDT) method="Toil" secret_note="Hi there! How are you?"
|
|
||||||
```
|
|
||||||
|
|
||||||
(Again, note that in actual usage this would have color.)
|
|
||||||
|
|
||||||
## Deployment Environment
|
|
||||||
|
|
||||||
Of course in a real application system you should (probably) create a different kind
|
|
||||||
of logger for each deployment environment.
|
|
||||||
|
|
||||||
Even though the PrettyWritingRouter is great for a development deployment environment
|
|
||||||
(i.e., "DEV") it is probably not appropriate for a production deployment environment
|
|
||||||
(i.e., "PROD").
|
|
||||||
|
|
||||||
For example:
|
|
||||||
```golang
|
|
||||||
var logger log.Logger
|
|
||||||
|
|
||||||
switch deploymentEnvironment {
|
|
||||||
case "DEV":
|
|
||||||
router := log.NewPrettyWritingRouter(os.Stdout)
|
|
||||||
|
|
||||||
logger = log.New(router)
|
|
||||||
case "PROD":
|
|
||||||
verboseRouter = log.NewDiscardingRouter()
|
|
||||||
if isVerboseMode {
|
|
||||||
verboseRouter = NewCustomVerboseRouter()
|
|
||||||
}
|
|
||||||
|
|
||||||
panicDetectionRouter := log.NewFilteringRouter(NewCustomerPanicRecordingRouter(), filterOnlyPanicsFunc)
|
|
||||||
|
|
||||||
errorDetectionRouter := log.NewFilteringRouter(NewCustomerPanicRecordingRouter(), filterOnlyErrorsFunc)
|
|
||||||
|
|
||||||
router := NewFanoutRouter(verboseRouter, panicDetectionRouter, errorDetectionRouter)
|
|
||||||
|
|
||||||
logger = log.New(router)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
func newContext(cascade ...interface{}) map[string]interface{} {
|
||||||
|
|
||||||
|
context := make(map[string]interface{})
|
||||||
|
|
||||||
|
for _,x := range cascade {
|
||||||
|
switch xx := x.(type) {
|
||||||
|
case map[string]string:
|
||||||
|
for key, value := range xx {
|
||||||
|
context[key] = value
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
for key, value := range xx {
|
||||||
|
context[key] = value
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
context["text"] = xx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestNewContext(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Cascade []interface{}
|
||||||
|
Expected map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{},
|
||||||
|
Expected: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
"apple",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"apple",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"banana",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
"cherry",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"cherry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"fig":"THIS SHOULD BE REPLACED",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"fig":"THIS SHOULD REMAIN",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"fig":"THIS SHOULD REMAIN",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestLoop:
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
|
||||||
|
context := newContext(test.Cascade...)
|
||||||
|
|
||||||
|
if expected, actual := len(test.Expected), len(context); expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected length to be %d but actually was %d.", testNumber, expected, actual)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
for expectedKey, expectedValue := range test.Expected {
|
||||||
|
if _, ok := context[expectedKey]; !ok {
|
||||||
|
t.Errorf("For test #%d, expected key %q to be in resulting context but wasn't.", testNumber, expectedKey)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := expectedValue, context[expectedKey]; expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected value for key %q to be %q in resulting context, but was actually %q.", testNumber, expectedKey, expected, actual)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
// NewCopyingRouter returns an initialized CopyingRouter.
|
||||||
|
func NewCopyingRouter(subrouter Router) *CopyingRouter {
|
||||||
|
copies := make([]struct{Message string ; Context map[string]interface{}}, 0, 8)
|
||||||
|
|
||||||
|
router := CopyingRouter {
|
||||||
|
subrouter:subrouter,
|
||||||
|
copies:copies,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &router
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// CopyingRouter is a Router that copies a message (and its context) to memory, and then
|
||||||
|
// re-routes a message (and its context) to a sub-router.
|
||||||
|
//
|
||||||
|
// This router is NOT designed to be used for an indefinite number of routings, and instead
|
||||||
|
// should only be used for a limited amount of routings, as it stores all the copies in
|
||||||
|
// memory.
|
||||||
|
type CopyingRouter struct {
|
||||||
|
subrouter Router
|
||||||
|
copies []struct{Message string ; Context map[string]interface{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (router *CopyingRouter) Route(message string, context map[string]interface{}) error {
|
||||||
|
if nil == router {
|
||||||
|
return errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
copy := struct{Message string ; Context map[string]interface{}}{
|
||||||
|
Message: message,
|
||||||
|
Context: context,
|
||||||
|
}
|
||||||
|
router.copies = append(router.copies, copy)
|
||||||
|
|
||||||
|
return router.subrouter.Route(message, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (router *CopyingRouter) Copies() <-chan struct{Message string ; Context map[string]interface{}} {
|
||||||
|
ch := make(chan struct{Message string ; Context map[string]interface{}})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for _, copy := range router.copies {
|
||||||
|
ch <- copy
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestNewCopyingRouter(t *testing.T) {
|
||||||
|
|
||||||
|
randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
|
||||||
|
|
||||||
|
|
||||||
|
router := NewCopyingRouter(NewDiscardingRouter())
|
||||||
|
if nil == router {
|
||||||
|
t.Errorf("After trying to create a copying router, expected it to be not nil, but was: %v", router)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lenInitialCopies := 0
|
||||||
|
for range router.Copies() {
|
||||||
|
lenInitialCopies++
|
||||||
|
}
|
||||||
|
if expected, actual := 0, lenInitialCopies; expected != actual {
|
||||||
|
t.Errorf("After creating a copying router, expected copies to be length %d, but was actually %d,", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message := fmt.Sprintf("%x", randomness.Int63n(9999999999))
|
||||||
|
|
||||||
|
context := make(map[string]interface{})
|
||||||
|
limit := randomness.Int63n(30)
|
||||||
|
for i:=int64(0); i<limit; i++ {
|
||||||
|
context[ fmt.Sprintf("%x", randomness.Int63n(1000*limit)) ] = fmt.Sprintf("%x", randomness.Int63n(999999999999999))
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Route(message, context) // Just make sure it doesn't panic or deadlok, by calling this.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestCopyingRouterRoute(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Data []struct{Message string ; Context map[string]interface{}}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Data: []struct{Message string ; Context map[string]interface{}}{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Data: []struct{Message string ; Context map[string]interface{}}{
|
||||||
|
struct{Message string ; Context map[string]interface{}}{
|
||||||
|
Message: "apple banana cherry",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"one":1,
|
||||||
|
"two":2,
|
||||||
|
"three":3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Data: []struct{Message string ; Context map[string]interface{}}{
|
||||||
|
struct{Message string ; Context map[string]interface{}}{
|
||||||
|
Message: "apple",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"one":1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
struct{Message string ; Context map[string]interface{}}{
|
||||||
|
Message: "banana",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"two":2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
struct{Message string ; Context map[string]interface{}}{
|
||||||
|
Message: "cherry",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"cherry":3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TLoop: for testNumber, test := range tests {
|
||||||
|
|
||||||
|
router := NewCopyingRouter(NewDiscardingRouter())
|
||||||
|
|
||||||
|
for _, datum := range test.Data {
|
||||||
|
router.Route(datum.Message, datum.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lenCopies := 0
|
||||||
|
for range router.Copies() {
|
||||||
|
lenCopies++
|
||||||
|
}
|
||||||
|
if expected, actual := len(test.Data), lenCopies; expected != actual {
|
||||||
|
t.Errorf("For test #%d, after creating a copying router and (potentially) doing some routing, expected copies to be length %d, but was actually %d.", testNumber, expected, actual)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
datumNumber := 0
|
||||||
|
for actualDatum := range router.Copies() {
|
||||||
|
if expected, actual := test.Data[datumNumber].Message, actualDatum.Message; expected != actual {
|
||||||
|
t.Errorf("For test #%d, after creating a copying router and (potentially) doing some routing, expected message for copies datum #%d to be %q, but was actually %q.", testNumber, datumNumber, expected, actual)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := len(test.Data[datumNumber].Context), len(actualDatum.Context); expected != actual {
|
||||||
|
t.Errorf("For test #%d, after creating a copying router and (potentially) doing some routing, expected length of context for copies datum #%d to be %d, but was actually %d.", testNumber, datumNumber, expected, actual)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
for expectedKey, expectedValue := range test.Data[datumNumber].Context {
|
||||||
|
if _, ok := actualDatum.Context[expectedKey]; !ok {
|
||||||
|
t.Errorf("For test #%d, after creating a copying router and (potentially) doing some routing, expected context for copies datum #%d to have key %q, but didn't.", testNumber, datumNumber, expectedKey)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualValue := actualDatum.Context[expectedKey]; expectedValue != actualValue {
|
||||||
|
t.Errorf("For test #%d, after creating a copying router and (potentially) doing some routing, expected value for context for copies datum #%d at key %q to have value [%v], but actually had [%v].", testNumber, datumNumber, expectedKey, expectedValue, actualValue)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datumNumber++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/reiver/go-dotquote"
|
||||||
|
"github.com/reiver/go-oi"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDefaultWritingRouter returns an initialized DefaultWritingRouter
|
||||||
|
func NewDefaultWritingRouter(writer io.Writer) *DefaultWritingRouter {
|
||||||
|
return NewDefaultWritingRouterWithPrefix(writer, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultWritingRouterWithPrefix returns an initialized DefaultWritingRouter
|
||||||
|
func NewDefaultWritingRouterWithPrefix(writer io.Writer, prefix map[string]interface{}) *DefaultWritingRouter {
|
||||||
|
var prefixBuffer []byte
|
||||||
|
if 0 < len(prefix) {
|
||||||
|
prefixBuffer = dotquote.AppendMap(prefixBuffer, prefix)
|
||||||
|
prefixBuffer = append(prefixBuffer, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
router := DefaultWritingRouter{
|
||||||
|
writer:writer,
|
||||||
|
prefix:prefixBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &router
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultWritingRouter is a router that writes the log in a default format.
|
||||||
|
//
|
||||||
|
// A DefaultWritingRouter is appropriate for a production (i.e., "PROD")
|
||||||
|
// deployment enviornment.
|
||||||
|
type DefaultWritingRouter struct {
|
||||||
|
writer io.Writer
|
||||||
|
prefix []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (router *DefaultWritingRouter) Route(message string, context map[string]interface{}) error {
|
||||||
|
if nil == router {
|
||||||
|
return errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
writer := router.writer
|
||||||
|
if nil == writer {
|
||||||
|
// Nothing to do, so just return silently.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var buffer [512]byte
|
||||||
|
p := buffer[:0]
|
||||||
|
|
||||||
|
if prefix := router.prefix; 0 < len(prefix) {
|
||||||
|
p = append(p, prefix...)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
p = dotquote.AppendString(p, message, "text")
|
||||||
|
p = append(p, ' ')
|
||||||
|
p = dotquote.AppendString(p, time.Now().String(), "when")
|
||||||
|
if trace := calltrace(); nil != trace {
|
||||||
|
p = append(p, ' ')
|
||||||
|
p = dotquote.AppendStrings(p, trace, "calltrace")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an error, then get the error.Error() into the log too.
|
||||||
|
if errorFieldValue, ok := context["~error"]; ok {
|
||||||
|
if err, ok := errorFieldValue.(error); ok {
|
||||||
|
p = append(p, ' ')
|
||||||
|
p = dotquote.AppendString(p, fmt.Sprintf("%T", err), "error", "type")
|
||||||
|
p = append(p, ' ')
|
||||||
|
p = dotquote.AppendString(p, err.Error(), "error", "text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 < len(context) {
|
||||||
|
p = append(p, ' ')
|
||||||
|
p = dotquote.AppendMap(p, context, "ctx")
|
||||||
|
}
|
||||||
|
|
||||||
|
p = append(p, '\n')
|
||||||
|
|
||||||
|
_,_ = oi.LongWrite(router.writer, p)
|
||||||
|
|
||||||
|
//@TODO: Should this be checking for errors from oi.LongWrite()?
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestDefaultWritingRouterRoute(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Message string
|
||||||
|
Context map[string]interface{}
|
||||||
|
ExpectContains []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Message: "Hello world!",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": 3.3,
|
||||||
|
"kiwi": true,
|
||||||
|
"~error": errors.New("test error"),
|
||||||
|
},
|
||||||
|
ExpectContains: []string{
|
||||||
|
`"text"="Hello world!"`,
|
||||||
|
` "ctx"."apple"="one" "ctx"."banana"="2" "ctx"."cherry"="3.300000" "ctx"."kiwi"="true"`,
|
||||||
|
` "error"."type"="*errors.errorString" "error"."text"="test error" `,
|
||||||
|
` "when"="`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Message: "Apple\tBANANA\nCherry",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": 3.3,
|
||||||
|
"kiwi": true,
|
||||||
|
"~error": errors.New("test error"),
|
||||||
|
"more": map[string]interface{}{
|
||||||
|
"ONE": "1",
|
||||||
|
"TWO": "2",
|
||||||
|
"THREE": "3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectContains: []string{
|
||||||
|
`"text"="Apple\tBANANA\nCherry"`,
|
||||||
|
` "ctx"."apple"="one" "ctx"."banana"="2" "ctx"."cherry"="3.300000" "ctx"."kiwi"="true"`,
|
||||||
|
` "error"."type"="*errors.errorString" "error"."text"="test error" `,
|
||||||
|
` "ctx"."more"."ONE"="1" "ctx"."more"."THREE"="3" "ctx"."more"."TWO"="2"`,
|
||||||
|
` "when"="`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Message: "Apple\tBANANA\nCherry",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": 3.3,
|
||||||
|
"kiwi": true,
|
||||||
|
"~error": errors.New("test error"),
|
||||||
|
"more": map[string]interface{}{
|
||||||
|
"ONE": "1",
|
||||||
|
"TWO": "2",
|
||||||
|
"THREE": "3",
|
||||||
|
"FOUR": map[string]interface{}{
|
||||||
|
"a": "1st",
|
||||||
|
"b": "2nd",
|
||||||
|
"c": []string{
|
||||||
|
"th",
|
||||||
|
"i",
|
||||||
|
"rd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectContains: []string{
|
||||||
|
`"text"="Apple\tBANANA\nCherry"`,
|
||||||
|
` "ctx"."apple"="one" "ctx"."banana"="2" "ctx"."cherry"="3.300000" "ctx"."kiwi"="true"`,
|
||||||
|
` "error"."type"="*errors.errorString" "error"."text"="test error" `,
|
||||||
|
` "ctx"."more"."FOUR"."a"="1st" "ctx"."more"."FOUR"."b"="2nd" "ctx"."more"."FOUR"."c"=["th","i","rd"] "ctx"."more"."ONE"="1" "ctx"."more"."THREE"="3" "ctx"."more"."TWO"="2"`,
|
||||||
|
` "when"="`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
var router Router = NewDefaultWritingRouter(&buffer)
|
||||||
|
|
||||||
|
ctx := map[string]interface{}{}
|
||||||
|
for n, v := range test.Context {
|
||||||
|
ctx[n] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := router.Route(test.Message, ctx); nil != err {
|
||||||
|
t.Errorf("For test #%d, did not expect an error, but actually got one: (%T) %v", testNumber, err, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
actual := buffer.String()
|
||||||
|
for expectedNumber, expectContains := range test.ExpectContains {
|
||||||
|
|
||||||
|
if !strings.Contains(actual, expectContains) {
|
||||||
|
t.Errorf("For test #%d and expected #%d, expect to contain, actual:\n==)>%s<(==\n==)>%s<(==", testNumber, expectedNumber, expectContains, actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestDefaultWritingRouterWithPrefixRoute(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Message string
|
||||||
|
Context map[string]interface{}
|
||||||
|
Prefix map[string]interface{}
|
||||||
|
ExpectContains []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Message: "Hello world!",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": 3.3,
|
||||||
|
"kiwi": true,
|
||||||
|
"~error": errors.New("test error"),
|
||||||
|
},
|
||||||
|
Prefix: map[string]interface{}{
|
||||||
|
"name": "backendapi",
|
||||||
|
"number": "123",
|
||||||
|
},
|
||||||
|
ExpectContains: []string{
|
||||||
|
`"name"="backendapi" "number"="123" "text"="Hello world!" "when"="`,
|
||||||
|
` "ctx"."apple"="one" "ctx"."banana"="2" "ctx"."cherry"="3.300000" "ctx"."kiwi"="true"`,
|
||||||
|
` "error"."type"="*errors.errorString" "error"."text"="test error" `,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Message: "Apple\tBANANA\nCherry",
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": 3.3,
|
||||||
|
"kiwi": true,
|
||||||
|
"~error": errors.New("test error"),
|
||||||
|
"more": map[string]interface{}{
|
||||||
|
"ONE": "1",
|
||||||
|
"TWO": "2",
|
||||||
|
"THREE": "3",
|
||||||
|
"FOUR": map[string]interface{}{
|
||||||
|
"a": "1st",
|
||||||
|
"b": "2nd",
|
||||||
|
"c": []string{
|
||||||
|
"th",
|
||||||
|
"i",
|
||||||
|
"rd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Prefix: map[string]interface{}{
|
||||||
|
"app": map[string]interface{}{
|
||||||
|
"name": "backendapi",
|
||||||
|
"build": map[string]interface{}{
|
||||||
|
"number": 123,
|
||||||
|
"hash": "4a844b2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectContains: []string{
|
||||||
|
`"app"."build"."hash"="4a844b2" "app"."build"."number"="123" "app"."name"="backendapi" "text"="Apple\tBANANA\nCherry" "when"="`,
|
||||||
|
` "ctx"."apple"="one" "ctx"."banana"="2" "ctx"."cherry"="3.300000" "ctx"."kiwi"="true"`,
|
||||||
|
` "error"."type"="*errors.errorString" "error"."text"="test error" `,
|
||||||
|
` "ctx"."more"."FOUR"."a"="1st" "ctx"."more"."FOUR"."b"="2nd" "ctx"."more"."FOUR"."c"=["th","i","rd"] "ctx"."more"."ONE"="1" "ctx"."more"."THREE"="3" "ctx"."more"."TWO"="2"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
var router Router = NewDefaultWritingRouterWithPrefix(&buffer, test.Prefix)
|
||||||
|
|
||||||
|
ctx := map[string]interface{}{}
|
||||||
|
for n, v := range test.Context {
|
||||||
|
ctx[n] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := router.Route(test.Message, ctx); nil != err {
|
||||||
|
t.Errorf("For test #%d, did not expect an error, but actually got one: (%T) %v", testNumber, err, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
actual := buffer.String()
|
||||||
|
for expectedNumber, expectContains := range test.ExpectContains {
|
||||||
|
|
||||||
|
if !strings.Contains(actual, expectContains) {
|
||||||
|
t.Errorf("For test #%d and expected #%d, expect to contain, actual:\n==)>%s<(==\n==)>%s<(==", testNumber, expectedNumber, expectContains, actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
singltonDiscardingRouter = DiscardingRouter{}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// NewDiscardingRouter returns an initialized DiscardingRouter.
|
||||||
|
func NewDiscardingRouter() *DiscardingRouter {
|
||||||
|
return &singltonDiscardingRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DiscardingRouter is a Router that discards any message (and its context)
|
||||||
|
// it is asked to route.
|
||||||
|
//
|
||||||
|
// Conceptually it is similar to /dev/null
|
||||||
|
type DiscardingRouter struct{}
|
||||||
|
|
||||||
|
|
||||||
|
func (router *DiscardingRouter) Route(message string, context map[string]interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestNewDiscardingRouter(t *testing.T) {
|
||||||
|
|
||||||
|
randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
|
||||||
|
|
||||||
|
|
||||||
|
router := NewDiscardingRouter()
|
||||||
|
if nil == router {
|
||||||
|
t.Errorf("After trying to create a discard router, expected it to be not nil, but was: %v", router)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message := fmt.Sprintf("%x", randomness.Int63n(9999999999))
|
||||||
|
|
||||||
|
context := make(map[string]interface{})
|
||||||
|
limit := randomness.Int63n(30)
|
||||||
|
for i:=int64(0); i<limit; i++ {
|
||||||
|
context[ fmt.Sprintf("%x", randomness.Int63n(1000*limit)) ] = fmt.Sprintf("%x", randomness.Int63n(999999999999999))
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Route(message, context) // Just make sure it doesn't panic or deadlok, by calling this.
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
Package flog provides structured and formatted logging.
|
||||||
|
|
||||||
|
Basic Usage
|
||||||
|
|
||||||
|
Basic usage of the flogger looks like this:
|
||||||
|
|
||||||
|
router := flog.NewPrettyWritingRouter(os.Stdout)
|
||||||
|
|
||||||
|
flogger := flog.New(router)
|
||||||
|
|
||||||
|
Once you have the flogger, you can do things such as:
|
||||||
|
|
||||||
|
flogger.Print("Hello world!")
|
||||||
|
flogger.Println("Hello world!")
|
||||||
|
flogger.Printf("Hello %s!", name)
|
||||||
|
|
||||||
|
flogger.Panic("Uh oh!")
|
||||||
|
flogger.Panicln("Uh oh!")
|
||||||
|
flogger.Panicf("Uh oh, had a problem happen: %s.", problemDescription)
|
||||||
|
|
||||||
|
flogger.Fatal("Something really bad happened!")
|
||||||
|
flogger.Fatalln("Something really bad happened!")
|
||||||
|
flogger.Fatalf("Something really bad happened: %s.", problemDescription)
|
||||||
|
|
||||||
|
BTW, if the PrettyWritingRouter was being used, then this:
|
||||||
|
|
||||||
|
flogger.Print("Hello world!")
|
||||||
|
|
||||||
|
Would generate output like the following:
|
||||||
|
|
||||||
|
Hello world! (2015-10-10 17:28:49.397356044 -0700 PDT)
|
||||||
|
|
||||||
|
(Although note that in actual usage this would have color.)
|
||||||
|
|
||||||
|
(Note that for for other routers the actual output would look very different!
|
||||||
|
What the output looks like is router dependent.)
|
||||||
|
|
||||||
|
Structured Logging
|
||||||
|
|
||||||
|
But those method calls all generated unstructure data.
|
||||||
|
|
||||||
|
To include structured data the flogger's With method needs to be used.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
newFlogger := flogger.With(map[string]interface{}{
|
||||||
|
"method":"Toil",
|
||||||
|
"secret_note":"Hi there! How are you?",
|
||||||
|
})
|
||||||
|
|
||||||
|
Then if the PrettyWritingRouter was being used, then this:
|
||||||
|
|
||||||
|
newFlogger.Print("Hello world!")
|
||||||
|
|
||||||
|
Would generate output like the following:
|
||||||
|
|
||||||
|
Hello world! (2015-10-10 17:28:49.397356044 -0700 PDT) method="Toil" secret_note="Hi there! How are you?"
|
||||||
|
|
||||||
|
(Again, note that in actual usage this would have color.)
|
||||||
|
|
||||||
|
Deployment Environment
|
||||||
|
|
||||||
|
Of course in a real application system you should (probably) create a different kind
|
||||||
|
of flogger for each deployment environment.
|
||||||
|
|
||||||
|
Even though the PrettyWritingRouter is great for a development deployment environment
|
||||||
|
(i.e., "DEV") it is probably not appropriate for a production deployment environment
|
||||||
|
(i.e., "PROD").
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
var flogger flog.Flogger
|
||||||
|
|
||||||
|
switch deploymentEnvironment {
|
||||||
|
case "DEV":
|
||||||
|
router := flog.NewPrettyWritingRouter(os.Stdout)
|
||||||
|
|
||||||
|
flogger = flog.New(router)
|
||||||
|
case "PROD":
|
||||||
|
verboseRouter = flog.NewDiscardingRouter()
|
||||||
|
if isVerboseMode {
|
||||||
|
verboseRouter = NewCustomVerboseRouter()
|
||||||
|
}
|
||||||
|
|
||||||
|
panicDetectionRouter := flog.NewFilteringRouter(NewCustomerPanicRecordingRouter(), filterOnlyPanicsFunc)
|
||||||
|
|
||||||
|
errorDetectionRouter := flog.NewFilteringRouter(NewCustomerPanicRecordingRouter(), filterOnlyErrorsFunc)
|
||||||
|
|
||||||
|
router := NewFanoutRouter(verboseRouter, panicDetectionRouter, errorDetectionRouter)
|
||||||
|
|
||||||
|
flogger = flog.New(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
More Routers
|
||||||
|
|
||||||
|
In addition to the builtin routers that go-flog comes with, other external
|
||||||
|
routers are also available. These include:
|
||||||
|
|
||||||
|
go-slackchannelrouter:
|
||||||
|
Makes it so log messages get posted to a Slack channel.
|
||||||
|
https://github.com/reiver/go-slackchannelrouter
|
||||||
|
|
||||||
|
*/
|
||||||
|
package flog
|
|
@ -0,0 +1,198 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestNewErrorContext(t *testing.T) {
|
||||||
|
|
||||||
|
err1 := errors.New("error 1")
|
||||||
|
err2 := errors.New("error 2")
|
||||||
|
err3 := errors.New("error 3")
|
||||||
|
err4 := errors.New("error 4")
|
||||||
|
err5 := errors.New("error 5")
|
||||||
|
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
BaseContext map[string]interface{}
|
||||||
|
V []interface{}
|
||||||
|
Expected map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
// Nothing here.
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
1, "two",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
1, "two", 3.0,
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
1, "two", 3.0,
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
err1,
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
"~errors": []error{
|
||||||
|
err1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
err1, err2,
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
"~errors": []error{
|
||||||
|
err1, err2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
err1, err2, err3,
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
"~errors": []error{
|
||||||
|
err1, err2, err3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
err1, err2, err3, err4,
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
"~errors": []error{
|
||||||
|
err1, err2, err3, err4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
err1, err2, err3, err4, err5,
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
"~errors": []error{
|
||||||
|
err1, err2, err3, err4, err5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
BaseContext: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
},
|
||||||
|
V: []interface{}{
|
||||||
|
1, err1, "two", err2, 3.0, err3, '4',
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
"~errors": []error{
|
||||||
|
err1, err2, err3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
|
||||||
|
actualContext := newErrorContext(test.BaseContext, test.V...)
|
||||||
|
if actual := actualContext; nil == actual {
|
||||||
|
t.Errorf("For test #%d, did not expected nil, but actually got %v", testNumber, actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := len(test.Expected), len(actualContext); expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected %d, but actually got %d.", testNumber, expected, actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := test.Expected, actualContext; !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("For test #%d, expected...\n%#v\nbut actually got...\n%#v", testNumber, expected, actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -8,4 +8,5 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNilReceiver = errors.New("Nil Receiver")
|
errNilReceiver = errors.New("Nil Receiver")
|
||||||
|
errNilRouter = errors.New("Nil Router")
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/reiver/go-manyerrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// NewFanoutRouter returns an initialized FanoutRouter.
|
||||||
|
func NewFanoutRouter(subrouters ...Router) *FanoutRouter {
|
||||||
|
router := FanoutRouter{
|
||||||
|
subrouters:subrouters,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &router
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FanoutRouter is a Router that re-routes any message (and its context) it
|
||||||
|
// receives to all of its sub-routers.
|
||||||
|
type FanoutRouter struct {
|
||||||
|
subrouters []Router
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (router *FanoutRouter) Route(message string, context map[string]interface{}) error {
|
||||||
|
if nil == router {
|
||||||
|
return errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := []error{}
|
||||||
|
|
||||||
|
for _, subrouter := range router.subrouters {
|
||||||
|
if err := subrouter.Route(message, context); nil != err {
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 < len(errors) {
|
||||||
|
return manyerrors.New(errors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,350 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/reiver/go-manyerrors"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestNewFanoutRouter(t *testing.T) {
|
||||||
|
|
||||||
|
randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
|
||||||
|
|
||||||
|
|
||||||
|
router := NewFanoutRouter()
|
||||||
|
if nil == router {
|
||||||
|
t.Errorf("After trying to create a discard router, expected it to be not nil, but was: %v", router)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message := fmt.Sprintf("%x", randomness.Int63n(9999999999))
|
||||||
|
|
||||||
|
context := make(map[string]interface{})
|
||||||
|
limit := randomness.Int63n(30)
|
||||||
|
for i:=int64(0); i<limit; i++ {
|
||||||
|
context[ fmt.Sprintf("%x", randomness.Int63n(1000*limit)) ] = fmt.Sprintf("%x", randomness.Int63n(999999999999999))
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Route(message, context) // Just make sure it doesn't panic or deadlok, by calling this.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestFanoutRouterRoute(t *testing.T) {
|
||||||
|
|
||||||
|
numApples := 0
|
||||||
|
numBananas := 0
|
||||||
|
numCherries := 0
|
||||||
|
numFigs := 0
|
||||||
|
numKiwis := 0
|
||||||
|
numApplesOrFigs := 0
|
||||||
|
|
||||||
|
appleRouter := NewFilteringRouter(NewDiscardingRouter(), func(message string, context map[string]interface{}) bool {
|
||||||
|
if "apple" == message {
|
||||||
|
numApples++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
bananaRouter := NewFilteringRouter(NewDiscardingRouter(), func(message string, context map[string]interface{}) bool {
|
||||||
|
if "banana" == message {
|
||||||
|
numBananas++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
cherryRouter := NewFilteringRouter(NewDiscardingRouter(), func(message string, context map[string]interface{}) bool {
|
||||||
|
if "cherry" == message {
|
||||||
|
numCherries++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
figRouter := NewFilteringRouter(NewDiscardingRouter(), func(message string, context map[string]interface{}) bool {
|
||||||
|
if "fig" == message {
|
||||||
|
numFigs++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
kiwiRouter := NewFilteringRouter(NewDiscardingRouter(), func(message string, context map[string]interface{}) bool {
|
||||||
|
if "kiwi" == message {
|
||||||
|
numKiwis++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
appleOrFigRouter := NewFilteringRouter(NewDiscardingRouter(), func(message string, context map[string]interface{}) bool {
|
||||||
|
if "apple" == message || "fig" == message {
|
||||||
|
numApplesOrFigs++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
router := NewFanoutRouter(appleRouter, bananaRouter, cherryRouter, figRouter, kiwiRouter, appleOrFigRouter)
|
||||||
|
|
||||||
|
if expected, actual := 0, numApples; expected != actual {
|
||||||
|
t.Errorf("Initially expected numApples to be %d, but actually was %d.", expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numBananas; expected != actual {
|
||||||
|
t.Errorf("Initially expected numBananas to be %d, but actually was %d.", expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numCherries; expected != actual {
|
||||||
|
t.Errorf("Initially expected numCherries to be %d, but actually was %d.", expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numFigs; expected != actual {
|
||||||
|
t.Errorf("Initially expected numFigs to be %d, but actually was %d.", expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numKiwis; expected != actual {
|
||||||
|
t.Errorf("Initially expected numKiwis to be %d, but actually was %d.", expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numApplesOrFigs; expected != actual {
|
||||||
|
t.Errorf("Initially expected numApplesOrFigs to be %d, but actually was %d.", expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var message string
|
||||||
|
var context map[string]interface{} = map[string]interface{}{}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message = "apple"
|
||||||
|
if err := router.Route(message, context); nil != err {
|
||||||
|
switch errs := err.(type) {
|
||||||
|
case manyerrors.Errors:
|
||||||
|
t.Errorf("Received many error when trying to send message %q: %#v", message, errs.Errors())
|
||||||
|
default:
|
||||||
|
t.Errorf("Received error when trying to send message %q: %v", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := 1, numApples; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApples to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numBananas; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numBananas to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numCherries; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numCherries to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numKiwis; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numKiwis to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numApplesOrFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApplesOrFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message = "banana"
|
||||||
|
if err := router.Route(message, context); nil != err {
|
||||||
|
switch errs := err.(type) {
|
||||||
|
case manyerrors.Errors:
|
||||||
|
t.Errorf("Received many error when trying to send message %q: %#v", message, errs.Errors())
|
||||||
|
default:
|
||||||
|
t.Errorf("Received error when trying to send message %q: %v", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := 1, numApples; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApples to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numBananas; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numBananas to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numCherries; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numCherries to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numKiwis; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numKiwis to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numApplesOrFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApplesOrFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message = "cherry"
|
||||||
|
if err := router.Route(message, context); nil != err {
|
||||||
|
switch errs := err.(type) {
|
||||||
|
case manyerrors.Errors:
|
||||||
|
t.Errorf("Received many error when trying to send message %q: %#v", message, errs.Errors())
|
||||||
|
default:
|
||||||
|
t.Errorf("Received error when trying to send message %q: %v", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := 1, numApples; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApples to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numBananas; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numBananas to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numCherries; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numCherries to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numKiwis; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numKiwis to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numApplesOrFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApplesOrFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message = "fig"
|
||||||
|
if err := router.Route(message, context); nil != err {
|
||||||
|
switch errs := err.(type) {
|
||||||
|
case manyerrors.Errors:
|
||||||
|
t.Errorf("Received many error when trying to send message %q: %#v", message, errs.Errors())
|
||||||
|
default:
|
||||||
|
t.Errorf("Received error when trying to send message %q: %v", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := 1, numApples; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApples to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numBananas; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numBananas to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numCherries; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numCherries to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 0, numKiwis; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numKiwis to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 2, numApplesOrFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApplesOrFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message = "kiwi"
|
||||||
|
if err := router.Route(message, context); nil != err {
|
||||||
|
switch errs := err.(type) {
|
||||||
|
case manyerrors.Errors:
|
||||||
|
t.Errorf("Received many error when trying to send message %q: %#v", message, errs.Errors())
|
||||||
|
default:
|
||||||
|
t.Errorf("Received error when trying to send message %q: %v", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := 1, numApples; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApples to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numBananas; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numBananas to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numCherries; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numCherries to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numKiwis; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numKiwis to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 2, numApplesOrFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApplesOrFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message = "fig"
|
||||||
|
if err := router.Route(message, context); nil != err {
|
||||||
|
switch errs := err.(type) {
|
||||||
|
case manyerrors.Errors:
|
||||||
|
t.Errorf("Received many error when trying to send message %q: %#v", message, errs.Errors())
|
||||||
|
default:
|
||||||
|
t.Errorf("Received error when trying to send message %q: %v", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := 1, numApples; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApples to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numBananas; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numBananas to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numCherries; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numCherries to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 2, numFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 1, numKiwis; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numKiwis to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if expected, actual := 3, numApplesOrFigs; expected != actual {
|
||||||
|
t.Errorf("After sending message %q expected numApplesOrFigs to be %d, but actually was %d.", message, expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
// NewFilteringRouter returns an initialized FilteringRouter.
|
||||||
|
//
|
||||||
|
// 'subrouter' is the sub-router that a FilteringRouter will
|
||||||
|
// re-Route a 'message' (and 'context') to, but only on the
|
||||||
|
// condition that 'filterFn' returns 'true' for the 'message'
|
||||||
|
// and 'context' passed to it.
|
||||||
|
//
|
||||||
|
// An example for 'filterFn' is the following.
|
||||||
|
//
|
||||||
|
// func filterError(message string, context map[string]interface{})bool) bool {
|
||||||
|
// if datum, ok := context["error"]; !ok {
|
||||||
|
// return false
|
||||||
|
// } else if _, ok := datum.(error); !ok {
|
||||||
|
// return false
|
||||||
|
// } else {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This func will cause the router it only re-route messages whose context #1 has the
|
||||||
|
// key "error" and #2 where the value of the context at key "key" fits the builtin Go
|
||||||
|
// 'error' interface.
|
||||||
|
//
|
||||||
|
// So, for example, for 'filterError' this would pass:
|
||||||
|
//
|
||||||
|
// context := map[string]interface{}{
|
||||||
|
// "apple":1,
|
||||||
|
// "banana":2,
|
||||||
|
// "cherry":3,
|
||||||
|
// "error": errors.New("Something bad happened :-("),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// But, again for 'filterError', this would NOT pass:
|
||||||
|
//
|
||||||
|
// context := map[string]interface{}{
|
||||||
|
// "apple":1,
|
||||||
|
// "banana":2,
|
||||||
|
// "cherry":3,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Also, a rather useless example, but a 'filterFn' that would reject all messages (and
|
||||||
|
// contexts) is:
|
||||||
|
//
|
||||||
|
// func filterRejectAll(message string, context map[string]interface{})bool) bool {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// And also, another rather useless example, but a 'filterFn' that would allow all messages
|
||||||
|
// (and contexts) is:
|
||||||
|
//
|
||||||
|
// func filterAcceptAll(message string, context map[string]interface{})bool) bool {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func NewFilteringRouter(subrouter Router, filterFn func(string, map[string]interface{})bool) *FilteringRouter {
|
||||||
|
router := FilteringRouter{
|
||||||
|
subrouter:subrouter,
|
||||||
|
filterFn:filterFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &router
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FilteringRouter is a Router that conditionally re-routes or discards a message (and its context).
|
||||||
|
type FilteringRouter struct {
|
||||||
|
subrouter Router
|
||||||
|
filterFn func(string, map[string]interface{})bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (router *FilteringRouter) Route(message string, context map[string]interface{}) error {
|
||||||
|
if nil == router {
|
||||||
|
return errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
if router.filterFn(message, context) {
|
||||||
|
return router.subrouter.Route(message, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestFilteringRouterJustCreated(t *testing.T) {
|
||||||
|
|
||||||
|
randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
|
||||||
|
|
||||||
|
|
||||||
|
router := NewFilteringRouter(NewDiscardingRouter(), func(string, map[string]interface{}) bool {
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if nil == router {
|
||||||
|
t.Errorf("After trying to create a filtering router, expected it to be not nil, but was: %v", router)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message := fmt.Sprintf("%x", randomness.Int63n(9999999999))
|
||||||
|
|
||||||
|
context := make(map[string]interface{})
|
||||||
|
limit := randomness.Int63n(30)
|
||||||
|
for i:=int64(0); i<limit; i++ {
|
||||||
|
context[ fmt.Sprintf("%x", randomness.Int63n(1000*limit)) ] = fmt.Sprintf("%x", randomness.Int63n(999999999999999))
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Route(message, context) // Just make sure it doesn't panic or deadlok, by calling this.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestFilteringRouterJustFilterParameters(t *testing.T) {
|
||||||
|
|
||||||
|
randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
|
||||||
|
|
||||||
|
|
||||||
|
var filterMessage string
|
||||||
|
var filterContext map[string] interface{}
|
||||||
|
var filterResult = false
|
||||||
|
|
||||||
|
router := NewFilteringRouter(NewDiscardingRouter(), func(message string, context map[string]interface{}) bool {
|
||||||
|
filterMessage = message
|
||||||
|
filterContext = context
|
||||||
|
|
||||||
|
filterResult = !filterResult
|
||||||
|
|
||||||
|
return filterResult
|
||||||
|
})
|
||||||
|
|
||||||
|
const NUM_TESTS = 20
|
||||||
|
TLoop: for testNumber:=0; testNumber<NUM_TESTS; testNumber++ {
|
||||||
|
msg := fmt.Sprintf("string with random numbers: %d", randomness.Int63n(9999999999))
|
||||||
|
|
||||||
|
ctx := make(map[string]interface{})
|
||||||
|
lenCtx := randomness.Int63n(30)
|
||||||
|
for i:=int64(0); i<lenCtx; i++ {
|
||||||
|
ctx[ fmt.Sprintf("%x", randomness.Int63n(1000*lenCtx)) ] = fmt.Sprintf("%x", randomness.Int63n(999999999999999))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
router.Route(msg, ctx)
|
||||||
|
|
||||||
|
if expected, actual := msg, filterMessage; expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected message passed to filter func to be %q, but actually was %q.", testNumber, expected, actual)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := len(ctx), len(filterContext); expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected context passed to filter func to be len %d, but actually was %d.", testNumber, expected, actual)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
for expectedKey, expectedValue := range ctx {
|
||||||
|
if _, ok := filterContext[expectedKey]; !ok {
|
||||||
|
t.Errorf("For test #%d, expected context passed to filter func to have key %q, but didn't.", testNumber, expectedKey)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := expectedValue.(string), filterContext[expectedKey].(string); expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected context passed to filter func for key %q to have value %q, but actuall had %q.", testNumber, expectedKey, expected, actual)
|
||||||
|
continue TLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
type Flogger interface {
|
||||||
|
Debug(...interface{})
|
||||||
|
Debugf(string, ...interface{})
|
||||||
|
Debugln(...interface{})
|
||||||
|
|
||||||
|
Error(...interface{})
|
||||||
|
Errorf(string, ...interface{})
|
||||||
|
Errorfe(error, string, ...interface{})
|
||||||
|
Errorln(...interface{})
|
||||||
|
|
||||||
|
Fatal(...interface{})
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
Fatalln(...interface{})
|
||||||
|
|
||||||
|
Panic(...interface{})
|
||||||
|
Panicf(string, ...interface{})
|
||||||
|
Panicfv(interface{}, string, ...interface{})
|
||||||
|
Panicln(...interface{})
|
||||||
|
|
||||||
|
Print(...interface{})
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
Println(...interface{})
|
||||||
|
|
||||||
|
Trace(...interface{})
|
||||||
|
Tracef(string, ...interface{})
|
||||||
|
Traceln(...interface{})
|
||||||
|
|
||||||
|
Warn(...interface{})
|
||||||
|
Warnf(string, ...interface{})
|
||||||
|
Warnln(...interface{})
|
||||||
|
|
||||||
|
With(...interface{}) Flogger
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
type internalFlogger struct {
|
||||||
|
context map[string]interface{}
|
||||||
|
router Router
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// New returns an initialized Flogger.
|
||||||
|
func New(router Router, cascade ...interface{}) Flogger {
|
||||||
|
context := newContext(cascade...)
|
||||||
|
|
||||||
|
flogger := internalFlogger{
|
||||||
|
context:context,
|
||||||
|
router:router,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &flogger
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) route(message string, moreContext map[string]interface{}) error {
|
||||||
|
if nil == flogger {
|
||||||
|
return errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
context := newContext(flogger.context, moreContext)
|
||||||
|
|
||||||
|
router := flogger.router
|
||||||
|
if nil == router {
|
||||||
|
return errNilRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
return router.Route(message, context)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
debugContext = map[string]interface{}{
|
||||||
|
"~type":"debug",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Debug(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprint(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, debugContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Debugf(format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, debugContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Debugln(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintln(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, debugContext)
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
errorContext = map[string]interface{}{
|
||||||
|
"~type":"error",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Error(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprint(v...)
|
||||||
|
|
||||||
|
errCtx := newErrorContext(errorContext, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, errCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Errorf(format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
errCtx := newErrorContext(errorContext, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, errCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Errorfe(err error, format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
context := map[string]interface{}{}
|
||||||
|
for k,v := range errorContext {
|
||||||
|
context[k] = v
|
||||||
|
}
|
||||||
|
context["~error"] = err
|
||||||
|
|
||||||
|
flogger.route(msg, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Errorln(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintln(v...)
|
||||||
|
|
||||||
|
errCtx := newErrorContext(errorContext, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, errCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func newErrorContext(baseContext map[string]interface{}, v ...interface{}) map[string]interface{} {
|
||||||
|
|
||||||
|
|
||||||
|
// Collect any errors.
|
||||||
|
errs := []error{}
|
||||||
|
for _, datum := range v {
|
||||||
|
if err, ok := datum.(error); ok {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if 0 == len(errs) {
|
||||||
|
return baseContext
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Copy the base context.
|
||||||
|
context := map[string]interface{}{}
|
||||||
|
for k,v := range baseContext {
|
||||||
|
context[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Put the collected errors in this new context.
|
||||||
|
context["~errors"] = errs
|
||||||
|
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
fatalContext = map[string]interface{}{
|
||||||
|
"~type":"fatal",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Fatal(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprint(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, fatalContext)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Fatalf(format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, fatalContext)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Fatalln(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintln(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, fatalContext)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
panicContext = map[string]interface{}{
|
||||||
|
"~type":"panic",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Panic(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprint(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, panicContext)
|
||||||
|
panic(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Panicf(format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, panicContext)
|
||||||
|
panic(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Panicfv(panicValue interface{}, format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, panicContext)
|
||||||
|
panic(panicValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Panicln(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintln(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, panicContext)
|
||||||
|
panic(msg)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
printContext = map[string]interface{}{
|
||||||
|
"~type":"print",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Print(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprint(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, printContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Printf(format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, printContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Println(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintln(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, printContext)
|
||||||
|
}
|
|
@ -0,0 +1,352 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
|
||||||
|
flogger := New(NewDiscardingRouter())
|
||||||
|
if nil == flogger {
|
||||||
|
t.Errorf("Expected created flogger to not be nil, but was: %v", flogger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestNewForContext(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Cascade []interface{}
|
||||||
|
Expected map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{},
|
||||||
|
Expected: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
"apple",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"apple",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"banana",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
"cherry",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"cherry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"fig":"THIS SHOULD BE REPLACED",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"fig":"THIS SHOULD REMAIN",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"fig":"THIS SHOULD REMAIN",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestLoop:
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
|
||||||
|
flogger := New(NewDiscardingRouter(), test.Cascade...)
|
||||||
|
|
||||||
|
context := flogger.(*internalFlogger).context
|
||||||
|
|
||||||
|
if expected, actual := len(test.Expected), len(context); expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected length to be %d but actually was %d.", testNumber, expected, actual)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
for expectedKey, expectedValue := range test.Expected {
|
||||||
|
if _, ok := context[expectedKey]; !ok {
|
||||||
|
t.Errorf("For test #%d, expected key %q to be in resulting context but wasn't.", testNumber, expectedKey)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := expectedValue, context[expectedKey]; expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected value for key %q to be %q in resulting context, but was actually %q.", testNumber, expectedKey, expected, actual)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestInternalFloggerRouteNilReceiver(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Message string
|
||||||
|
MoreContext map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Message: "",
|
||||||
|
MoreContext: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: "",
|
||||||
|
MoreContext: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: "",
|
||||||
|
MoreContext: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": '3',
|
||||||
|
"kiwi": 4.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Message: "Hello world!",
|
||||||
|
MoreContext: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: "Hello world!",
|
||||||
|
MoreContext: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: "Hello world!",
|
||||||
|
MoreContext: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": '3',
|
||||||
|
"kiwi": 4.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Message: " ",
|
||||||
|
MoreContext: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: " ",
|
||||||
|
MoreContext: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: " ",
|
||||||
|
MoreContext: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": '3',
|
||||||
|
"kiwi": 4.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Message: "one\ntwo\tthree\r\n",
|
||||||
|
MoreContext: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: "one\ntwo\tthree\r\n",
|
||||||
|
MoreContext: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Message: "one\ntwo\tthree\r\n",
|
||||||
|
MoreContext: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": '3',
|
||||||
|
"kiwi": 4.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
|
||||||
|
var flogger *internalFlogger = nil
|
||||||
|
|
||||||
|
err := flogger.route(test.Message, test.MoreContext)
|
||||||
|
if nil == err {
|
||||||
|
t.Errorf("For test #%d, expected an error, but did not actually get one: %v", testNumber, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if expected, actual := errNilReceiver, err; expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected an error (%T) %q, but actually got (%T) %q", testNumber, expected, expected, actual, actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestInternalFloggerRouteNilRouter(t *testing.T) {
|
||||||
|
|
||||||
|
moreContexts := []map[string]interface{}{
|
||||||
|
nil,
|
||||||
|
map[string]interface{}{},
|
||||||
|
map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": '3',
|
||||||
|
"kiwi": 4.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
messages := []string{
|
||||||
|
"",
|
||||||
|
"Hello world!",
|
||||||
|
" ",
|
||||||
|
"one\ntwo\tthree\r\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Context map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Context: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Context: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Context: map[string]interface{}{
|
||||||
|
"apple": "one",
|
||||||
|
"banana": 2,
|
||||||
|
"cherry": '3',
|
||||||
|
"kiwi": 4.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
var flogger internalFlogger
|
||||||
|
|
||||||
|
flogger.context = test.Context
|
||||||
|
flogger.router = nil
|
||||||
|
|
||||||
|
for messageNumber, message := range messages {
|
||||||
|
for moreContextNumber, moreContext := range moreContexts {
|
||||||
|
err := flogger.route(message, moreContext)
|
||||||
|
if nil == err {
|
||||||
|
t.Errorf("For test #%d and message #%d and more context #%d, expected an error, but did not actually get one: %v", testNumber, messageNumber, moreContextNumber, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if expected, actual := errNilRouter, err; expected != actual {
|
||||||
|
t.Errorf("For test #%d and message #%d and more context #%d, expected an error (%T) %q, but actually got (%T) %q", testNumber, messageNumber, moreContextNumber, expected, expected, actual, actual)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
traceContext = map[string]interface{}{
|
||||||
|
"~type":"trace",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Trace(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprint(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, traceContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Tracef(format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, traceContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Traceln(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintln(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, traceContext)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
warnContext = map[string]interface{}{
|
||||||
|
"~type":"warn",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Warn(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprint(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, warnContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Warnf(format string, v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
flogger.route(msg, warnContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) Warnln(v ...interface{}) {
|
||||||
|
|
||||||
|
msg := fmt.Sprintln(v...)
|
||||||
|
|
||||||
|
flogger.route(msg, warnContext)
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
func (flogger *internalFlogger) With(cascade ...interface{}) Flogger {
|
||||||
|
|
||||||
|
var x interface{} = flogger.context
|
||||||
|
var xs []interface{} = []interface{}{x}
|
||||||
|
|
||||||
|
newCascade := append(xs, cascade...)
|
||||||
|
|
||||||
|
newFlogger := New(flogger.router, newCascade...)
|
||||||
|
|
||||||
|
return newFlogger
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestWith(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct{
|
||||||
|
Cascade1 []interface{}
|
||||||
|
Cascade2 []interface{}
|
||||||
|
Expected map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{},
|
||||||
|
Cascade2: []interface{}{},
|
||||||
|
Expected: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
"apple",
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"apple",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
"apple",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"apple",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
"apple",
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
"apple",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"apple",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
"apple",
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
"banana",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"banana",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
"cherry",
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"cherry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
"cherry",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"cherry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
"apple",
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
"banana",
|
||||||
|
"cherry",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"cherry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
"apple",
|
||||||
|
"banana",
|
||||||
|
"cherry",
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"text":"cherry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Cascade1: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"fig":"THIS SHOULD BE REPLACED",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Cascade2: []interface{}{
|
||||||
|
map[string]string{
|
||||||
|
"fig":"THIS SHOULD REMAIN",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expected: map[string]interface{}{
|
||||||
|
"apple":"one",
|
||||||
|
"banana":"two",
|
||||||
|
"cherry":"three",
|
||||||
|
"fig":"THIS SHOULD REMAIN",
|
||||||
|
"kiwi":"four",
|
||||||
|
"watermelon":"five",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TestLoop:
|
||||||
|
for testNumber, test := range tests {
|
||||||
|
|
||||||
|
flogger1 := New(NewDiscardingRouter(), test.Cascade1...)
|
||||||
|
|
||||||
|
flogger2 := flogger1.With(test.Cascade2...)
|
||||||
|
|
||||||
|
context := flogger2.(*internalFlogger).context
|
||||||
|
|
||||||
|
if expected, actual := len(test.Expected), len(context); expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected length to be %d but actually was %d.", testNumber, expected, actual)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
for expectedKey, expectedValue := range test.Expected {
|
||||||
|
if _, ok := context[expectedKey]; !ok {
|
||||||
|
t.Errorf("For test #%d, expected key %q to be in resulting context but wasn't.", testNumber, expectedKey)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected, actual := expectedValue, context[expectedKey]; expected != actual {
|
||||||
|
t.Errorf("For test #%d, expected value for key %q to be %q in resulting context, but was actually %q.", testNumber, expectedKey, expected, actual)
|
||||||
|
continue TestLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,15 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type internalLogger struct {
|
type internalLogger struct {
|
||||||
prefix string
|
prefix string
|
||||||
style string
|
style string
|
||||||
|
|
||||||
begin time.Time
|
|
||||||
|
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
|
|
||||||
mutedAlert bool
|
|
||||||
mutedDebug bool
|
mutedDebug bool
|
||||||
mutedError bool
|
mutedError bool
|
||||||
mutedFatal bool
|
mutedFatal bool
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (receiver internalLogger) AlertMuted() bool {
|
|
||||||
return receiver.mutedAlert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (receiver internalLogger) Alert(a ...interface{}) error {
|
|
||||||
s := fmt.Sprint(a...)
|
|
||||||
|
|
||||||
return receiver.Alertf("%s", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (receiver internalLogger) Alertf(format string, a ...interface{}) error {
|
|
||||||
err := fmt.Errorf(format, a...)
|
|
||||||
|
|
||||||
if receiver.AlertMuted() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var writer io.Writer = receiver.writer
|
|
||||||
if nil == writer {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var newformat string
|
|
||||||
{
|
|
||||||
var buffer strings.Builder
|
|
||||||
|
|
||||||
switch receiver.style{
|
|
||||||
case"color":
|
|
||||||
buffer.WriteString("☣️☣️☣️☣️☣️ ")
|
|
||||||
buffer.WriteString("\x1b[48;2;0;43;54m")
|
|
||||||
buffer.WriteString("\x1b[38;2;220;50;47m")
|
|
||||||
case "":
|
|
||||||
buffer.WriteString("[ALERT] ")
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
|
||||||
|
|
||||||
switch receiver.style {
|
|
||||||
case "color":
|
|
||||||
buffer.WriteString("\x1b[0m")
|
|
||||||
buffer.WriteString(" ☣️☣️☣️☣️☣️")
|
|
||||||
buffer.WriteRune('\n')
|
|
||||||
case "":
|
|
||||||
buffer.WriteRune('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
newformat = buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(receiver.writer, newformat, a...)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInternalLogger_Alertf(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct{
|
|
||||||
Format string
|
|
||||||
Array []interface{}
|
|
||||||
Expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Format: "",
|
|
||||||
Array: []interface{}(nil),
|
|
||||||
Expected: "[ALERT] \n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Format: "",
|
|
||||||
Array: []interface{}{},
|
|
||||||
Expected: "[ALERT] \n",
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
Format: "hello world",
|
|
||||||
Array: []interface{}(nil),
|
|
||||||
Expected: "[ALERT] hello world\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Format: "hello world",
|
|
||||||
Array: []interface{}{},
|
|
||||||
Expected: "[ALERT] hello world\n",
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
Format: "hello %s",
|
|
||||||
Array: []interface{}{"Joe"},
|
|
||||||
Expected: "[ALERT] hello Joe\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Format: "hello %s %s",
|
|
||||||
Array: []interface{}{"Joe", "Blow"},
|
|
||||||
Expected: "[ALERT] hello Joe Blow\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for testNumber, test := range tests {
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
logger := NewLogger(&buffer)
|
|
||||||
|
|
||||||
logger.Alertf(test.Format, test.Array...)
|
|
||||||
|
|
||||||
if expected, actual := test.Expected, buffer.String(); expected != actual {
|
|
||||||
t.Errorf("For tst #%d, the actual result is not what was expected.", testNumber)
|
|
||||||
t.Logf("EXPECTED: %q", expected)
|
|
||||||
t.Logf("ACTUAL: %q", actual)
|
|
||||||
t.Logf("ACTUAL: %s", actual)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (receiver internalLogger) Begin(a ...interface{}) Logger {
|
|
||||||
|
|
||||||
var funcName string = "<([-UNKNOWN-])>"
|
|
||||||
{
|
|
||||||
pc, _, _, ok := runtime.Caller(1)
|
|
||||||
if ok {
|
|
||||||
fn := runtime.FuncForPC(pc)
|
|
||||||
funcName = fn.Name()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := receiver.Prefix(funcName)
|
|
||||||
switch casted := logger.(type) {
|
|
||||||
case *internalLogger:
|
|
||||||
casted.begin = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
a = append([]interface{}{"BEGIN "}, a...)
|
|
||||||
|
|
||||||
logger.Debug(a...)
|
|
||||||
|
|
||||||
return logger
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Debugf(format string, a ...interface{}) {
|
||||||
buffer.WriteString("[debug] ")
|
buffer.WriteString("[debug] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
buffer.WriteString(format)
|
||||||
|
|
||||||
switch receiver.style {
|
switch receiver.style {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (receiver internalLogger) End(a ...interface{}) {
|
|
||||||
diff := time.Now().Sub(receiver.begin)
|
|
||||||
|
|
||||||
msg := fmt.Sprintf(" δt=%s", diff)
|
|
||||||
|
|
||||||
a = append([]interface{}{"END "}, a...)
|
|
||||||
a = append(a, msg)
|
|
||||||
|
|
||||||
receiver.Debug(a...)
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -40,7 +40,6 @@ func (receiver internalLogger) Errorf(format string, a ...interface{}) error {
|
||||||
buffer.WriteString("[ERROR] ")
|
buffer.WriteString("[ERROR] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
buffer.WriteString(format)
|
||||||
|
|
||||||
switch receiver.style {
|
switch receiver.style {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -41,7 +41,6 @@ func (receiver internalLogger) Fatalf(format string, a ...interface{}) {
|
||||||
buffer.WriteString("[PANIC] ")
|
buffer.WriteString("[PANIC] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
buffer.WriteString(format)
|
||||||
|
|
||||||
switch receiver.style {
|
switch receiver.style {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Highlightf(format string, a ...interface{}) {
|
||||||
buffer.WriteString("[HIGHLIGHT] ")
|
buffer.WriteString("[HIGHLIGHT] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
buffer.WriteString(format)
|
||||||
|
|
||||||
switch receiver.style {
|
switch receiver.style {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Informf(format string, a ...interface{}) {
|
||||||
buffer.WriteString("[inform] ")
|
buffer.WriteString("[inform] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
buffer.WriteString(format)
|
||||||
|
|
||||||
switch receiver.style {
|
switch receiver.style {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
func (receiver *internalLogger) MuteAlert() {
|
|
||||||
receiver.mutedDebug = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (receiver *internalLogger) MuteDebug() {
|
func (receiver *internalLogger) MuteDebug() {
|
||||||
receiver.mutedDebug = true
|
receiver.mutedDebug = true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -40,7 +40,6 @@ func (receiver internalLogger) Panicf(format string, a ...interface{}) {
|
||||||
buffer.WriteString("[PANIC] ")
|
buffer.WriteString("[PANIC] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
buffer.WriteString(format)
|
||||||
|
|
||||||
switch receiver.style {
|
switch receiver.style {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -17,7 +17,7 @@ func (receiver internalLogger) Prefix(newprefix ...string) Logger {
|
||||||
prefix := buffer.String()
|
prefix := buffer.String()
|
||||||
|
|
||||||
var logger internalLogger = receiver
|
var logger internalLogger = receiver
|
||||||
logger.prefix = prefix
|
logger.prefix += prefix
|
||||||
|
|
||||||
return &logger
|
return &logger
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -66,7 +66,7 @@ func TestLoggerPrefix(t *testing.T) {
|
||||||
|
|
||||||
newInternalLogger, casted := newLogger.(*internalLogger)
|
newInternalLogger, casted := newLogger.(*internalLogger)
|
||||||
if !casted {
|
if !casted {
|
||||||
t.Errorf("For test #%d, could not cast to log.internalLogger.", testNumber)
|
t.Errorf("For test #%d, could not cast to flog.internalLogger.", testNumber)
|
||||||
t.Logf("TYPE: %T", newLogger)
|
t.Logf("TYPE: %T", newLogger)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -101,20 +101,14 @@ func TestLoggerPrefix_inform(t *testing.T) {
|
||||||
log.Debug("hello DEBUG")
|
log.Debug("hello DEBUG")
|
||||||
log.Trace("hello TRACE")
|
log.Trace("hello TRACE")
|
||||||
|
|
||||||
|
const expected = "[PANIC] hello PANIC" + "\n" +
|
||||||
log = log.Prefix("four")
|
|
||||||
log.Highlight("new prefix FOUR")
|
|
||||||
|
|
||||||
|
|
||||||
const expected = "[PANIC] one: two: three: hello PANIC" + "\n" +
|
|
||||||
"RECOVER:hello PANIC" + "\n" +
|
"RECOVER:hello PANIC" + "\n" +
|
||||||
"[inform] one: two: three: hello INFORM" + "\n" +
|
"[inform] hello INFORM" + "\n" +
|
||||||
"[HIGHLIGHT] one: two: three: hello HIGHLIGHT" + "\n" +
|
"[HIGHLIGHT] hello HIGHLIGHT" + "\n" +
|
||||||
"[ERROR] one: two: three: hello ERROR" + "\n" +
|
"[ERROR] hello ERROR" + "\n" +
|
||||||
"[warn] one: two: three: hello WARN" + "\n" +
|
"[warn] hello WARN" + "\n" +
|
||||||
"[debug] one: two: three: hello DEBUG" + "\n" +
|
"[debug] hello DEBUG" + "\n" +
|
||||||
"[trace] one: two: three: hello TRACE" + "\n" +
|
"[trace] hello TRACE" + "\n"
|
||||||
"[HIGHLIGHT] one: two: three: four: new prefix FOUR" + "\n"
|
|
||||||
|
|
||||||
if actual := buffer.String(); expected != actual {
|
if actual := buffer.String(); expected != actual {
|
||||||
t.Error("The actual logs is not what was expected.")
|
t.Error("The actual logs is not what was expected.")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Tracef(format string, a ...interface{}) {
|
||||||
buffer.WriteString("[trace] ")
|
buffer.WriteString("[trace] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
buffer.WriteString(format)
|
||||||
|
|
||||||
switch receiver.style {
|
switch receiver.style {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
func (receiver *internalLogger) UnmuteAlert() {
|
|
||||||
receiver.mutedAlert = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (receiver *internalLogger) UnmuteDebug() {
|
func (receiver *internalLogger) UnmuteDebug() {
|
||||||
receiver.mutedDebug = false
|
receiver.mutedDebug = false
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Warnf(format string, a ...interface{}) {
|
||||||
buffer.WriteString("[warn] ")
|
buffer.WriteString("[warn] ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(receiver.prefix)
|
|
||||||
buffer.WriteString(format)
|
buffer.WriteString(format)
|
||||||
|
|
||||||
switch receiver.style {
|
switch receiver.style {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
12
logger.go
12
logger.go
|
@ -1,22 +1,12 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
Alert(...interface{}) error
|
|
||||||
Alertf(string, ...interface{}) error
|
|
||||||
MuteAlert()
|
|
||||||
UnmuteAlert()
|
|
||||||
AlertMuted() bool
|
|
||||||
|
|
||||||
Begin(...interface{}) Logger
|
|
||||||
|
|
||||||
Debug(...interface{})
|
Debug(...interface{})
|
||||||
Debugf(string, ...interface{})
|
Debugf(string, ...interface{})
|
||||||
MuteDebug()
|
MuteDebug()
|
||||||
UnmuteDebug()
|
UnmuteDebug()
|
||||||
DebugMuted() bool
|
DebugMuted() bool
|
||||||
|
|
||||||
End(...interface{})
|
|
||||||
|
|
||||||
Error(...interface{}) error
|
Error(...interface{}) error
|
||||||
Errorf(string, ...interface{}) error
|
Errorf(string, ...interface{}) error
|
||||||
MuteError()
|
MuteError()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package log
|
package flog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
// NewMappingRouter returns an initialized MappingRouter.
|
||||||
|
func NewMappingRouter(subrouter Router, fn func(string, map[string]interface{})(string, map[string]interface{})) *MappingRouter {
|
||||||
|
router := MappingRouter{
|
||||||
|
subrouter:subrouter,
|
||||||
|
fn:fn,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &router
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MappingRouter is a Router that can modify the message and context before
|
||||||
|
// re-routing it to its sub-router.
|
||||||
|
//
|
||||||
|
// Conceptually this is somewhat similar to "map" functions in functional
|
||||||
|
// programming.
|
||||||
|
type MappingRouter struct {
|
||||||
|
subrouter Router
|
||||||
|
fn func(string, map[string]interface{})(string, map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (router *MappingRouter) Route(message string, context map[string]interface{}) error {
|
||||||
|
if nil == router {
|
||||||
|
return errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
return router.subrouter.Route(router.fn(message, context))
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
|
||||||
|
// NewNonBlockingRouter returns an initialized NonBlockingRouter.
|
||||||
|
func NewNonBlockingRouter(subrouter Router) *NonBlockingRouter {
|
||||||
|
router := NonBlockingRouter{
|
||||||
|
subrouter:subrouter,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &router
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NonBlockingRouter is a Router when its Route method is call its does not
|
||||||
|
// block and dealing with the routing in parallel.
|
||||||
|
//
|
||||||
|
// Note that this means that if the application could terminate before this
|
||||||
|
// completes.
|
||||||
|
type NonBlockingRouter struct {
|
||||||
|
subrouter Router
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (router *NonBlockingRouter) Route(message string, context map[string]interface{}) error {
|
||||||
|
if nil == router {
|
||||||
|
return errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
router.subrouter.Route(message, context)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package flog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrettyWritingRouter returns an initialized PrettyWritingRouter
|
||||||
|
func NewPrettyWritingRouter(writer io.Writer) *PrettyWritingRouter {
|
||||||
|
router := PrettyWritingRouter{
|
||||||
|
writer:writer,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &router
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrettyWritingRouter is a router that writes a pretty version of
|
||||||
|
// the log it was give (including COLORS!) to the writer it was
|
||||||
|
// given when it was created.
|
||||||
|
//
|
||||||
|
// A PrettyWritingRouter is appropriate for a development (i.e., "DEV")
|
||||||
|
// deployment enviornment. (And probably not appropriate a production
|
||||||
|
// (i.e., "PROD") deployment environment.)
|
||||||
|
type PrettyWritingRouter struct {
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (router *PrettyWritingRouter) Route(message string, context map[string]interface{}) error {
|
||||||
|
if nil == router {
|
||||||
|
return errNilReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
const STYLE_FATAL = "\x1b[40;33;1m" // BG BLACK, FG YELLOW, BOLD
|
||||||
|
const STYLE_PANIC = "\x1b[40;31;1m" // BG BLACK, FG RED, BOLD
|
||||||
|
const STYLE_ERROR = "\x1b[41;37;1m" // BG RED, FG WHITE, BOLD
|
||||||
|
const STYLE_WARNING = "\x1b[43;37;1m" // BG YELLOW, FG WHITE, BOLD
|
||||||
|
const STYLE_NOTICE = "\x1b[42;33;1m" // BG GREEN, FG YELLOW, BOLD
|
||||||
|
const STYLE_TIMESTAMP = "\x1b[2m" // FAINT
|
||||||
|
const STYLE_MESSAGE = "\x1b[44;37;1m" // BG BLUE, FG WHITE, BOLD
|
||||||
|
const STYLE_DEFAULT = "\033[95m" // HEADER
|
||||||
|
const STYLE_RESET = "\033[0m" // RESET
|
||||||
|
const STYLE_CALLTRACE = "\033[40;36;1m" // BG BLACK, FG CYAN, BOLD
|
||||||
|
|
||||||
|
str := ""
|
||||||
|
|
||||||
|
if nil != context {
|
||||||
|
if _, ok := context["~fatal"]; ok {
|
||||||
|
str = fmt.Sprintf("%s 💀 💀 💀 💀 💀 %s\t%s", STYLE_FATAL, STYLE_RESET, str)
|
||||||
|
} else if _, ok := context["~panic"]; ok {
|
||||||
|
str = fmt.Sprintf("%s ☠ ☠ ☠ ☠ ☠ %s\t%s", STYLE_PANIC, STYLE_RESET, str)
|
||||||
|
} else if _, ok := context["~error"]; ok {
|
||||||
|
str = fmt.Sprintf("%s 😨 😨 😨 😨 😨 %s\t%s", STYLE_ERROR, STYLE_RESET, str)
|
||||||
|
} else if _, ok := context["~warn"]; ok {
|
||||||
|
str = fmt.Sprintf("%s 😟 😟 😟 😟 😟 %s\t%s", STYLE_WARNING, STYLE_RESET, str)
|
||||||
|
} else if _, ok := context["~print"]; ok {
|
||||||
|
str = fmt.Sprintf("%s 😮 😮 😮 😮 😮 %s\t%s", STYLE_NOTICE, STYLE_RESET, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str = fmt.Sprintf("%s%s%s%s\t(%s%v%s)", str, STYLE_MESSAGE, message, STYLE_RESET, STYLE_TIMESTAMP, time.Now(), STYLE_RESET)
|
||||||
|
|
||||||
|
// If we have an error, then get the error.Error() into the log too.
|
||||||
|
if errorFieldValue, ok := context["~error"]; ok {
|
||||||
|
if err, ok := errorFieldValue.(error); ok {
|
||||||
|
context["~~error"] = fmt.Sprintf("%s, {{%T}}", err.Error(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//@TODO: This is a potential heavy operation. Is there a better way
|
||||||
|
// to get the ultimate result this is trying to archive?
|
||||||
|
//
|
||||||
|
sortedKeys := make([]string, len(context))
|
||||||
|
i := 0
|
||||||
|
for key, _ := range context {
|
||||||
|
sortedKeys[i] = key
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(sortedKeys)
|
||||||
|
|
||||||
|
for _, key := range sortedKeys {
|
||||||
|
|
||||||
|
value := context[key]
|
||||||
|
|
||||||
|
style := STYLE_DEFAULT
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "~fatal", "~fatals":
|
||||||
|
style = STYLE_FATAL
|
||||||
|
case "~panic", "~panics":
|
||||||
|
style = STYLE_PANIC
|
||||||
|
case "~error", "~errors", "~~error":
|
||||||
|
style = STYLE_ERROR
|
||||||
|
case "~warning", "~warnings":
|
||||||
|
style = STYLE_WARNING
|
||||||
|
case "~print", "~prints":
|
||||||
|
style = STYLE_NOTICE
|
||||||
|
}
|
||||||
|
|
||||||
|
str = fmt.Sprintf("%s\t%s%s%s=%s%#v%s", str, style, key, STYLE_RESET, style, value, STYLE_RESET)
|
||||||
|
}
|
||||||
|
|
||||||
|
if trace := calltrace(); nil != trace {
|
||||||
|
str = fmt.Sprintf("%s\t⟨⟨", str)
|
||||||
|
for _, s := range trace {
|
||||||
|
str = fmt.Sprintf("%s %s%s%s,", str, STYLE_CALLTRACE, s, STYLE_RESET)
|
||||||
|
}
|
||||||
|
str = fmt.Sprintf("%s ⟩⟩", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(router.writer, str)
|
||||||
|
|
||||||
|
//@TODO: Should this be checking for errors from fmt.Fprintln()?
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue