diff --git a/go.mod b/go.mod index e83e024..6028b2b 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,8 @@ require ( github.com/jdgcs/ed25519 v0.0.0-20200408034030-96c10d46cdc3 github.com/jmoiron/sqlx v1.4.0 github.com/mr-tron/base58 v1.2.0 - github.com/newrelic/go-agent/v3 v3.20.1 + github.com/newrelic/go-agent/v3 v3.40.1 + github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzap v1.2.4 github.com/newrelic/go-agent/v3/integrations/nrpgx v1.0.0 github.com/ory/dockertest/v3 v3.7.0 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 diff --git a/go.sum b/go.sum index b01246d..22676b0 100644 --- a/go.sum +++ b/go.sum @@ -356,8 +356,10 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/newrelic/go-agent/v3 v3.3.0/go.mod h1:H28zDNUC0U/b7kLoY4EFOhuth10Xu/9dchozUiOseQQ= -github.com/newrelic/go-agent/v3 v3.20.1 h1:xxhPjE/j4z7n82FQV4izRjIkd4E10q4flqgzMj+DlLM= -github.com/newrelic/go-agent/v3 v3.20.1/go.mod h1:rT6ZUxJc5rQbWLyCtjqQCOcfb01lKRFbc1yMQkcboWM= +github.com/newrelic/go-agent/v3 v3.40.1 h1:8nb4R252Fpuc3oySvlHpDwqySqaPWL5nf7ZVEhqtUeA= +github.com/newrelic/go-agent/v3 v3.40.1/go.mod h1:X0TLXDo+ttefTIue1V96Y5seb8H6wqf6uUq4UpPsYj8= +github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzap v1.2.4 h1:Hf3pC0FNVhuO2AwruSRM4pyTBKHFaLohcF68dqScA64= +github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzap v1.2.4/go.mod h1:B+EpkW1/oOf6W3rprefGYXq7JIkhz3WR8nZNjZX3xqc= github.com/newrelic/go-agent/v3/integrations/nrpgx v1.0.0 h1:5pj3uXyWB0fpgbeK1yW51go6Y57uRG8F7w5Nu6kIiCQ= github.com/newrelic/go-agent/v3/integrations/nrpgx v1.0.0/go.mod h1:G4vsr8xgPwFxxwJSbE982D7rswRFEfoCaXPQWWWQyQo= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= diff --git a/grpc/app/app.go b/grpc/app/app.go index 4f812d4..b469edd 100644 --- a/grpc/app/app.go +++ b/grpc/app/app.go @@ -14,6 +14,7 @@ import ( "time" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "github.com/newrelic/go-agent/v3/integrations/logcontext-v2/nrzap" "github.com/newrelic/go-agent/v3/newrelic" "github.com/pkg/errors" "github.com/robfig/cron/v3" @@ -31,6 +32,8 @@ import ( "github.com/code-payments/ocp-server/osutil" ) +// todo: Better metrics provider abstraction so we're not directly tied to NR + // App is a long lived application that services network requests. // It is expected that App's have gRPC services, but is not a hard requirement. // @@ -41,10 +44,7 @@ type App interface { // Init initializes the application in a blocking fashion. When Init returns, it // is expected that the application is ready to start receiving requests (provided // there are gRPC handlers installed). - // - // todo: I'm not very happy with passing the New Relic app here. It's a temporary - // solution until we have something better in place. - Init(config Config, metricsProvider *newrelic.Application) error + Init(log *zap.Logger, metricsProvider *newrelic.Application, config Config) error // RegisterWithGRPC provides a mechanism for the application to register gRPC services // with the gRPC server. @@ -73,9 +73,11 @@ func init() { signal.Notify(osSigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP) } -func Run(log *zap.Logger, app App, options ...Option) error { +func Run(app App, options ...Option) error { flag.Parse() + log := zap.New(getLogCore(zap.InfoLevel)) + // viper.ReadInConfig only returns ConfigFileNotFoundError if it has to search // for a default config file because one hasn't been explicitly set. That is, // if we explicitly set a config file, and it does not exist, viper will not @@ -100,12 +102,8 @@ func Run(log *zap.Logger, app App, options ...Option) error { os.Exit(1) } - if len(config.AppName) == 0 { - log.With(zap.Error(err)).Error("must specify an application name") - os.Exit(1) - } + log = zap.New(getLogCore(getLogLevel(config.LogLevel))) - // todo: Better abstraction so we're not directly tied to NR var metricsProvider *newrelic.Application if len(config.NewRelicLicenseKey) > 0 { nr, err := newrelic.NewApplication( @@ -121,6 +119,18 @@ func Run(log *zap.Logger, app App, options ...Option) error { } metricsProvider = nr + + nrLogCore, err := nrzap.WrapBackgroundCore(getLogCore(getLogLevel(config.LogLevel)), metricsProvider) + if err != nil { + log.With(zap.Error(err)).Error("error wrapping logs with new relic") + return err + } + log = zap.New(nrLogCore) + } + + if len(config.AppName) == 0 { + log.With(zap.Error(err)).Error("must specify an application name") + os.Exit(1) } // We don't want to expose pprof/expvar publically, so we reset the default @@ -253,7 +263,7 @@ func Run(log *zap.Logger, app App, options ...Option) error { o(&opts) } - if err := app.Init(config.AppConfig, metricsProvider); err != nil { + if err := app.Init(log, metricsProvider, config.AppConfig); err != nil { log.With(zap.Error(err)).Error("failed to initialize application") os.Exit(1) } diff --git a/grpc/app/config.go b/grpc/app/config.go index 1af8e83..23c36b1 100644 --- a/grpc/app/config.go +++ b/grpc/app/config.go @@ -15,6 +15,8 @@ type Config map[string]interface{} type BaseConfig struct { AppName string `mapstructure:"app_name"` + LogLevel string `mapstructure:"log_level"` + ListenAddress string `mapstructure:"listen_address"` InsecureListenAddress string `mapstructure:"insecure_listen_address"` DebugListenAddress string `mapstructure:"debug_listen_address"` @@ -57,6 +59,8 @@ type BaseConfig struct { } var defaultConfig = BaseConfig{ + LogLevel: "info", + ListenAddress: ":8085", InsecureListenAddress: "localhost:8086", DebugListenAddress: ":8123", diff --git a/grpc/app/logging.go b/grpc/app/logging.go new file mode 100644 index 0000000..772b954 --- /dev/null +++ b/grpc/app/logging.go @@ -0,0 +1,31 @@ +package app + +import ( + "os" + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func getLogCore(level zapcore.LevelEnabler) zapcore.Core { + return zapcore.NewCore( + zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), + zapcore.AddSync(os.Stdout), + zap.InfoLevel, + ) +} + +func getLogLevel(value string) zapcore.LevelEnabler { + switch strings.ToLower(value) { + case "debug": + return zap.DebugLevel + case "info": + return zap.InfoLevel + case "warn": + return zap.WarnLevel + case "error": + return zap.ErrorLevel + } + return zap.InfoLevel +}