initial commit. this is still a work in progress.

master
Charles Iliya Krempeaux 2015-10-10 00:29:18 -07:00
commit f151008688
18 changed files with 1058 additions and 0 deletions

19
LICENSE 100644
View File

@ -0,0 +1,19 @@
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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

11
README.md 100644
View File

@ -0,0 +1,11 @@
# go-flog
A library that provides structured and formatted logging for the Go programming language.
## Documention
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-flog?status.svg)](https://godoc.org/github.com/reiver/go-flog)

24
context.go 100644
View File

@ -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
}

154
context_test.go 100644
View File

@ -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
}
}
}
}

16
discard_router.go 100644
View File

@ -0,0 +1,16 @@
package flog
func NewDiscardRouter() *DiscardRouter {
router := DiscardRouter{}
return &router
}
type DiscardRouter struct{}
func (router *DiscardRouter) Route(message string, context map[string]interface{}) error {
return nil
}

View File

@ -0,0 +1,16 @@
package flog
import (
"testing"
)
func TestNewDiscardRouter(t *testing.T) {
router := NewDiscardRouter()
if nil == router {
t.Errorf("After trying to create a discard router, expected it to be not nil, but was: %v", router)
}
}

25
fatal.go 100644
View File

@ -0,0 +1,25 @@
package flog
import (
"os"
)
func (flogger *internalFlogger) Fatal(v ...interface{}) {
flogger.Print(v...)
os.Exit(1)
}
func (flogger *internalFlogger) Fatalf(format string, v ...interface{}) {
flogger.Printf(format, v...)
os.Exit(1)
}
func (flogger *internalFlogger) Fatalln(v ...interface{}) {
flogger.Println(v...)
os.Exit(1)
}

38
filtered_router.go 100644
View File

@ -0,0 +1,38 @@
package flog
func NewFilteredRouter() *FilteredRouter {
registry := make([]struct{FilterFn func(string, map[string]interface{})bool ; Subrouter Router}, 0, 2)
router := FilteredRouter{
registry:registry,
}
return &router
}
type FilteredRouter struct {
registry []struct{FilterFn func(string, map[string]interface{})bool ; Subrouter Router}
}
func (router *FilteredRouter) Route(message string, context map[string]interface{}) error {
for _, datum := range router.registry {
if datum.FilterFn(message, context) {
return datum.Subrouter.Route(message, context)
}
}
return nil
}
func (router *FilteredRouter) Register(subrouter Router, filterFn func(string, map[string]interface{})bool) {
datum := struct{FilterFn func(string, map[string]interface{})bool ; Subrouter Router}{
FilterFn: filterFn,
Subrouter: subrouter,
}
router.registry = append(router.registry, datum)
}

View File

@ -0,0 +1,82 @@
package flog
import (
"testing"
"fmt"
"math/rand"
"time"
)
func TestFilteredRouterJustCreated(t *testing.T) {
router := NewFilteredRouter()
router.Register(NewDiscardRouter(), func(string, map[string]interface{}) bool {
return false
})
if nil == router {
t.Errorf("After trying to create a filtered router, expected it to be not nil, but was: %v", router)
}
}
func TestFilteredRouterJustFilterParameters(t *testing.T) {
randomness := rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
var filterMessage string
var filterContext map[string] interface{}
var filterResult = false
router := NewFilteredRouter()
router.Register(NewDiscardRouter(), 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
}
}
}
}

26
flog.go 100644
View File

@ -0,0 +1,26 @@
package flog
type internalFlogger struct {
//@TODO: Do we really want this here?
// logs []string
context map[string]interface{}
router Router
}
func New(router Router, cascade ...interface{}) Flogger {
//@TODO: Do we really want this here?
// logs := make([]string, 0, 8)
context := newContext(cascade...)
flogger := internalFlogger{
//@TODO: Do we really want this here?
// logs:logs,
context:context,
router:router,
}
return &flogger
}

179
flog_test.go 100644
View File

@ -0,0 +1,179 @@
package flog
import (
"testing"
)
func TestNew(t *testing.T) {
flogger := New(NewDiscardRouter())
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(NewDiscardRouter(), 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
}
}
}
}

18
flogger.go 100644
View File

@ -0,0 +1,18 @@
package flog
type Flogger interface {
Fatal(...interface{})
Fatalf(string, ...interface{})
Fatalln(...interface{})
Panic(...interface{})
Panicf(string, ...interface{})
Panicln(...interface{})
Print(...interface{})
Printf(string, ...interface{})
Println(...interface{})
With(...interface{}) Flogger
}

25
panic.go 100644
View File

@ -0,0 +1,25 @@
package flog
import (
"fmt"
)
func (flogger *internalFlogger) Panic(v ...interface{}) {
flogger.Print(v...)
panic(fmt.Sprint(v...))
}
func (flogger *internalFlogger) Panicf(format string, v ...interface{}) {
flogger.Printf(format, v...)
panic(fmt.Sprintf(format, v...))
}
func (flogger *internalFlogger) Panicln(v ...interface{}) {
flogger.Println(v...)
panic(fmt.Sprintln(v...))
}

34
print.go 100644
View File

@ -0,0 +1,34 @@
package flog
import (
"fmt"
)
func (flogger *internalFlogger) Print(v ...interface{}) {
msg := fmt.Sprint(v...)
//@TODO: Do we really want this here?
// flogger.logs = append(flogger.logs, msg)
flogger.router.Route(msg, flogger.context)
}
func (flogger *internalFlogger) Printf(format string, v ...interface{}) {
msg := fmt.Sprintf(format, v...)
//@TODO: Do we really want this here?
// flogger.logs = append(flogger.logs, msg)
flogger.router.Route(msg, flogger.context)
}
func (flogger *internalFlogger) Println(v ...interface{}) {
msg := fmt.Sprintln(v...)
//@TODO: Do we really want this here?
// flogger.logs = append(flogger.logs, msg)
flogger.router.Route(msg, flogger.context)
}

6
router.go 100644
View File

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

14
with.go 100644
View File

@ -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
}

329
with_test.go 100644
View File

@ -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(NewDiscardRouter(), 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
}
}
}
}

42
writing_router.go 100644
View File

@ -0,0 +1,42 @@
package flog
import (
"fmt"
"io"
"time"
)
func NewWritingRouter(writer io.Writer) *WritingRouter {
router := WritingRouter{
writer:writer,
}
return &router
}
type WritingRouter struct {
writer io.Writer
}
func (router *WritingRouter) Route(message string, context map[string]interface{}) error {
const BOLD = "\033[1m"
const HEADER = "\033[95m"
const UNDERLINE = "\033[4m"
const ENDC = "\033[0m"
str := fmt.Sprintf("%s%s%s\t(%s%v%s)", UNDERLINE, message, ENDC, HEADER, time.Now(), ENDC)
for key, value := range context {
str = fmt.Sprintf("%s\t%s%s%s=%q", str, HEADER, key, ENDC, value)
}
fmt.Fprintln(router.writer, str)
//@TODO: Should this be checking for errors from fmt.Fprintln()?
return nil
}