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