github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/config/config.go (about)

     1  // Copyright © 2021 Kaleido, Inc.
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package config
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  	"os"
    24  	"sort"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/kaleido-io/firefly/internal/i18n"
    29  	"github.com/kaleido-io/firefly/internal/log"
    30  	"github.com/kaleido-io/firefly/pkg/fftypes"
    31  	"github.com/sirupsen/logrus"
    32  	"github.com/spf13/viper"
    33  )
    34  
    35  // The following keys can be access from the root configuration.
    36  // Plugins are resonsible for defining their own keys using the Config interface
    37  var (
    38  	// APIDefaultFilterLimit is the default limit that will be applied to filtered queries on the API
    39  	APIDefaultFilterLimit = rootKey("api.defaultFilterLimit")
    40  	// APIMaxFilterLimit is the maximum limit that can be specified by an API call
    41  	APIMaxFilterLimit = rootKey("api.maxFilterLimit")
    42  	// APIMaxFilterSkip is the maximum skip value that can be specified on the API
    43  	APIMaxFilterSkip = rootKey("api.maxFilterLimit")
    44  	// APIRequestTimeout is the server side timeout for API calls (context timeout), to avoid the server continuing processing when the client gives up
    45  	APIRequestTimeout = rootKey("api.requestTimeout")
    46  	// BatchManagerReadPageSize is the size of each page of messages read from the database into memory when assembling batches
    47  	BatchManagerReadPageSize = rootKey("batch.manager.readPageSize")
    48  	// BatchManagerReadPollTimeout is how long without any notifications of new messages to wait, before doing a page query
    49  	BatchManagerReadPollTimeout = rootKey("batch.manager.pollTimeout")
    50  	// BatchRetryFactor is the retry backoff factor for database operations performed by the batch manager
    51  	BatchRetryFactor = rootKey("batch.retry.factor")
    52  	// BatchRetryInitDelay is the retry initial delay for database operations
    53  	BatchRetryInitDelay = rootKey("batch.retry.initDelay")
    54  	// BatchRetryMaxDelay is the maximum delay between retry attempts
    55  	BatchRetryMaxDelay = rootKey("batch.retry.maxDelay")
    56  	// BlockchainType is the name of the blockchain interface plugin being used by this firefly name
    57  	BlockchainType = rootKey("blockchain.type")
    58  	// BroadcastBatchAgentTimeout how long to keep around a batching agent for a sending identity before disposal
    59  	BroadcastBatchAgentTimeout = rootKey("broadcast.batch.agentTimeout")
    60  	// BroadcastBatchSize is the maximum size of a batch for broadcast messages
    61  	BroadcastBatchSize = rootKey("broadcast.batch.size")
    62  	// BroadcastBatchTimeout is the timeout to wait for a batch to fill, before sending
    63  	BroadcastBatchTimeout = rootKey("broadcast.batch.timeout")
    64  	// PrivateMessagingBatchAgentTimeout how long to keep around a batching agent for a sending identity before disposal
    65  	PrivateMessagingBatchAgentTimeout = rootKey("privatemessaging.batch.agentTimeout")
    66  	// PrivateMessagingBatchSize is the maximum size of a batch for broadcast messages
    67  	PrivateMessagingBatchSize = rootKey("privatemessaging.batch.size")
    68  	// PrivateMessagingBatchTimeout is the timeout to wait for a batch to fill, before sending
    69  	PrivateMessagingBatchTimeout = rootKey("privatemessaging.batch.timeout")
    70  	// PrivateMessagingOpCorrelationRetries how many times to correlate an event for an operation (such as tx submission) back to an operation.
    71  	// Needed because the operation update might come back before we are finished persisting the ID of the request
    72  	PrivateMessagingOpCorrelationRetries = rootKey("privatemessaging.opCorrelationRetries")
    73  	// PrivateMessagingRetryFactor the backoff factor to use for retry of database operations
    74  	PrivateMessagingRetryFactor = rootKey("privatemessaging.retry.factor")
    75  	// PrivateMessagingRetryInitDelay the initial delay to use for retry of data base operations
    76  	PrivateMessagingRetryInitDelay = rootKey("privatemessaging.retry.initDelay")
    77  	// PrivateMessagingRetryMaxDelay the maximum delay to use for retry of data base operations
    78  	PrivateMessagingRetryMaxDelay = rootKey("privatemessaging.retry.maxDelay")
    79  	// CorsAllowCredentials CORS setting to control whether a browser allows credentials to be sent to this API
    80  	CorsAllowCredentials = rootKey("cors.credentials")
    81  	// CorsAllowedHeaders CORS setting to control the allowed headers
    82  	CorsAllowedHeaders = rootKey("cors.headers")
    83  	// CorsAllowedMethods CORS setting to control the allowed methods
    84  	CorsAllowedMethods = rootKey("cors.methods")
    85  	// CorsAllowedOrigins CORS setting to control the allowed origins
    86  	CorsAllowedOrigins = rootKey("cors.origins")
    87  	// CorsDebug is whether debug is enabled for the CORS implementation
    88  	CorsDebug = rootKey("cors.debug")
    89  	// CorsEnabled is whether cors is enabled
    90  	CorsEnabled = rootKey("cors.enabled")
    91  	// CorsMaxAge is the maximum age a browser should rely on CORS checks
    92  	CorsMaxAge = rootKey("cors.maxAge")
    93  	// DataexchangeType is the name of the data exchange plugin being used by this firefly name
    94  	DataexchangeType = rootKey("dataexchange.type")
    95  	// DatabaseType the type of the database interface plugin to use
    96  	DatabaseType = rootKey("database.type")
    97  	// DebugPort a HTTP port on which to enable the go debugger
    98  	DebugPort = rootKey("debug.port")
    99  	// EventTransportsDefault the default event transport for new subscriptions
   100  	EventTransportsDefault = rootKey("event.transports.default")
   101  	// EventTransportsEnabled which event interface plugins are enabled
   102  	EventTransportsEnabled = rootKey("event.transports.enabled")
   103  	// EventAggregatorFirstEvent the first event the aggregator should process, if no previous offest is stored in the DB
   104  	EventAggregatorFirstEvent = rootKey("event.aggregator.firstEvent")
   105  	// EventAggregatorBatchSize the maximum number of records to read from the DB before performing an aggregation run
   106  	EventAggregatorBatchSize = rootKey("event.aggregator.batchSize")
   107  	// EventAggregatorBatchTimeout how long to wait for new events to arrive before performing aggregation on a page of events
   108  	EventAggregatorBatchTimeout = rootKey("event.aggregator.batchTimeout")
   109  	// EventAggregatorOpCorrelationRetries how many times to correlate an event for an operation (such as tx submission) back to an operation.
   110  	// Needed because the operation update might come back before we are finished persisting the ID of the request
   111  	EventAggregatorOpCorrelationRetries = rootKey("event.aggregator.opCorrelationRetries")
   112  	// EventAggregatorPollTimeout the time to wait without a notification of new events, before trying a select on the table
   113  	EventAggregatorPollTimeout = rootKey("event.aggregator.pollTimeout")
   114  	// EventAggregatorRetryFactor the backoff factor to use for retry of database operations
   115  	EventAggregatorRetryFactor = rootKey("event.aggregator.retry.factor")
   116  	// EventAggregatorRetryInitDelay the initial delay to use for retry of data base operations
   117  	EventAggregatorRetryInitDelay = rootKey("event.aggregator.retry.initDelay")
   118  	// EventAggregatorRetryMaxDelay the maximum delay to use for retry of data base operations
   119  	EventAggregatorRetryMaxDelay = rootKey("event.aggregator.retry.maxDelay")
   120  	// EventDispatcherPollTimeout the time to wait without a notification of new events, before trying a select on the table
   121  	EventDispatcherPollTimeout = rootKey("event.dispatcher.pollTimeout")
   122  	// EventDispatcherBufferLength the number of events + attachments an individual dispatcher should hold in memory ready for delivery to the subscription
   123  	EventDispatcherBufferLength = rootKey("event.dispatcher.bufferLength")
   124  	// EventDispatcherBatchTimeout a short time to wait for new events to arrive before re-polling for new events
   125  	EventDispatcherBatchTimeout = rootKey("event.dispatcher.batchTimeout")
   126  	// EventDispatcherRetryFactor the backoff factor to use for retry of database operations
   127  	EventDispatcherRetryFactor = rootKey("event.dispatcher.retry.factor")
   128  	// EventDispatcherRetryInitDelay he initial delay to use for retry of data base operations
   129  	EventDispatcherRetryInitDelay = rootKey("event.dispatcher.retry.initDelay")
   130  	// EventDispatcherRetryMaxDelay he maximum delay to use for retry of data base operations
   131  	EventDispatcherRetryMaxDelay = rootKey("event.dispatcher.retry.maxDelay")
   132  	// GroupCacheSize cache size for private group addresses
   133  	GroupCacheSize = rootKey("group.cache.size")
   134  	// GroupCacheTTL cache time-to-live for private group addresses
   135  	GroupCacheTTL = rootKey("group.cache.ttl")
   136  	// HttpAddress the local address to listen on for HTTP/Websocket connections (empty means any address)
   137  	HTTPAddress = rootKey("http.address")
   138  	// HttpPort the local port to listen on for HTTP/Websocket connections
   139  	HTTPPort = rootKey("http.port")
   140  	// HttpReadTimeout the write timeout for the HTTP server
   141  	HTTPReadTimeout = rootKey("http.readTimeout")
   142  	// HttpTLSCAFile the TLS certificate authority file for the HTTP server
   143  	HTTPTLSCAFile = rootKey("http.tls.caFile")
   144  	// HttpTLSCertFile the TLS certificate file for the HTTP server
   145  	HTTPTLSCertFile = rootKey("http.tls.certFile")
   146  	// HttpTLSClientAuth whether the HTTP server requires a mutual TLS connection
   147  	HTTPTLSClientAuth = rootKey("http.tls.clientAuth")
   148  	// HttpTLSEnabled whether TLS is enabled for the HTTP server
   149  	HTTPTLSEnabled = rootKey("http.tls.enabled")
   150  	// HttpTLSKeyFile the private key file for TLS on the server
   151  	HTTPTLSKeyFile = rootKey("http.tls.keyFile")
   152  	// HttpWriteTimeout the write timeout for the HTTP server
   153  	HTTPWriteTimeout = rootKey("http.writeTimeout")
   154  	// IdentityType is the name of the inentity interface plugin being used by this firefly name
   155  	IdentityType = rootKey("identity.type")
   156  	// Lang is the language to use for translation
   157  	Lang = rootKey("lang")
   158  	// LogForceColor forces color to be enabled, even if we do not detect a TTY
   159  	LogForceColor = rootKey("log.forceColor")
   160  	// LogLevel is the logging level
   161  	LogLevel = rootKey("log.level")
   162  	// LogNoColor forces color to be disabled, even if we detect a TTY
   163  	LogNoColor = rootKey("log.noColor")
   164  	// LogTimeFormat is a string format for timestamps
   165  	LogTimeFormat = rootKey("log.timeFormat")
   166  	// LogUTC sets log timestamps to the UTC timezone
   167  	LogUTC = rootKey("log.utc")
   168  	// NamespacesDefault is the default namespace - must be in the predefines list
   169  	NamespacesDefault = rootKey("namespaces.default")
   170  	// NamespacesPredefined is a list of namespaces to ensure exists, without requiring a broadcast from the network
   171  	NamespacesPredefined = rootKey("namespaces.predefined")
   172  	// NodeName is a description for the node
   173  	NodeName = rootKey("node.name")
   174  	// NodeDescription is a description for the node
   175  	NodeDescription = rootKey("node.description")
   176  	// OrgName is the short name o the org
   177  	OrgName = rootKey("org.name")
   178  	// OrgIdentity is the signing identity allocated to the organization (can be the same as the nodes)
   179  	OrgIdentity = rootKey("org.identity")
   180  	// OrgDescription is a description for the org
   181  	OrgDescription = rootKey("org.description")
   182  	// OrchestratorStartupAttempts is how many time to attempt to connect to core infrastructure on startup
   183  	OrchestratorStartupAttempts = rootKey("orchestrator.startupAttempts")
   184  	// PublicStorageType specifies which public storage interface plugin to use
   185  	PublicStorageType = rootKey("publicstorage.type")
   186  	// SubscriptionDefaultsReadAhead default read ahead to enable for subscriptions that do not explicitly configure readahead
   187  	SubscriptionDefaultsReadAhead = rootKey("subscription.defaults.batchSize")
   188  	// SubscriptionMax maximum number of pre-defined subscriptions that can exist (note for high fan-out consider connecting a dedicated pub/sub broker to the dispatcher)
   189  	SubscriptionMax = rootKey("subscription.max")
   190  	// SubscriptionsRetryInitialDelay is the initial retry delay
   191  	SubscriptionsRetryInitialDelay = rootKey("subscription.retry.initDelay")
   192  	// SubscriptionsRetryMaxDelay is the initial retry delay
   193  	SubscriptionsRetryMaxDelay = rootKey("subscription.retry.maxDelay")
   194  	// SubscriptionsRetryFactor the backoff factor to use for retry of database operations
   195  	SubscriptionsRetryFactor = rootKey("event.dispatcher.retry.factor")
   196  	// UIPath the path on which to serve the UI
   197  	UIPath = rootKey("ui.path")
   198  	// ValidatorCacheSize
   199  	ValidatorCacheSize = rootKey("validator.cache.size")
   200  	// ValidatorCacheTTL
   201  	ValidatorCacheTTL = rootKey("validator.cache.ttl")
   202  )
   203  
   204  // Prefix represents the global configuration, at a nested point in
   205  // the config hierarchy. This allows plugins to define their
   206  // Note that all values are GLOBAL so this cannot be used for per-instance
   207  // customization. Rather for global initialization of plugins.
   208  type Prefix interface {
   209  	AddKnownKey(key string, defValue ...interface{})
   210  	SubPrefix(suffix string) Prefix
   211  	Set(key string, value interface{})
   212  	Resolve(key string) string
   213  
   214  	GetString(key string) string
   215  	GetBool(key string) bool
   216  	GetInt(key string) int
   217  	GetInt64(key string) int64
   218  	GetByteSize(key string) int64
   219  	GetUint(key string) uint
   220  	GetDuration(key string) time.Duration
   221  	GetStringSlice(key string) []string
   222  	GetObject(key string) fftypes.JSONObject
   223  	GetObjectArray(key string) fftypes.JSONObjectArray
   224  	Get(key string) interface{}
   225  }
   226  
   227  // RootKey key are the known configuration keys
   228  type RootKey string
   229  
   230  func Reset() {
   231  	viper.Reset()
   232  
   233  	// Set defaults
   234  	viper.SetDefault(string(APIDefaultFilterLimit), 25)
   235  	viper.SetDefault(string(APIRequestTimeout), "120s")
   236  	viper.SetDefault(string(APIMaxFilterLimit), 250)
   237  	viper.SetDefault(string(APIMaxFilterSkip), 1000) // protects database (skip+limit pagination is not for bulk operations)
   238  	viper.SetDefault(string(APIRequestTimeout), "120s")
   239  	viper.SetDefault(string(BatchManagerReadPageSize), 100)
   240  	viper.SetDefault(string(BatchManagerReadPollTimeout), "30s")
   241  	viper.SetDefault(string(BatchRetryFactor), 2.0)
   242  	viper.SetDefault(string(BatchRetryFactor), 2.0)
   243  	viper.SetDefault(string(BatchRetryInitDelay), "250ms")
   244  	viper.SetDefault(string(BatchRetryInitDelay), "250ms")
   245  	viper.SetDefault(string(BatchRetryMaxDelay), "30s")
   246  	viper.SetDefault(string(BatchRetryMaxDelay), "30s")
   247  	viper.SetDefault(string(BroadcastBatchAgentTimeout), "2m")
   248  	viper.SetDefault(string(BroadcastBatchSize), 200)
   249  	viper.SetDefault(string(BroadcastBatchTimeout), "1s")
   250  	viper.SetDefault(string(CorsAllowCredentials), true)
   251  	viper.SetDefault(string(CorsAllowedHeaders), []string{"*"})
   252  	viper.SetDefault(string(CorsAllowedMethods), []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete})
   253  	viper.SetDefault(string(CorsAllowedOrigins), []string{"*"})
   254  	viper.SetDefault(string(CorsEnabled), true)
   255  	viper.SetDefault(string(CorsMaxAge), 600)
   256  	viper.SetDefault(string(DataexchangeType), "https")
   257  	viper.SetDefault(string(DebugPort), -1)
   258  	viper.SetDefault(string(EventAggregatorFirstEvent), fftypes.SubOptsFirstEventOldest)
   259  	viper.SetDefault(string(EventAggregatorBatchSize), 50)
   260  	viper.SetDefault(string(EventAggregatorBatchTimeout), "250ms")
   261  	viper.SetDefault(string(EventAggregatorPollTimeout), "30s")
   262  	viper.SetDefault(string(EventAggregatorRetryFactor), 2.0)
   263  	viper.SetDefault(string(EventAggregatorRetryInitDelay), "100ms")
   264  	viper.SetDefault(string(EventAggregatorRetryMaxDelay), "30s")
   265  	viper.SetDefault(string(EventAggregatorOpCorrelationRetries), 3)
   266  	viper.SetDefault(string(EventDispatcherBufferLength), 5)
   267  	viper.SetDefault(string(EventDispatcherBatchTimeout), "250ms")
   268  	viper.SetDefault(string(EventDispatcherPollTimeout), "30s")
   269  	viper.SetDefault(string(EventTransportsEnabled), []string{"websockets"})
   270  	viper.SetDefault(string(EventTransportsDefault), "websockets")
   271  	viper.SetDefault(string(GroupCacheSize), "1Mb")
   272  	viper.SetDefault(string(GroupCacheTTL), "1h")
   273  	viper.SetDefault(string(HTTPAddress), "127.0.0.1")
   274  	viper.SetDefault(string(HTTPPort), 5000)
   275  	viper.SetDefault(string(HTTPReadTimeout), "15s")
   276  	viper.SetDefault(string(HTTPWriteTimeout), "15s")
   277  	viper.SetDefault(string(IdentityType), "onchain")
   278  	viper.SetDefault(string(Lang), "en")
   279  	viper.SetDefault(string(LogLevel), "info")
   280  	viper.SetDefault(string(LogTimeFormat), "2006-01-02T15:04:05.000Z07:00")
   281  	viper.SetDefault(string(LogUTC), false)
   282  	viper.SetDefault(string(NamespacesDefault), "default")
   283  	viper.SetDefault(string(NamespacesPredefined), fftypes.JSONObjectArray{{"name": "default", "description": "Default predefined namespace"}})
   284  	viper.SetDefault(string(OrchestratorStartupAttempts), 5)
   285  	viper.SetDefault(string(PrivateMessagingRetryFactor), 2.0)
   286  	viper.SetDefault(string(PrivateMessagingRetryInitDelay), "100ms")
   287  	viper.SetDefault(string(PrivateMessagingRetryMaxDelay), "30s")
   288  	viper.SetDefault(string(PrivateMessagingOpCorrelationRetries), 3)
   289  	viper.SetDefault(string(PrivateMessagingBatchAgentTimeout), "2m")
   290  	viper.SetDefault(string(PrivateMessagingBatchSize), 200)
   291  	viper.SetDefault(string(PrivateMessagingBatchTimeout), "1s")
   292  	viper.SetDefault(string(SubscriptionDefaultsReadAhead), 0)
   293  	viper.SetDefault(string(SubscriptionMax), 500)
   294  	viper.SetDefault(string(SubscriptionsRetryInitialDelay), "250ms")
   295  	viper.SetDefault(string(SubscriptionsRetryMaxDelay), "30s")
   296  	viper.SetDefault(string(SubscriptionsRetryFactor), 2.0)
   297  	viper.SetDefault(string(ValidatorCacheSize), "1Mb")
   298  	viper.SetDefault(string(ValidatorCacheTTL), "1h")
   299  
   300  	i18n.SetLang(GetString(Lang))
   301  }
   302  
   303  // ReadConfig initializes the config
   304  func ReadConfig(cfgFile string) error {
   305  	Reset()
   306  
   307  	// Set precedence order for reading config location
   308  	viper.SetEnvPrefix("firefly")
   309  	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
   310  	viper.AutomaticEnv()
   311  	viper.SetConfigType("yaml")
   312  	if cfgFile != "" {
   313  		f, err := os.Open(cfgFile)
   314  		if err == nil {
   315  			defer f.Close()
   316  			err = viper.ReadConfig(f)
   317  		}
   318  		return err
   319  	}
   320  	viper.SetConfigName("firefly.core")
   321  	viper.AddConfigPath("/etc/firefly/")
   322  	viper.AddConfigPath("$HOME/.firefly")
   323  	viper.AddConfigPath(".")
   324  	return viper.ReadInConfig()
   325  }
   326  
   327  var root = &configPrefix{
   328  	keys: map[string]bool{}, // All keys go here, including those defined in sub prefixies
   329  }
   330  
   331  // ark adds a root key, used to define the keys that are used within the core
   332  func rootKey(k string) RootKey {
   333  	root.AddKnownKey(k)
   334  	return RootKey(k)
   335  }
   336  
   337  // GetKnownKeys gets the known keys
   338  func GetKnownKeys() []string {
   339  	var keys []string
   340  	for k := range root.keys {
   341  		keys = append(keys, k)
   342  	}
   343  	sort.Strings(keys)
   344  	return keys
   345  }
   346  
   347  // configPrefix is the main config structure passed to plugins, and used for root to wrap viper
   348  type configPrefix struct {
   349  	prefix string
   350  	keys   map[string]bool
   351  }
   352  
   353  // NewPluginConfig creates a new plugin configuration object, at the specified prefix
   354  func NewPluginConfig(prefix string) Prefix {
   355  	if !strings.HasSuffix(prefix, ".") {
   356  		prefix += "."
   357  	}
   358  	return &configPrefix{
   359  		prefix: prefix,
   360  		keys:   root.keys,
   361  	}
   362  }
   363  
   364  func (c *configPrefix) prefixKey(k string) string {
   365  	key := c.prefix + k
   366  	if !c.keys[key] {
   367  		panic(fmt.Sprintf("Undefined configuration key '%s'", key))
   368  	}
   369  	return key
   370  }
   371  
   372  func (c *configPrefix) SubPrefix(suffix string) Prefix {
   373  	return &configPrefix{
   374  		prefix: c.prefix + suffix + ".",
   375  		keys:   root.keys,
   376  	}
   377  }
   378  
   379  func (c *configPrefix) AddKnownKey(k string, defValue ...interface{}) {
   380  	key := c.prefix + k
   381  	if len(defValue) == 1 {
   382  		viper.SetDefault(key, defValue[0])
   383  	} else if len(defValue) > 0 {
   384  		viper.SetDefault(key, defValue)
   385  	}
   386  	c.keys[key] = true
   387  }
   388  
   389  // GetString gets a configuration string
   390  func GetString(key RootKey) string {
   391  	return root.GetString(string(key))
   392  }
   393  func (c *configPrefix) GetString(key string) string {
   394  	return viper.GetString(c.prefixKey(key))
   395  }
   396  
   397  // GetStringSlice gets a configuration string array
   398  func GetStringSlice(key RootKey) []string {
   399  	return root.GetStringSlice(string(key))
   400  }
   401  func (c *configPrefix) GetStringSlice(key string) []string {
   402  	return viper.GetStringSlice(c.prefixKey(key))
   403  }
   404  
   405  // GetBool gets a configuration bool
   406  func GetBool(key RootKey) bool {
   407  	return root.GetBool(string(key))
   408  }
   409  func (c *configPrefix) GetBool(key string) bool {
   410  	return viper.GetBool(c.prefixKey(key))
   411  }
   412  
   413  // GetDuration gets a configuration time duration with consistent semantics
   414  func GetDuration(key RootKey) time.Duration {
   415  	return root.GetDuration(string(key))
   416  }
   417  func (c *configPrefix) GetDuration(key string) time.Duration {
   418  	return fftypes.ParseToDuration(viper.GetString(c.prefixKey(key)))
   419  }
   420  
   421  // GetByteSize get a size in bytes
   422  func GetByteSize(key RootKey) int64 {
   423  	return root.GetByteSize(string(key))
   424  }
   425  func (c *configPrefix) GetByteSize(key string) int64 {
   426  	return fftypes.ParseToByteSize(c.GetString(key))
   427  }
   428  
   429  // GetUint gets a configuration uint
   430  func GetUint(key RootKey) uint {
   431  	return root.GetUint(string(key))
   432  }
   433  func (c *configPrefix) GetUint(key string) uint {
   434  	return viper.GetUint(c.prefixKey(key))
   435  }
   436  
   437  // GetInt gets a configuration uint
   438  func GetInt(key RootKey) int {
   439  	return root.GetInt(string(key))
   440  }
   441  func (c *configPrefix) GetInt(key string) int {
   442  	return viper.GetInt(c.prefixKey(key))
   443  }
   444  
   445  // GetInt64 gets a configuration uint
   446  func GetInt64(key RootKey) int64 {
   447  	return root.GetInt64(string(key))
   448  }
   449  func (c *configPrefix) GetInt64(key string) int64 {
   450  	return viper.GetInt64(c.prefixKey(key))
   451  }
   452  
   453  // GetFloat64 gets a configuration uint
   454  func GetFloat64(key RootKey) float64 {
   455  	return root.GetFloat64(string(key))
   456  }
   457  func (c *configPrefix) GetFloat64(key string) float64 {
   458  	return viper.GetFloat64(c.prefixKey(key))
   459  }
   460  
   461  // GetObject gets a configuration map
   462  func GetObject(key RootKey) fftypes.JSONObject {
   463  	return root.GetObject(string(key))
   464  }
   465  func (c *configPrefix) GetObject(key string) fftypes.JSONObject {
   466  	return fftypes.JSONObject(viper.GetStringMap(c.prefixKey(key)))
   467  }
   468  
   469  // GetObjectArray gets an array of configuration maps
   470  func GetObjectArray(key RootKey) fftypes.JSONObjectArray {
   471  	return root.GetObjectArray(string(key))
   472  }
   473  func (c *configPrefix) GetObjectArray(key string) fftypes.JSONObjectArray {
   474  	v, _ := fftypes.ToJSONObjectArray(viper.Get(c.prefixKey(key)))
   475  	return v
   476  }
   477  
   478  // Get gets a configuration in raw form
   479  func Get(key RootKey) interface{} {
   480  	return root.Get(string(key))
   481  }
   482  func (c *configPrefix) Get(key string) interface{} {
   483  	return viper.Get(c.prefixKey(key))
   484  }
   485  
   486  // Set allows runtime setting of config (used in unit tests)
   487  func Set(key RootKey, value interface{}) {
   488  	root.Set(string(key), value)
   489  }
   490  func (c *configPrefix) Set(key string, value interface{}) {
   491  	viper.Set(c.prefixKey(key), value)
   492  }
   493  
   494  // Resolve gives the fully qualified path of a key
   495  func (c *configPrefix) Resolve(key string) string {
   496  	return c.prefixKey(key)
   497  }
   498  
   499  // SetupLogging initializes logging
   500  func SetupLogging(ctx context.Context) {
   501  	log.SetFormatting(log.Formatting{
   502  		DisableColor:    GetBool(LogNoColor),
   503  		ForceColor:      GetBool(LogForceColor),
   504  		TimestampFormat: GetString(LogTimeFormat),
   505  		UTC:             GetBool(LogUTC),
   506  	})
   507  	log.SetLevel(GetString(LogLevel))
   508  	log.L(ctx).Debugf("Log level: %s", logrus.GetLevel())
   509  }