@cryptotaxi247 / kubo / commits / 2c88e342d

feat(elog) implement event logger

a wrapper around the util.Logger metadata is loggable License: MIT Signed-off-by: Brian Tiger Chow <brian@perfmode.com>

Brian Tiger Chow committed Nov 15, 2014 at 16:08 UTC 2c88e342dbfed3492c8c7a782279dcdfb902207f
6 files changed +292 -1
cmd/ipfs2/main.go
+3 -1
@@ -9,6 +9,7 @@ import (
9 "runtime/pprof"
10 "syscall"
11
12 + // TODO rm direct reference to go-logging
13 logging "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-logging"
14 ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
15 manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr/net"
@@ -22,10 +23,11 @@ import (
23 updates "github.com/jbenet/go-ipfs/updates"
24 u "github.com/jbenet/go-ipfs/util"
25 "github.com/jbenet/go-ipfs/util/debugerror"
26 + elog "github.com/jbenet/go-ipfs/util/elog"
27 )
28
29 // log is the command logger
28 -var log = u.Logger("cmd/ipfs")
30 +var log = elog.Logger("cmd/ipfs")
31
32 // signal to output help
33 var errHelpRequested = errors.New("Help Requested")
util/elog/context.go new
+35
@@ -0,0 +1,35 @@
1 +package elog
2 +
3 +import (
4 + "errors"
5 +
6 + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
7 +)
8 +
9 +type key int
10 +
11 +const metadataKey key = 0
12 +
13 +func ContextWithMetadata(ctx context.Context, l Loggable) context.Context {
14 + existing, err := MetadataFromContext(ctx)
15 + if err != nil {
16 + // context does not contain meta. just set the new metadata
17 + child := context.WithValue(ctx, metadataKey, l.Loggable())
18 + return child
19 + }
20 +
21 + merged := DeepMerge(existing, l.Loggable())
22 + child := context.WithValue(ctx, metadataKey, merged)
23 + return child
24 +}
25 +
26 +func MetadataFromContext(ctx context.Context) (Metadata, error) {
27 + value := ctx.Value(metadataKey)
28 + if value != nil {
29 + metadata, ok := value.(Metadata)
30 + if ok {
31 + return metadata, nil
32 + }
33 + }
34 + return nil, errors.New("context contains no metadata")
35 +}
util/elog/context_test.go new
+44
@@ -0,0 +1,44 @@
1 +package elog
2 +
3 +import (
4 + "testing"
5 +
6 + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
7 +)
8 +
9 +func TestContextContainsMetadata(t *testing.T) {
10 + t.Parallel()
11 +
12 + m := Metadata{"foo": "bar"}
13 + ctx := ContextWithMetadata(context.Background(), m)
14 + got, err := MetadataFromContext(ctx)
15 + if err != nil {
16 + t.Fatal(err)
17 + }
18 +
19 + _, exists := got["foo"]
20 + if !exists {
21 + t.Fail()
22 + }
23 +}
24 +
25 +func TestContextWithPreexistingMetadata(t *testing.T) {
26 + t.Parallel()
27 +
28 + ctx := ContextWithMetadata(context.Background(), Metadata{"hello": "world"})
29 + ctx = ContextWithMetadata(ctx, Metadata{"goodbye": "earth"})
30 +
31 + got, err := MetadataFromContext(ctx)
32 + if err != nil {
33 + t.Fatal(err)
34 + }
35 +
36 + _, exists := got["hello"]
37 + if !exists {
38 + t.Fatal("original key not present")
39 + }
40 + _, exists = got["goodbye"]
41 + if !exists {
42 + t.Fatal("new key not present")
43 + }
44 +}
util/elog/log.go new
+75
@@ -0,0 +1,75 @@
1 +package elog
2 +
3 +import (
4 + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
5 + logging "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-logging"
6 + "github.com/jbenet/go-ipfs/util"
7 +)
8 +
9 +var eloggers = map[string]*logging.Logger{}
10 +
11 +func init() {
12 + SetupLogging()
13 +}
14 +
15 +type EventLogger interface {
16 + StandardLogger
17 + Event(ctx context.Context, event string, m ...Metadata)
18 +}
19 +
20 +type StandardLogger interface {
21 + Critical(args ...interface{})
22 + Criticalf(format string, args ...interface{})
23 + Debug(args ...interface{})
24 + Debugf(format string, args ...interface{})
25 + Error(args ...interface{})
26 + Errorf(format string, args ...interface{})
27 + Fatal(args ...interface{})
28 + Fatalf(format string, args ...interface{})
29 + Info(args ...interface{})
30 + Infof(format string, args ...interface{})
31 + Notice(args ...interface{})
32 + Noticef(format string, args ...interface{})
33 + Panic(args ...interface{})
34 + Panicf(format string, args ...interface{})
35 + Warning(args ...interface{})
36 + Warningf(format string, args ...interface{})
37 +}
38 +
39 +// Logger retrieves a particular event logger
40 +func Logger(system string) EventLogger {
41 + return &eventLogger{util.Logger(system)}
42 +}
43 +
44 +// eventLogger implements the EventLogger and wraps a go-logging Logger
45 +type eventLogger struct {
46 + *logging.Logger
47 +}
48 +
49 +func (el *eventLogger) Event(ctx context.Context, event string, metadata ...Metadata) {
50 + existing, err := MetadataFromContext(ctx)
51 + if err != nil {
52 + existing = Metadata{}
53 + }
54 + accum := existing
55 + for _, datum := range metadata {
56 + accum = DeepMerge(accum, datum)
57 + }
58 + accum["event"] = event
59 +
60 + str, err := accum.JsonString()
61 + if err != nil {
62 + return
63 + }
64 + el.Logger.Info(str)
65 +}
66 +
67 +// SetupLogging will initialize the logger backend and set the flags.
68 +func SetupLogging() {
69 + // fmt := logging.DefaultFormatter
70 +
71 + // f, err := os.Create("events.ipfslog")
72 + // if err != nil {
73 + // panic("failed to open file for event logger")
74 + // }
75 +}
util/elog/metadata.go new
+87
@@ -0,0 +1,87 @@
1 +package elog
2 +
3 +import (
4 + "encoding/json"
5 + "errors"
6 + "reflect"
7 +
8 + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go-uuid/uuid"
9 +)
10 +
11 +// Metadata is a convenience type for generic maps
12 +type Metadata map[string]interface{}
13 +
14 +// Loggable describes objects that can be marshalled into Metadata for logging
15 +type Loggable interface {
16 + Loggable() Metadata
17 +}
18 +
19 +// UniqueEvent returns a Metadata with the string key and UUID value
20 +func UniqueEvent(key string) Metadata {
21 + return Metadata{
22 + key: uuid.New(),
23 + }
24 +}
25 +
26 +// DeepMerge merges the second Metadata parameter into the first.
27 +// Nested Metadata are merged recursively. Primitives are over-written.
28 +func DeepMerge(b, a Metadata) Metadata {
29 + out := Metadata{}
30 + for k, v := range b {
31 + out[k] = v
32 + }
33 + for k, v := range a {
34 +
35 + maybe, err := Metadatify(v)
36 + if err != nil {
37 + // if the new value is not meta. just overwrite the dest vaue
38 + out[k] = v
39 + continue
40 + }
41 +
42 + // it is meta. What about dest?
43 + outv, exists := out[k]
44 + if !exists {
45 + // the new value is meta, but there's no dest value. just write it
46 + out[k] = v
47 + continue
48 + }
49 +
50 + outMetadataValue, err := Metadatify(outv)
51 + if err != nil {
52 + // the new value is meta and there's a dest value, but the dest
53 + // value isn't meta. just overwrite
54 + out[k] = v
55 + continue
56 + }
57 +
58 + // both are meta. merge them.
59 + out[k] = DeepMerge(outMetadataValue, maybe)
60 + }
61 + return out
62 +}
63 +
64 +// Loggable implements the Loggable interface
65 +func (m Metadata) Loggable() Metadata {
66 + // NB: method defined on value to avoid de-referencing nil Metadata
67 + return m
68 +}
69 +
70 +func (m Metadata) JsonString() (string, error) {
71 + // NB: method defined on value
72 + b, err := json.Marshal(m)
73 + return string(b), err
74 +}
75 +
76 +// Metadatify converts maps into Metadata
77 +func Metadatify(i interface{}) (Metadata, error) {
78 + value := reflect.ValueOf(i)
79 + if value.Kind() == reflect.Map {
80 + m := map[string]interface{}{}
81 + for _, k := range value.MapKeys() {
82 + m[k.String()] = value.MapIndex(k).Interface()
83 + }
84 + return Metadata(m), nil
85 + }
86 + return nil, errors.New("is not a map")
87 +}
util/elog/metadata_test.go new
+48
@@ -0,0 +1,48 @@
1 +package elog
2 +
3 +import "testing"
4 +
5 +func TestOverwrite(t *testing.T) {
6 + t.Parallel()
7 +
8 + under := Metadata{
9 + "a": Metadata{
10 + "b": Metadata{
11 + "c": Metadata{
12 + "d": "the original value",
13 + "other": "SURVIVE",
14 + },
15 + },
16 + },
17 + }
18 + over := Metadata{
19 + "a": Metadata{
20 + "b": Metadata{
21 + "c": Metadata{
22 + "d": "a new value",
23 + },
24 + },
25 + },
26 + }
27 +
28 + out := DeepMerge(under, over)
29 +
30 + dval := out["a"].(Metadata)["b"].(Metadata)["c"].(Metadata)["d"].(string)
31 + if dval != "a new value" {
32 + t.Fatal(dval)
33 + }
34 + surv := out["a"].(Metadata)["b"].(Metadata)["c"].(Metadata)["other"].(string)
35 + if surv != "SURVIVE" {
36 + t.Fatal(surv)
37 + }
38 +}
39 +
40 +func TestMarshalJSON(t *testing.T) {
41 + bs, _ := Metadata{"a": "b"}.JsonString()
42 + t.Log(bs)
43 +}
44 +
45 +func TestMetadataIsLoggable(t *testing.T) {
46 + func(l Loggable) {
47 + }(Metadata{})
48 +}