Compare commits

..

10 Commits

60 changed files with 355 additions and 2934 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2015 Charles Iliya Krempeaux <charles@reptile.ca> :: http://changelog.ca/ Copyright (c) 2015 Charles Iliya Krempeaux :: http://changelog.ca/
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

113
README.md
View File

@ -1,18 +1,115 @@
# go-flog # go-log
A library that provides structured and formatted logging for the Go programming language. A library that provides structured and formatted logging for the Go programming language.
(This Go package was originally named `flog`, but in version 2 was renamed to `log`.)
## Documention ## Online Documention
Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-flog Online documentation, which includes examples, can be found at: http://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) [![GoDoc](https://godoc.org/github.com/reiver/go-log?status.svg)](https://godoc.org/github.com/reiver/go-log)
## Basic Usage
## More Routers Basic usage of this logger looks like this:
In addition to the builtin routers that go-flog comes with, other external ```golang
routers are also available. These include: router := log.NewPrettyWritingRouter(os.Stdout)
* [go-slackchannelrouter](https://github.com/reiver/go-slackchannelrouter) Makes it so log messages get posted to a [Slack](https://slack.com/) channel. 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)
}
```

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"

View File

@ -1,24 +0,0 @@
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
}

View File

@ -1,154 +0,0 @@
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
}
}
}
}

View File

@ -1,55 +0,0 @@
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
}

View File

@ -1,141 +0,0 @@
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++
}
}
}

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"os" "os"

View File

@ -1,92 +0,0 @@
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
}

View File

@ -1,226 +0,0 @@
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
}
}
}
}
}

View File

@ -1,24 +0,0 @@
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
}

View File

@ -1,33 +0,0 @@
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.
}

104
doc.go
View File

@ -1,104 +0,0 @@
/*
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

View File

@ -1,198 +0,0 @@
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
}
}
}

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
@ -8,5 +8,4 @@ import (
var ( var (
errNilReceiver = errors.New("Nil Receiver") errNilReceiver = errors.New("Nil Receiver")
errNilRouter = errors.New("Nil Router")
) )

View File

@ -1,44 +0,0 @@
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
}

View File

@ -1,350 +0,0 @@
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
}
}

View File

@ -1,86 +0,0 @@
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
}

View File

@ -1,91 +0,0 @@
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
}
}
}
}

View File

@ -1,36 +0,0 @@
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
}

View File

@ -1,37 +0,0 @@
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)
}

View File

@ -1,35 +0,0 @@
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)
}

View File

@ -1,86 +0,0 @@
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
}

View File

@ -1,39 +0,0 @@
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)
}

View File

@ -1,46 +0,0 @@
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)
}

View File

@ -1,35 +0,0 @@
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)
}

View File

@ -1,352 +0,0 @@
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
}
}
}
}
}

View File

@ -1,35 +0,0 @@
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)
}

View File

@ -1,35 +0,0 @@
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)
}

View File

@ -1,14 +0,0 @@
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
}

View File

@ -1,329 +0,0 @@
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
}
}
}
}

View File

@ -1,15 +1,19 @@
package flog package log
import ( import (
"io" "io"
"time"
) )
type internalLogger struct { type internalLogger struct {
prefix string prefix string
style string style string
begin time.Time
writer io.Writer writer io.Writer
mutedAlert bool
mutedDebug bool mutedDebug bool
mutedError bool mutedError bool
mutedFatal bool mutedFatal bool

View File

@ -0,0 +1,62 @@
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
}

View File

@ -0,0 +1,70 @@
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
}
}
}

View File

@ -0,0 +1,30 @@
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
}

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"
@ -45,6 +45,7 @@ func (receiver internalLogger) Debugf(format string, a ...interface{}) {
buffer.WriteString("[debug] ") buffer.WriteString("[debug] ")
} }
buffer.WriteString(receiver.prefix)
buffer.WriteString(format) buffer.WriteString(format)
switch receiver.style { switch receiver.style {

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"bytes" "bytes"

View File

@ -0,0 +1,17 @@
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...)
}

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"
@ -40,6 +40,7 @@ func (receiver internalLogger) Errorf(format string, a ...interface{}) error {
buffer.WriteString("[ERROR] ") buffer.WriteString("[ERROR] ")
} }
buffer.WriteString(receiver.prefix)
buffer.WriteString(format) buffer.WriteString(format)
switch receiver.style { switch receiver.style {

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"
@ -41,6 +41,7 @@ func (receiver internalLogger) Fatalf(format string, a ...interface{}) {
buffer.WriteString("[PANIC] ") buffer.WriteString("[PANIC] ")
} }
buffer.WriteString(receiver.prefix)
buffer.WriteString(format) buffer.WriteString(format)
switch receiver.style { switch receiver.style {

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"
@ -45,6 +45,7 @@ func (receiver internalLogger) Highlightf(format string, a ...interface{}) {
buffer.WriteString("[HIGHLIGHT] ") buffer.WriteString("[HIGHLIGHT] ")
} }
buffer.WriteString(receiver.prefix)
buffer.WriteString(format) buffer.WriteString(format)
switch receiver.style { switch receiver.style {

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"
@ -45,6 +45,7 @@ func (receiver internalLogger) Informf(format string, a ...interface{}) {
buffer.WriteString("[inform] ") buffer.WriteString("[inform] ")
} }
buffer.WriteString(receiver.prefix)
buffer.WriteString(format) buffer.WriteString(format)
switch receiver.style { switch receiver.style {

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,8 @@
package flog package log
func (receiver *internalLogger) MuteAlert() {
receiver.mutedDebug = true
}
func (receiver *internalLogger) MuteDebug() { func (receiver *internalLogger) MuteDebug() {
receiver.mutedDebug = true receiver.mutedDebug = true

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"
@ -40,6 +40,7 @@ func (receiver internalLogger) Panicf(format string, a ...interface{}) {
buffer.WriteString("[PANIC] ") buffer.WriteString("[PANIC] ")
} }
buffer.WriteString(receiver.prefix)
buffer.WriteString(format) buffer.WriteString(format)
switch receiver.style { switch receiver.style {

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"strings" "strings"
@ -17,7 +17,7 @@ func (receiver internalLogger) Prefix(newprefix ...string) Logger {
prefix := buffer.String() prefix := buffer.String()
var logger internalLogger = receiver var logger internalLogger = receiver
logger.prefix += prefix logger.prefix = prefix
return &logger return &logger
} }

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"bytes" "bytes"
@ -66,7 +66,7 @@ func TestLoggerPrefix(t *testing.T) {
newInternalLogger, casted := newLogger.(*internalLogger) newInternalLogger, casted := newLogger.(*internalLogger)
if !casted { if !casted {
t.Errorf("For test #%d, could not cast to flog.internalLogger.", testNumber) t.Errorf("For test #%d, could not cast to log.internalLogger.", testNumber)
t.Logf("TYPE: %T", newLogger) t.Logf("TYPE: %T", newLogger)
continue continue
} }
@ -101,14 +101,20 @@ func TestLoggerPrefix_inform(t *testing.T) {
log.Debug("hello DEBUG") log.Debug("hello DEBUG")
log.Trace("hello TRACE") log.Trace("hello TRACE")
const expected = "[PANIC] hello PANIC" + "\n" +
log = log.Prefix("four")
log.Highlight("new prefix FOUR")
const expected = "[PANIC] one: two: three: hello PANIC" + "\n" +
"RECOVER:hello PANIC" + "\n" + "RECOVER:hello PANIC" + "\n" +
"[inform] hello INFORM" + "\n" + "[inform] one: two: three: hello INFORM" + "\n" +
"[HIGHLIGHT] hello HIGHLIGHT" + "\n" + "[HIGHLIGHT] one: two: three: hello HIGHLIGHT" + "\n" +
"[ERROR] hello ERROR" + "\n" + "[ERROR] one: two: three: hello ERROR" + "\n" +
"[warn] hello WARN" + "\n" + "[warn] one: two: three: hello WARN" + "\n" +
"[debug] hello DEBUG" + "\n" + "[debug] one: two: three: hello DEBUG" + "\n" +
"[trace] hello TRACE" + "\n" "[trace] one: two: three: hello TRACE" + "\n" +
"[HIGHLIGHT] one: two: three: four: new prefix FOUR" + "\n"
if actual := buffer.String(); expected != actual { if actual := buffer.String(); expected != actual {
t.Error("The actual logs is not what was expected.") t.Error("The actual logs is not what was expected.")

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"
@ -45,6 +45,7 @@ func (receiver internalLogger) Tracef(format string, a ...interface{}) {
buffer.WriteString("[trace] ") buffer.WriteString("[trace] ")
} }
buffer.WriteString(receiver.prefix)
buffer.WriteString(format) buffer.WriteString(format)
switch receiver.style { switch receiver.style {

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,8 @@
package flog package log
func (receiver *internalLogger) UnmuteAlert() {
receiver.mutedAlert = false
}
func (receiver *internalLogger) UnmuteDebug() { func (receiver *internalLogger) UnmuteDebug() {
receiver.mutedDebug = false receiver.mutedDebug = false

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"fmt" "fmt"
@ -45,6 +45,7 @@ func (receiver internalLogger) Warnf(format string, a ...interface{}) {
buffer.WriteString("[warn] ") buffer.WriteString("[warn] ")
} }
buffer.WriteString(receiver.prefix)
buffer.WriteString(format) buffer.WriteString(format)
switch receiver.style { switch receiver.style {

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"bytes" "bytes"

View File

@ -1,12 +1,22 @@
package flog package log
type Logger interface { type Logger interface {
Alert(...interface{}) error
Alertf(string, ...interface{}) error
MuteAlert()
UnmuteAlert()
AlertMuted() bool
Begin(...interface{}) Logger
Debug(...interface{}) Debug(...interface{})
Debugf(string, ...interface{}) Debugf(string, ...interface{})
MuteDebug() MuteDebug()
UnmuteDebug() UnmuteDebug()
DebugMuted() bool DebugMuted() bool
End(...interface{})
Error(...interface{}) error Error(...interface{}) error
Errorf(string, ...interface{}) error Errorf(string, ...interface{}) error
MuteError() MuteError()

View File

@ -1,4 +1,4 @@
package flog package log
import ( import (
"testing" "testing"

View File

@ -1,32 +0,0 @@
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))
}

View File

@ -1,34 +0,0 @@
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
}

View File

@ -1,117 +0,0 @@
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
}

View File

@ -1,6 +0,0 @@
package flog
type Router interface {
Route(message string, context map[string]interface{}) error
}