Repository: coinbase/rosetta-cli
Pull request #382 (2023-01-13 23:08:08)

add customized info/metadata
Fixes # .

This pr is enabled by this pr in sdk https://github.com/coinbase/rosetta-sdk-go/pull/465

it shows how cli inject the metadata into sdk 

<img width="1285" alt="Screenshot 2023-01-13 at 1 43 41 PM" src="https://user-images.githubusercontent.com/96205264/212435122-f2e4d662-d5fe-4009-bd1c-de47d119e09f.png">


### Motivation
<!--
Does this solve a bug? Enable a new use-case? Improve an existing behavior? Concrete examples are helpful here.
-->

### Solution
<!--
What is the solution here from a high level. What are the key technical decisions and why were they made?
-->

### Open questions
<!--
(optional) Any open questions or feedback on design desired?
-->



Patch:
diff --git a/cmd/check_construction.go b/cmd/check_construction.go
index 3371878..933f579 100644
--- a/cmd/check_construction.go
+++ b/cmd/check_construction.go
@@ -21,6 +21,7 @@ import (
 
 	cliErrs "github.com/coinbase/rosetta-cli/pkg/errors"
 	"github.com/coinbase/rosetta-cli/pkg/logger"
+	"github.com/fatih/color"
 
 	"github.com/coinbase/rosetta-cli/pkg/results"
 	"github.com/coinbase/rosetta-cli/pkg/tester"
@@ -54,6 +55,7 @@ and UTXO-based blockchains). However, we plan to add support for testing
 arbitrary scenarios (for example, staking and governance).`,
 		RunE: runCheckConstructionCmd,
 	}
+	constructionMetadata string
 )
 
 func runCheckConstructionCmd(_ *cobra.Command, _ []string) error {
@@ -66,6 +68,10 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error {
 		)
 	}
 
+	metadataMap := logger.ConvertStringToMap(Config.InfoMetaData)
+	metadataMap = logger.AddRequestUUIDToMap(metadataMap, Config.RequestUUID)
+	constructionMetadata = logger.ConvertMapToString(metadataMap)
+
 	ensureDataDirectoryExists()
 	ctx, cancel := context.WithCancel(Context)
 
@@ -74,6 +80,7 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error {
 		fetcher.WithRetryElapsedTime(time.Duration(Config.RetryElapsedTime) * time.Second),
 		fetcher.WithTimeout(time.Duration(Config.HTTPTimeout) * time.Second),
 		fetcher.WithMaxRetries(Config.MaxRetries),
+		fetcher.WithMetaData(metadata),
 	}
 	if Config.ForceRetry {
 		fetcherOpts = append(fetcherOpts, fetcher.WithForceRetry())
@@ -87,22 +94,26 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error {
 	_, _, fetchErr := fetcher.InitializeAsserter(ctx, Config.Network, Config.ValidationFile)
 	if fetchErr != nil {
 		cancel()
+		err := fmt.Errorf("unable to initialize asserter for fetcher: %w%s", fetchErr.Err, constructionMetadata)
+		color.Red(err.Error())
 		return results.ExitConstruction(
 			Config,
 			nil,
 			nil,
-			fmt.Errorf("unable to initialize asserter for fetcher: %w", fetchErr.Err),
+			err,
 		)
 	}
 
 	_, err := utils.CheckNetworkSupported(ctx, Config.Network, fetcher)
 	if err != nil {
 		cancel()
+		err = fmt.Errorf("unable to confirm network %s is supported: %w%s", types.PrintStruct(Config.Network), err, constructionMetadata)
+		color.Red(err.Error())
 		return results.ExitConstruction(
 			Config,
 			nil,
 			nil,
-			fmt.Errorf("unable to confirm network %s is supported: %w", types.PrintStruct(Config.Network), err),
+			err,
 		)
 	}
 
@@ -111,11 +122,13 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error {
 			ctx, fetcher, Config.Network, asserterConfigurationFile,
 		); err != nil {
 			cancel()
+			err = fmt.Errorf("network options don't match asserter configuration file %s: %w%s", asserterConfigurationFile, err, constructionMetadata)
+			color.Red(err.Error())
 			return results.ExitConstruction(
 				Config,
 				nil,
 				nil,
-				fmt.Errorf("network options don't match asserter configuration file %s: %w", asserterConfigurationFile, err),
+				err,
 			)
 		}
 	}
@@ -129,27 +142,30 @@ func runCheckConstructionCmd(_ *cobra.Command, _ []string) error {
 		&SignalReceived,
 	)
 	if err != nil {
+		err = fmt.Errorf("unable to initialize construction tester: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
 		return results.ExitConstruction(
 			Config,
 			nil,
 			nil,
-			fmt.Errorf("unable to initialize construction tester: %w", err),
+			err,
 		)
 	}
 	defer constructionTester.CloseDatabase(ctx)
 
 	if err := constructionTester.PerformBroadcasts(ctx); err != nil {
+		err = fmt.Errorf("unable to perform broadcasts: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
 		return results.ExitConstruction(
 			Config,
 			nil,
 			nil,
-			fmt.Errorf("unable to perform broadcasts: %w", err),
+			err,
 		)
 	}
 
 	g, ctx := errgroup.WithContext(ctx)
-	ctx = logger.AddRequestUUIDToContext(ctx, Config.RequestUUID)
-	ctx = logger.AddInfoMetaDataToContext(ctx, Config.InfoMetaData)
+	ctx = logger.AddMetadataMapToContext(ctx, metadataMap)
 
 	g.Go(func() error {
 		return constructionTester.StartPeriodicLogger(ctx)
diff --git a/cmd/check_data.go b/cmd/check_data.go
index 5d338b4..026285e 100644
--- a/cmd/check_data.go
+++ b/cmd/check_data.go
@@ -25,6 +25,7 @@ import (
 	"github.com/coinbase/rosetta-sdk-go/fetcher"
 	"github.com/coinbase/rosetta-sdk-go/types"
 	"github.com/coinbase/rosetta-sdk-go/utils"
+	"github.com/fatih/color" 
 	"github.com/spf13/cobra"
 	"golang.org/x/sync/errgroup"
 )
@@ -69,17 +70,23 @@ bootstrap balance config. You can look at the examples folder for an example
 of what one of these files looks like.`,
 		RunE: runCheckDataCmd,
 	}
+	metadata string
 )
 
 func runCheckDataCmd(_ *cobra.Command, _ []string) error {
 	ensureDataDirectoryExists()
 	ctx, cancel := context.WithCancel(Context)
 
+	metadataMap := logger.ConvertStringToMap(Config.InfoMetaData)
+	metadataMap = logger.AddRequestUUIDToMap(metadataMap, Config.RequestUUID)
+	metadata = logger.ConvertMapToString(metadataMap)
+
 	fetcherOpts := []fetcher.Option{
 		fetcher.WithMaxConnections(Config.MaxOnlineConnections),
 		fetcher.WithRetryElapsedTime(time.Duration(Config.RetryElapsedTime) * time.Second),
 		fetcher.WithTimeout(time.Duration(Config.HTTPTimeout) * time.Second),
 		fetcher.WithMaxRetries(Config.MaxRetries),
+		fetcher.WithMetaData(metadata),
 	}
 	if Config.ForceRetry {
 		fetcherOpts = append(fetcherOpts, fetcher.WithForceRetry())
@@ -93,11 +100,13 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error {
 	_, _, fetchErr := fetcher.InitializeAsserter(ctx, Config.Network, Config.ValidationFile)
 	if fetchErr != nil {
 		cancel()
+		err := fmt.Errorf("unable to initialize asserter for fetcher: %w%s", fetchErr.Err, metadata)
+		color.Red(err.Error())
 		return results.ExitData(
 			Config,
 			nil,
 			nil,
-			fmt.Errorf("unable to initialize asserter for fetcher: %w", fetchErr.Err),
+			err,
 			"",
 			"",
 		)
@@ -106,11 +115,13 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error {
 	networkStatus, err := utils.CheckNetworkSupported(ctx, Config.Network, fetcher)
 	if err != nil {
 		cancel()
+		err = fmt.Errorf("unable to confirm network %s is supported: %w%s", types.PrintStruct(Config.Network), err, metadata)
+		color.Red(err.Error())
 		return results.ExitData(
 			Config,
 			nil,
 			nil,
-			fmt.Errorf("unable to confirm network %s is supported: %w", types.PrintStruct(Config.Network), err),
+			err,
 			"",
 			"",
 		)
@@ -121,11 +132,13 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error {
 			ctx, fetcher, Config.Network, asserterConfigurationFile,
 		); err != nil {
 			cancel()
+			err = fmt.Errorf("network options don't match asserter configuration file %s: %w%s", asserterConfigurationFile, err, metadata)
+			color.Red(err.Error())
 			return results.ExitData(
 				Config,
 				nil,
 				nil,
-				fmt.Errorf("network options don't match asserter configuration file %s: %w", asserterConfigurationFile, err),
+				err,
 				"",
 				"",
 			)
@@ -143,11 +156,13 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error {
 		&SignalReceived,
 	)
 	if err != nil {
+		err = fmt.Errorf("unable to initialize data tester: %w%s", err, metadata)
+		color.Red(err.Error())
 		return results.ExitData(
 			Config,
 			nil,
 			nil,
-			fmt.Errorf("unable to initialize data tester: %w", err),
+			err,
 			"",
 			"",
 		)
@@ -155,8 +170,7 @@ func runCheckDataCmd(_ *cobra.Command, _ []string) error {
 	defer dataTester.CloseDatabase(ctx)
 
 	g, ctx := errgroup.WithContext(ctx)
-	ctx = logger.AddRequestUUIDToContext(ctx, Config.RequestUUID)
-	ctx = logger.AddInfoMetaDataToContext(ctx, Config.InfoMetaData)
+	ctx = logger.AddMetadataMapToContext(ctx, metadataMap)
 	
 	g.Go(func() error {
 		return dataTester.StartPeriodicLogger(ctx)
diff --git a/configuration/types.go b/configuration/types.go
index 617d710..01763a0 100644
--- a/configuration/types.go
+++ b/configuration/types.go
@@ -461,8 +461,14 @@ type Configuration struct {
 	// then this value must be true.
 	CoinSupported bool `json:"coin_supported"`
 
-	// InfoMetaData is a map of key:value
-	// which aims to show in the log for search
+	// InfoMetaData is a string, rosetta-cli will convert it into a map[string]string
+	// key-value are separated by ":"
+	// different key-value pairs are separated by ","
+	// an example: if users want to record "instance_name" as "1234", and "blockchain_name" as "Bitcoin", 
+	// this field would be "instance_name:1234,blockchain_name:Bitcoin"
+	// if adding spaces before and after ":" and ",", it will be trimmed when building map
+	// " instance_name : xxxx , blockchain_name : xxxx " will be recorded same as
+	// "instance_name:xxxx,blockchain_name:xxxx"
 	InfoMetaData string `json:"info_metadata,omitempty"`
 
 	Construction *ConstructionConfiguration `json:"construction"`
diff --git a/go.mod b/go.mod
index fdfc466..d39a1e3 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,8 @@ module github.com/coinbase/rosetta-cli
 go 1.16
 
 require (
-	github.com/coinbase/rosetta-sdk-go v0.8.1
+	github.com/coinbase/rosetta-sdk-go v0.8.3-0.20230113233127-4c691644d82a
+	github.com/coinbase/rosetta-sdk-go/types v1.0.0
 	github.com/fatih/color v1.13.0
 	github.com/google/go-cmp v0.5.6 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
diff --git a/go.sum b/go.sum
index 2d23f0e..190c849 100644
--- a/go.sum
+++ b/go.sum
@@ -98,8 +98,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
 github.com/coinbase/kryptology v1.8.0 h1:Aoq4gdTsJhSU3lNWsD5BWmFSz2pE0GlmrljaOxepdYY=
 github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI=
-github.com/coinbase/rosetta-sdk-go v0.8.1 h1:WE+Temc8iz7Ra7sCpV9ymBJx78vItqFJ2xcSiPet1Pc=
-github.com/coinbase/rosetta-sdk-go v0.8.1/go.mod h1:tXPR6AIW9ogsH4tYIaFOKOgfJNanCvcyl7JKLd4DToc=
+github.com/coinbase/rosetta-sdk-go v0.8.3-0.20230113233127-4c691644d82a h1:Bym3+QUv9zIij91/4yX3hs5E2TsDOJ6+8egOeF8KBYI=
+github.com/coinbase/rosetta-sdk-go v0.8.3-0.20230113233127-4c691644d82a/go.mod h1:ChOHc+BNq7zqJDDkui0DA124GOvlAiRbdgAc1U9GMDQ=
+github.com/coinbase/rosetta-sdk-go/types v1.0.0 h1:jpVIwLcPoOeCR6o1tU+Xv7r5bMONNbHU7MuEHboiFuA=
+github.com/coinbase/rosetta-sdk-go/types v1.0.0/go.mod h1:eq7W2TMRH22GTW0N0beDnN931DW0/WOI1R2sdHNHG4c=
 github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
 github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
 github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
@@ -328,8 +330,8 @@ github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjK
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
-github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index 427d9a3..5ade4f3 100644
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -44,7 +44,7 @@ type contextKey int
 const (
 	RequestUUID contextKey = iota
 
-	InfoMetaDataKey contextKey = iota
+	MetadataMapKey contextKey = iota
 
 	// blockStreamFile contains the stream of processed
 	// blocks and whether they were added or removed.
@@ -85,8 +85,7 @@ type Logger struct {
 	logTransactions   bool
 	logBalanceChanges bool
 	logReconciliation bool
-	logRequestUUID    string
-	logInfoMetaData   map[string]string
+	logMetadataMap    map[string]string
 
 	lastStatsMessage    string
 	lastProgressMessage string
@@ -103,11 +102,10 @@ func NewLogger(
 	logReconciliation bool,
 	checkType CheckType,
 	network *types.NetworkIdentifier,
-	logRequestUUID string,
-	logInfoMetaData   map[string]string,
+	logMetadataMap   map[string]string,
 	fields ...zap.Field,
 ) (*Logger, error) {
-	zapLogger, err := buildZapLogger(checkType, network, logRequestUUID, fields...)
+	zapLogger, err := buildZapLogger(checkType, network, fields...)
 	if err != nil {
 		return nil, fmt.Errorf("failed to build zap logger: %w", err)
 	}
@@ -117,8 +115,7 @@ func NewLogger(
 		logTransactions:   logTransactions,
 		logBalanceChanges: logBalanceChanges,
 		logReconciliation: logReconciliation,
-		logRequestUUID:    logRequestUUID,
-		logInfoMetaData:   logInfoMetaData,
+		logMetadataMap:    logMetadataMap,
 		zapLogger:         zapLogger,
 	}, nil
 }
@@ -126,7 +123,6 @@ func NewLogger(
 func buildZapLogger(
 	checkType CheckType,
 	network *types.NetworkIdentifier,
-	requestUUID string,
 	fields ...zap.Field,
 ) (*zap.Logger, error) {
 	config := zap.NewProductionConfig()
@@ -135,7 +131,6 @@ func buildZapLogger(
 	baseSlice := []zap.Field{
 		zap.String("blockchain", network.Blockchain),
 		zap.String("network", network.Network),
-		zap.String("requestUUID", requestUUID),
 		zap.String("check_type", string(checkType)),
 	}
 	mergedSlice := append(baseSlice, fields...)
@@ -166,8 +161,7 @@ func (l *Logger) LogDataStatus(ctx context.Context, status *results.CheckDataSta
 		status.Stats.ReconciliationCoverage*utils.OneHundred,
 	)
 
-	statsMessage = AddRequestUUID(statsMessage, l.logRequestUUID)
-	statsMessage = AddInfoMetaData(statsMessage, l.logInfoMetaData)
+	statsMessage = AddMetadata(statsMessage, l.logMetadataMap)
 
 	// Don't print out the same stats message twice.
 	if statsMessage == l.lastStatsMessage {
@@ -193,8 +187,7 @@ func (l *Logger) LogDataStatus(ctx context.Context, status *results.CheckDataSta
 		status.Progress.ReconcilerLastIndex,
 	)
 
-	progressMessage = AddRequestUUID(progressMessage, l.logRequestUUID)
-	progressMessage = AddInfoMetaData(progressMessage, l.logInfoMetaData)
+	progressMessage = AddMetadata(progressMessage, l.logMetadataMap)
 
 	// Don't print out the same progress message twice.
 	if progressMessage == l.lastProgressMessage {
@@ -223,8 +216,7 @@ func (l *Logger) LogConstructionStatus(
 		return
 	}
 
-	statsMessage = AddRequestUUID(statsMessage, l.logRequestUUID)
-	statsMessage = AddInfoMetaData(statsMessage, l.logInfoMetaData)
+	statsMessage = AddMetadata(statsMessage, l.logMetadataMap)
 	
 	l.lastStatsMessage = statsMessage
 	color.Cyan(statsMessage)
@@ -240,8 +232,7 @@ func LogMemoryStats(ctx context.Context) {
 		memUsage.System,
 		memUsage.GarbageCollections,
 	)
-	statsMessage = AddRequestUUIDFromContext(ctx, statsMessage)
-	statsMessage = AddInfoMetaDataFromContext(ctx, statsMessage)
+	statsMessage = AddMetadataMapFromContext(ctx, statsMessage)
 	color.Cyan(statsMessage)
 }
 
@@ -276,8 +267,7 @@ func (l *Logger) AddBlockStream(
 		block.ParentBlockIdentifier.Index,
 		block.ParentBlockIdentifier.Hash,
 	)
-	blockString = AddRequestUUID(blockString, l.logRequestUUID)
-	blockString = AddInfoMetaData(blockString, l.logInfoMetaData)
+	blockString = AddMetadata(blockString, l.logMetadataMap)
 	color.Cyan(blockString)
 	if _, err := f.WriteString(blockString); err != nil {
 		return fmt.Errorf("failed to write block string %s: %w", blockString, err)
@@ -315,8 +305,7 @@ func (l *Logger) RemoveBlockStream(
 		block.Index,
 		block.Hash,
 	)
-	blockString = AddRequestUUID(blockString, l.logRequestUUID)
-	blockString = AddInfoMetaData(blockString, l.logInfoMetaData)
+	blockString = AddMetadata(blockString, l.logMetadataMap)
 	color.Cyan(blockString)
 	_, err = f.WriteString(blockString)
 	if err != nil {
@@ -358,8 +347,7 @@ func (l *Logger) TransactionStream(
 			block.BlockIdentifier.Index,
 			block.BlockIdentifier.Hash,
 		)
-		transactionString = AddRequestUUID(transactionString, l.logRequestUUID)
-		transactionString = AddInfoMetaData(transactionString, l.logInfoMetaData)
+		transactionString = AddMetadata(transactionString, l.logMetadataMap)
 		color.Cyan(transactionString)
 		_, err = f.WriteString(transactionString)
 		if err != nil {
@@ -395,8 +383,7 @@ func (l *Logger) TransactionStream(
 				symbol,
 				*op.Status,
 			)
-			transactionOperationString = AddRequestUUID(transactionOperationString, l.logRequestUUID)
-			transactionOperationString = AddInfoMetaData(transactionOperationString, l.logInfoMetaData)
+			transactionOperationString = AddMetadata(transactionOperationString, l.logMetadataMap)
 			color.Cyan(transactionOperationString)
 			_, err = f.WriteString(transactionOperationString)
 			if err != nil {
@@ -442,8 +429,7 @@ func (l *Logger) BalanceStream(
 			balanceChange.Block.Index,
 			balanceChange.Block.Hash,
 		)
-		balanceLog = AddRequestUUID(balanceLog, l.logRequestUUID)
-		balanceLog = AddInfoMetaData(balanceLog, l.logInfoMetaData)
+		balanceLog = AddMetadata(balanceLog, l.logMetadataMap)
 		color.Cyan(balanceLog)
 		if _, err := f.WriteString(fmt.Sprintf("%s\n", balanceLog)); err != nil {
 			err = fmt.Errorf("failed to write balance log %s: %w", balanceLog, err)
@@ -487,8 +473,7 @@ func (l *Logger) ReconcileSuccessStream(
 		types.AccountString(account),
 		block.Index,
 	)
-	reconciledLog = AddRequestUUID(reconciledLog, l.logRequestUUID)
-	reconciledLog = AddInfoMetaData(reconciledLog, l.logInfoMetaData)
+	reconciledLog = AddMetadata(reconciledLog, l.logMetadataMap)
 	color.Cyan(reconciledLog)
 
 	reconciliationSuccessString := fmt.Sprintf(
@@ -500,8 +485,7 @@ func (l *Logger) ReconcileSuccessStream(
 		block.Index,
 		block.Hash,
 	)
-	reconciliationSuccessString = AddRequestUUID(reconciliationSuccessString, l.logRequestUUID)
-	reconciliationSuccessString = AddInfoMetaData(reconciliationSuccessString, l.logInfoMetaData)
+	reconciliationSuccessString = AddMetadata(reconciliationSuccessString, l.logMetadataMap)
 	color.Cyan(reconciliationSuccessString)
 	
 	_, err = f.WriteString(reconciliationSuccessString)
@@ -574,8 +558,7 @@ func (l *Logger) ReconcileFailureStream(
 		computedBalance,
 		liveBalance,
 	)
-	reconciliationFailureString = AddRequestUUID(reconciliationFailureString, l.logRequestUUID)
-	reconciliationFailureString = AddInfoMetaData(reconciliationFailureString, l.logInfoMetaData)
+	reconciliationFailureString = AddMetadata(reconciliationFailureString, l.logMetadataMap)
 	color.Cyan(reconciliationFailureString)
 	_, err = f.WriteString(reconciliationFailureString)
 	if err != nil {
@@ -617,6 +600,19 @@ func (l *Logger) Fatal(msg string, fields ...zap.Field) {
 	l.zapLogger.Fatal(msg, fields...)
 }
 
+// return a string of metadata
+func (l *Logger) GetMetadata() string{
+	metadatMap := l.logMetadataMap
+	metadata := ConvertMapToString(metadatMap)
+	return metadata
+}
+
+// return a map of metadatMap
+func (l *Logger) GetMetadataMap() map[string]string{
+	metadatMap := l.logMetadataMap
+	return metadatMap
+}
+
 // Helper function to close log file
 func closeFile(f *os.File) {
 	err := f.Close()
@@ -636,81 +632,53 @@ func LogTransactionCreated(
 	)
 }
 
-// Add requestUUID to the tip
-func AddRequestUUIDFromContext(ctx context.Context, msg string) string {
-	requestUUID := requestUUIDFromContext(ctx)
-	if requestUUID != "" {
-		msg = fmt.Sprintf("%s, RequestID: %s", msg, requestUUID)
-	}
-	return msg
-}
-
-// Add requestUUID to the tip
-func AddRequestUUID(msg string, requestUUID string) string {
-	if requestUUID != "" {
-		msg = fmt.Sprintf("%s, RequestID: %s", msg, requestUUID)
-	}
-	return msg
-}
-
-// AddRequestUUIDToContext will add a requestUUIDto the context, and return the new context
-func AddRequestUUIDToContext(ctx context.Context, uuid string) context.Context {
-	return context.WithValue(ctx, RequestUUID, uuid)
-}
-
-// requestUUIDFromContext is used to extract a request UUID from a context
-func requestUUIDFromContext(ctx context.Context) string {
-	switch v := ctx.Value(RequestUUID).(type) {
-	case string:
-		return v
-	default:
-		return ""
-	}
-}
-
 // Add InfoMetaData k-v pairs to the tip
-func AddInfoMetaDataFromContext(ctx context.Context, msg string) string {
-	logInfoMetaData := InfoMetaDataFromContext(ctx)
-	if len(logInfoMetaData) != 0 {
-		for k, v := range logInfoMetaData {
-			msg = fmt.Sprintf("%s, %s: %s", msg, k, v)
+func AddMetadataMapFromContext(ctx context.Context, msg string) string {
+	metadataMap := metadataMapFromContext(ctx)
+	if len(metadataMap) != 0 {
+		for k, v := range metadataMap {
+			if len(k) != 0 && len(v) != 0 {
+				msg = fmt.Sprintf("%s, %s: %s", msg, k, v)
+			}
 		}
 	}
 	return msg
 }
 
-// Add InfoMetaData k-v pairs to the tip
-func AddInfoMetaData(msg string, logInfoMetaData map[string]string) string {
-	if len(logInfoMetaData) != 0 {
-		for k, v := range logInfoMetaData {
-			msg = fmt.Sprintf("%s, %s: %s", msg, k, v)
+// AddMetadataMapToContext will add InfoMetaData to the context, and return the new context
+func AddMetadataMapToContext(ctx context.Context, metadataMap map[string]string) context.Context {
+	return context.WithValue(ctx, MetadataMapKey, metadataMap)
+}
+
+// AddMetadata k-v pairs to the tip
+func AddMetadata(msg string, metadataMap map[string]string) string {
+	if len(metadataMap) != 0 {
+		for k, v := range metadataMap {
+			if len(k) != 0 && len(v) != 0 {
+				msg = fmt.Sprintf("%s, %s: %s", msg, k, v)
+			}
 		}
 	}
 	return msg
 }
 
-// AddInfoMetaDataToContext will add InfoMetaData to the context, and return the new context
-func AddInfoMetaDataToContext(ctx context.Context, InfoMetaData string) context.Context {
-	return context.WithValue(ctx, InfoMetaDataKey, InfoMetaData)
-}
-
-// InfoMetaDataFromContext is used to extract InfoMetaData from a context
-func InfoMetaDataFromContext(ctx context.Context) map[string]string {
-	var metadata string
-	switch v := ctx.Value(InfoMetaDataKey).(type) {
-	case string:
-		metadata = v
+// metadataMapFromContext is used to extract metadataMap from a context
+func metadataMapFromContext(ctx context.Context) map[string]string {
+	var metadataMap map[string]string
+	switch v := ctx.Value(MetadataMapKey).(type) {
+	case map[string]string:
+		metadataMap = v
 	default:
-		metadata = ""
+		metadataMap = nil
 	}
-	return ConvertStringToMap(metadata)
+	return metadataMap
 }
 
 // ConvertStringToMap is used to convert a string to map by split , and ;
 func ConvertStringToMap(metadata string) map[string]string {
-	InfoMetaDataMap := make(map[string]string)
+	metadataMap := make(map[string]string)
 	if len(metadata) == 0 {
-		return InfoMetaDataMap
+		return metadataMap
 	}
 	pairs := strings.Split(metadata, ",")
 	for _, pair := range pairs {
@@ -719,8 +687,29 @@ func ConvertStringToMap(metadata string) map[string]string {
 			log := fmt.Sprintf("the %s from %s could be transfer to key value pair", pair, metadata)
 			color.Yellow(log)
 		} else {
-			InfoMetaDataMap[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
+			metadataMap[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
+		}
+	}
+	return metadataMap
+}
+
+// add requesrUUID to metadataMap
+func AddRequestUUIDToMap(metadataMap map[string]string, requestUUID string) map[string]string{
+	if len(requestUUID) > 0 {
+		metadataMap["RequestID"] = requestUUID
+	}
+	return metadataMap
+}
+
+// convert metadataMap to a string, aims to support fmt.Errorf
+func ConvertMapToString(metadataMap map[string]string) string {
+	metadata := ""
+	if len(metadataMap) != 0 {
+		for k, v := range metadataMap {
+			if len(k) != 0 && len(v) != 0 {
+				metadata = fmt.Sprintf("%s, %s: %s", metadata, k, v)
+			}
 		}
 	}
-	return InfoMetaDataMap
+	return metadata;
 }


Test patch:
diff --git a/pkg/tester/construction.go b/pkg/tester/construction.go
index ad26185..9c18138 100644
--- a/pkg/tester/construction.go
+++ b/pkg/tester/construction.go
@@ -54,6 +54,7 @@ const (
 )
 
 var _ http.Handler = (*ConstructionTester)(nil)
+var constructionMetadata string
 
 // ConstructionTester coordinates the `check:construction` test.
 type ConstructionTester struct {
@@ -84,11 +85,18 @@ func InitializeConstruction(
 	signalReceived *bool,
 ) (*ConstructionTester, error) {
 	dataPath, err := utils.CreateCommandPath(config.DataDirectory, constructionCmdName, network)
+	metadataMap := logger.ConvertStringToMap(config.InfoMetaData)
+	metadataMap = logger.AddRequestUUIDToMap(metadataMap, config.RequestUUID)
+	constructionMetadata = logger.ConvertMapToString(metadataMap)
 	if err != nil {
-		return nil, fmt.Errorf("failed to create command path: %w", err)
+		err = fmt.Errorf("failed to create command path: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	opts := []database.BadgerOption{}
+	// add constructionMetadata into localStore
+	opts = append(opts, database.WithMetaData(constructionMetadata))
 	if config.CompressionDisabled {
 		opts = append(opts, database.WithoutCompression())
 	}
@@ -99,14 +107,19 @@ func InitializeConstruction(
 		)
 	}
 
+	// add constructionMetadata into localStore
 	localStore, err := database.NewBadgerDatabase(ctx, dataPath, opts...)
 	if err != nil {
-		return nil, fmt.Errorf("unable to initialize database: %w", err)
+		err = fmt.Errorf("unable to initialize database: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	networkOptions, fetchErr := onlineFetcher.NetworkOptionsRetry(ctx, network, nil)
-	if err != nil {
-		return nil, fmt.Errorf("unable to get network options: %w", fetchErr.Err)
+	if fetchErr != nil {
+		err := fmt.Errorf("unable to get network options: %w%s", fetchErr.Err, constructionMetadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	if len(networkOptions.Allow.BalanceExemptions) > 0 &&
@@ -115,7 +128,7 @@ func InitializeConstruction(
 	}
 
 	counterStorage := modules.NewCounterStorage(localStore)
-	logInfoMetaData := logger.ConvertStringToMap(config.InfoMetaData)
+	//add constructionMetadata into logger
 	logger, err := logger.NewLogger(
 		dataPath,
 		false,
@@ -124,11 +137,12 @@ func InitializeConstruction(
 		false,
 		logger.Construction,
 		network,
-		config.RequestUUID,
-		logInfoMetaData,
+		metadataMap,
 	)
 	if err != nil {
-		return nil, fmt.Errorf("unable to initialize logger with error: %w", err)
+		err = fmt.Errorf("unable to initialize logger with error: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	blockStorage := modules.NewBlockStorage(localStore, config.SerialBlockWorkers)
@@ -173,12 +187,13 @@ func InitializeConstruction(
 		blockStorage,
 		onlineFetcher,
 	)
-
+	//add constructionMetadata into fetcher
 	fetcherOpts := []fetcher.Option{
 		fetcher.WithMaxConnections(config.Construction.MaxOfflineConnections),
 		fetcher.WithAsserter(onlineFetcher.Asserter),
 		fetcher.WithTimeout(time.Duration(config.HTTPTimeout) * time.Second),
 		fetcher.WithMaxRetries(config.MaxRetries),
+		fetcher.WithMetaData(constructionMetadata),
 	}
 	if config.Construction.ForceRetry {
 		fetcherOpts = append(fetcherOpts, fetcher.WithForceRetry())
@@ -192,13 +207,17 @@ func InitializeConstruction(
 	// Import prefunded account and save to database
 	err = keyStorage.ImportAccounts(ctx, config.Construction.PrefundedAccounts)
 	if err != nil {
+		err = fmt.Errorf("%w%s", err, constructionMetadata)
+		color.Red(err.Error())
 		return nil, err
 	}
 
 	// Load all accounts for network
 	accounts, err := keyStorage.GetAllAccounts(ctx)
 	if err != nil {
-		return nil, fmt.Errorf("unable to load addresses: %w", err)
+		err = fmt.Errorf("unable to load addresses: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	// Track balances on all addresses
@@ -206,7 +225,7 @@ func InitializeConstruction(
 		balanceStorageHelper.AddInterestingAddress(account.Address)
 	}
 
-	log.Printf("construction tester initialized with %d accounts\n", len(accounts))
+	color.Cyan("construction tester initialized with %d accounts%s\n", len(accounts), constructionMetadata)
 
 	// Load prefunded accounts
 	var accountBalanceRequests []*utils.AccountBalanceRequest
@@ -234,12 +253,16 @@ func InitializeConstruction(
 
 	accBalances, err := utils.GetAccountBalances(ctx, onlineFetcher, accountBalanceRequests)
 	if err != nil {
-		return nil, fmt.Errorf("unable to get account balances: %w", err)
+		err = fmt.Errorf("unable to get account balances: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	err = balanceStorage.SetBalanceImported(ctx, nil, accBalances)
 	if err != nil {
-		return nil, fmt.Errorf("unable to set balances: %w", err)
+		err = fmt.Errorf("unable to set balances: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	// -------------------------------------------------------------------------
@@ -249,7 +272,9 @@ func InitializeConstruction(
 	if config.CoinSupported {
 		acctCoins, errAccCoins := utils.GetAccountCoins(ctx, onlineFetcher, acctCoinsReqs)
 		if errAccCoins != nil {
-			return nil, fmt.Errorf("unable to get account coins: %w", errAccCoins)
+			err = fmt.Errorf("unable to get account coins: %w%s", errAccCoins, constructionMetadata)
+			color.Red(err.Error())
+			return nil, err
 		}
 
 		// Extract accounts from account coins requests
@@ -260,7 +285,9 @@ func InitializeConstruction(
 
 		err = coinStorage.SetCoinsImported(ctx, accts, acctCoins)
 		if err != nil {
-			return nil, fmt.Errorf("unable to set coin balances: %w", err)
+			err = fmt.Errorf("unable to set coin balances: %w%s", err, constructionMetadata)
+			color.Red(err.Error())
+			return nil, err
 		}
 	}
 
@@ -294,7 +321,9 @@ func InitializeConstruction(
 		config.Construction.Workflows,
 	)
 	if err != nil {
-		log.Fatalf("unable to create coordinator: %s", err.Error())
+		msg := fmt.Sprintf("unable to create coordinator: %s%s", err.Error(), constructionMetadata)
+		color.Red(msg)
+		log.Fatalf(msg)
 	}
 
 	broadcastHandler := processor.NewBroadcastStorageHandler(
@@ -307,6 +336,7 @@ func InitializeConstruction(
 
 	broadcastStorage.Initialize(broadcastHelper, broadcastHandler)
 
+	//add constructionMetadata into syncer
 	syncer := statefulsyncer.New(
 		ctx,
 		network,
@@ -320,6 +350,7 @@ func InitializeConstruction(
 		statefulsyncer.WithMaxConcurrency(config.MaxSyncConcurrency),
 		statefulsyncer.WithPastBlockLimit(config.MaxReorgDepth),
 		statefulsyncer.WithSeenConcurrency(int64(config.SeenBlockWorkers)),
+		statefulsyncer.WithMetaData(constructionMetadata),
 	)
 
 	return &ConstructionTester{
@@ -342,7 +373,9 @@ func InitializeConstruction(
 // CloseDatabase closes the database used by ConstructionTester.
 func (t *ConstructionTester) CloseDatabase(ctx context.Context) {
 	if err := t.database.Close(ctx); err != nil {
-		log.Fatalf("error closing database: %s", err.Error())
+		msg := fmt.Sprintf("error closing database: %s%s", err.Error(), constructionMetadata)
+		color.Red(msg)
+		log.Fatalf(msg)
 	}
 }
 
@@ -379,7 +412,9 @@ func (t *ConstructionTester) checkTip(ctx context.Context) (int64, error) {
 		t.onlineFetcher,
 	)
 	if err != nil {
-		return -1, fmt.Errorf("failed to check network tip: %w", err)
+		err = fmt.Errorf("failed to check network tip: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return -1, err
 	}
 
 	if atTip {
@@ -398,14 +433,16 @@ func (t *ConstructionTester) waitForTip(ctx context.Context) (int64, error) {
 		// Don't wait any time before first tick if at tip.
 		tipIndex, err := t.checkTip(ctx)
 		if err != nil {
-			return -1, fmt.Errorf("failed to check tip: %w", err)
+			err = fmt.Errorf("failed to check tip: %w%s", err, constructionMetadata)
+			color.Red(err.Error())
+			return -1, err
 		}
 
 		if tipIndex != -1 {
 			return tipIndex, nil
 		}
 
-		log.Println("waiting for implementation to reach tip before testing...")
+		color.Cyan("waiting for implementation to reach tip before testing...%s", constructionMetadata)
 
 		select {
 		case <-ctx.Done():
@@ -430,10 +467,14 @@ func (t *ConstructionTester) StartSyncer(
 		// we will unnecessarily sync tons of blocks before reaching any that matter.
 		startIndex, err = t.waitForTip(ctx)
 		if err != nil {
-			return fmt.Errorf("unable to wait for tip: %w", err)
+			err = fmt.Errorf("unable to wait for tip: %w%s", err, constructionMetadata)
+			color.Red(err.Error())
+			return err
 		}
 	} else if err != nil {
-		return fmt.Errorf("unable to get last block synced: %w", err)
+		err = fmt.Errorf("unable to get last block synced: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return err
 	}
 
 	return t.syncer.Sync(ctx, startIndex, -1)
@@ -448,10 +489,12 @@ func (t *ConstructionTester) StartConstructor(
 	if t.config.Construction.ClearBroadcasts {
 		broadcasts, err := t.broadcastStorage.ClearBroadcasts(ctx)
 		if err != nil {
-			return fmt.Errorf("unable to clear broadcasts: %w", err)
+			err = fmt.Errorf("unable to clear broadcasts: %w%s", err, constructionMetadata)
+			color.Red(err.Error())
+			return err
 		}
 
-		log.Printf("cleared %d broadcasts\n", len(broadcasts))
+		color.Cyan("cleared %d broadcasts%s\n", len(broadcasts), constructionMetadata)
 	}
 
 	return t.coordinator.Process(ctx)
@@ -482,10 +525,12 @@ func (t *ConstructionTester) PerformBroadcasts(ctx context.Context) error {
 		return nil
 	}
 
-	color.Magenta("Rebroadcasting all transactions...")
+	color.Magenta("Rebroadcasting all transactions...%s", constructionMetadata)
 
 	if err := t.broadcastStorage.BroadcastAll(ctx, false); err != nil {
-		return fmt.Errorf("unable to broadcast all transactions: %w", err)
+		err = fmt.Errorf("unable to broadcast all transactions: %w%s", err, constructionMetadata)
+		color.Red(err.Error())
+		return err
 	}
 
 	return nil
@@ -514,7 +559,9 @@ func (t *ConstructionTester) WatchEndConditions(
 			for workflow, minOccurences := range endConditions {
 				completed, err := t.jobStorage.Completed(ctx, workflow)
 				if err != nil {
-					return fmt.Errorf("unable to fetch completed %s: %w", workflow, err)
+					err = fmt.Errorf("unable to fetch completed %s: %w%s", workflow, err, constructionMetadata)
+					color.Red(err.Error())
+					return err
 				}
 
 				if len(completed) < minOccurences {
@@ -564,12 +611,12 @@ func (t *ConstructionTester) returnFunds(
 
 	err := g.Wait()
 	if *t.signalReceived {
-		color.Red("Fund return halted")
+		color.Red("Fund return halted%s", constructionMetadata)
 		return
 	}
 
 	if !returnFundsSuccess {
-		log.Printf("unable to return funds %v\n", err)
+		color.Cyan("unable to return funds %v%s\n", err, constructionMetadata)
 	}
 }
 
@@ -579,15 +626,18 @@ func (t *ConstructionTester) HandleErr(
 	sigListeners *[]context.CancelFunc,
 ) error {
 	if *t.signalReceived {
+		err = fmt.Errorf("%v: %w%s", err.Error(), cliErrs.ErrConstructionCheckHalt, constructionMetadata)
+		color.Red(err.Error())
 		return results.ExitConstruction(
 			t.config,
 			t.counterStorage,
 			t.jobStorage,
-			fmt.Errorf("%v: %w", err.Error(), cliErrs.ErrConstructionCheckHalt),
+			err,
 		)
 	}
 
 	if !t.reachedEndConditions {
+		color.Red("%v%s", err, constructionMetadata)
 		return results.ExitConstruction(t.config, t.counterStorage, t.jobStorage, err)
 	}
 
diff --git a/pkg/tester/data.go b/pkg/tester/data.go
index b687e88..0487fa6 100644
--- a/pkg/tester/data.go
+++ b/pkg/tester/data.go
@@ -85,6 +85,7 @@ const (
 
 var _ http.Handler = (*DataTester)(nil)
 var _ statefulsyncer.PruneHelper = (*DataTester)(nil)
+var metadata string
 
 // DataTester coordinates the `check:data` test.
 type DataTester struct {
@@ -131,15 +132,19 @@ func loadAccounts(filePath string) ([]*types.AccountCurrency, error) {
 
 	accounts := []*types.AccountCurrency{}
 	if err := utils.LoadAndParse(filePath, &accounts); err != nil {
-		return nil, fmt.Errorf("unable to load and parse %s: %w", filePath, err)
+		err = fmt.Errorf("unable to load and parse %s: %w%s", filePath, err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
-	log.Printf(
-		"Found %d accounts at %s: %s\n",
+	msg := fmt.Sprintf(
+		"Found %d accounts at %s: %s%s\n",
 		len(accounts),
 		filePath,
 		types.PrettyPrintStruct(accounts),
+		metadata,
 	)
+	color.Cyan(msg)
 
 	return accounts, nil
 }
@@ -147,7 +152,9 @@ func loadAccounts(filePath string) ([]*types.AccountCurrency, error) {
 // CloseDatabase closes the database used by DataTester.
 func (t *DataTester) CloseDatabase(ctx context.Context) {
 	if err := t.database.Close(ctx); err != nil {
-		log.Fatalf("error closing database: %s", err.Error())
+		msg := fmt.Sprintf("error closing database: %s%s", err.Error(), metadata)
+		color.Red(msg)
+		log.Fatalf(msg)
 	}
 }
 
@@ -163,11 +170,19 @@ func InitializeData(
 	signalReceived *bool,
 ) (*DataTester, error) {
 	dataPath, err := utils.CreateCommandPath(config.DataDirectory, dataCmdName, network)
+	metadataMap := logger.ConvertStringToMap(config.InfoMetaData)
+	metadataMap = logger.AddRequestUUIDToMap(metadataMap, config.RequestUUID)
+	metadata = logger.ConvertMapToString(metadataMap)
+
 	if err != nil {
-		return nil, fmt.Errorf("failed to create command path: %w", err)
+		err = fmt.Errorf("failed to create command path: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
+	//add metadata into localStore
 	opts := []database.BadgerOption{}
+	opts = append(opts, database.WithMetaData(metadata))
 	dataPathBackup := dataPath
 
 	if config.AllInMemoryEnabled {
@@ -213,26 +228,31 @@ func InitializeData(
 		}
 	}
 
+	//add metadata into localStore
 	localStore, err := database.NewBadgerDatabase(ctx, dataPathBackup, opts...)
 	if err != nil {
-		return nil, fmt.Errorf("unable to initialize database: %w", err)
+		err = fmt.Errorf("unable to initialize database: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	exemptAccounts, err := loadAccounts(config.Data.ExemptAccounts)
 	if err != nil {
-		return nil, fmt.Errorf("unable to load exempt accounts: %w", err)
+		err = fmt.Errorf("unable to load exempt accounts: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	interestingAccounts, err := loadAccounts(config.Data.InterestingAccounts)
 	if err != nil {
-		return nil, fmt.Errorf("unable to load interesting accounts: %w", err)
+		err = fmt.Errorf("unable to load interesting accounts: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	counterStorage := modules.NewCounterStorage(localStore)
 	blockStorage := modules.NewBlockStorage(localStore, config.SerialBlockWorkers)
 	balanceStorage := modules.NewBalanceStorage(localStore)
-
-	logInfoMetaData := logger.ConvertStringToMap(config.InfoMetaData)
 	logger, err := logger.NewLogger(
 		dataPath,
 		config.Data.LogBlocks,
@@ -241,11 +261,12 @@ func InitializeData(
 		config.Data.LogReconciliations,
 		logger.Data,
 		network,
-		config.RequestUUID,
-		logInfoMetaData,
+		metadataMap,
 	)
 	if err != nil {
-		return nil, fmt.Errorf("unable to initialize logger with error: %w", err)
+		err = fmt.Errorf("unable to initialize logger with error: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	var forceInactiveReconciliation bool
@@ -258,7 +279,6 @@ func InitializeData(
 		balanceStorage,
 		&forceInactiveReconciliation,
 	)
-
 	reconcilerHandler := processor.NewReconcilerHandler(
 		logger,
 		counterStorage,
@@ -269,16 +289,22 @@ func InitializeData(
 	// Get all previously seen accounts
 	seenAccounts, err := balanceStorage.GetAllAccountCurrency(ctx)
 	if err != nil {
-		return nil, fmt.Errorf("unable to get previously seen accounts: %w", err)
+		err = fmt.Errorf("unable to get previously seen accounts: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	networkOptions, fetchErr := fetcher.NetworkOptionsRetry(ctx, network, nil)
 	if fetchErr != nil {
-		log.Fatalf("unable to get network options: %s", fetchErr.Err.Error())
+		msg := fmt.Sprintf("unable to get network options: %s%s", fetchErr.Err.Error(), metadata)
+		color.Red(msg)
+		log.Fatalf(msg)
 	}
 
 	if len(networkOptions.Allow.BalanceExemptions) > 0 && config.Data.InitialBalanceFetchDisabled {
-		return nil, fmt.Errorf("found balance exemptions but initial balance fetch disabled")
+		err = fmt.Errorf("found balance exemptions but initial balance fetch disabled%s", metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	parser := parser.New(
@@ -295,6 +321,7 @@ func InitializeData(
 		historicalBalanceEnabled = networkOptions.Allow.HistoricalBalanceLookup
 	}
 
+	//add metadata into reconciler
 	rOpts := []reconciler.Option{
 		reconciler.WithActiveConcurrency(int(config.Data.ActiveReconciliationConcurrency)),
 		reconciler.WithInactiveConcurrency(int(config.Data.InactiveReconciliationConcurrency)),
@@ -302,6 +329,7 @@ func InitializeData(
 		reconciler.WithSeenAccounts(seenAccounts),
 		reconciler.WithInactiveFrequency(int64(config.Data.InactiveReconciliationFrequency)),
 		reconciler.WithBalancePruning(),
+		reconciler.WithMetaData(metadata),
 	}
 	if config.Data.ReconcilerActiveBacklog != nil {
 		rOpts = append(rOpts, reconciler.WithBacklogSize(*config.Data.ReconcilerActiveBacklog))
@@ -313,6 +341,7 @@ func InitializeData(
 		rOpts = append(rOpts, reconciler.WithDebugLogging())
 	}
 
+	//add metadata into reconciler
 	r := reconciler.New(
 		reconcilerHelper,
 		reconcilerHandler,
@@ -366,12 +395,16 @@ func InitializeData(
 					genesisBlock,
 				)
 				if err != nil {
-					return nil, fmt.Errorf("unable to bootstrap balances: %w", err)
+					err = fmt.Errorf("unable to bootstrap balances: %w%s", err, metadata)
+					color.Red(err.Error())
+					return nil, err
 				}
 			case err != nil:
-				return nil, fmt.Errorf("unable to get head block identifier: %w", err)
+				err = fmt.Errorf("unable to get head block identifier: %w%s", err, metadata)
+				color.Red(err.Error())
+				return nil, err
 			default:
-				log.Println("Skipping balance bootstrapping because already started syncing")
+				color.Cyan("Skipping balance bootstrapping because already started syncing%s", metadata)
 			}
 		}
 	}
@@ -383,11 +416,13 @@ func InitializeData(
 		blockWorkers = append(blockWorkers, coinStorage)
 	}
 
+	//add metadata into statefulsyncer
 	statefulSyncerOptions := []statefulsyncer.Option{
 		statefulsyncer.WithCacheSize(syncer.DefaultCacheSize),
 		statefulsyncer.WithMaxConcurrency(config.MaxSyncConcurrency),
 		statefulsyncer.WithPastBlockLimit(config.MaxReorgDepth),
 		statefulsyncer.WithSeenConcurrency(int64(config.SeenBlockWorkers)),
+		statefulsyncer.WithMetaData(metadata),
 	}
 	if config.Data.PruningFrequency != nil {
 		statefulSyncerOptions = append(
@@ -396,6 +431,7 @@ func InitializeData(
 		)
 	}
 
+	//add metadata into syncer
 	syncer := statefulsyncer.New(
 		ctx,
 		network,
@@ -545,7 +581,10 @@ func (t *DataTester) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	)
 
 	if err := json.NewEncoder(w).Encode(status); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
+		msg := err.Error()
+		msg = fmt.Sprintf("%s%s", msg, metadata)
+		color.Red(msg)
+		http.Error(w, msg, http.StatusInternalServerError)
 	}
 }
 
@@ -560,7 +599,9 @@ func (t *DataTester) syncedStatus(ctx context.Context) (bool, int64, error) {
 		t.blockStorage,
 	)
 	if err != nil {
-		return false, -1, fmt.Errorf("failed to check storage tip: %w", err)
+		err = fmt.Errorf("failed to check storage tip: %w%s", err, metadata)
+		color.Red(err.Error())
+		return false, -1, err
 	}
 
 	var blockIndex int64 = -1
@@ -587,9 +628,10 @@ func (t *DataTester) EndAtTipLoop(
 		case <-tc.C:
 			atTip, blockIndex, err := t.syncedStatus(ctx)
 			if err != nil {
-				log.Printf(
-					"unable to evaluate if syncer is at tip: %s",
+				color.Red(
+					"unable to evaluate if syncer is at tip: %s%s",
 					err.Error(),
+					metadata,
 				)
 				continue
 			}
@@ -600,6 +642,12 @@ func (t *DataTester) EndAtTipLoop(
 					"Tip: %d",
 					blockIndex,
 				)
+				msg := fmt.Sprintf(
+					"%s%s",
+					t.endConditionDetail,
+					metadata,
+				)
+				color.Cyan(msg)
 				t.cancel()
 				return
 			}
@@ -625,9 +673,10 @@ func (t *DataTester) EndReconciliationCoverage( // nolint:gocognit
 		case <-tc.C:
 			atTip, blockIndex, err := t.syncedStatus(ctx)
 			if err != nil {
-				log.Printf(
-					"unable to evaluate syncer height or if at tip: %s",
+				color.Red(
+					"unable to evaluate syncer height or if at tip: %s%s",
 					err.Error(),
+					metadata,
 				)
 				continue
 			}
@@ -686,9 +735,10 @@ func (t *DataTester) EndReconciliationCoverage( // nolint:gocognit
 			if reconciliationCoverage.AccountCount != nil {
 				allAccounts, err := t.balanceStorage.GetAllAccountCurrency(ctx)
 				if err != nil {
-					log.Printf(
-						"unable to get account count: %s",
+					color.Red(
+						"unable to get account count: %s%s",
 						err.Error(),
+						metadata,
 					)
 					continue
 				}
@@ -705,9 +755,10 @@ func (t *DataTester) EndReconciliationCoverage( // nolint:gocognit
 
 			coverage, err := t.balanceStorage.ReconciliationCoverage(ctx, coverageIndex)
 			if err != nil {
-				log.Printf(
-					"unable to get reconciliation coverage: %s",
+				color.Red(
+					"unable to get reconciliation coverage: %s%s",
 					err.Error(),
+					metadata,
 				)
 				continue
 			}
@@ -715,18 +766,25 @@ func (t *DataTester) EndReconciliationCoverage( // nolint:gocognit
 			if coverage >= reconciliationCoverage.Coverage {
 				t.endCondition = configuration.ReconciliationCoverageEndCondition
 				t.endConditionDetail = fmt.Sprintf(
-					"Coverage: %f%%",
+					"Coverage: %f",
 					coverage*utils.OneHundred,
 				)
+				msg := fmt.Sprintf(
+					"%s%s",
+					t.endConditionDetail,
+					metadata,
+				)
+				color.Cyan(msg)
 				t.cancel()
 				return
 			}
 
 			color.Cyan(fmt.Sprintf(
-				"[END CONDITIONS] Waiting for reconciliation coverage after block %d (%f%%) to surpass requirement (%f%%)",
+				"[END CONDITIONS] Waiting for reconciliation coverage after block %d (%f%%) to surpass requirement (%f%%)%s",
 				firstTipIndex,
 				coverage*utils.OneHundred,
 				reconciliationCoverage.Coverage*utils.OneHundred,
+				metadata,
 			))
 		}
 	}
@@ -751,6 +809,12 @@ func (t *DataTester) EndDurationLoop(
 				"Seconds: %d",
 				int(duration.Seconds()),
 			)
+			msg := fmt.Sprintf(
+				"%s%s",
+				t.endConditionDetail,
+				metadata,
+			)
+			color.Cyan(msg)
 			t.cancel()
 			return
 		}
@@ -788,22 +852,30 @@ func (t *DataTester) WatchEndConditions(
 func (t *DataTester) CompleteReconciliations(ctx context.Context) (int64, error) {
 	activeReconciliations, err := t.counterStorage.Get(ctx, modules.ActiveReconciliationCounter)
 	if err != nil {
-		return -1, fmt.Errorf("failed to get active reconciliations counter: %w", err)
+		err = fmt.Errorf("failed to get active reconciliations counter: %w%s", err, metadata)
+		color.Red(err.Error())
+		return -1, fmt.Errorf("failed to get active reconciliations counter: %w%s", err, metadata)
 	}
 
 	exemptReconciliations, err := t.counterStorage.Get(ctx, modules.ExemptReconciliationCounter)
 	if err != nil {
-		return -1, fmt.Errorf("failed to get exempt reconciliations counter: %w", err)
+		err = fmt.Errorf("failed to get exempt reconciliations counter: %w%s", err, metadata)
+		color.Red(err.Error())
+		return -1, err
 	}
 
 	failedReconciliations, err := t.counterStorage.Get(ctx, modules.FailedReconciliationCounter)
 	if err != nil {
-		return -1, fmt.Errorf("failed to get failed reconciliations counter: %w", err)
+		err = fmt.Errorf("failed to get failed reconciliations counter: %w%s", err, metadata)
+		color.Red(err.Error())
+		return -1, err
 	}
 
 	skippedReconciliations, err := t.counterStorage.Get(ctx, modules.SkippedReconciliationsCounter)
 	if err != nil {
-		return -1, fmt.Errorf("failed to get skipped reconciliations counter: %w", err)
+		err = fmt.Errorf("failed to get skipped reconciliations counter: %w%s", err, metadata)
+		color.Red(err.Error())
+		return -1, err
 	}
 
 	return activeReconciliations.Int64() +
@@ -823,7 +895,9 @@ func (t *DataTester) WaitForEmptyQueue(
 	// and only exit when that many reconciliations have been performed.
 	startingComplete, err := t.CompleteReconciliations(ctx)
 	if err != nil {
-		return fmt.Errorf("failed to complete reconciliations: %w", err)
+		err = fmt.Errorf("failed to complete reconciliations: %w%s", err, metadata)
+		color.Red(err.Error())
+		return err
 	}
 	startingRemaining := t.reconciler.QueueSize()
 
@@ -831,8 +905,9 @@ func (t *DataTester) WaitForEmptyQueue(
 	defer tc.Stop()
 
 	color.Cyan(
-		"[PROGRESS] remaining reconciliations: %d",
+		"[PROGRESS] remaining reconciliations: %d%s",
 		startingRemaining,
+		metadata,
 	)
 
 	for {
@@ -844,12 +919,16 @@ func (t *DataTester) WaitForEmptyQueue(
 			// We force cached counts to be written before
 			// determining if we should exit.
 			if err := t.reconcilerHandler.UpdateCounts(ctx); err != nil {
-				return fmt.Errorf("failed to update count: %w", err)
+				err = fmt.Errorf("failed to update count: %w%s", err, metadata)
+				color.Red(err.Error())
+				return err
 			}
 
 			nowComplete, err := t.CompleteReconciliations(ctx)
 			if err != nil {
-				return fmt.Errorf("failed to complete reconciliations: %w", err)
+				err = fmt.Errorf("failed to complete reconciliations: %w%s", err, metadata)
+				color.Red(err.Error())
+				return err
 			}
 
 			completed := nowComplete - startingComplete
@@ -860,8 +939,9 @@ func (t *DataTester) WaitForEmptyQueue(
 			}
 
 			color.Cyan(
-				"[PROGRESS] remaining reconciliations: %d",
+				"[PROGRESS] remaining reconciliations: %d%s",
 				remaining,
+				metadata,
 			)
 		}
 	}
@@ -873,7 +953,7 @@ func (t *DataTester) DrainReconcilerQueue(
 	ctx context.Context,
 	sigListeners *[]context.CancelFunc,
 ) error {
-	color.Cyan("draining reconciler backlog (you can disable this in your configuration file)")
+	color.Cyan("draining reconciler backlog (you can disable this in your configuration file)%s", metadata)
 
 	// To cancel all execution, need to call multiple cancel functions.
 	ctx, cancel := context.WithCancel(ctx)
@@ -898,7 +978,7 @@ func (t *DataTester) DrainReconcilerQueue(
 	}
 
 	if errors.Is(err, context.Canceled) {
-		color.Cyan("drained reconciler backlog")
+		color.Cyan("drained reconciler backlog%s", metadata)
 		return nil
 	}
 
@@ -914,11 +994,13 @@ func (t *DataTester) HandleErr(err error, sigListeners *[]context.CancelFunc) er
 	ctx := context.Background()
 
 	if *t.signalReceived {
+		err = fmt.Errorf("%v: %w%s", err.Error(), cliErrs.ErrDataCheckHalt, metadata)
+		color.Red(err.Error())
 		return results.ExitData(
 			t.config,
 			t.counterStorage,
 			t.balanceStorage,
-			fmt.Errorf("%v: %w", err.Error(), cliErrs.ErrDataCheckHalt),
+			err,
 			"",
 			"",
 		)
@@ -932,6 +1014,12 @@ func (t *DataTester) HandleErr(err error, sigListeners *[]context.CancelFunc) er
 			"Index: %d",
 			*t.config.Data.EndConditions.Index,
 		)
+		msg := fmt.Sprintf(
+			"%s%s",
+			t.endConditionDetail,
+			metadata,
+		)
+		color.Cyan(msg)
 	}
 
 	// End condition will only be populated if there is
@@ -942,16 +1030,19 @@ func (t *DataTester) HandleErr(err error, sigListeners *[]context.CancelFunc) er
 			t.reconciler.QueueSize() > 0 {
 			if t.config.Data.ReconciliationDrainDisabled {
 				color.Cyan(
-					"skipping reconciler backlog drain (you can enable this in your configuration file)",
+					"skipping reconciler backlog drain (you can enable this in your configuration file)%s",
+					metadata,
 				)
 			} else {
 				drainErr := t.DrainReconcilerQueue(ctx, sigListeners)
 				if drainErr != nil {
+					err = fmt.Errorf("%w%s", drainErr, metadata)
+					color.Red(err.Error())
 					return results.ExitData(
 						t.config,
 						t.counterStorage,
 						t.balanceStorage,
-						drainErr,
+						err,
 						"",
 						"",
 					)
@@ -971,6 +1062,8 @@ func (t *DataTester) HandleErr(err error, sigListeners *[]context.CancelFunc) er
 
 	fmt.Printf("\n")
 	if t.reconcilerHandler.InactiveFailure == nil {
+		err = fmt.Errorf("%w%s", err, metadata)
+		color.Red(err.Error())
 		return results.ExitData(
 			t.config,
 			t.counterStorage,
@@ -983,8 +1076,11 @@ func (t *DataTester) HandleErr(err error, sigListeners *[]context.CancelFunc) er
 
 	if !t.historicalBalanceEnabled {
 		color.Yellow(
-			"Can't find the block missing operations automatically, please enable historical balance lookup",
+			"Can't find the block missing operations automatically, please enable historical balance lookup%s",
+			metadata,
 		)
+		err = fmt.Errorf("%w%s", err, metadata)
+		color.Red(err.Error())
 		return results.ExitData(
 			t.config,
 			t.counterStorage,
@@ -996,7 +1092,9 @@ func (t *DataTester) HandleErr(err error, sigListeners *[]context.CancelFunc) er
 	}
 
 	if t.config.Data.InactiveDiscrepancySearchDisabled {
-		color.Yellow("Search for inactive reconciliation discrepancy is disabled")
+		color.Yellow("Search for inactive reconciliation discrepancy is disabled%s", metadata)
+		err = fmt.Errorf("%w%s", err, metadata)
+		color.Red(err.Error())
 		return results.ExitData(
 			t.config,
 			t.counterStorage,
@@ -1007,7 +1105,11 @@ func (t *DataTester) HandleErr(err error, sigListeners *[]context.CancelFunc) er
 		)
 	}
 
-	return t.FindMissingOps(ctx, err, sigListeners)
+	return t.FindMissingOps(
+		ctx, 
+		err,
+		sigListeners,
+	)
 }
 
 // FindMissingOps logs the types.BlockIdentifier of a block
@@ -1018,7 +1120,7 @@ func (t *DataTester) FindMissingOps(
 	originalErr error,
 	sigListeners *[]context.CancelFunc,
 ) error {
-	color.Cyan("Searching for block with missing operations...hold tight")
+	color.Cyan("Searching for block with missing operations...hold tight%s", metadata)
 	badBlock, err := t.recursiveOpSearch(
 		ctx,
 		sigListeners,
@@ -1027,7 +1129,7 @@ func (t *DataTester) FindMissingOps(
 		t.reconcilerHandler.InactiveFailureBlock.Index,
 	)
 	if err != nil {
-		color.Yellow("could not find block with missing ops: %s", err.Error())
+		color.Yellow("could not find block with missing ops: %s%s", err.Error(), metadata)
 		return results.ExitData(
 			t.config,
 			t.counterStorage,
@@ -1039,10 +1141,11 @@ func (t *DataTester) FindMissingOps(
 	}
 
 	color.Yellow(
-		"Missing ops for %s in block %d:%s",
+		"Missing ops for %s in block %d:%s%s",
 		types.AccountString(t.reconcilerHandler.InactiveFailure.Account),
 		badBlock.Index,
 		badBlock.Hash,
+		metadata,
 	)
 
 	return results.ExitData(
@@ -1069,13 +1172,17 @@ func (t *DataTester) recursiveOpSearch(
 	// Always use a temporary directory to find missing ops
 	tmpDir, err := utils.CreateTempDir()
 	if err != nil {
-		return nil, fmt.Errorf("unable to create temporary directory: %w", err)
+		err = fmt.Errorf("unable to create temporary directory: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 	defer utils.RemoveTempDir(tmpDir)
 
 	localStore, err := database.NewBadgerDatabase(ctx, tmpDir)
 	if err != nil {
-		return nil, fmt.Errorf("unable to initialize database: %w", err)
+		err = fmt.Errorf("unable to initialize database: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	counterStorage := modules.NewCounterStorage(localStore)
@@ -1090,12 +1197,13 @@ func (t *DataTester) recursiveOpSearch(
 		false,
 		logger.Data,
 		t.network,
-		EmptyRequestUUID,
-		nil,
+		t.logger.GetMetadataMap(),
 	)
 
 	if err != nil {
-		return nil, fmt.Errorf("unable to initialize logger with error: %w", err)
+		err = fmt.Errorf("unable to initialize logger with error: %w%s", err, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	t.forceInactiveReconciliation = types.Bool(false)
@@ -1187,7 +1295,9 @@ func (t *DataTester) recursiveOpSearch(
 	// Close database before starting another search, otherwise we will
 	// have n databases open when we find the offending block.
 	if storageErr := localStore.Close(ctx); storageErr != nil {
-		return nil, fmt.Errorf("unable to close database: %w", storageErr)
+		err = fmt.Errorf("unable to close database: %w%s", storageErr, metadata)
+		color.Red(err.Error())
+		return nil, err
 	}
 
 	if *t.signalReceived {
@@ -1206,18 +1316,22 @@ func (t *DataTester) recursiveOpSearch(
 
 		newEnd := endIndex - InactiveFailureLookbackWindow
 		if newEnd <= newStart {
-			return nil, fmt.Errorf(
-				"next window to check has start index %d <= end index %d",
+			err = fmt.Errorf(
+				"next window to check has start index %d <= end index %d%s",
 				newStart,
 				newEnd,
+				metadata,
 			)
+			color.Red(err.Error())
+			return nil, err
 		}
 
 		color.Cyan(
-			"Unable to find missing ops in block range %d-%d, now searching %d-%d",
+			"Unable to find missing ops in block range %d-%d, now searching %d-%d%s",
 			startIndex, endIndex,
 			newStart,
 			newEnd,
+			metadata,
 		)
 
 		return t.recursiveOpSearch(