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
|
||||
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.
|
||||
|
||||
(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
|
||||
router := log.NewPrettyWritingRouter(os.Stdout)
|
||||
In addition to the builtin routers that go-flog comes with, other external
|
||||
routers are also available. These include:
|
||||
|
||||
logger := log.New(router)
|
||||
```
|
||||
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)
|
||||
}
|
||||
```
|
||||
* [go-slackchannelrouter](https://github.com/reiver/go-slackchannelrouter) Makes it so log messages get posted to a [Slack](https://slack.com/) channel.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"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 (
|
||||
"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 (
|
||||
|
@ -8,4 +8,5 @@ import (
|
|||
|
||||
var (
|
||||
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 (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type internalLogger struct {
|
||||
prefix string
|
||||
style string
|
||||
|
||||
begin time.Time
|
||||
|
||||
writer io.Writer
|
||||
|
||||
mutedAlert bool
|
||||
mutedDebug bool
|
||||
mutedError 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 (
|
||||
"fmt"
|
||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Debugf(format string, a ...interface{}) {
|
|||
buffer.WriteString("[debug] ")
|
||||
}
|
||||
|
||||
buffer.WriteString(receiver.prefix)
|
||||
buffer.WriteString(format)
|
||||
|
||||
switch receiver.style {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"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 (
|
||||
"fmt"
|
||||
|
@ -40,7 +40,6 @@ func (receiver internalLogger) Errorf(format string, a ...interface{}) error {
|
|||
buffer.WriteString("[ERROR] ")
|
||||
}
|
||||
|
||||
buffer.WriteString(receiver.prefix)
|
||||
buffer.WriteString(format)
|
||||
|
||||
switch receiver.style {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -41,7 +41,6 @@ func (receiver internalLogger) Fatalf(format string, a ...interface{}) {
|
|||
buffer.WriteString("[PANIC] ")
|
||||
}
|
||||
|
||||
buffer.WriteString(receiver.prefix)
|
||||
buffer.WriteString(format)
|
||||
|
||||
switch receiver.style {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Highlightf(format string, a ...interface{}) {
|
|||
buffer.WriteString("[HIGHLIGHT] ")
|
||||
}
|
||||
|
||||
buffer.WriteString(receiver.prefix)
|
||||
buffer.WriteString(format)
|
||||
|
||||
switch receiver.style {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Informf(format string, a ...interface{}) {
|
|||
buffer.WriteString("[inform] ")
|
||||
}
|
||||
|
||||
buffer.WriteString(receiver.prefix)
|
||||
buffer.WriteString(format)
|
||||
|
||||
switch receiver.style {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
package log
|
||||
|
||||
func (receiver *internalLogger) MuteAlert() {
|
||||
receiver.mutedDebug = true
|
||||
}
|
||||
package flog
|
||||
|
||||
func (receiver *internalLogger) MuteDebug() {
|
||||
receiver.mutedDebug = true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -40,7 +40,6 @@ func (receiver internalLogger) Panicf(format string, a ...interface{}) {
|
|||
buffer.WriteString("[PANIC] ")
|
||||
}
|
||||
|
||||
buffer.WriteString(receiver.prefix)
|
||||
buffer.WriteString(format)
|
||||
|
||||
switch receiver.style {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
@ -17,7 +17,7 @@ func (receiver internalLogger) Prefix(newprefix ...string) Logger {
|
|||
prefix := buffer.String()
|
||||
|
||||
var logger internalLogger = receiver
|
||||
logger.prefix = prefix
|
||||
logger.prefix += prefix
|
||||
|
||||
return &logger
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -66,7 +66,7 @@ func TestLoggerPrefix(t *testing.T) {
|
|||
|
||||
newInternalLogger, casted := newLogger.(*internalLogger)
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
@ -101,20 +101,14 @@ func TestLoggerPrefix_inform(t *testing.T) {
|
|||
log.Debug("hello DEBUG")
|
||||
log.Trace("hello TRACE")
|
||||
|
||||
|
||||
log = log.Prefix("four")
|
||||
log.Highlight("new prefix FOUR")
|
||||
|
||||
|
||||
const expected = "[PANIC] one: two: three: hello PANIC" + "\n" +
|
||||
"RECOVER:hello PANIC" + "\n" +
|
||||
"[inform] one: two: three: hello INFORM" + "\n" +
|
||||
"[HIGHLIGHT] one: two: three: hello HIGHLIGHT" + "\n" +
|
||||
"[ERROR] one: two: three: hello ERROR" + "\n" +
|
||||
"[warn] one: two: three: hello WARN" + "\n" +
|
||||
"[debug] one: two: three: hello DEBUG" + "\n" +
|
||||
"[trace] one: two: three: hello TRACE" + "\n" +
|
||||
"[HIGHLIGHT] one: two: three: four: new prefix FOUR" + "\n"
|
||||
const expected = "[PANIC] hello PANIC" + "\n" +
|
||||
"RECOVER:hello PANIC" + "\n" +
|
||||
"[inform] hello INFORM" + "\n" +
|
||||
"[HIGHLIGHT] hello HIGHLIGHT" + "\n" +
|
||||
"[ERROR] hello ERROR" + "\n" +
|
||||
"[warn] hello WARN" + "\n" +
|
||||
"[debug] hello DEBUG" + "\n" +
|
||||
"[trace] hello TRACE" + "\n"
|
||||
|
||||
if actual := buffer.String(); expected != actual {
|
||||
t.Error("The actual logs is not what was expected.")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Tracef(format string, a ...interface{}) {
|
|||
buffer.WriteString("[trace] ")
|
||||
}
|
||||
|
||||
buffer.WriteString(receiver.prefix)
|
||||
buffer.WriteString(format)
|
||||
|
||||
switch receiver.style {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
package log
|
||||
|
||||
func (receiver *internalLogger) UnmuteAlert() {
|
||||
receiver.mutedAlert = false
|
||||
}
|
||||
package flog
|
||||
|
||||
func (receiver *internalLogger) UnmuteDebug() {
|
||||
receiver.mutedDebug = false
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -45,7 +45,6 @@ func (receiver internalLogger) Warnf(format string, a ...interface{}) {
|
|||
buffer.WriteString("[warn] ")
|
||||
}
|
||||
|
||||
buffer.WriteString(receiver.prefix)
|
||||
buffer.WriteString(format)
|
||||
|
||||
switch receiver.style {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
12
logger.go
12
logger.go
|
@ -1,22 +1,12 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
type Logger interface {
|
||||
Alert(...interface{}) error
|
||||
Alertf(string, ...interface{}) error
|
||||
MuteAlert()
|
||||
UnmuteAlert()
|
||||
AlertMuted() bool
|
||||
|
||||
Begin(...interface{}) Logger
|
||||
|
||||
Debug(...interface{})
|
||||
Debugf(string, ...interface{})
|
||||
MuteDebug()
|
||||
UnmuteDebug()
|
||||
DebugMuted() bool
|
||||
|
||||
End(...interface{})
|
||||
|
||||
Error(...interface{}) error
|
||||
Errorf(string, ...interface{}) error
|
||||
MuteError()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package log
|
||||
package flog
|
||||
|
||||
import (
|
||||
"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