From 01bf0c4f4489e386f8b2a2d5e90aa288fabf9da3 Mon Sep 17 00:00:00 2001
From: Silas Davis <silas@erisindustries.com>
Date: Sat, 17 Dec 2016 18:23:31 +0000
Subject: [PATCH] Add vector valued logger

---
 client/client.go                             |  2 +-
 cmd/serve.go                                 |  2 +
 consensus/consensus.go                       |  3 +-
 consensus/tendermint/tendermint.go           |  6 +-
 core/core.go                                 | 21 +++---
 logging/convention.go                        | 52 ++++++++++++++
 logging/lifecycle/lifecycle.go               | 10 ++-
 logging/loggers/info_trace_logger.go         | 15 ++--
 logging/loggers/vector_valued_logger.go      | 29 ++++++++
 logging/loggers/vector_valued_logger_test.go | 17 +++++
 logging/metadata.go                          |  5 ++
 logging/structure/structure.go               | 75 +++++++++++++++++---
 logging/structure/structure_test.go          | 11 +++
 rpc/tendermint/test/common.go                |  2 +
 rpc/tendermint/test/shared.go                |  2 +
 15 files changed, 219 insertions(+), 33 deletions(-)
 create mode 100644 logging/convention.go
 create mode 100644 logging/loggers/vector_valued_logger.go
 create mode 100644 logging/loggers/vector_valued_logger_test.go

diff --git a/client/client.go b/client/client.go
index 24eeb033..34275c8b 100644
--- a/client/client.go
+++ b/client/client.go
@@ -80,7 +80,7 @@ func NewErisNodeClient(rpcString string) *ErisNodeClient {
 // it needs to be initialised before go-rpc, hence it's placement here.
 func init() {
 	h := tendermint_log.LvlFilterHandler(tendermint_log.LvlWarn, tendermint_log.StdoutHandler)
-    tendermint_log.Root().SetHandler(h)
+	tendermint_log.Root().SetHandler(h)
 }
 
 //------------------------------------------------------------------------------------
diff --git a/cmd/serve.go b/cmd/serve.go
index 8d23e4b3..f8b99c08 100644
--- a/cmd/serve.go
+++ b/cmd/serve.go
@@ -115,6 +115,8 @@ func NewCoreFromDo(do *definitions.Do) (*core.Core, error) {
 	// Capture all logging from tendermint/tendermint and tendermint/go-*
 	// dependencies
 	lifecycle.CaptureTendermintLog15Output(logger)
+	// And from stdlib go log
+	lifecycle.CaptureStdlibLogOutput(logger)
 
 	cmdLogger := logger.With("command", "serve")
 
diff --git a/consensus/consensus.go b/consensus/consensus.go
index 03296eed..01fc2d12 100644
--- a/consensus/consensus.go
+++ b/consensus/consensus.go
@@ -28,7 +28,8 @@ func LoadConsensusEngineInPipe(moduleConfig *config.ModuleConfig,
 	pipe definitions.Pipe) error {
 	switch moduleConfig.Name {
 	case "tendermint":
-		tmint, err := tendermint.NewTendermint(moduleConfig, pipe.GetApplication())
+		tmint, err := tendermint.NewTendermint(moduleConfig, pipe.GetApplication(),
+		pipe.Logger().With())
 		if err != nil {
 			return fmt.Errorf("Failed to load Tendermint node: %v", err)
 		}
diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go
index 87553697..545e4c1a 100644
--- a/consensus/tendermint/tendermint.go
+++ b/consensus/tendermint/tendermint.go
@@ -41,6 +41,7 @@ import (
 	// files  "github.com/eris-ltd/eris-db/files"
 	blockchain_types "github.com/eris-ltd/eris-db/blockchain/types"
 	consensus_types "github.com/eris-ltd/eris-db/consensus/types"
+	"github.com/eris-ltd/eris-db/logging/loggers"
 	"github.com/eris-ltd/eris-db/txs"
 	"github.com/tendermint/go-wire"
 )
@@ -57,7 +58,8 @@ var _ consensus_types.ConsensusEngine = (*Tendermint)(nil)
 var _ blockchain_types.Blockchain = (*Tendermint)(nil)
 
 func NewTendermint(moduleConfig *config.ModuleConfig,
-	application manager_types.Application) (*Tendermint, error) {
+	application manager_types.Application,
+	logger loggers.InfoTraceLogger) (*Tendermint, error) {
 	// re-assert proper configuration for module
 	if moduleConfig.Version != GetTendermintVersion().GetMinorVersionString() {
 		return nil, fmt.Errorf("Version string %s did not match %s",
@@ -74,7 +76,7 @@ func NewTendermint(moduleConfig *config.ModuleConfig,
 	tendermintConfigViper, err := config.ViperSubConfig(moduleConfig.Config, "configuration")
 	if tendermintConfigViper == nil {
 		return nil,
-				fmt.Errorf("Failed to extract Tendermint configuration subtree: %s", err)
+			fmt.Errorf("Failed to extract Tendermint configuration subtree: %s", err)
 	}
 	// wrap a copy of the viper config in a tendermint/go-config interface
 	tmintConfig := GetTendermintConfig(tendermintConfigViper)
diff --git a/core/core.go b/core/core.go
index c4e08963..3bd32fdc 100644
--- a/core/core.go
+++ b/core/core.go
@@ -22,20 +22,19 @@ import (
 	// TODO: [ben] swap out go-events with eris-db/event (currently unused)
 	events "github.com/tendermint/go-events"
 
-	log "github.com/eris-ltd/eris-logger"
-
-	config "github.com/eris-ltd/eris-db/config"
-	consensus "github.com/eris-ltd/eris-db/consensus"
-	definitions "github.com/eris-ltd/eris-db/definitions"
-	event "github.com/eris-ltd/eris-db/event"
-	manager "github.com/eris-ltd/eris-db/manager"
+	"github.com/eris-ltd/eris-db/config"
+	"github.com/eris-ltd/eris-db/consensus"
+	"github.com/eris-ltd/eris-db/definitions"
+	"github.com/eris-ltd/eris-db/event"
+	"github.com/eris-ltd/eris-db/manager"
 	// rpc_v0 is carried over from Eris-DBv0.11 and before on port 1337
 	rpc_v0 "github.com/eris-ltd/eris-db/rpc/v0"
 	// rpc_tendermint is carried over from Eris-DBv0.11 and before on port 46657
 
+	"github.com/eris-ltd/eris-db/logging"
 	"github.com/eris-ltd/eris-db/logging/loggers"
 	rpc_tendermint "github.com/eris-ltd/eris-db/rpc/tendermint/core"
-	server "github.com/eris-ltd/eris-db/server"
+	"github.com/eris-ltd/eris-db/server"
 )
 
 // Core is the high-level structure
@@ -60,15 +59,15 @@ func NewCore(chainId string,
 	if err != nil {
 		return nil, fmt.Errorf("Failed to load application pipe: %v", err)
 	}
-	log.Debug("Loaded pipe with application manager")
+	logging.TraceMsg(logger, "Loaded pipe with application manager")
 	// pass the consensus engine into the pipe
 	if e := consensus.LoadConsensusEngineInPipe(consensusConfig, pipe); e != nil {
 		return nil, fmt.Errorf("Failed to load consensus engine in pipe: %v", e)
 	}
 	tendermintPipe, err := pipe.GetTendermintPipe()
 	if err != nil {
-		log.Warn(fmt.Sprintf("Tendermint gateway not supported by %s",
-			managerConfig.Version))
+		logging.TraceMsg(logger, "Tendermint gateway not supported by manager",
+			"manager-version", managerConfig.Version)
 	}
 	return &Core{
 		chainId:        chainId,
diff --git a/logging/convention.go b/logging/convention.go
new file mode 100644
index 00000000..7a105853
--- /dev/null
+++ b/logging/convention.go
@@ -0,0 +1,52 @@
+package logging
+
+import (
+	"github.com/eris-ltd/eris-db/logging/loggers"
+	"github.com/eris-ltd/eris-db/logging/structure"
+	"github.com/eris-ltd/eris-db/util/slice"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+// Helper functions for InfoTraceLoggers, sort of extension methods to loggers
+// to centralise and establish logging conventions on top of in with the base
+// logging interface
+
+// Record structured Info log line with a message and conventional keys
+func InfoMsgVals(logger loggers.InfoTraceLogger, message string, vals ...interface{}) {
+	MsgVals(kitlog.LoggerFunc(logger.Info), message, vals...)
+}
+
+// Record structured Trace log line with a message and conventional keys
+func TraceMsgVals(logger loggers.InfoTraceLogger, message string, vals ...interface{}) {
+	MsgVals(kitlog.LoggerFunc(logger.Trace), message, vals...)
+}
+
+// Record structured Info log line with a message
+func InfoMsg(logger loggers.InfoTraceLogger, message string, keyvals ...interface{}) {
+	Msg(kitlog.LoggerFunc(logger.Info), message, keyvals...)
+}
+
+// Record structured Trace log line with a message
+func TraceMsg(logger loggers.InfoTraceLogger, message string, keyvals ...interface{}) {
+	Msg(kitlog.LoggerFunc(logger.Trace), message, keyvals...)
+}
+
+func WithScope(logger loggers.InfoTraceLogger, scopes... string) loggers.InfoTraceLogger {
+	return logger.With()
+}
+
+// Record a structured log line with a message
+func Msg(logger kitlog.Logger, message string, keyvals ...interface{}) error {
+	return logger.Log(slice.CopyPrepend(keyvals, structure.MessageKey, message))
+}
+
+// Record a structured log line with a message and conventional keys
+func MsgVals(logger kitlog.Logger, message string, vals ...interface{}) error {
+	keyvals := make([]interface{}, len(vals)*2)
+	for i := 0; i < len(vals); i++ {
+		kv := i * 2
+		keyvals[kv] = structure.KeyFromValue(vals[i])
+		keyvals[kv+1] = vals[i]
+	}
+	return Msg(logger, message, keyvals)
+}
diff --git a/logging/lifecycle/lifecycle.go b/logging/lifecycle/lifecycle.go
index fd85a324..914791fe 100644
--- a/logging/lifecycle/lifecycle.go
+++ b/logging/lifecycle/lifecycle.go
@@ -6,11 +6,12 @@ import (
 	"os"
 
 	"github.com/eris-ltd/eris-db/logging"
+	"github.com/eris-ltd/eris-db/logging/adapters/stdlib"
 	tmLog15adapter "github.com/eris-ltd/eris-db/logging/adapters/tendermint_log15"
 	"github.com/eris-ltd/eris-db/logging/loggers"
+	"github.com/eris-ltd/eris-db/logging/structure"
 	kitlog "github.com/go-kit/kit/log"
 	tmLog15 "github.com/tendermint/log15"
-	"github.com/eris-ltd/eris-db/logging/structure"
 )
 
 func NewLoggerFromConfig(LoggingConfig logging.LoggingConfig) loggers.InfoTraceLogger {
@@ -33,5 +34,10 @@ func NewLogger(infoLogger, traceLogger kitlog.Logger) loggers.InfoTraceLogger {
 func CaptureTendermintLog15Output(infoTraceLogger loggers.InfoTraceLogger) {
 	tmLog15.Root().SetHandler(
 		tmLog15adapter.InfoTraceLoggerAsLog15Handler(infoTraceLogger.
-			With(structure.ComponentKey, "tendermint")))
+			With(structure.ComponentKey, "tendermint_log15")))
+}
+
+func CaptureStdlibLogOutput(infoTraceLogger loggers.InfoTraceLogger) {
+	stdlib.CaptureRootLogger(infoTraceLogger.
+		With(structure.ComponentKey, "stdlib_log"))
 }
diff --git a/logging/loggers/info_trace_logger.go b/logging/loggers/info_trace_logger.go
index 7374477a..db2e744c 100644
--- a/logging/loggers/info_trace_logger.go
+++ b/logging/loggers/info_trace_logger.go
@@ -66,15 +66,16 @@ var _ InfoTraceLogger = (*infoTraceLogger)(nil)
 var _ kitlog.Logger = (InfoTraceLogger)(nil)
 
 func NewInfoTraceLogger(infoLogger, traceLogger kitlog.Logger) InfoTraceLogger {
-	// We will never halt progress a log emitter. If log output takes too long
-	// will start dropping log lines by using a ring buffer.
+	// We will never halt the progress of a log emitter. If log output takes too
+	// long will start dropping log lines by using a ring buffer.
 	// We also guard against any concurrency bugs in underlying loggers by feeding
 	// them from a single channel
-	logger := kitlog.NewContext(NonBlockingLogger(MultipleChannelLogger(
-		map[string]kitlog.Logger{
-			InfoChannelName:  infoLogger,
-			TraceChannelName: traceLogger,
-		})))
+	logger := kitlog.NewContext(NonBlockingLogger(VectorValuedLogger(
+		MultipleChannelLogger(
+			map[string]kitlog.Logger{
+				InfoChannelName:  infoLogger,
+				TraceChannelName: traceLogger,
+			}))))
 	return &infoTraceLogger{
 		infoLogger: logger.With(
 			structure.ChannelKey, InfoChannelName,
diff --git a/logging/loggers/vector_valued_logger.go b/logging/loggers/vector_valued_logger.go
new file mode 100644
index 00000000..d7f50d76
--- /dev/null
+++ b/logging/loggers/vector_valued_logger.go
@@ -0,0 +1,29 @@
+package loggers
+
+import (
+	"github.com/eris-ltd/eris-db/logging/structure"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+// Treat duplicate keys
+type vectorValuedLogger struct {
+	logger kitlog.Logger
+}
+
+var _ kitlog.Logger = &vectorValuedLogger{}
+
+func (vvl *vectorValuedLogger) Log(keyvals ...interface{}) error {
+	keys, vals := structure.KeyValuesVector(keyvals)
+	kvs := make([]interface{}, len(keys)*2)
+	for i := 0; i < len(keys); i++ {
+		kv := i * 2
+		key := keys[i]
+		kvs[kv] = key
+		kvs[kv+1] = vals[key]
+	}
+	return vvl.logger.Log(kvs...)
+}
+
+func VectorValuedLogger(logger kitlog.Logger) *vectorValuedLogger {
+	return &vectorValuedLogger{logger: logger}
+}
diff --git a/logging/loggers/vector_valued_logger_test.go b/logging/loggers/vector_valued_logger_test.go
new file mode 100644
index 00000000..d6bc54eb
--- /dev/null
+++ b/logging/loggers/vector_valued_logger_test.go
@@ -0,0 +1,17 @@
+package loggers
+
+import (
+	"testing"
+
+	. "github.com/eris-ltd/eris-db/util/slice"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestVectorValuedLogger(t *testing.T) {
+	logger := newTestLogger()
+	vvl := VectorValuedLogger(logger)
+	vvl.Log("foo", "bar", "seen", 1, "seen", 3, "seen", 2)
+
+	assert.Equal(t, Slice("foo", "bar", "seen", Slice(1, 3, 2)),
+		logger.logLines[0])
+}
diff --git a/logging/metadata.go b/logging/metadata.go
index 3fbed2cf..b0ce3f3d 100644
--- a/logging/metadata.go
+++ b/logging/metadata.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/eris-ltd/eris-db/logging/loggers"
 	"github.com/eris-ltd/eris-db/logging/structure"
+	"github.com/eris-ltd/mint-client/Godeps/_workspace/src/github.com/inconshreveable/log15/stack"
 	kitlog "github.com/go-kit/kit/log"
 )
 
@@ -25,3 +26,7 @@ func WithMetadata(infoTraceLogger loggers.InfoTraceLogger) loggers.InfoTraceLogg
 	return infoTraceLogger.With(structure.TimeKey, defaultTimestampUTCValuer,
 		structure.CallerKey, kitlog.Caller(infoTraceLoggerCallDepth))
 }
+
+func CallersValuer() kitlog.Valuer {
+	return func() interface{} { return stack.Callers() }
+}
diff --git a/logging/structure/structure.go b/logging/structure/structure.go
index 9420b200..151fda92 100644
--- a/logging/structure/structure.go
+++ b/logging/structure/structure.go
@@ -1,20 +1,29 @@
 package structure
 
-import . "github.com/eris-ltd/eris-db/util/slice"
+import (
+	"reflect"
+
+	. "github.com/eris-ltd/eris-db/util/slice"
+)
 
 const (
-	// Key for go time.Time object
+	// Log time (time.Time)
 	TimeKey = "time"
-	// Key for call site for log invocation
+	// Call site for log invocation (go-stack.Call)
 	CallerKey = "caller"
-	// Key for String name for level
-	LevelKey   = "level"
-	// Key to switch on for channel in a multiple channel logging context
+	// Level name (string)
+	LevelKey = "level"
+	// Channel name in a multiple channel logging context
 	ChannelKey = "channel"
-	// Key for string message
+	// Log message (string)
 	MessageKey = "message"
-	// Key for module or function or struct that is the subject of the logging
+	// Top-level component (choose one) name
 	ComponentKey = "component"
+	// Vector-valued scope
+	ScopeKey = "scope"
+	// Globally unique identifier persisting while a single instance (root process)
+	// of this program/service is running
+	RunId = "run_id"
 )
 
 // Pull the specified values from a structured log line into a map.
@@ -49,6 +58,42 @@ func ValuesAndContext(keyvals []interface{},
 	return vals, context
 }
 
+type vector []interface{}
+
+func newVector(vals... interface{}) vector {
+	return vals
+}
+
+func (v vector) Slice() []interface{} {
+	return v
+}
+
+// Returns the unique keys in keyvals and a map of values where values of
+// repeated keys are merged into a slice of those values in the order which they
+// appeared
+func KeyValuesVector(keyvals []interface{}) ([]interface{}, map[interface{}]interface{}) {
+	keys := []interface{}{}
+	vals := make(map[interface{}]interface{}, len(keyvals)/2)
+	for i := 0; i < 2*(len(keyvals)/2); i += 2 {
+		key := keyvals[i]
+		val := keyvals[i+1]
+		switch oldVal := vals[key].(type) {
+		case nil:
+			keys = append(keys, key)
+			vals[key] = val
+		case vector:
+			// if this is, in fact, only the second time we have seen key and the
+			// first time it had a value of []interface{} then here we are doing the
+			// wrong thing by appending val. We will end up with
+			// Slice(..., val) rather than Slice(Slice(...), val)
+			vals[key] = vector(append(oldVal, val))
+		default:
+			vals[key] = newVector(oldVal, val)
+		}
+	}
+	return keys, vals
+}
+
 // Return a single value corresponding to key in keyvals
 func Value(keyvals []interface{}, key interface{}) interface{} {
 	for i := 0; i < 2*(len(keyvals)/2); i += 2 {
@@ -57,4 +102,16 @@ func Value(keyvals []interface{}, key interface{}) interface{} {
 		}
 	}
 	return nil
-}
\ No newline at end of file
+}
+
+// Obtain a canonical key from a value. Useful for structured logging where the
+// type of value alone may be sufficient to determine its key. Providing this
+// function centralises any convention over type names
+func KeyFromValue(val interface{}) string {
+	switch val.(type) {
+	case string:
+		return "text"
+	default:
+		return reflect.TypeOf(val).Name()
+	}
+}
diff --git a/logging/structure/structure_test.go b/logging/structure/structure_test.go
index 2fbdaa3e..36f48c52 100644
--- a/logging/structure/structure_test.go
+++ b/logging/structure/structure_test.go
@@ -13,3 +13,14 @@ func TestValuesAndContext(t *testing.T) {
 	assert.Equal(t, map[interface{}]interface{}{"hello": 1, "fish": 3}, vals)
 	assert.Equal(t, Slice("dog", 2, "fork", 5), ctx)
 }
+
+func TestKeyValuesToMap(t *testing.T) {
+	kvs := Slice("scope", "lawnmower",
+		"occupation", "fish brewer",
+		"scope", "hose pipe",
+		"scope", "rake")
+	assert.Equal(t, map[interface{}]interface{}{
+		"occupation": "fish brewer",
+		"scope":     Slice("lawnmower", "hose pipe", "rake")},
+		KeyValuesToMap(kvs))
+}
diff --git a/rpc/tendermint/test/common.go b/rpc/tendermint/test/common.go
index 76909182..a060833c 100644
--- a/rpc/tendermint/test/common.go
+++ b/rpc/tendermint/test/common.go
@@ -6,6 +6,7 @@ package test
 import (
 	"fmt"
 
+	vm "github.com/eris-ltd/eris-db/manager/eris-mint/evm"
 	rpc_core "github.com/eris-ltd/eris-db/rpc/tendermint/core"
 	"github.com/eris-ltd/eris-db/test/fixtures"
 )
@@ -17,6 +18,7 @@ func TestWrapper(runner func() int) int {
 
 	defer ffs.RemoveAll()
 
+	vm.SetDebug(true)
 	err := initGlobalVariables(ffs)
 
 	if err != nil {
diff --git a/rpc/tendermint/test/shared.go b/rpc/tendermint/test/shared.go
index 8ddce9b3..8c01b6a2 100644
--- a/rpc/tendermint/test/shared.go
+++ b/rpc/tendermint/test/shared.go
@@ -88,6 +88,8 @@ func initGlobalVariables(ffs *fixtures.FileFixtures) error {
 	// To spill tendermint logs on the floor:
 	// lifecycle.CaptureTendermintLog15Output(loggers.NewNoopInfoTraceLogger())
 	lifecycle.CaptureTendermintLog15Output(logger)
+	lifecycle.CaptureStdlibLogOutput(logger)
+
 	testCore, err = core.NewCore("testCore", consensusConfig, managerConfig,
 		logger)
 	if err != nil {
-- 
GitLab