diff --git a/cmd/eris-db.go b/cmd/eris-db.go
index 605b12060b4f669c249dd4a04b05937f6b568262..bcd555c555d474df9736cba01280ce666da6edb1 100644
--- a/cmd/eris-db.go
+++ b/cmd/eris-db.go
@@ -21,17 +21,12 @@ import (
 	"strconv"
 	"strings"
 
-	cobra "github.com/spf13/cobra"
+	"github.com/spf13/cobra"
 
-	log "github.com/eris-ltd/eris-logger"
-
-	definitions "github.com/eris-ltd/eris-db/definitions"
-	version "github.com/eris-ltd/eris-db/version"
+	"github.com/eris-ltd/eris-db/definitions"
+	"github.com/eris-ltd/eris-db/version"
 )
 
-// Global Do struct
-var do *definitions.Do
-
 var ErisDbCmd = &cobra.Command{
 	Use:   "eris-db",
 	Short: "Eris-DB is the server side of the eris chain.",
@@ -43,38 +38,26 @@ Made with <3 by Eris Industries.
 
 Complete documentation is available at https://monax.io/docs/documentation
 ` + "\nVERSION:\n " + version.VERSION,
-	PersistentPreRun: func(cmd *cobra.Command, args []string) {
-
-		log.SetLevel(log.WarnLevel)
-		if do.Verbose {
-			log.SetLevel(log.InfoLevel)
-		} else if do.Debug {
-			log.SetLevel(log.DebugLevel)
-		}
-	},
 	Run: func(cmd *cobra.Command, args []string) { cmd.Help() },
 }
 
 func Execute() {
-	InitErisDbCli()
-	AddGlobalFlags()
-	AddCommands()
+	do := definitions.NewDo()
+	AddGlobalFlags(do)
+	AddCommands(do)
 	ErisDbCmd.Execute()
 }
 
-func InitErisDbCli() {
-	// initialise an empty Do struct for command execution
-	do = definitions.NewDo()
-}
-
-func AddGlobalFlags() {
-	ErisDbCmd.PersistentFlags().BoolVarP(&do.Verbose, "verbose", "v", defaultVerbose(), "verbose output; more output than no output flags; less output than debug level; default respects $ERIS_DB_VERBOSE")
-	ErisDbCmd.PersistentFlags().BoolVarP(&do.Debug, "debug", "d", defaultDebug(), "debug level output; the most output available for eris-db; if it is too chatty use verbose flag; default respects $ERIS_DB_DEBUG")
+func AddGlobalFlags(do *definitions.Do) {
+	ErisDbCmd.PersistentFlags().BoolVarP(&do.Verbose, "verbose", "v",
+		defaultVerbose(),
+		"verbose output; more output than no output flags; less output than debug level; default respects $ERIS_DB_VERBOSE")
+	ErisDbCmd.PersistentFlags().BoolVarP(&do.Debug, "debug", "d", defaultDebug(),
+		"debug level output; the most output available for eris-db; if it is too chatty use verbose flag; default respects $ERIS_DB_DEBUG")
 }
 
-func AddCommands() {
-	buildServeCommand()
-	ErisDbCmd.AddCommand(ServeCmd)
+func AddCommands(do *definitions.Do) {
+	ErisDbCmd.AddCommand(buildServeCommand(do))
 }
 
 //------------------------------------------------------------------------------
diff --git a/cmd/serve.go b/cmd/serve.go
index eccf3388ab0c14bc167c8211ce3a9e82208a39f4..8d23e4b3b585108040e78671be510d281d2d60f3 100644
--- a/cmd/serve.go
+++ b/cmd/serve.go
@@ -17,19 +17,19 @@
 package commands
 
 import (
+	"fmt"
 	"os"
 	"os/signal"
 	"path"
 	"syscall"
 
-	cobra "github.com/spf13/cobra"
-
-	log "github.com/eris-ltd/eris-logger"
-
-	"fmt"
+	"github.com/spf13/cobra"
 
-	core "github.com/eris-ltd/eris-db/core"
-	util "github.com/eris-ltd/eris-db/util"
+	"github.com/eris-ltd/eris-db/core"
+	"github.com/eris-ltd/eris-db/definitions"
+	"github.com/eris-ltd/eris-db/logging/lifecycle"
+	"github.com/eris-ltd/eris-db/logging/structure"
+	"github.com/eris-ltd/eris-db/util"
 )
 
 const (
@@ -41,148 +41,157 @@ var DefaultConfigFilename = fmt.Sprintf("%s.%s",
 	DefaultConfigBasename,
 	DefaultConfigType)
 
-var ServeCmd = &cobra.Command{
-	Use:   "serve",
-	Short: "Eris-DB serve starts an eris-db node with client API enabled by default.",
-	Long: `Eris-DB serve starts an eris-db node with client API enabled by default.
+// build the serve subcommand
+func buildServeCommand(do *definitions.Do) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "serve",
+		Short: "Eris-DB serve starts an eris-db node with client API enabled by default.",
+		Long: `Eris-DB serve starts an eris-db node with client API enabled by default.
 The Eris-DB node is modularly configured for the consensus engine and application
 manager.  The client API can be disabled.`,
-	Example: fmt.Sprintf(`$ eris-db serve -- will start the Eris-DB node based on the configuration file "%s" in the current working directory
+		Example: fmt.Sprintf(`$ eris-db serve -- will start the Eris-DB node based on the configuration file "%s" in the current working directory
 $ eris-db serve --work-dir <path-to-working-directory> -- will start the Eris-DB node based on the configuration file "%s" in the provided working directory
 $ eris-db serve --chain-id <CHAIN_ID> -- will overrule the configuration entry assert_chain_id`,
-		DefaultConfigFilename, DefaultConfigFilename),
-	PreRun: func(cmd *cobra.Command, args []string) {
-		// if WorkDir was not set by a flag or by $ERIS_DB_WORKDIR
-		// NOTE [ben]: we can consider an `Explicit` flag that eliminates
-		// the use of any assumptions while starting Eris-DB
-		if do.WorkDir == "" {
-			if currentDirectory, err := os.Getwd(); err != nil {
-				log.Fatalf("No directory provided and failed to get current working directory: %v", err)
+			DefaultConfigFilename, DefaultConfigFilename),
+		PreRun: func(cmd *cobra.Command, args []string) {
+			// if WorkDir was not set by a flag or by $ERIS_DB_WORKDIR
+			// NOTE [ben]: we can consider an `Explicit` flag that eliminates
+			// the use of any assumptions while starting Eris-DB
+			if do.WorkDir == "" {
+				if currentDirectory, err := os.Getwd(); err != nil {
+					panic(fmt.Sprintf("No directory provided and failed to get current "+
+						"working directory: %v", err))
+					os.Exit(1)
+				} else {
+					do.WorkDir = currentDirectory
+				}
+			}
+			if !util.IsDir(do.WorkDir) {
+				panic(fmt.Sprintf("Provided working directory %s is not a directory",
+					do.WorkDir))
 				os.Exit(1)
-			} else {
-				do.WorkDir = currentDirectory
 			}
-		}
-		if !util.IsDir(do.WorkDir) {
-			log.Fatalf("Provided working directory %s is not a directory", do.WorkDir)
-		}
-	},
-	Run: Serve,
-}
-
-// build the serve subcommand
-func buildServeCommand() {
-	addServeFlags()
+		},
+		Run: ServeRunner(do),
+	}
+	addServeFlags(do, cmd)
+	return cmd
 }
 
-func addServeFlags() {
-	ServeCmd.PersistentFlags().StringVarP(&do.ChainId, "chain-id", "c",
+func addServeFlags(do *definitions.Do, serveCmd *cobra.Command) {
+	serveCmd.PersistentFlags().StringVarP(&do.ChainId, "chain-id", "c",
 		defaultChainId(), "specify the chain id to use for assertion against the genesis file or the existing state. If omitted, and no id is set in $CHAIN_ID, then assert_chain_id is used from the configuration file.")
-	ServeCmd.PersistentFlags().StringVarP(&do.WorkDir, "work-dir", "w",
+	serveCmd.PersistentFlags().StringVarP(&do.WorkDir, "work-dir", "w",
 		defaultWorkDir(), "specify the working directory for the chain to run.  If omitted, and no path set in $ERIS_DB_WORKDIR, the current working directory is taken.")
-	ServeCmd.PersistentFlags().StringVarP(&do.DataDir, "data-dir", "",
+	serveCmd.PersistentFlags().StringVarP(&do.DataDir, "data-dir", "",
 		defaultDataDir(), "specify the data directory.  If omitted and not set in $ERIS_DB_DATADIR, <working_directory>/data is taken.")
-	ServeCmd.PersistentFlags().BoolVarP(&do.DisableRpc, "disable-rpc", "",
+	serveCmd.PersistentFlags().BoolVarP(&do.DisableRpc, "disable-rpc", "",
 		defaultDisableRpc(), "indicate for the RPC to be disabled. If omitted the RPC is enabled by default, unless (deprecated) $ERISDB_API is set to false.")
 }
 
 //------------------------------------------------------------------------------
 // functions
-
-// serve() prepares the environment and sets up the core for Eris_DB to run.
-// After the setup succeeds, serve() starts the core and halts for core to
-// terminate.
-func Serve(cmd *cobra.Command, args []string) {
-	// load configuration from a single location to avoid a wrong configuration
-	// file is loaded.
-	err := do.ReadConfig(do.WorkDir, DefaultConfigBasename, DefaultConfigType)
-	if err != nil {
-		log.WithFields(log.Fields{
-			"directory": do.WorkDir,
-			"file":      DefaultConfigFilename,
-		}).Fatalf("Fatal error reading configuration")
-		os.Exit(1)
-	}
-	// if do.ChainId is not yet set, load chain_id for assertion from configuration file
-	if do.ChainId == "" {
-		if do.ChainId = do.Config.GetString("chain.assert_chain_id"); do.ChainId == "" {
-			log.Fatalf("Failed to read non-empty string for ChainId from config.")
-			os.Exit(1)
-		}
-	}
+func NewCoreFromDo(do *definitions.Do) (*core.Core, error) {
 	// load the genesis file path
 	do.GenesisFile = path.Join(do.WorkDir,
 		do.Config.GetString("chain.genesis_file"))
+
 	if do.Config.GetString("chain.genesis_file") == "" {
-		log.Fatalf("Failed to read non-empty string for genesis file from config.")
-		os.Exit(1)
+		return nil, fmt.Errorf("The config value chain.genesis_file is empty, " +
+			"but should be set to the location of the genesis.json file.")
 	}
 	// Ensure data directory is set and accessible
 	if err := do.InitialiseDataDirectory(); err != nil {
-		log.Fatalf("Failed to initialise data directory (%s): %v", do.DataDir, err)
-		os.Exit(1)
+		return nil, fmt.Errorf("Failed to initialise data directory (%s): %v", do.DataDir, err)
 	}
-	log.WithFields(log.Fields{
-		"chainId":          do.ChainId,
-		"workingDirectory": do.WorkDir,
-		"dataDirectory":    do.DataDir,
-		"genesisFile":      do.GenesisFile,
-	}).Info("Eris-DB serve configuring")
 
-	consensusConfig, err := core.LoadConsensusModuleConfig(do)
+	loggerConfig, err := core.LoadLoggingConfig(do)
 	if err != nil {
-		log.Fatalf("Failed to load consensus module configuration: %s.", err)
-		os.Exit(1)
+		return nil, fmt.Errorf("Failed to load logging config: %s", err)
 	}
 
-	managerConfig, err := core.LoadApplicationManagerModuleConfig(do)
+	// Create a root logger to pass through to dependencies
+	logger := lifecycle.NewLoggerFromConfig(*loggerConfig)
+	// Capture all logging from tendermint/tendermint and tendermint/go-*
+	// dependencies
+	lifecycle.CaptureTendermintLog15Output(logger)
+
+	cmdLogger := logger.With("command", "serve")
+
+	// if do.ChainId is not yet set, load chain_id for assertion from configuration file
+
+	if do.ChainId == "" {
+		if do.ChainId = do.Config.GetString("chain.assert_chain_id"); do.ChainId == "" {
+			return nil, fmt.Errorf("The config chain.assert_chain_id is empty, " +
+				"but should be set to the chain_id of the chain we are trying to run.")
+		}
+	}
+
+	cmdLogger.Info("chainId", do.ChainId,
+		"workingDirectory", do.WorkDir,
+		"dataDirectory", do.DataDir,
+		"genesisFile", do.GenesisFile,
+		structure.MessageKey, "Loading configuration for serve command")
+
+	consensusConfig, err := core.LoadConsensusModuleConfig(do)
 	if err != nil {
-		log.Fatalf("Failed to load application manager module configuration: %s.", err)
-		os.Exit(1)
+		return nil, fmt.Errorf("Failed to load consensus module configuration: %s.", err)
 	}
-	log.WithFields(log.Fields{
-		"consensusModule":    consensusConfig.Version,
-		"applicationManager": managerConfig.Version,
-	}).Debug("Modules configured")
 
-	newCore, err := core.NewCore(do.ChainId, consensusConfig, managerConfig)
+	managerConfig, err := core.LoadApplicationManagerModuleConfig(do)
 	if err != nil {
-		log.Fatalf("Failed to load core: %s", err)
+		return nil, fmt.Errorf("Failed to load application manager module configuration: %s.", err)
 	}
 
-	if !do.DisableRpc {
-		serverConfig, err := core.LoadServerConfig(do)
-		if err != nil {
-			log.Fatalf("Failed to load server configuration: %s.", err)
-			os.Exit(1)
-		}
+	cmdLogger.Info("consensusModule", consensusConfig.Version,
+		"applicationManager", managerConfig.Version,
+		structure.MessageKey, "Modules configured")
+
+	return core.NewCore(do.ChainId, consensusConfig, managerConfig, logger)
+}
 
-		serverProcess, err := newCore.NewGatewayV0(serverConfig)
+// ServeRunner() returns a command runner that prepares the environment and sets
+// up the core for Eris-DB to run. After the setup succeeds, it starts the core
+// and waits for the core to terminate.
+func ServeRunner(do *definitions.Do) func(*cobra.Command, []string) {
+	return func(cmd *cobra.Command, args []string) {
+		// load configuration from a single location to avoid a wrong configuration
+		// file is loaded.
+		err := do.ReadConfig(do.WorkDir, DefaultConfigBasename, DefaultConfigType)
 		if err != nil {
-			log.Fatalf("Failed to load servers: %s.", err)
-			os.Exit(1)
+			util.Fatalf("Fatal error reading configuration from %s/%s", do.WorkDir,
+				DefaultConfigFilename)
 		}
-		err = serverProcess.Start()
+
+		newCore, err := NewCoreFromDo(do)
+
 		if err != nil {
-			log.Fatalf("Failed to start servers: %s.", err)
-			os.Exit(1)
+			util.Fatalf("Failed to load core: %s", err)
 		}
-		_, err = newCore.NewGatewayTendermint(serverConfig)
-		if err != nil {
-			log.Fatalf("Failed to start Tendermint gateway")
+
+		if !do.DisableRpc {
+			serverConfig, err := core.LoadServerConfig(do)
+			if err != nil {
+				util.Fatalf("Failed to load server configuration: %s.", err)
+			}
+			serverProcess, err := newCore.NewGatewayV0(serverConfig)
+			if err != nil {
+				util.Fatalf("Failed to load servers: %s.", err)
+			}
+			err = serverProcess.Start()
+			if err != nil {
+				util.Fatalf("Failed to start servers: %s.", err)
+			}
+			_, err = newCore.NewGatewayTendermint(serverConfig)
+			if err != nil {
+				util.Fatalf("Failed to start Tendermint gateway")
+			}
+			<-serverProcess.StopEventChannel()
+		} else {
+			signals := make(chan os.Signal, 1)
+			signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
+			fmt.Fprintf(os.Stderr, "Received %s signal. Marmots out.", <-signals)
 		}
-		<-serverProcess.StopEventChannel()
-	} else {
-		signals := make(chan os.Signal, 1)
-		done := make(chan bool, 1)
-		signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
-		go func() {
-			signal := <-signals
-			// TODO: [ben] clean up core; in a manner consistent with enabled rpc
-			log.Fatalf("Received %s signal. Marmots out.", signal)
-			done <- true
-		}()
-		<-done
 	}
 }
 
diff --git a/config/config.go b/config/config.go
index a37cf96f9dcfd2c13ac278fc22fa48a3869209b1..32dc2c320950ee81bd0780cae6499baabd9c8824 100644
--- a/config/config.go
+++ b/config/config.go
@@ -174,3 +174,14 @@ func GetConfigurationFileBytes(chainId, moniker, seeds string, chainImageName st
 
 	return buffer.Bytes(), nil
 }
+
+func GetExampleConfigFileBytes() ([]byte, error) {
+	return GetConfigurationFileBytes(
+		"simplechain",
+		"delectable_marmot",
+		"192.168.168.255",
+		"db:latest",
+		true,
+		"46657",
+		"eris-db")
+}
diff --git a/config/config_test.go b/config/config_test.go
index 79286f35b4eb6fd79923d8ced725121ea218843d..e01346e846133f931b825009eb627aa6616f4b3b 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -17,13 +17,21 @@
 package config
 
 import (
+	"bytes"
 	"testing"
+
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
 )
 
-func TestConfigurationFileBytes(t *testing.T) {
-	// TODO: [ben] parse written bytes for comparison with expected parameters
-	if _, err := GetConfigurationFileBytes("simplechain", "marmot", "noseeds", "db:latest",
-		true, "", "eris-db"); err != nil {
-		t.Errorf("Error writing configuration file bytes: %s", err)
-	}
+// Since the logic for generating configuration files (in eris-cm) is split from
+// the logic for consuming them
+func TestGeneratedConfigIsUsable(t *testing.T) {
+	bs, err := GetExampleConfigFileBytes()
+	assert.NoError(t, err, "Should be able to create example config")
+	buf := bytes.NewBuffer(bs)
+	conf := viper.New()
+	viper.SetConfigType("toml")
+	err = conf.ReadConfig(buf)
+	assert.NoError(t, err, "Should be able to read example config into Viper")
 }
diff --git a/config/dump_config_test.go b/config/dump_config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ed71746c3ad2ff2aafe9132c572d8b6de97da530
--- /dev/null
+++ b/config/dump_config_test.go
@@ -0,0 +1,19 @@
+// +build dumpconfig
+
+// Space above matters
+package config
+
+import (
+	"io/ioutil"
+	"testing"
+	"github.com/stretchr/testify/assert"
+)
+
+// This is a little convenience for getting a config file dump. Just run:
+// go test -tags dumpconfig ./config
+// This pseudo test won't run unless the dumpconfig tag is 
+func TestDumpConfig(t *testing.T) {
+	bs, err := GetExampleConfigFileBytes()
+	assert.NoError(t, err, "Should be able to create example config")
+	ioutil.WriteFile("config_dump.toml", bs, 0644)
+}
diff --git a/config/viper.go b/config/viper.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd56d89e67a20fa55cf1bcfd1e2487071acb5318
--- /dev/null
+++ b/config/viper.go
@@ -0,0 +1,29 @@
+package config
+
+import (
+	"fmt"
+	"github.com/spf13/viper"
+)
+
+// Safely get the subtree from a viper config, returning an error if it could not
+// be obtained for any reason.
+func ViperSubConfig(conf *viper.Viper, configSubtreePath string) (subConfig *viper.Viper, err error) {
+	// Viper internally panics if `moduleName` contains an unallowed
+	// character (eg, a dash).
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("Viper panicked trying to read config subtree: %s",
+				configSubtreePath)
+		}
+	}()
+	if !conf.IsSet(configSubtreePath) {
+		return nil, fmt.Errorf("Failed to read config subtree: %s",
+			configSubtreePath)
+	}
+	subConfig = conf.Sub(configSubtreePath)
+	if subConfig == nil {
+		return nil, fmt.Errorf("Failed to read config subtree: %s",
+			configSubtreePath)
+	}
+	return subConfig, err
+}
diff --git a/consensus/consensus.go b/consensus/consensus.go
index f0795a5dde853f3ec6ee1aa046241aba5c25d250..03296eed028a0151a8a5ae3d696d58107caa96c9 100644
--- a/consensus/consensus.go
+++ b/consensus/consensus.go
@@ -28,13 +28,12 @@ func LoadConsensusEngineInPipe(moduleConfig *config.ModuleConfig,
 	pipe definitions.Pipe) error {
 	switch moduleConfig.Name {
 	case "tendermint":
-		tendermint, err := tendermint.NewTendermint(moduleConfig,
-			pipe.GetApplication())
+		tmint, err := tendermint.NewTendermint(moduleConfig, pipe.GetApplication())
 		if err != nil {
 			return fmt.Errorf("Failed to load Tendermint node: %v", err)
 		}
 
-		err = pipe.SetConsensusEngine(tendermint)
+		err = pipe.SetConsensusEngine(tmint)
 		if err != nil {
 			return fmt.Errorf("Failed to load Tendermint in pipe as "+
 				"ConsensusEngine: %v", err)
@@ -42,7 +41,7 @@ func LoadConsensusEngineInPipe(moduleConfig *config.ModuleConfig,
 
 		// For Tendermint we have a coupled Blockchain and ConsensusEngine
 		// implementation, so load it at the same time as ConsensusEngine
-		err = pipe.SetBlockchain(tendermint)
+		err = pipe.SetBlockchain(tmint)
 		if err != nil {
 			return fmt.Errorf("Failed to load Tendermint in pipe as "+
 				"Blockchain: %v", err)
diff --git a/consensus/tendermint/config.go b/consensus/tendermint/config.go
index 1bbb74bd6aa35be73c28c7bb75adea040cfddfbb..094d7979e429ae16ec2bd3ff39e59e926895a77d 100644
--- a/consensus/tendermint/config.go
+++ b/consensus/tendermint/config.go
@@ -23,10 +23,10 @@ import (
 	"path"
 	"time"
 
-	viper "github.com/spf13/viper"
+	"github.com/spf13/viper"
 	tendermintConfig "github.com/tendermint/go-config"
 
-	config "github.com/eris-ltd/eris-db/config"
+	"github.com/eris-ltd/eris-db/config"
 )
 
 // NOTE [ben] Compiler check to ensure TendermintConfig successfully implements
@@ -147,7 +147,8 @@ func (tmintConfig *TendermintConfig) GetMapString(key string) map[string]string
 func (tmintConfig *TendermintConfig) GetConfig(key string) tendermintConfig.Config {
 	// TODO: [ben] log out a warning as this indicates a potentially breaking code
 	// change from Tendermints side
-	if !tmintConfig.subTree.IsSet(key) {
+	subTree, _ := config.ViperSubConfig(tmintConfig.subTree, key)
+	if subTree == nil {
 		return &TendermintConfig{
 			subTree: viper.New(),
 		}
diff --git a/consensus/tendermint/tendermint.go b/consensus/tendermint/tendermint.go
index 237fb4a0bd9b3da558fa73a7014b0f14546db661..87553697880fcd144c14c0bf70ff3eab3cdcf0c2 100644
--- a/consensus/tendermint/tendermint.go
+++ b/consensus/tendermint/tendermint.go
@@ -71,10 +71,10 @@ func NewTendermint(moduleConfig *config.ModuleConfig,
 	if !moduleConfig.Config.IsSet("configuration") {
 		return nil, fmt.Errorf("Failed to extract Tendermint configuration subtree.")
 	}
-	tendermintConfigViper := moduleConfig.Config.Sub("configuration")
+	tendermintConfigViper, err := config.ViperSubConfig(moduleConfig.Config, "configuration")
 	if tendermintConfigViper == nil {
 		return nil,
-			fmt.Errorf("Failed to extract Tendermint configuration subtree.")
+				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/config.go b/core/config.go
index a2a82dbd1e07e649962f8ee37e2f61b48ceb25aa..cb8239ffee07b25c1cc5039cc32a87a7670c8975 100644
--- a/core/config.go
+++ b/core/config.go
@@ -24,13 +24,14 @@ import (
 	"os"
 	"path"
 
-	config "github.com/eris-ltd/eris-db/config"
-	consensus "github.com/eris-ltd/eris-db/consensus"
-	definitions "github.com/eris-ltd/eris-db/definitions"
-	manager "github.com/eris-ltd/eris-db/manager"
-	server "github.com/eris-ltd/eris-db/server"
-	util "github.com/eris-ltd/eris-db/util"
-	version "github.com/eris-ltd/eris-db/version"
+	"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/logging"
+	"github.com/eris-ltd/eris-db/manager"
+	"github.com/eris-ltd/eris-db/server"
+	"github.com/eris-ltd/eris-db/util"
+	"github.com/eris-ltd/eris-db/version"
 	"github.com/spf13/viper"
 )
 
@@ -75,17 +76,14 @@ func LoadModuleConfig(conf *viper.Viper, rootWorkDir, rootDataDir,
 			fmt.Errorf("Failed to create module data directory %s.", dataDir)
 	}
 	// load configuration subtree for module
-	// TODO: [ben] Viper internally panics if `moduleName` contains an unallowed
-	// character (eg, a dash).  Either this needs to be wrapped in a go-routine
-	// and recovered from or a PR to viper is needed to address this bug.
 	if !conf.IsSet(moduleName) {
 		return nil, fmt.Errorf("Failed to read configuration section for %s",
 			moduleName)
 	}
-	subConfig := conf.Sub(moduleName)
+	subConfig, err := config.ViperSubConfig(conf, moduleName)
 	if subConfig == nil {
-		return nil,
-			fmt.Errorf("Failed to read configuration section for %s.", moduleName)
+		return nil, fmt.Errorf("Failed to read configuration section for %s: %s",
+			moduleName, err)
 	}
 
 	return &config.ModuleConfig{
@@ -104,19 +102,24 @@ func LoadModuleConfig(conf *viper.Viper, rootWorkDir, rootDataDir,
 // LoadServerModuleConfig wraps specifically for the servers run by core
 func LoadServerConfig(do *definitions.Do) (*server.ServerConfig, error) {
 	// load configuration subtree for servers
-	if !do.Config.IsSet("servers") {
-		return nil, fmt.Errorf("Failed to read configuration section for servers")
-	}
-	subConfig := do.Config.Sub("servers")
-	if subConfig == nil {
-		return nil,
-			fmt.Errorf("Failed to read configuration section for servers")
+	subConfig, err := config.ViperSubConfig(do.Config, "servers")
+	if err != nil {
+		return nil, err
 	}
 	serverConfig, err := server.ReadServerConfig(subConfig)
+	if err != nil {
+		return nil, err
+	}
 	serverConfig.ChainId = do.ChainId
 	return serverConfig, err
 }
 
+func LoadLoggingConfig(do *definitions.Do) (*logging.LoggingConfig, error) {
+	//subConfig, err := SubConfig(conf, "logging")
+	loggingConfig := &logging.LoggingConfig{}
+	return loggingConfig, nil
+}
+
 //------------------------------------------------------------------------------
 // Helper functions
 
diff --git a/core/core.go b/core/core.go
index 03f56b56a47927b09d8b2bdc9e5a2302da1fcd84..c4e08963023b7de5ba4611a35ccf134dd378414d 100644
--- a/core/core.go
+++ b/core/core.go
@@ -32,6 +32,8 @@ import (
 	// 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/loggers"
 	rpc_tendermint "github.com/eris-ltd/eris-db/rpc/tendermint/core"
 	server "github.com/eris-ltd/eris-db/server"
 )
@@ -44,14 +46,16 @@ type Core struct {
 	tendermintPipe definitions.TendermintPipe
 }
 
-func NewCore(chainId string, consensusConfig *config.ModuleConfig,
-	managerConfig *config.ModuleConfig) (*Core, error) {
+func NewCore(chainId string,
+	consensusConfig *config.ModuleConfig,
+	managerConfig *config.ModuleConfig,
+	logger loggers.InfoTraceLogger) (*Core, error) {
 	// start new event switch, TODO: [ben] replace with eris-db/event
 	evsw := events.NewEventSwitch()
 	evsw.Start()
 
 	// start a new application pipe that will load an application manager
-	pipe, err := manager.NewApplicationPipe(managerConfig, evsw,
+	pipe, err := manager.NewApplicationPipe(managerConfig, evsw, logger,
 		consensusConfig.Version)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to load application pipe: %v", err)
@@ -65,12 +69,6 @@ func NewCore(chainId string, consensusConfig *config.ModuleConfig,
 	if err != nil {
 		log.Warn(fmt.Sprintf("Tendermint gateway not supported by %s",
 			managerConfig.Version))
-		return &Core{
-			chainId:        chainId,
-			evsw:           evsw,
-			pipe:           pipe,
-			tendermintPipe: nil,
-		}, nil
 	}
 	return &Core{
 		chainId:        chainId,
diff --git a/definitions/pipe.go b/definitions/pipe.go
index f15c60df5cbe4aceda0a9421ed824c35bb0b3ea3..68b4abc07a59902f5b63b6d350c8247732fef6aa 100644
--- a/definitions/pipe.go
+++ b/definitions/pipe.go
@@ -33,6 +33,7 @@ import (
 	event "github.com/eris-ltd/eris-db/event"
 	manager_types "github.com/eris-ltd/eris-db/manager/types"
 	"github.com/eris-ltd/eris-db/txs"
+	"github.com/eris-ltd/eris-db/logging/loggers"
 )
 
 type Pipe interface {
@@ -43,6 +44,7 @@ type Pipe interface {
 	Transactor() Transactor
 	// Hash of Genesis state
 	GenesisHash() []byte
+	Logger() loggers.InfoTraceLogger
 	// NOTE: [ben] added to Pipe interface on 0.12 refactor
 	GetApplication() manager_types.Application
 	SetConsensusEngine(consensusEngine consensus_types.ConsensusEngine) error
diff --git a/docs/generator.go b/docs/generator.go
index fb430fc067be744340d07c486593563e994a75fd..2d34fb2a358ecdb4991e8ce002851d440846d8c1 100644
--- a/docs/generator.go
+++ b/docs/generator.go
@@ -13,6 +13,7 @@ import (
 	clientCommands "github.com/eris-ltd/eris-db/client/cmd"
 	"github.com/eris-ltd/eris-db/version"
 	"github.com/spf13/cobra"
+	"github.com/eris-ltd/eris-db/definitions"
 )
 
 // Repository maintainers should customize the next two lines.
@@ -115,9 +116,9 @@ func AddClientToDB(dbCmd, clientCmd *cobra.Command) error {
 func main() {
 	// Repository maintainers should populate the top level command object.
 	erisDbCommand := commands.ErisDbCmd
-	commands.InitErisDbCli()
-	commands.AddCommands()
-	commands.AddGlobalFlags()
+	do := definitions.NewDo()
+	commands.AddGlobalFlags(do)
+	commands.AddCommands(do)
 
 	erisClientCommand := clientCommands.ErisClientCmd
 	clientCommands.InitErisClientInit()
diff --git a/glide.lock b/glide.lock
index 76c7e0b2c0dc6b668890e5388cc0820b9d4b430c..cfd0d6066dea987150500aa66f1a88ff75ad7e73 100644
--- a/glide.lock
+++ b/glide.lock
@@ -30,8 +30,6 @@ imports:
   - go/common
 - name: github.com/eris-ltd/eris-keys
   version: 114ebc77443db9a153692233294e48bc7e184215
-- name: github.com/eris-ltd/eris-logger
-  version: ea48a395d6ecc0eccc67a26da9fc7a6106fabb84
 - name: github.com/fsnotify/fsnotify
   version: 30411dbcefb7a1da7e84f75530ad3abe4011b4f8
 - name: github.com/gin-gonic/gin
@@ -215,4 +213,24 @@ imports:
   version: ecde8c8f16df93a994dda8936c8f60f0c26c28ab
 - name: gopkg.in/yaml.v2
   version: a83829b6f1293c91addabc89d0571c246397bbf4
+- name: github.com/go-kit/kit
+  version: f66b0e13579bfc5a48b9e2a94b1209c107ea1f41
+  subpackages:
+  - log
+- name: github.com/eapache/channels
+  version: 47238d5aae8c0fefd518ef2bee46290909cf8263
+- name: github.com/eapache/queue
+  version: 44cc805cf13205b55f69e14bcb69867d1ae92f98
+- name: github.com/go-logfmt/logfmt
+  version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
+- name: github.com/go-stack/stack
+  version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
+- name: github.com/Sirupsen/logrus
+  version: d26492970760ca5d33129d2d799e34be5c4782eb
+- name: github.com/inconshreveable/log15
+  version: 46a701a619de90c65a78c04d1a58bf02585e9701
+  subpackages:
+  - term
+- name: github.com/eris-ltd/eris-logger
+  version: ea48a395d6ecc0eccc67a26da9fc7a6106fabb84
 devImports: []
diff --git a/glide.yaml b/glide.yaml
index ca8e52b5c438cf55e2cb5a85ca74617f8d53c485..7b6f0398c08f09f4ba7de4b54177d805a4a48583 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -1,6 +1,5 @@
 package: github.com/eris-ltd/eris-db
 import:
-- package: github.com/eris-ltd/eris-logger
 - package: github.com/eris-ltd/eris-keys
 - package: github.com/spf13/cobra
 - package: github.com/spf13/viper
@@ -15,5 +14,18 @@ import:
   - ripemd160
 - package: gopkg.in/fatih/set.v0
 - package: gopkg.in/tylerb/graceful.v1
-- package: golang.org/x/net/http2
+- package: golang.org/x/net
+  subpackages:
+  - http2
 - package: github.com/eris-ltd/common
+- package: github.com/go-kit/kit
+  version: ^0.3.0
+- package: github.com/eapache/channels
+  version: ~1.1.0
+- package: github.com/go-logfmt/logfmt
+  version: ^0.3.0
+- package: github.com/go-stack/stack
+  version: ^1.5.2
+- package: github.com/inconshreveable/log15
+- package: github.com/Sirupsen/logrus
+  version: ^0.11.0
diff --git a/logging/adapters/adapters.go b/logging/adapters/adapters.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b0cec85a77743071a9487b730c4d3ad4e77825c
--- /dev/null
+++ b/logging/adapters/adapters.go
@@ -0,0 +1,2 @@
+package adapters
+
diff --git a/logging/adapters/logrus/logrus.go b/logging/adapters/logrus/logrus.go
new file mode 100644
index 0000000000000000000000000000000000000000..708bac4d0cc7680bcd4c4ccf64ca07e21aaa9c3e
--- /dev/null
+++ b/logging/adapters/logrus/logrus.go
@@ -0,0 +1,23 @@
+package adapters
+
+import (
+	"github.com/Sirupsen/logrus"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+type logrusLogger struct {
+	logger logrus.Logger
+}
+
+var _ kitlog.Logger = (*logrusLogger)(nil)
+
+func NewLogrusLogger(logger logrus.Logger) *logrusLogger {
+	return &logrusLogger{
+		logger: logger,
+	}
+}
+
+func (ll *logrusLogger) Log(keyvals... interface{}) error {
+	return nil
+}
+
diff --git a/logging/adapters/stdlib/capture.go b/logging/adapters/stdlib/capture.go
new file mode 100644
index 0000000000000000000000000000000000000000..dfa0a85f1bd3994744e1a64a909e620644d1a508
--- /dev/null
+++ b/logging/adapters/stdlib/capture.go
@@ -0,0 +1,26 @@
+package stdlib
+
+import (
+	"io"
+	"log"
+
+	"github.com/eris-ltd/eris-db/logging/loggers"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+func Capture(stdLibLogger log.Logger,
+logger loggers.InfoTraceLogger) io.Writer {
+	adapter := newAdapter(logger)
+	stdLibLogger.SetOutput(adapter)
+	return adapter
+}
+
+func CaptureRootLogger(logger loggers.InfoTraceLogger) io.Writer {
+	adapter := newAdapter(logger)
+	log.SetOutput(adapter)
+	return adapter
+}
+
+func newAdapter(logger loggers.InfoTraceLogger) io.Writer {
+	return kitlog.NewStdlibAdapter(logger)
+}
diff --git a/logging/adapters/tendermint_log15/capture.go b/logging/adapters/tendermint_log15/capture.go
new file mode 100644
index 0000000000000000000000000000000000000000..afc5d29eedcc61aacbbe8318775da50e74be0468
--- /dev/null
+++ b/logging/adapters/tendermint_log15/capture.go
@@ -0,0 +1,47 @@
+package adapters
+
+import (
+	"github.com/eris-ltd/eris-db/logging/loggers"
+	kitlog "github.com/go-kit/kit/log"
+	"github.com/tendermint/log15"
+)
+
+type infoTraceLoggerAsLog15Handler struct {
+	logger loggers.InfoTraceLogger
+}
+
+var _ log15.Handler = (*infoTraceLoggerAsLog15Handler)(nil)
+
+type log15HandlerAsKitLogger struct {
+	handler log15.Handler
+}
+
+var _ kitlog.Logger = (*log15HandlerAsKitLogger)(nil)
+
+func (l *log15HandlerAsKitLogger) Log(keyvals ...interface{}) error {
+	record := LogLineToRecord(keyvals...)
+	return l.handler.Log(record)
+}
+
+func (h *infoTraceLoggerAsLog15Handler) Log(record *log15.Record) error {
+	if record.Lvl < log15.LvlDebug {
+		// Send to Critical, Warning, Error, and Info to the Info channel
+		h.logger.Info(RecordToLogLine(record)...)
+	} else {
+		// Send to Debug to the Trace channel
+		h.logger.Trace(RecordToLogLine(record)...)
+	}
+	return nil
+}
+
+func Log15HandlerAsKitLogger(handler log15.Handler) kitlog.Logger {
+	return &log15HandlerAsKitLogger{
+		handler: handler,
+	}
+}
+
+func InfoTraceLoggerAsLog15Handler(logger loggers.InfoTraceLogger) log15.Handler {
+	return &infoTraceLoggerAsLog15Handler{
+		logger: logger,
+	}
+}
diff --git a/logging/adapters/tendermint_log15/convert.go b/logging/adapters/tendermint_log15/convert.go
new file mode 100644
index 0000000000000000000000000000000000000000..52cda465988566abeda50ac6ab28fad5c8a90838
--- /dev/null
+++ b/logging/adapters/tendermint_log15/convert.go
@@ -0,0 +1,70 @@
+package adapters
+
+import (
+	"time"
+
+	"github.com/eris-ltd/eris-db/logging/loggers"
+	"github.com/eris-ltd/eris-db/logging/structure"
+	. "github.com/eris-ltd/eris-db/util/slice"
+	"github.com/go-stack/stack"
+	"github.com/tendermint/log15"
+)
+
+// Convert a go-kit log line (i.e. keyvals... interface{}) into a log15 record
+// This allows us to use log15 output handlers
+func LogLineToRecord(keyvals ...interface{}) *log15.Record {
+	vals, ctx := structure.ValuesAndContext(keyvals, structure.TimeKey,
+		structure.MessageKey, structure.CallerKey, structure.LevelKey)
+
+	// Mapping of log line to Record is on a best effort basis
+	theTime, _ := vals[structure.TimeKey].(time.Time)
+	call, _ := vals[structure.CallerKey].(stack.Call)
+	level, _ := vals[structure.LevelKey].(string)
+	message, _ := vals[structure.MessageKey].(string)
+
+	return &log15.Record{
+		Time: theTime,
+		Lvl:  Log15LvlFromString(level),
+		Msg:  message,
+		Call: call,
+		Ctx:  append(ctx, structure.CallerKey, call),
+		KeyNames: log15.RecordKeyNames{
+			Time: structure.TimeKey,
+			Msg:  structure.MessageKey,
+			Lvl:  structure.LevelKey,
+		}}
+}
+
+// Convert a log15 record to a go-kit log line (i.e. keyvals... interface{})
+// This allows us to capture output from dependencies using log15
+func RecordToLogLine(record *log15.Record) []interface{} {
+	return Concat(
+		Slice(
+			structure.TimeKey, record.Time,
+			structure.CallerKey, record.Call,
+			structure.LevelKey, record.Lvl.String(),
+		),
+		record.Ctx,
+		Slice(
+			structure.MessageKey, record.Msg,
+		))
+}
+
+// Collapse our weak notion of leveling and log15's into a log15.Lvl
+func Log15LvlFromString(level string) log15.Lvl {
+	if level == "" {
+		return log15.LvlDebug
+	}
+	switch level {
+	case loggers.InfoLevelName:
+		return log15.LvlInfo
+	case loggers.TraceLevelName:
+		return log15.LvlDebug
+	default:
+		lvl, err := log15.LvlFromString(level)
+		if err == nil {
+			return lvl
+		}
+		return log15.LvlDebug
+	}
+}
diff --git a/logging/config.go b/logging/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..b43b1b5545ba47586cb3e61a01589bac4de564c0
--- /dev/null
+++ b/logging/config.go
@@ -0,0 +1,11 @@
+package logging
+
+type (
+	SinkConfig struct {
+		Channels []string
+	}
+
+	LoggingConfig struct {
+		Sinks []SinkConfig
+	}
+)
diff --git a/logging/lifecycle/lifecycle.go b/logging/lifecycle/lifecycle.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd85a3248a1d5329d66077b4b8d8720d8d7a242a
--- /dev/null
+++ b/logging/lifecycle/lifecycle.go
@@ -0,0 +1,37 @@
+package lifecycle
+
+// No package in ./logging/... should depend on lifecycle
+
+import (
+	"os"
+
+	"github.com/eris-ltd/eris-db/logging"
+	tmLog15adapter "github.com/eris-ltd/eris-db/logging/adapters/tendermint_log15"
+	"github.com/eris-ltd/eris-db/logging/loggers"
+	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 {
+	infoLogger := kitlog.NewLogfmtLogger(os.Stderr)
+	traceLogger := kitlog.NewLogfmtLogger(os.Stderr)
+	return logging.WithMetadata(loggers.NewInfoTraceLogger(infoLogger, traceLogger))
+}
+
+func NewStdErrLogger() loggers.InfoTraceLogger {
+	logger := tmLog15adapter.Log15HandlerAsKitLogger(
+		tmLog15.StreamHandler(os.Stderr, tmLog15.TerminalFormat()))
+	return NewLogger(logger, logger)
+}
+
+func NewLogger(infoLogger, traceLogger kitlog.Logger) loggers.InfoTraceLogger {
+	infoTraceLogger := loggers.NewInfoTraceLogger(infoLogger, traceLogger)
+	return logging.WithMetadata(infoTraceLogger)
+}
+
+func CaptureTendermintLog15Output(infoTraceLogger loggers.InfoTraceLogger) {
+	tmLog15.Root().SetHandler(
+		tmLog15adapter.InfoTraceLoggerAsLog15Handler(infoTraceLogger.
+			With(structure.ComponentKey, "tendermint")))
+}
diff --git a/logging/loggers/channel_logger.go b/logging/loggers/channel_logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..20e824ef804deaf9de64266d94c1ef8338d47344
--- /dev/null
+++ b/logging/loggers/channel_logger.go
@@ -0,0 +1,73 @@
+package loggers
+
+import (
+	"github.com/eapache/channels"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+const (
+	LoggingRingBufferCap channels.BufferCap = 100
+)
+
+type ChannelLogger struct {
+	ch channels.Channel
+}
+
+var _ kitlog.Logger = (*ChannelLogger)(nil)
+
+// Creates a Logger that uses a uses a non-blocking channel.
+//
+// We would like calls to Log to never block so we use a channel implementation
+// that is non-blocking on writes and is able to be so by using a finite ring
+// buffer.
+func newChannelLogger() *ChannelLogger {
+	return &ChannelLogger{
+		ch: channels.NewRingChannel(LoggingRingBufferCap),
+	}
+}
+
+func (cl *ChannelLogger) Log(keyvals ...interface{}) error {
+	cl.ch.In() <- keyvals
+	// We don't have a way to pass on any logging errors, but that's okay: Log is
+	// a maximal interface and the error return type is only there for special
+	// cases.
+	return nil
+}
+
+// Read a log line by waiting until one is available and returning it
+func (cl *ChannelLogger) WaitReadLogLine() []interface{} {
+	log := <-cl.ch.Out()
+	// We are passing slices of interfaces down this channel (go-kit log's Log
+	// interface type), a panic is the right thing to do if this type assertion
+	// fails.
+	return log.([]interface{})
+}
+
+// Tries to read a log line from the channel buffer or returns nil if none is
+// immediately available
+func (cl *ChannelLogger) ReadLogLine() []interface{} {
+	select {
+	case log := <-cl.ch.Out():
+		// See WaitReadLogLine
+		return log.([]interface{})
+	default:
+		return nil
+	}
+}
+
+// Enters an infinite loop that will drain any log lines from the passed logger.
+//
+// Exits if the channel is closed.
+func (cl *ChannelLogger) DrainChannelToLogger(logger kitlog.Logger) {
+	for cl.ch.Out() != nil {
+		logger.Log(cl.WaitReadLogLine()...)
+	}
+}
+
+// Wraps an underlying Logger baseLogger to provide a Logger that is
+// is non-blocking on calls to Log.
+func NonBlockingLogger(logger kitlog.Logger) *ChannelLogger {
+	cl := newChannelLogger()
+	go cl.DrainChannelToLogger(logger)
+	return cl
+}
diff --git a/logging/loggers/channel_logger_test.go b/logging/loggers/channel_logger_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..087bd5b4146dd82b4624a3f8c17d126bb8739196
--- /dev/null
+++ b/logging/loggers/channel_logger_test.go
@@ -0,0 +1,32 @@
+package loggers
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"fmt"
+)
+
+func TestChannelLogger(t *testing.T) {
+	cl := newChannelLogger()
+
+	// Push a larger number of log messages than will fit into ring buffer
+	for i := 0; i < int(LoggingRingBufferCap)+10; i++ {
+		cl.Log("log line", i)
+	}
+
+	// Observe that oldest 10 messages are overwritten (so first message is 10)
+	for i := 0; i < int(LoggingRingBufferCap); i++ {
+		ll := cl.WaitReadLogLine()
+		assert.Equal(t, 10+i, ll[1])
+	}
+
+	assert.Nil(t, cl.ReadLogLine(), "Since we have drained the buffer there "+
+		"should be no more log lines.")
+}
+
+func TestBlether(t *testing.T) {
+	var bs []byte
+	ext := append(bs, )
+	fmt.Println(ext)
+}
\ No newline at end of file
diff --git a/logging/loggers/info_trace_logger.go b/logging/loggers/info_trace_logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..7374477a92a40a80a8d726bf4da136f203e645af
--- /dev/null
+++ b/logging/loggers/info_trace_logger.go
@@ -0,0 +1,126 @@
+package loggers
+
+import (
+	"github.com/eris-ltd/eris-db/logging/structure"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+const (
+	InfoChannelName  = "Info"
+	TraceChannelName = "Trace"
+
+	InfoLevelName  = InfoChannelName
+	TraceLevelName = TraceChannelName
+)
+
+type infoTraceLogger struct {
+	infoLogger  *kitlog.Context
+	traceLogger *kitlog.Context
+}
+
+// InfoTraceLogger maintains two independent concurrently-safe channels of
+// logging. The idea behind the independence is that you can ignore one channel
+// with no performance penalty. For more fine grained filtering or aggregation
+// the Info and Trace loggers can be decorated loggers that perform arbitrary
+// filtering/routing/aggregation on log messages.
+type InfoTraceLogger interface {
+	// Send a log message to the default channel
+	kitlog.Logger
+
+	// Send an log message to the Info channel, formed of a sequence of key value
+	// pairs. Info messages should be operationally interesting to a human who is
+	// monitoring the logs. But not necessarily a human who is trying to
+	// understand or debug the system. Any handled errors or warnings should be
+	// sent to the Info channel (where you may wish to tag them with a suitable
+	// key-value pair to categorise them as such).
+	Info(keyvals ...interface{})
+
+	// Send an log message to the Trace channel, formed of a sequence of key-value
+	// pairs. Trace messages can be used for any state change in the system that
+	// may be of interest to a machine consumer or a human who is trying to debug
+	// the system or trying to understand the system in detail. If the messages
+	// are very point-like and contain little structure, consider using a metric
+	// instead.
+	Trace(keyvals ...interface{})
+
+	// A logging context (see go-kit log's Context). Takes a sequence key values
+	// via With or WithPrefix and ensures future calls to log will have those
+	// contextual values appended to the call to an underlying logger.
+	// Values can be dynamic by passing an instance of the kitlog.Valuer interface
+	// This provides an interface version of the kitlog.Context struct to be used
+	// For implementations that wrap a kitlog.Context. In addition it makes no
+	// assumption about the name or signature of the logging method(s).
+	// See InfoTraceLogger
+
+	// Establish a context by appending contextual key-values to any existing
+	// contextual values
+	With(keyvals ...interface{}) InfoTraceLogger
+
+	// Establish a context by prepending contextual key-values to any existing
+	// contextual values
+	WithPrefix(keyvals ...interface{}) InfoTraceLogger
+}
+
+// Interface assertions
+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 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,
+		})))
+	return &infoTraceLogger{
+		infoLogger: logger.With(
+			structure.ChannelKey, InfoChannelName,
+			structure.LevelKey, InfoLevelName,
+		),
+		traceLogger: logger.With(
+			structure.ChannelKey, TraceChannelName,
+			structure.LevelKey, TraceLevelName,
+		),
+	}
+}
+
+func NewNoopInfoTraceLogger() InfoTraceLogger {
+	noopLogger := kitlog.NewNopLogger()
+	return NewInfoTraceLogger(noopLogger, noopLogger)
+}
+
+func (l *infoTraceLogger) With(keyvals ...interface{}) InfoTraceLogger {
+	return &infoTraceLogger{
+		infoLogger:  l.infoLogger.With(keyvals...),
+		traceLogger: l.traceLogger.With(keyvals...),
+	}
+}
+
+func (l *infoTraceLogger) WithPrefix(keyvals ...interface{}) InfoTraceLogger {
+	return &infoTraceLogger{
+		infoLogger:  l.infoLogger.WithPrefix(keyvals...),
+		traceLogger: l.traceLogger.WithPrefix(keyvals...),
+	}
+}
+
+func (l *infoTraceLogger) Info(keyvals ...interface{}) {
+	// We send Info and Trace log lines down the same pipe to keep them ordered
+	l.infoLogger.Log(keyvals...)
+}
+
+func (l *infoTraceLogger) Trace(keyvals ...interface{}) {
+	l.traceLogger.Log(keyvals...)
+}
+
+// If logged to as a plain kitlog logger presume the message is for Trace
+// This favours keeping Info reasonably quiet. Note that an InfoTraceLogger
+// aware adapter can make its own choices, but we tend to thing of logs from
+// dependencies as less interesting than logs generated by us or specifically
+// routed by us.
+func (l *infoTraceLogger) Log(keyvals ...interface{}) error {
+	l.Trace(keyvals...)
+	return nil
+}
diff --git a/logging/loggers/info_trace_logger_test.go b/logging/loggers/info_trace_logger_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..505f2606badbeb178f9046982a6d130030225456
--- /dev/null
+++ b/logging/loggers/info_trace_logger_test.go
@@ -0,0 +1,14 @@
+package loggers
+
+import (
+	"os"
+	"testing"
+
+	kitlog "github.com/go-kit/kit/log"
+)
+
+func TestLogger(t *testing.T) {
+	stderrLogger := kitlog.NewLogfmtLogger(os.Stderr)
+	logger := NewInfoTraceLogger(stderrLogger, stderrLogger)
+	logger.Trace("hello", "barry")
+}
diff --git a/logging/loggers/logging_test.go b/logging/loggers/logging_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..78166fc91ca74989abeb7e08daf793dce10595a2
--- /dev/null
+++ b/logging/loggers/logging_test.go
@@ -0,0 +1,21 @@
+package loggers
+
+import "errors"
+
+type testLogger struct {
+	logLines [][]interface{}
+	err      error
+}
+
+func newErrorLogger(errMessage string) *testLogger {
+	return &testLogger{err: errors.New(errMessage)}
+}
+
+func newTestLogger() *testLogger {
+	return &testLogger{}
+}
+
+func (tl *testLogger) Log(keyvals ...interface{}) error {
+	tl.logLines = append(tl.logLines, keyvals)
+	return tl.err
+}
diff --git a/logging/loggers/multiple_channel_logger.go b/logging/loggers/multiple_channel_logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..9f68b4df84e9957638239cb46861fc72f4b4a3da
--- /dev/null
+++ b/logging/loggers/multiple_channel_logger.go
@@ -0,0 +1,37 @@
+package loggers
+
+import (
+	"fmt"
+
+	"github.com/eris-ltd/eris-db/logging/structure"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+// This represents a 'SELECT ONE' type logger. When logged to it will search
+// for the ChannelKey field, look that up in its map and send the log line there
+// Otherwise logging is a noop (but an error will be returned - which is optional)
+type MultipleChannelLogger map[string]kitlog.Logger
+
+var _ kitlog.Logger = MultipleChannelLogger(nil)
+
+// Like go-kit log's Log method only logs a message to the specified channelName
+// which must be a member of this MultipleChannelLogger
+func (mcl MultipleChannelLogger) Log(keyvals ...interface{}) error {
+	channel := structure.Value(keyvals, structure.ChannelKey)
+	if channel == nil {
+		return fmt.Errorf("MultipleChannelLogger could not select channel because" +
+				" '%s' was not set in log message", structure.ChannelKey)
+	}
+	channelName, ok := channel.(string)
+	if !ok {
+		return fmt.Errorf("MultipleChannelLogger could not select channel because" +
+				" channel was set to non-string value %v", channel)
+	}
+	logger := mcl[channelName]
+	if logger == nil {
+		return fmt.Errorf("Could not log to channel '%s', since it is not "+
+			"registered with this MultipleChannelLogger (the underlying logger may "+
+			"have been nil when passed to NewMultipleChannelLogger)", channelName)
+	}
+	return logger.Log(keyvals...)
+}
diff --git a/logging/loggers/multiple_channel_logger_test.go b/logging/loggers/multiple_channel_logger_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..db8f5013a13c636af96c2791d0ee5e00a38695fc
--- /dev/null
+++ b/logging/loggers/multiple_channel_logger_test.go
@@ -0,0 +1,28 @@
+package loggers
+
+import (
+	"runtime"
+	"testing"
+	"time"
+
+	"github.com/eris-ltd/eris-db/logging/structure"
+	kitlog "github.com/go-kit/kit/log"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMultipleChannelLogger(t *testing.T) {
+	boringLogger, interestingLogger := newTestLogger(), newTestLogger()
+	mcl := kitlog.NewContext(MultipleChannelLogger(map[string]kitlog.Logger{
+		"Boring":      boringLogger,
+		"Interesting": interestingLogger,
+	}))
+	err := mcl.With("time", kitlog.Valuer(func() interface{} { return "aa" })).
+		Log(structure.ChannelKey, "Boring", "foo", "bar")
+	assert.NoError(t, err, "Should log without an error")
+	// Wait for channel to drain
+	time.Sleep(time.Second)
+	runtime.Gosched()
+	assert.Equal(t, []interface{}{"time", "aa", structure.ChannelKey, "Boring",
+		"foo", "bar"},
+		boringLogger.logLines[0])
+}
diff --git a/logging/loggers/multiple_output_logger.go b/logging/loggers/multiple_output_logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..9f3cb5326aa1a98d0f3fcfad6462051965749064
--- /dev/null
+++ b/logging/loggers/multiple_output_logger.go
@@ -0,0 +1,50 @@
+package loggers
+
+import (
+	"strings"
+
+	kitlog "github.com/go-kit/kit/log"
+)
+
+// This represents an 'AND' type logger. When logged to it will log to each of
+// the loggers in the slice.
+type MultipleOutputLogger []kitlog.Logger
+
+var _ kitlog.Logger = MultipleOutputLogger(nil)
+
+func (mol MultipleOutputLogger) Log(keyvals ...interface{}) error {
+	var errs []error
+	for _, logger := range mol {
+		err := logger.Log(keyvals...)
+		if err != nil {
+			errs = append(errs, err)
+		}
+	}
+	return combineErrors(errs)
+}
+
+// Creates a logger that forks log messages to each of its outputLoggers
+func NewMultipleOutputLogger(outputLoggers ...kitlog.Logger) kitlog.Logger {
+	return MultipleOutputLogger(outputLoggers)
+}
+
+type multipleErrors []error
+
+func combineErrors(errs []error) error {
+	switch len(errs) {
+	case 0:
+		return nil
+	case 1:
+		return errs[0]
+	default:
+		return multipleErrors(errs)
+	}
+}
+
+func (errs multipleErrors) Error() string {
+	var errStrings []string
+	for _, err := range errs {
+		errStrings = append(errStrings, err.Error())
+	}
+	return strings.Join(errStrings, ";")
+}
diff --git a/logging/loggers/multiple_output_logger_test.go b/logging/loggers/multiple_output_logger_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..786c60064947d3475c60275f45140ce39edde199
--- /dev/null
+++ b/logging/loggers/multiple_output_logger_test.go
@@ -0,0 +1,18 @@
+package loggers
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewMultipleOutputLogger(t *testing.T) {
+	a, b := newErrorLogger("error a"), newErrorLogger("error b")
+	mol := NewMultipleOutputLogger(a, b)
+	logLine := []interface{}{"msg", "hello"}
+	err := mol.Log(logLine...)
+	expected := [][]interface{}{logLine}
+	assert.Equal(t, expected, a.logLines)
+	assert.Equal(t, expected, b.logLines)
+	assert.IsType(t, multipleErrors{}, err)
+}
diff --git a/logging/metadata.go b/logging/metadata.go
new file mode 100644
index 0000000000000000000000000000000000000000..3fbed2cf4c819a5e28c0788515a3920f45b209ee
--- /dev/null
+++ b/logging/metadata.go
@@ -0,0 +1,27 @@
+package logging
+
+import (
+	"time"
+
+	"github.com/eris-ltd/eris-db/logging/loggers"
+	"github.com/eris-ltd/eris-db/logging/structure"
+	kitlog "github.com/go-kit/kit/log"
+)
+
+const (
+	// To get the Caller information correct on the log, we need to count the
+	// number of calls from a log call in the code to the time it hits a kitlog
+	// context: [log call site (5), Info/Trace (4), MultipleChannelLogger.Log (3),
+	// kitlog.Context.Log (2), kitlog.bindValues (1) (binding occurs),
+	// kitlog.Caller (0), stack.caller]
+	infoTraceLoggerCallDepth = 5
+)
+
+var defaultTimestampUTCValuer kitlog.Valuer = func() interface{} {
+	return time.Now()
+}
+
+func WithMetadata(infoTraceLogger loggers.InfoTraceLogger) loggers.InfoTraceLogger {
+	return infoTraceLogger.With(structure.TimeKey, defaultTimestampUTCValuer,
+		structure.CallerKey, kitlog.Caller(infoTraceLoggerCallDepth))
+}
diff --git a/logging/structure/structure.go b/logging/structure/structure.go
new file mode 100644
index 0000000000000000000000000000000000000000..9420b20095b4fabab87ce707f082d17968f90ba3
--- /dev/null
+++ b/logging/structure/structure.go
@@ -0,0 +1,60 @@
+package structure
+
+import . "github.com/eris-ltd/eris-db/util/slice"
+
+const (
+	// Key for go time.Time object
+	TimeKey = "time"
+	// Key for call site for log invocation
+	CallerKey = "caller"
+	// Key for String name for level
+	LevelKey   = "level"
+	// Key to switch on for channel in a multiple channel logging context
+	ChannelKey = "channel"
+	// Key for string message
+	MessageKey = "message"
+	// Key for module or function or struct that is the subject of the logging
+	ComponentKey = "component"
+)
+
+// Pull the specified values from a structured log line into a map.
+// Assumes keys are single-valued.
+// Returns a map of the key-values from the requested keys and
+// the unmatched remainder keyvals as context as a slice of key-values.
+func ValuesAndContext(keyvals []interface{},
+	keys ...interface{}) (map[interface{}]interface{}, []interface{}) {
+	vals := make(map[interface{}]interface{}, len(keys))
+	context := make([]interface{}, len(keyvals))
+	copy(context, keyvals)
+	deletions := 0
+	// We can't really do better than a linear scan of both lists here. N is small
+	// so screw the asymptotics.
+	// Guard against odd-length list
+	for i := 0; i < 2*(len(keyvals)/2); i += 2 {
+		for k := 0; k < len(keys); k++ {
+			if keyvals[i] == keys[k] {
+				// Pull the matching key-value pair into vals to return
+				vals[keys[k]] = keyvals[i+1]
+				// Delete the key once it's found
+				keys = DeleteAt(keys, k)
+				// And remove the key-value pair from context
+				context = Delete(context, i-deletions, 2)
+				// Keep a track of how much we've shrunk the context to offset next
+				// deletion
+				deletions += 2
+				break
+			}
+		}
+	}
+	return vals, context
+}
+
+// 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 {
+		if keyvals[i] == key {
+			return keyvals[i+1]
+		}
+	}
+	return nil
+}
\ No newline at end of file
diff --git a/logging/structure/structure_test.go b/logging/structure/structure_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2fbdaa3e5e1c525c5ccf836b430ad13ebbb5333a
--- /dev/null
+++ b/logging/structure/structure_test.go
@@ -0,0 +1,15 @@
+package structure
+
+import (
+	"testing"
+
+	. "github.com/eris-ltd/eris-db/util/slice"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestValuesAndContext(t *testing.T) {
+	keyvals := Slice("hello", 1, "dog", 2, "fish", 3, "fork", 5)
+	vals, ctx := ValuesAndContext(keyvals, "hello", "fish")
+	assert.Equal(t, map[interface{}]interface{}{"hello": 1, "fish": 3}, vals)
+	assert.Equal(t, Slice("dog", 2, "fork", 5), ctx)
+}
diff --git a/logging/terminal.go b/logging/terminal.go
new file mode 100644
index 0000000000000000000000000000000000000000..e8eea7d8107484f1f5bd79a745f042e77fa49e8f
--- /dev/null
+++ b/logging/terminal.go
@@ -0,0 +1,29 @@
+package logging
+
+import (
+	"github.com/eris-ltd/eris-db/logging/structure"
+	"github.com/go-kit/kit/log/term"
+)
+
+func Colors(keyvals ...interface{}) term.FgBgColor {
+	for i := 0; i < len(keyvals)-1; i += 2 {
+		if keyvals[i] != structure.LevelKey {
+			continue
+		}
+		switch keyvals[i+1] {
+		case "debug":
+			return term.FgBgColor{Fg: term.DarkGray}
+		case "info":
+			return term.FgBgColor{Fg: term.Gray}
+		case "warn":
+			return term.FgBgColor{Fg: term.Yellow}
+		case "error":
+			return term.FgBgColor{Fg: term.Red}
+		case "crit":
+			return term.FgBgColor{Fg: term.Gray, Bg: term.DarkRed}
+		default:
+			return term.FgBgColor{}
+		}
+	}
+	return term.FgBgColor{}
+}
diff --git a/manager/eris-mint/pipe.go b/manager/eris-mint/pipe.go
index dc9b64875319a7fb50c6fb6c269778f2d9272d5a..d9691f27081580b8a7e45d603bbc8bf955744c6f 100644
--- a/manager/eris-mint/pipe.go
+++ b/manager/eris-mint/pipe.go
@@ -28,8 +28,6 @@ import (
 	tm_types "github.com/tendermint/tendermint/types"
 	tmsp_types "github.com/tendermint/tmsp/types"
 
-	log "github.com/eris-ltd/eris-logger"
-
 	"github.com/eris-ltd/eris-db/account"
 	blockchain_types "github.com/eris-ltd/eris-db/blockchain/types"
 	imath "github.com/eris-ltd/eris-db/common/math/integral"
@@ -38,12 +36,15 @@ import (
 	core_types "github.com/eris-ltd/eris-db/core/types"
 	"github.com/eris-ltd/eris-db/definitions"
 	edb_event "github.com/eris-ltd/eris-db/event"
+	"github.com/eris-ltd/eris-db/logging/loggers"
+	"github.com/eris-ltd/eris-db/logging/structure"
 	vm "github.com/eris-ltd/eris-db/manager/eris-mint/evm"
 	"github.com/eris-ltd/eris-db/manager/eris-mint/state"
 	state_types "github.com/eris-ltd/eris-db/manager/eris-mint/state/types"
 	manager_types "github.com/eris-ltd/eris-db/manager/types"
 	rpc_tm_types "github.com/eris-ltd/eris-db/rpc/tendermint/core/types"
 	"github.com/eris-ltd/eris-db/txs"
+	log "github.com/eris-ltd/eris-logger"
 )
 
 type erisMintPipe struct {
@@ -59,6 +60,7 @@ type erisMintPipe struct {
 	// Genesis cache
 	genesisDoc   *state_types.GenesisDoc
 	genesisState *state.State
+	logger       loggers.InfoTraceLogger
 }
 
 // NOTE [ben] Compiler check to ensure erisMintPipe successfully implements
@@ -70,7 +72,8 @@ var _ definitions.Pipe = (*erisMintPipe)(nil)
 var _ definitions.TendermintPipe = (*erisMintPipe)(nil)
 
 func NewErisMintPipe(moduleConfig *config.ModuleConfig,
-	eventSwitch *go_events.EventSwitch) (*erisMintPipe, error) {
+	eventSwitch *go_events.EventSwitch,
+	logger loggers.InfoTraceLogger) (*erisMintPipe, error) {
 
 	startedState, genesisDoc, err := startState(moduleConfig.DataDir,
 		moduleConfig.Config.GetString("db_backend"), moduleConfig.GenesisFile,
@@ -79,11 +82,11 @@ func NewErisMintPipe(moduleConfig *config.ModuleConfig,
 		return nil, fmt.Errorf("Failed to start state: %v", err)
 	}
 	// assert ChainId matches genesis ChainId
-	log.WithFields(log.Fields{
-		"chainId":         startedState.ChainID,
-		"lastBlockHeight": startedState.LastBlockHeight,
-		"lastBlockHash":   startedState.LastBlockHash,
-	}).Debug("Loaded state")
+	logger.Info(
+		"chainId", startedState.ChainID,
+		"lastBlockHeight", startedState.LastBlockHeight,
+		"lastBlockHash", startedState.LastBlockHash,
+		structure.MessageKey, "Loaded state")
 	// start the application
 	erisMint := NewErisMint(startedState, eventSwitch)
 
@@ -108,6 +111,7 @@ func NewErisMintPipe(moduleConfig *config.ModuleConfig,
 		// authority - this is a sort of dependency injection pattern
 		consensusEngine: nil,
 		blockchain:      nil,
+		logger:          logger,
 	}
 
 	// NOTE: [Silas]
@@ -183,6 +187,10 @@ func startState(dataDir, backend, genesisFile, chainId string) (*state.State,
 //------------------------------------------------------------------------------
 // Implement definitions.Pipe for erisMintPipe
 
+func (pipe *erisMintPipe) Logger() loggers.InfoTraceLogger {
+	return pipe.logger
+}
+
 func (pipe *erisMintPipe) Accounts() definitions.Accounts {
 	return pipe.accounts
 }
diff --git a/manager/eris-mint/state/state.go b/manager/eris-mint/state/state.go
index a36bc8ec2f607eaa505fcf8b1b7ad66c04a44033..20106e3386d7818d681ebebaa8e8a2c24379b1e1 100644
--- a/manager/eris-mint/state/state.go
+++ b/manager/eris-mint/state/state.go
@@ -12,7 +12,6 @@ import (
 	ptypes "github.com/eris-ltd/eris-db/permission/types"
 	"github.com/eris-ltd/eris-db/txs"
 
-	. "github.com/tendermint/go-common"
 	dbm "github.com/tendermint/go-db"
 	"github.com/tendermint/go-events"
 	"github.com/tendermint/go-merkle"
@@ -20,6 +19,7 @@ import (
 
 	core_types "github.com/eris-ltd/eris-db/core/types"
 	"github.com/tendermint/tendermint/types"
+	"github.com/eris-ltd/eris-db/util"
 )
 
 var (
@@ -77,7 +77,7 @@ func LoadState(db dbm.DB) *State {
 		s.nameReg.Load(nameRegHash)
 		if *err != nil {
 			// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
-			Exit(Fmt("Data has been corrupted or its spec has changed: %v\n", *err))
+			util.Fatalf("Data has been corrupted or its spec has changed: %v\n", *err)
 		}
 		// TODO: ensure that buf is completely read.
 	}
@@ -101,7 +101,10 @@ func (s *State) Save() {
 	//wire.WriteByteSlice(s.validatorInfos.Hash(), buf, n, err)
 	wire.WriteByteSlice(s.nameReg.Hash(), buf, n, err)
 	if *err != nil {
-		PanicCrisis(*err)
+		// TODO: [Silas] Do something better than this, really serialising ought to
+		// be error-free
+		util.Fatalf("Could not serialise state in order to save the state, " +
+				"cannot continue, error: %s", *err)
 	}
 	s.DB.Set(stateKey, buf.Bytes())
 }
@@ -401,7 +404,7 @@ func (s *State) SetFireable(evc events.Fireable) {
 func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*GenesisDoc, *State) {
 	jsonBlob, err := ioutil.ReadFile(genDocFile)
 	if err != nil {
-		Exit(Fmt("Couldn't read GenesisDoc file: %v", err))
+		util.Fatalf("Couldn't read GenesisDoc file: %v", err)
 	}
 	genDoc := GenesisDocFromJSON(jsonBlob)
 	return genDoc, MakeGenesisState(db, genDoc)
@@ -409,7 +412,7 @@ func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*GenesisDoc, *State
 
 func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State {
 	if len(genDoc.Validators) == 0 {
-		Exit(Fmt("The genesis file has no validators"))
+		util.Fatalf("The genesis file has no validators")
 	}
 
 	if genDoc.GenesisTime.IsZero() {
diff --git a/manager/manager.go b/manager/manager.go
index 406648cd3ef36bc56857a55689c3b233870e633a..276d872f1da331ec8ae83d21729a22135a5d00d3 100644
--- a/manager/manager.go
+++ b/manager/manager.go
@@ -21,12 +21,13 @@ import (
 
 	events "github.com/tendermint/go-events"
 
-	log "github.com/eris-ltd/eris-logger"
-
 	config "github.com/eris-ltd/eris-db/config"
 	definitions "github.com/eris-ltd/eris-db/definitions"
 	erismint "github.com/eris-ltd/eris-db/manager/eris-mint"
 	// types       "github.com/eris-ltd/eris-db/manager/types"
+
+	"github.com/eris-ltd/eris-db/logging/loggers"
+	"github.com/eris-ltd/eris-db/logging/structure"
 )
 
 // NewApplicationPipe returns an initialised Pipe interface
@@ -35,18 +36,18 @@ import (
 // of an application.  It is feasible this will be insufficient to support
 // different types of applications later down the line.
 func NewApplicationPipe(moduleConfig *config.ModuleConfig,
-	evsw *events.EventSwitch, consensusMinorVersion string) (definitions.Pipe,
+	evsw *events.EventSwitch, logger loggers.InfoTraceLogger,
+	consensusMinorVersion string) (definitions.Pipe,
 	error) {
 	switch moduleConfig.Name {
 	case "erismint":
 		if err := erismint.AssertCompatibleConsensus(consensusMinorVersion); err != nil {
 			return nil, err
 		}
-		log.WithFields(log.Fields{
-			"compatibleConsensus": consensusMinorVersion,
-			"erisMintVersion":     erismint.GetErisMintVersion().GetVersionString(),
-		}).Debug("Loading ErisMint")
-		return erismint.NewErisMintPipe(moduleConfig, evsw)
+		logger.Info("compatibleConsensus", consensusMinorVersion,
+			"erisMintVersion", erismint.GetErisMintVersion().GetVersionString(),
+			structure.MessageKey, "Loading ErisMint")
+		return erismint.NewErisMintPipe(moduleConfig, evsw, logger)
 	}
 	return nil, fmt.Errorf("Failed to return Pipe for %s", moduleConfig.Name)
 }
diff --git a/rpc/tendermint/client/client_test.go b/rpc/tendermint/client/client_test.go
index 2580508d828b88b457cc683f78a21c145aee5e9b..52e0125e7645dd9d87e101c65c68dc60fa9b5faa 100644
--- a/rpc/tendermint/client/client_test.go
+++ b/rpc/tendermint/client/client_test.go
@@ -30,5 +30,4 @@ func TestMapsAndValues(t *testing.T) {
 
 	_, _, err = mapAndValues("Foo", 4, 4, "Bar")
 	assert.Error(t, err, "Should be an error to provide non-string keys")
-
 }
diff --git a/rpc/tendermint/test/shared.go b/rpc/tendermint/test/shared.go
index d211bd5d11c6fd7c078ce12665366f0a48d0a134..8ddce9b340f3aa507ccbffe1fe736408e41cb6b3 100644
--- a/rpc/tendermint/test/shared.go
+++ b/rpc/tendermint/test/shared.go
@@ -21,6 +21,7 @@ import (
 
 	"path"
 
+	"github.com/eris-ltd/eris-db/logging/lifecycle"
 	state_types "github.com/eris-ltd/eris-db/manager/eris-mint/state/types"
 	"github.com/spf13/viper"
 	tm_common "github.com/tendermint/go-common"
@@ -83,7 +84,12 @@ func initGlobalVariables(ffs *fixtures.FileFixtures) error {
 	// Set up priv_validator.json before we start tendermint (otherwise it will
 	// create its own one.
 	saveNewPriv()
-	testCore, err = core.NewCore("testCore", consensusConfig, managerConfig)
+	logger := lifecycle.NewStdErrLogger()
+	// To spill tendermint logs on the floor:
+	// lifecycle.CaptureTendermintLog15Output(loggers.NewNoopInfoTraceLogger())
+	lifecycle.CaptureTendermintLog15Output(logger)
+	testCore, err = core.NewCore("testCore", consensusConfig, managerConfig,
+		logger)
 	if err != nil {
 		return err
 	}
diff --git a/test/mock/pipe.go b/test/mock/pipe.go
index e279ad18253e2fc755f9948d8ec5108d89588a41..ecd245ec87e2c9934e360ae72869210e134ee820 100644
--- a/test/mock/pipe.go
+++ b/test/mock/pipe.go
@@ -14,6 +14,7 @@ import (
 	td "github.com/eris-ltd/eris-db/test/testdata/testdata"
 	"github.com/eris-ltd/eris-db/txs"
 
+	"github.com/eris-ltd/eris-db/logging/loggers"
 	"github.com/tendermint/go-crypto"
 	"github.com/tendermint/go-p2p"
 	mintTypes "github.com/tendermint/tendermint/types"
@@ -29,24 +30,20 @@ type MockPipe struct {
 	events          event.EventEmitter
 	namereg         definitions.NameReg
 	transactor      definitions.Transactor
+	logger          loggers.InfoTraceLogger
 }
 
 // Create a new mock tendermint pipe.
 func NewMockPipe(td *td.TestData) definitions.Pipe {
-	accounts := &accounts{td}
-	blockchain := &blockchain{td}
-	consensusEngine := &consensusEngine{td}
-	eventer := &eventer{td}
-	namereg := &namereg{td}
-	transactor := &transactor{td}
 	return &MockPipe{
-		td,
-		accounts,
-		blockchain,
-		consensusEngine,
-		eventer,
-		namereg,
-		transactor,
+		testData:        td,
+		accounts:        &accounts{td},
+		blockchain:      &blockchain{td},
+		consensusEngine: &consensusEngine{td},
+		events:          &eventer{td},
+		namereg:         &namereg{td},
+		transactor:      &transactor{td},
+		logger:          loggers.NewNoopInfoTraceLogger(),
 	}
 }
 
@@ -75,6 +72,10 @@ func (pipe *MockPipe) Transactor() definitions.Transactor {
 	return pipe.transactor
 }
 
+func (pipe *MockPipe) Logger() loggers.InfoTraceLogger {
+	return pipe.logger
+}
+
 func (pipe *MockPipe) GetApplication() manager_types.Application {
 	// TODO: [ben] mock application
 	return nil
diff --git a/txs/log.go b/txs/log.go
deleted file mode 100644
index b967a58d0ef7d4701fc15f5ab3dfee1f046b15f5..0000000000000000000000000000000000000000
--- a/txs/log.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package txs
-
-import (
-	"github.com/tendermint/go-logger"
-)
-
-var log = logger.New("module", "types")
diff --git a/util/os.go b/util/os.go
new file mode 100644
index 0000000000000000000000000000000000000000..54f41b16b420f9b2b92fdf3dbfd9959cb81b772f
--- /dev/null
+++ b/util/os.go
@@ -0,0 +1,13 @@
+package util
+
+import (
+	"fmt"
+	"os"
+)
+
+// Prints an error message to stderr and exits with status code 1
+func Fatalf(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format, args...)
+	os.Exit(1)
+}
+
diff --git a/util/slice/slice.go b/util/slice/slice.go
new file mode 100644
index 0000000000000000000000000000000000000000..55332d87f5c96aa753f6f0580b306f77385a5134
--- /dev/null
+++ b/util/slice/slice.go
@@ -0,0 +1,64 @@
+package slice
+
+func Slice(elements ...interface{}) []interface{} {
+	return elements
+}
+
+func EmptySlice() []interface{} {
+	return []interface{}{}
+}
+
+// Like append but on the interface{} type and always to a fresh backing array
+// so can be used safely with slices over arrays you did not create.
+func CopyAppend(slice []interface{}, elements ...interface{}) []interface{} {
+	sliceLength := len(slice)
+	newSlice := make([]interface{}, sliceLength+len(elements))
+	for i, e := range slice {
+		newSlice[i] = e
+	}
+	for i, e := range elements {
+		newSlice[sliceLength+i] = e
+	}
+	return newSlice
+}
+
+// Prepend elements to slice in the order they appear
+func CopyPrepend(slice []interface{}, elements ...interface{}) []interface{} {
+	elementsLength := len(elements)
+	newSlice := make([]interface{}, len(slice)+elementsLength)
+	for i, e := range elements {
+		newSlice[i] = e
+	}
+	for i, e := range slice {
+		newSlice[elementsLength+i] = e
+	}
+	return newSlice
+}
+
+// Concatenate slices into a single slice
+func Concat(slices ...[]interface{}) []interface{} {
+	offset := 0
+	for _, slice := range slices {
+		offset += len(slice)
+	}
+	concat := make([]interface{}, offset)
+	offset = 0
+	for _, slice := range slices {
+		for i, e := range slice {
+			concat[offset+i] = e
+		}
+		offset += len(slice)
+	}
+	return concat
+}
+
+// Deletes n elements starting with the ith from a slice by splicing.
+// Beware uses append so the underlying backing array will be modified!
+func Delete(slice []interface{}, i int, n int) []interface{} {
+	return append(slice[:i], slice[i+n:]...)
+}
+
+//
+func DeleteAt(slice []interface{}, i int) []interface{} {
+	return Delete(slice, i, 1)
+}
diff --git a/util/slice/slice_test.go b/util/slice/slice_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4a1f53e71ad0f5c4518e73c1d7b2eaa34ef8abc8
--- /dev/null
+++ b/util/slice/slice_test.go
@@ -0,0 +1,36 @@
+package slice
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCopyAppend(t *testing.T) {
+	assert.Equal(t, Slice(1, "two", "three", 4),
+		CopyAppend(Slice(1, "two"), "three", 4))
+	assert.Equal(t, EmptySlice(), CopyAppend(nil))
+	assert.Equal(t, Slice(1), CopyAppend(nil, 1))
+	assert.Equal(t, Slice(1), CopyAppend(Slice(1)))
+}
+
+func TestCopyPrepend(t *testing.T) {
+	assert.Equal(t, Slice("three", 4, 1, "two"),
+		CopyPrepend(Slice(1, "two"), "three", 4))
+	assert.Equal(t, EmptySlice(), CopyPrepend(nil))
+	assert.Equal(t, Slice(1), CopyPrepend(nil, 1))
+	assert.Equal(t, Slice(1), CopyPrepend(Slice(1)))
+}
+
+func TestConcat(t *testing.T) {
+	assert.Equal(t, Slice(1,2,3,4,5), Concat(Slice(1,2,3,4,5)))
+	assert.Equal(t, Slice(1,2,3,4,5), Concat(Slice(1,2,3),Slice(4,5)))
+	assert.Equal(t, Slice(1,2,3,4,5), Concat(Slice(1),Slice(2,3),Slice(4,5)))
+	assert.Equal(t, EmptySlice(), Concat(nil))
+	assert.Equal(t, Slice(1), Concat(nil, Slice(), Slice(1)))
+	assert.Equal(t, Slice(1), Concat(Slice(1), Slice(), nil))
+}
+
+func TestDelete(t *testing.T) {
+	assert.Equal(t, Slice(1,2,4,5), Delete(Slice(1,2,3,4,5), 2, 1))
+}
\ No newline at end of file