feat: add Sentry setup

This commit is contained in:
2022-12-16 16:02:16 +01:00
parent c5e43bd0a9
commit 577ad601ca
2 changed files with 61 additions and 10 deletions
+59 -10
View File
@@ -9,12 +9,14 @@ import (
"reflect" "reflect"
"sync" "sync"
"syscall" "syscall"
"time"
"github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground" "github.com/99designs/gqlgen/graphql/playground"
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
"github.com/apex/log" "github.com/apex/log"
"github.com/apex/log/handlers/json" "github.com/apex/log/handlers/json"
"github.com/getsentry/sentry-go"
sentryhttp "github.com/getsentry/sentry-go/http" sentryhttp "github.com/getsentry/sentry-go/http"
"github.com/rs/cors" "github.com/rs/cors"
"github.com/sparetimecoders/goamqp" "github.com/sparetimecoders/goamqp"
@@ -30,21 +32,30 @@ import (
"gitlab.com/unboundsoftware/schemas/store" "gitlab.com/unboundsoftware/schemas/store"
) )
var CLI struct { type CLI struct {
AmqpURL string `name:"amqp-url" env:"AMQP_URL" help:"URL to use to connect to RabbitMQ" default:"amqp://user:password@localhost:5672/"` AmqpURL string `name:"amqp-url" env:"AMQP_URL" help:"URL to use to connect to RabbitMQ" default:"amqp://user:password@localhost:5672/"`
Port int `name:"port" env:"PORT" help:"Listen-port for GraphQL API" default:"8080"` Port int `name:"port" env:"PORT" help:"Listen-port for GraphQL API" default:"8080"`
APIKey string `name:"api-key" env:"API_KEY" help:"The API-key that is required"` APIKey string `name:"api-key" env:"API_KEY" help:"The API-key that is required"`
LogLevel string `name:"log-level" env:"LOG_LEVEL" help:"The level of logging to use (debug, info, warn, error, fatal)" default:"info"` LogLevel string `name:"log-level" env:"LOG_LEVEL" help:"The level of logging to use (debug, info, warn, error, fatal)" default:"info"`
DatabaseURL string `name:"postgres-url" env:"POSTGRES_URL" help:"URL to use to connect to Postgres" default:"postgres://postgres:postgres@:5432/schemas?sslmode=disable"` DatabaseURL string `name:"postgres-url" env:"POSTGRES_URL" help:"URL to use to connect to Postgres" default:"postgres://postgres:postgres@:5432/schemas?sslmode=disable"`
DatabaseDriverName string `name:"db-driver" env:"DB_DRIVER" help:"Driver to use to connect to db" default:"postgres"` DatabaseDriverName string `name:"db-driver" env:"DB_DRIVER" help:"Driver to use to connect to db" default:"postgres"`
SentryConfig
} }
type SentryConfig struct {
DSN string `name:"sentry-dsn" env:"SENTRY_DSN" help:"Sentry dsn" default:""`
Environment string `name:"sentry-environment" env:"SENTRY_ENVIRONMENT" help:"Sentry environment" default:"development"`
}
var buildVersion = "none"
const serviceName = "schemas" const serviceName = "schemas"
func main() { func main() {
_ = kong.Parse(&CLI) var cli CLI
_ = kong.Parse(&cli)
log.SetHandler(json.New(os.Stdout)) log.SetHandler(json.New(os.Stdout))
log.SetLevelFromString(CLI.LogLevel) log.SetLevelFromString(cli.LogLevel)
logger := log.WithField("service", serviceName) logger := log.WithField("service", serviceName)
closeEvents := make(chan error) closeEvents := make(chan error)
@@ -52,16 +63,22 @@ func main() {
closeEvents, closeEvents,
logger, logger,
ConnectAMQP, ConnectAMQP,
cli,
); err != nil { ); err != nil {
logger.WithError(err).Error("process error") logger.WithError(err).Error("process error")
} }
} }
func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url string) (Connection, error)) error { func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url string) (Connection, error), cli CLI) error {
if err := setupSentry(logger, cli.SentryConfig); err != nil {
return err
}
defer sentry.Flush(2 * time.Second)
rootCtx, rootCancel := context.WithCancel(context.Background()) rootCtx, rootCancel := context.WithCancel(context.Background())
defer rootCancel() defer rootCancel()
db, err := store.SetupDB(CLI.DatabaseDriverName, CLI.DatabaseURL) db, err := store.SetupDB(cli.DatabaseDriverName, cli.DatabaseURL)
if err != nil { if err != nil {
return fmt.Errorf("failed to setup DB: %v", err) return fmt.Errorf("failed to setup DB: %v", err)
} }
@@ -85,7 +102,7 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url
return fmt.Errorf("failed to create event publisher: %v", err) return fmt.Errorf("failed to create event publisher: %v", err)
} }
amqp.New(eventPublisher) amqp.New(eventPublisher)
conn, err := connectToAmqpFunc(CLI.AmqpURL) conn, err := connectToAmqpFunc(cli.AmqpURL)
if err != nil { if err != nil {
return fmt.Errorf("failed to connect to AMQP: %v", err) return fmt.Errorf("failed to connect to AMQP: %v", err)
} }
@@ -121,7 +138,7 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url
logger.Info("Started") logger.Info("Started")
mux := http.NewServeMux() mux := http.NewServeMux()
httpSrv := &http.Server{Addr: fmt.Sprintf(":%d", CLI.Port), Handler: mux} httpSrv := &http.Server{Addr: fmt.Sprintf(":%d", cli.Port), Handler: mux}
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
@@ -179,7 +196,7 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url
Resolvers: resolver, Resolvers: resolver,
Complexity: generated.ComplexityRoot{}, Complexity: generated.ComplexityRoot{},
} }
apiKeyMiddleware := middleware.NewApiKey(CLI.APIKey, logger) apiKeyMiddleware := middleware.NewApiKey(cli.APIKey, logger)
config.Directives.HasApiKey = apiKeyMiddleware.Directive config.Directives.HasApiKey = apiKeyMiddleware.Directive
srv := handler.NewDefaultServer(generated.NewExecutableSchema( srv := handler.NewDefaultServer(generated.NewExecutableSchema(
config, config,
@@ -187,10 +204,10 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url
sentryHandler := sentryhttp.New(sentryhttp.Options{Repanic: true}) sentryHandler := sentryhttp.New(sentryhttp.Options{Repanic: true})
mux.Handle("/", sentryHandler.HandleFunc(playground.Handler("GraphQL playground", "/query"))) mux.Handle("/", sentryHandler.HandleFunc(playground.Handler("GraphQL playground", "/query")))
mux.Handle("/health", sentryHandler.HandleFunc(healthFunc)) mux.Handle("/health", http.HandlerFunc(healthFunc))
mux.Handle("/query", cors.AllowAll().Handler(sentryHandler.Handle(apiKeyMiddleware.Handler(srv)))) mux.Handle("/query", cors.AllowAll().Handler(sentryHandler.Handle(apiKeyMiddleware.Handler(srv))))
logger.Infof("connect to http://localhost:%d/ for GraphQL playground", CLI.Port) logger.Infof("connect to http://localhost:%d/ for GraphQL playground", cli.Port)
if err := httpSrv.ListenAndServe(); err != nil { if err := httpSrv.ListenAndServe(); err != nil {
logger.WithError(err).Error("listen http") logger.WithError(err).Error("listen http")
@@ -206,6 +223,38 @@ func healthFunc(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("OK")) _, _ = w.Write([]byte("OK"))
} }
func setupSentry(logger log.Interface, args SentryConfig) error {
if args.Environment == "" {
return fmt.Errorf("no Sentry environment supplied, exiting")
}
cfg := sentry.ClientOptions{
Dsn: args.DSN,
Environment: args.Environment,
Release: fmt.Sprintf("%s-%s", serviceName, buildVersion),
}
switch args.Environment {
case "development":
cfg.Debug = true
cfg.EnableTracing = false
cfg.TracesSampleRate = 0.0
case "production":
if args.DSN == "" {
return fmt.Errorf("no DSN supplied for non-dev environment, exiting")
}
cfg.Debug = false
cfg.EnableTracing = true
cfg.TracesSampleRate = 1.0
default:
return fmt.Errorf("illegal environment %s", args.Environment)
}
if err := sentry.Init(cfg); err != nil {
return fmt.Errorf("sentry setup: %w", err)
}
logger.Infof("configured Sentry for env: %s", args.Environment)
return nil
}
func ConnectAMQP(url string) (Connection, error) { func ConnectAMQP(url string) (Connection, error) {
return goamqp.NewFromURL(serviceName, url) return goamqp.NewFromURL(serviceName, url)
} }
+2
View File
@@ -14,6 +14,8 @@ spec:
data: data:
POSTGRES_URL: "postgres://{{ .DB_USERNAME }}:{{ .DB_PASSWORD }}@{{ .DB_HOST }}:{{ .DB_PORT }}/schemas?sslmode=disable" POSTGRES_URL: "postgres://{{ .DB_USERNAME }}:{{ .DB_PASSWORD }}@{{ .DB_HOST }}:{{ .DB_PORT }}/schemas?sslmode=disable"
API_KEY: "{{ .API_KEY }}" API_KEY: "{{ .API_KEY }}"
SENTRY_DSN: "{{ .SENTRY_DSN }}"
SENTRY_ENVIRONMENT: "{{ .SENTRY_ENVIRONMENT }}"
dataFrom: dataFrom:
- extract: - extract:
key: services/schemas key: services/schemas