From 89cbb4d322c2c04b772089bbf07fdcbd4b556292 Mon Sep 17 00:00:00 2001 From: Jeff Mendoza Date: Tue, 14 Jan 2025 14:16:06 -0800 Subject: [PATCH 1/2] Add otel instrumentation to http/grpc/sql libraries. Also add cli arg to the clis that use those libraries to enable exporting metrics and traces. Docs under pkg/metrics. Default to off and also change prometheus default to off. Signed-off-by: Jeff Mendoza --- cmd/guaccollect/cmd/deps_dev.go | 17 +++ cmd/guaccollect/cmd/github.go | 16 +++ cmd/guaccollect/cmd/license.go | 20 +++- cmd/guaccollect/cmd/osv.go | 16 +++ cmd/guaccollect/cmd/root.go | 1 + cmd/guacgql/cmd/root.go | 3 + cmd/guacgql/cmd/server.go | 30 +++-- cmd/guacingest/cmd/ingest.go | 25 +++- cmd/guacingest/cmd/root.go | 13 +- cmd/guacone/cmd/certifier.go | 2 +- cmd/guacone/cmd/deps_dev.go | 21 +++- cmd/guacone/cmd/eol.go | 15 +++ cmd/guacone/cmd/files.go | 31 ++++- cmd/guacone/cmd/github.go | 35 +++++- cmd/guacone/cmd/license.go | 17 ++- cmd/guacone/cmd/osv.go | 15 +++ go.mod | 18 ++- go.sum | 14 +++ .../client/depsdevclient/deps_dev_client.go | 3 +- .../backends/ent/backend/migrations.go | 11 +- pkg/certifier/eol/eol.go | 3 +- pkg/cli/store.go | 4 +- pkg/metrics/README.md | 47 +++++++- pkg/metrics/otel.go | 113 ++++++++++++++++++ pkg/version/version.go | 5 +- 25 files changed, 455 insertions(+), 40 deletions(-) create mode 100644 pkg/metrics/otel.go diff --git a/cmd/guaccollect/cmd/deps_dev.go b/cmd/guaccollect/cmd/deps_dev.go index a23df10e90..f54634edb6 100644 --- a/cmd/guaccollect/cmd/deps_dev.go +++ b/cmd/guaccollect/cmd/deps_dev.go @@ -30,6 +30,7 @@ import ( "github.com/guacsec/guac/pkg/handler/collector" "github.com/guacsec/guac/pkg/handler/collector/deps_dev" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -53,6 +54,8 @@ type depsDevOptions struct { publishToQueue bool // sets artificial latency on the deps.dev collector (default to nil) addedLatency *time.Duration + // enable otel + enableOtel bool } var depsDevCmd = &cobra.Command{ @@ -90,6 +93,7 @@ you have access to read and write to the respective blob store.`, viper.GetInt("prometheus-port"), viper.GetBool("publish-to-queue"), viper.GetString("deps-dev-latency"), + viper.GetBool("enable-otel"), args, ) if err != nil { @@ -97,6 +101,16 @@ you have access to read and write to the respective blob store.`, _ = cmd.Help() os.Exit(1) } + + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + // Register collector depsDevCollector, err := deps_dev.NewDepsCollector(ctx, opts.dataSource, opts.poll, opts.retrieveDependencies, 30*time.Second, opts.addedLatency) if err != nil { @@ -117,6 +131,7 @@ you have access to read and write to the respective blob store.`, } initializeNATsandCollector(ctx, opts.pubsubAddr, opts.blobAddr, opts.publishToQueue) + shutdown(ctx) }, } @@ -133,6 +148,7 @@ func validateDepsDevFlags( prometheusPort int, pubToQueue bool, addedLatencyStr string, + enableOtel bool, args []string, ) (depsDevOptions, error) { var opts depsDevOptions @@ -143,6 +159,7 @@ func validateDepsDevFlags( opts.enablePrometheus = enablePrometheus opts.prometheusPort = prometheusPort opts.publishToQueue = pubToQueue + opts.enableOtel = enableOtel if addedLatencyStr != "" { addedLatency, err := time.ParseDuration(addedLatencyStr) diff --git a/cmd/guaccollect/cmd/github.go b/cmd/guaccollect/cmd/github.go index 9b400cfbb3..9dc57984e0 100644 --- a/cmd/guaccollect/cmd/github.go +++ b/cmd/guaccollect/cmd/github.go @@ -25,6 +25,7 @@ import ( csubclient "github.com/guacsec/guac/pkg/collectsub/client" "github.com/guacsec/guac/pkg/collectsub/datasource/csubsource" "github.com/guacsec/guac/pkg/collectsub/datasource/inmemsource" + "github.com/guacsec/guac/pkg/metrics" "github.com/guacsec/guac/pkg/cli" @@ -62,6 +63,8 @@ type githubOptions struct { ownerRepoName string // enable/disable message publish to queue publishToQueue bool + // enable otel + enableOtel bool } var githubCmd = &cobra.Command{ @@ -102,6 +105,7 @@ you have access to read and write to the respective blob store.`, viper.GetBool("use-csub"), viper.GetBool("service-poll"), viper.GetBool("publish-to-queue"), + viper.GetBool("enable-otel"), args) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -109,6 +113,15 @@ you have access to read and write to the respective blob store.`, os.Exit(1) } + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + // GITHUB_TOKEN is the default token name ghc, err := githubclient.NewGithubClient(ctx, os.Getenv("GITHUB_TOKEN")) if err != nil { @@ -154,6 +167,7 @@ you have access to read and write to the respective blob store.`, } initializeNATsandCollector(ctx, opts.pubsubAddr, opts.blobAddr, opts.publishToQueue) + shutdown(ctx) }, } @@ -169,6 +183,7 @@ func validateGithubFlags( useCsub, poll bool, pubToQueue bool, + enableOtel bool, args []string, ) (githubOptions, error) { var opts githubOptions @@ -179,6 +194,7 @@ func validateGithubFlags( opts.sbomName = sbomName opts.workflowFileName = workflowFileName opts.publishToQueue = pubToQueue + opts.enableOtel = enableOtel if useCsub { csubOpts, err := csubclient.ValidateCsubClientFlags(csubAddr, csubTls, csubTlsSkipVerify) diff --git a/cmd/guaccollect/cmd/license.go b/cmd/guaccollect/cmd/license.go index dfa0cafb08..920274cc3b 100644 --- a/cmd/guaccollect/cmd/license.go +++ b/cmd/guaccollect/cmd/license.go @@ -30,6 +30,7 @@ import ( "github.com/guacsec/guac/pkg/certifier/components/root_package" "github.com/guacsec/guac/pkg/cli" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -58,6 +59,8 @@ type cdOptions struct { // last time the scan was done in hours, if not set it will return // all packages to check lastScan *int + // enable otel + enableOtel bool } var cdCmd = &cobra.Command{ @@ -90,6 +93,7 @@ you have access to read and write to the respective blob store.`, viper.GetString("certifier-latency"), viper.GetInt("certifier-batch-size"), viper.GetInt("last-scan"), + viper.GetBool("enable-otel"), ) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -100,6 +104,15 @@ you have access to read and write to the respective blob store.`, ctx := logging.WithLogger(context.Background()) logger := logging.FromContext(ctx) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + if err := certify.RegisterCertifier(clearlydefined.NewClearlyDefinedCertifier, certifier.CertifierClearlyDefined); err != nil { logger.Fatalf("unable to register certifier: %v", err) } @@ -115,6 +128,7 @@ you have access to read and write to the respective blob store.`, } initializeNATsandCertifier(ctx, opts.blobAddr, opts.pubsubAddr, opts.poll, opts.publishToQueue, opts.interval, packageQueryFunc()) + shutdown(ctx) }, } @@ -134,7 +148,10 @@ func validateCDFlags( poll bool, pubToQueue bool, certifierLatencyStr string, - batchSize int, lastScan int) (cdOptions, error) { + batchSize int, + lastScan int, + enableOtel bool, +) (cdOptions, error) { var opts cdOptions @@ -144,6 +161,7 @@ func validateCDFlags( opts.blobAddr = blobAddr opts.poll = poll opts.publishToQueue = pubToQueue + opts.enableOtel = enableOtel i, err := time.ParseDuration(interval) if err != nil { diff --git a/cmd/guaccollect/cmd/osv.go b/cmd/guaccollect/cmd/osv.go index 0df54f63ad..06d3a2dd31 100644 --- a/cmd/guaccollect/cmd/osv.go +++ b/cmd/guaccollect/cmd/osv.go @@ -38,6 +38,7 @@ import ( "github.com/guacsec/guac/pkg/handler/collector" "github.com/guacsec/guac/pkg/handler/processor" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -68,6 +69,8 @@ type osvOptions struct { lastScan *int // adds metadata to vulnerabities during collection addVulnMetadata bool + // enable otel + enableOtel bool } var osvCmd = &cobra.Command{ @@ -101,6 +104,7 @@ you have access to read and write to the respective blob store.`, viper.GetInt("certifier-batch-size"), viper.GetInt("last-scan"), viper.GetBool("add-vuln-metadata"), + viper.GetBool("enable-otel"), ) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -111,6 +115,15 @@ you have access to read and write to the respective blob store.`, ctx := logging.WithLogger(context.Background()) logger := logging.FromContext(ctx) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + if err := certify.RegisterCertifier(func() certifier.Certifier { certifierOpts := []osv.CertifierOpts{} if opts.addVulnMetadata { @@ -132,6 +145,7 @@ you have access to read and write to the respective blob store.`, } initializeNATsandCertifier(ctx, opts.blobAddr, opts.pubsubAddr, opts.poll, opts.publishToQueue, opts.interval, packageQueryFunc()) + shutdown(ctx) }, } @@ -146,6 +160,7 @@ func validateOSVFlags( certifierLatencyStr string, batchSize int, lastScan int, addVulnMetadata bool, + enableOtel bool, ) (osvOptions, error) { var opts osvOptions @@ -156,6 +171,7 @@ func validateOSVFlags( opts.poll = poll opts.publishToQueue = pubToQueue opts.addVulnMetadata = addVulnMetadata + opts.enableOtel = enableOtel i, err := time.ParseDuration(interval) if err != nil { diff --git a/cmd/guaccollect/cmd/root.go b/cmd/guaccollect/cmd/root.go index 9347bb1359..133c61e4ea 100644 --- a/cmd/guaccollect/cmd/root.go +++ b/cmd/guaccollect/cmd/root.go @@ -40,6 +40,7 @@ func init() { "enable-prometheus", "publish-to-queue", "gql-addr", + "enable-otel", }) if err != nil { fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err) diff --git a/cmd/guacgql/cmd/root.go b/cmd/guacgql/cmd/root.go index 87001e515b..9cb7de7c12 100644 --- a/cmd/guacgql/cmd/root.go +++ b/cmd/guacgql/cmd/root.go @@ -35,6 +35,7 @@ var flags = struct { tlsKeyFile string debug bool tracegql bool + enableOtel bool }{} var rootCmd = &cobra.Command{ @@ -48,6 +49,7 @@ var rootCmd = &cobra.Command{ flags.tlsKeyFile = viper.GetString("gql-tls-key-file") flags.debug = viper.GetBool("gql-debug") flags.tracegql = viper.GetBool("gql-trace") + flags.enableOtel = viper.GetBool("enable-otel") startServer(cmd) }, @@ -65,6 +67,7 @@ func init() { "gql-backend", "gql-trace", "enable-prometheus", + "enable-otel", }) if err != nil { fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err) diff --git a/cmd/guacgql/cmd/server.go b/cmd/guacgql/cmd/server.go index 4339ab583f..19b9a531e6 100644 --- a/cmd/guacgql/cmd/server.go +++ b/cmd/guacgql/cmd/server.go @@ -26,22 +26,23 @@ import ( "syscall" "time" - // import all known backends - _ "github.com/guacsec/guac/pkg/assembler/backends/neo4j" - _ "github.com/guacsec/guac/pkg/assembler/backends/neptune" - _ "github.com/guacsec/guac/pkg/assembler/backends/ent/backend" - _ "github.com/guacsec/guac/pkg/assembler/backends/keyvalue" - _ "github.com/guacsec/guac/pkg/assembler/backends/arangodb" - "github.com/99designs/gqlgen/graphql/handler/debug" "github.com/99designs/gqlgen/graphql/playground" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "github.com/guacsec/guac/pkg/assembler/backends" + // import all known backends + _ "github.com/guacsec/guac/pkg/assembler/backends/arangodb" + _ "github.com/guacsec/guac/pkg/assembler/backends/ent/backend" + _ "github.com/guacsec/guac/pkg/assembler/backends/keyvalue" + _ "github.com/guacsec/guac/pkg/assembler/backends/neo4j" + _ "github.com/guacsec/guac/pkg/assembler/backends/neptune" "github.com/guacsec/guac/pkg/assembler/server" "github.com/guacsec/guac/pkg/logging" "github.com/guacsec/guac/pkg/metrics" "github.com/guacsec/guac/pkg/version" - "github.com/spf13/cobra" - "github.com/spf13/viper" ) func startServer(cmd *cobra.Command) { @@ -80,6 +81,16 @@ func startServer(cmd *cobra.Command) { srvHandler = srv } + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if flags.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + srvHandler = otelhttp.NewHandler(srvHandler, "/") + } + if flags.tracegql { tracer := &debug.Tracer{} srv.Use(tracer) @@ -115,6 +126,7 @@ func startServer(cmd *cobra.Command) { ctx, cf := context.WithCancel(ctx) go func() { _ = server.Shutdown(ctx) + _ = shutdown(ctx) done <- true }() select { diff --git a/cmd/guacingest/cmd/ingest.go b/cmd/guacingest/cmd/ingest.go index 5802d46946..18db0d437b 100644 --- a/cmd/guacingest/cmd/ingest.go +++ b/cmd/guacingest/cmd/ingest.go @@ -35,6 +35,7 @@ import ( "github.com/guacsec/guac/pkg/handler/processor/process" "github.com/guacsec/guac/pkg/ingestor" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -49,6 +50,7 @@ type options struct { queryLicenseOnIngestion bool queryEOLOnIngestion bool queryDepsDevOnIngestion bool + enableOtel bool } func ingest(cmd *cobra.Command, args []string) { @@ -63,6 +65,7 @@ func ingest(cmd *cobra.Command, args []string) { viper.GetBool("add-vuln-on-ingest"), viper.GetBool("add-license-on-ingest"), viper.GetBool("add-eol-on-ingest"), + viper.GetBool("enable-otel"), args) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -74,6 +77,15 @@ func ingest(cmd *cobra.Command, args []string) { logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + if strings.HasPrefix(opts.pubsubAddr, "nats://") { // initialize jetstream // TODO: pass in credentials file for NATS secure login @@ -138,12 +150,20 @@ func ingest(cmd *cobra.Command, args []string) { s := <-sigs logger.Infof("Signal received: %s, shutting down gracefully\n", s.String()) cf() + shutdown(ctx) wg.Wait() } -func validateFlags(pubsubAddr, blobAddr, csubAddr, graphqlEndpoint, headerFile string, csubTls, csubTlsSkipVerify bool, - queryVulnIngestion bool, queryLicenseIngestion bool, queryEOLIngestion bool, args []string) (options, error) { +func validateFlags( + pubsubAddr, blobAddr, csubAddr, graphqlEndpoint, headerFile string, + csubTls, csubTlsSkipVerify bool, + queryVulnIngestion bool, + queryLicenseIngestion bool, + queryEOLIngestion bool, + enableOtel bool, + args []string, +) (options, error) { var opts options opts.pubsubAddr = pubsubAddr opts.blobAddr = blobAddr @@ -157,6 +177,7 @@ func validateFlags(pubsubAddr, blobAddr, csubAddr, graphqlEndpoint, headerFile s opts.queryVulnOnIngestion = queryVulnIngestion opts.queryLicenseOnIngestion = queryLicenseIngestion opts.queryEOLOnIngestion = queryEOLIngestion + opts.enableOtel = enableOtel return opts, nil } diff --git a/cmd/guacingest/cmd/root.go b/cmd/guacingest/cmd/root.go index fd1a2d21ae..691e031c47 100644 --- a/cmd/guacingest/cmd/root.go +++ b/cmd/guacingest/cmd/root.go @@ -30,8 +30,17 @@ import ( func init() { cobra.OnInitialize(cli.InitConfig) - set, err := cli.BuildFlags([]string{"pubsub-addr", "blob-addr", "csub-addr", "gql-addr", - "header-file", "add-vuln-on-ingest", "add-license-on-ingest", "add-eol-on-ingest"}) + set, err := cli.BuildFlags([]string{ + "pubsub-addr", + "blob-addr", + "csub-addr", + "gql-addr", + "header-file", + "add-vuln-on-ingest", + "add-license-on-ingest", + "add-eol-on-ingest", + "enable-otel", + }) if err != nil { fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err) os.Exit(1) diff --git a/cmd/guacone/cmd/certifier.go b/cmd/guacone/cmd/certifier.go index 6f9aa4c3c9..e37471348e 100644 --- a/cmd/guacone/cmd/certifier.go +++ b/cmd/guacone/cmd/certifier.go @@ -30,7 +30,7 @@ var certifierCmd = &cobra.Command{ } func init() { - set, err := cli.BuildFlags([]string{"poll", "interval"}) + set, err := cli.BuildFlags([]string{"poll", "interval", "enable-otel"}) if err != nil { fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err) os.Exit(1) diff --git a/cmd/guacone/cmd/deps_dev.go b/cmd/guacone/cmd/deps_dev.go index f1da0370ee..054df42fe3 100644 --- a/cmd/guacone/cmd/deps_dev.go +++ b/cmd/guacone/cmd/deps_dev.go @@ -38,6 +38,7 @@ import ( "github.com/guacsec/guac/pkg/handler/processor" "github.com/guacsec/guac/pkg/ingestor" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" ) type depsDevOptions struct { @@ -55,6 +56,7 @@ type depsDevOptions struct { queryEOLOnIngestion bool // sets artificial latency on the deps.dev collector (default to nil) addedLatency *time.Duration + enableOtel bool } var depsDevCmd = &cobra.Command{ @@ -72,6 +74,15 @@ var depsDevCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + // Register collector depsDevCollector, err := deps_dev.NewDepsCollector(ctx, opts.dataSource, opts.poll, opts.retrieveDependencies, 30*time.Second, opts.addedLatency) if err != nil { @@ -140,6 +151,7 @@ var depsDevCmd = &cobra.Command{ logger.Infof("Deps dev collector completed") } cf() + shutdown(ctx) wg.Wait() if gotErr { @@ -159,6 +171,7 @@ func validateDepsDevFlags(args []string) (*depsDevOptions, client.Client, error) queryVulnOnIngestion: viper.GetBool("add-vuln-on-ingest"), queryLicenseOnIngestion: viper.GetBool("add-license-on-ingest"), queryEOLOnIngestion: viper.GetBool("add-eol-on-ingest"), + enableOtel: viper.GetBool("enable-otel"), } addedLatencyStr := viper.GetString("deps-dev-latency") @@ -214,7 +227,13 @@ func validateDepsDevFlags(args []string) (*depsDevOptions, client.Client, error) } func init() { - set, err := cli.BuildFlags([]string{"poll", "retrieve-dependencies", "use-csub", "deps-dev-latency"}) + set, err := cli.BuildFlags([]string{ + "poll", + "retrieve-dependencies", + "use-csub", + "deps-dev-latency", + "enable-otel", + }) if err != nil { fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err) os.Exit(1) diff --git a/cmd/guacone/cmd/eol.go b/cmd/guacone/cmd/eol.go index 4dbc5631bf..896305bcd5 100644 --- a/cmd/guacone/cmd/eol.go +++ b/cmd/guacone/cmd/eol.go @@ -36,6 +36,7 @@ import ( "github.com/guacsec/guac/pkg/handler/processor" "github.com/guacsec/guac/pkg/ingestor" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -53,6 +54,7 @@ type eolOptions struct { addedLatency *time.Duration batchSize int lastScan *int + enableOtel bool } var eolCmd = &cobra.Command{ @@ -70,6 +72,7 @@ var eolCmd = &cobra.Command{ viper.GetString("certifier-latency"), viper.GetInt("certifier-batch-size"), viper.GetInt("last-scan"), + viper.GetBool("enable-otel"), ) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -81,6 +84,15 @@ var eolCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + if err := certify.RegisterCertifier(eol.NewEOLCertifier, eol.EOLCollector); err != nil { logger.Fatalf("unable to register certifier: %v", err) } @@ -202,6 +214,7 @@ var eolCmd = &cobra.Command{ logger.Infof("All certifiers completed") } ingestionStop <- true + shutdown(ctx) wg.Wait() cf() @@ -223,11 +236,13 @@ func validateEOLFlags( csubTlsSkipVerify bool, certifierLatencyStr string, batchSize int, lastScan int, + enableOtel bool, ) (eolOptions, error) { var opts eolOptions opts.graphqlEndpoint = graphqlEndpoint opts.headerFile = headerFile opts.poll = poll + opts.enableOtel = enableOtel if interval == "" { // 14 days by default diff --git a/cmd/guacone/cmd/files.go b/cmd/guacone/cmd/files.go index 0d09b20ca2..12a4bd146f 100644 --- a/cmd/guacone/cmd/files.go +++ b/cmd/guacone/cmd/files.go @@ -35,6 +35,7 @@ import ( "github.com/guacsec/guac/pkg/ingestor/verifier" "github.com/guacsec/guac/pkg/ingestor/verifier/sigstore_verifier" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -55,6 +56,7 @@ type fileOptions struct { queryLicenseOnIngestion bool queryEOLOnIngestion bool queryDepsDevOnIngestion bool + enableOtel bool } var filesCmd = &cobra.Command{ @@ -73,6 +75,7 @@ var filesCmd = &cobra.Command{ viper.GetBool("add-license-on-ingest"), viper.GetBool("add-eol-on-ingest"), viper.GetBool("add-depsdev-on-ingest"), + viper.GetBool("enable-otel"), args) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -84,6 +87,15 @@ var filesCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + // Register Keystore inmemory := inmemory.NewInmemoryProvider() err = key.RegisterKeyProvider(inmemory, inmemory.Type()) @@ -172,14 +184,23 @@ var filesCmd = &cobra.Command{ } else { logger.Infof("completed ingesting %v documents of %v", totalSuccess, totalNum) } + shutdown(ctx) }, } -func validateFilesFlags(keyPath, keyID, graphqlEndpoint, headerFile, csubAddr string, csubTls, csubTlsSkipVerify bool, - queryVulnIngestion bool, queryLicenseIngestion bool, queryEOLIngestion bool, queryDepsDevOnIngestion bool, args []string) (fileOptions, error) { +func validateFilesFlags(keyPath, keyID, graphqlEndpoint, headerFile, csubAddr string, + csubTls, csubTlsSkipVerify bool, + queryVulnIngestion bool, + queryLicenseIngestion bool, + queryEOLIngestion bool, + queryDepsDevOnIngestion bool, + enableOtel bool, + args []string, +) (fileOptions, error) { var opts fileOptions opts.graphqlEndpoint = graphqlEndpoint opts.headerFile = headerFile + opts.enableOtel = enableOtel if keyPath != "" { if strings.HasSuffix(keyPath, "pem") { @@ -210,7 +231,11 @@ func validateFilesFlags(keyPath, keyID, graphqlEndpoint, headerFile, csubAddr st } func init() { - set, err := cli.BuildFlags([]string{"verifier-key-path", "verifier-key-id"}) + set, err := cli.BuildFlags([]string{ + "verifier-key-path", + "verifier-key-id", + "enable-otel", + }) if err != nil { fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err) os.Exit(1) diff --git a/cmd/guacone/cmd/github.go b/cmd/guacone/cmd/github.go index 332e4cf616..5b48efeb5f 100644 --- a/cmd/guacone/cmd/github.go +++ b/cmd/guacone/cmd/github.go @@ -29,6 +29,7 @@ import ( "github.com/guacsec/guac/pkg/collectsub/datasource/csubsource" "github.com/guacsec/guac/pkg/handler/processor" "github.com/guacsec/guac/pkg/ingestor" + "github.com/guacsec/guac/pkg/metrics" "os/signal" @@ -71,6 +72,7 @@ type githubOptions struct { queryLicenseOnIngestion bool queryEOLOnIngestion bool queryDepsDevOnIngestion bool + enableOtel bool } var githubCmd = &cobra.Command{ @@ -95,6 +97,7 @@ var githubCmd = &cobra.Command{ viper.GetBool("add-license-on-ingest"), viper.GetBool("add-eol-on-ingest"), viper.GetBool("add-depsdev-on-ingest"), + viper.GetBool("enable-otel"), args) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -106,6 +109,15 @@ var githubCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + // GITHUB_TOKEN is the default token name ghc, err := githubclient.NewGithubClient(ctx, os.Getenv("GITHUB_TOKEN")) if err != nil { @@ -210,6 +222,7 @@ var githubCmd = &cobra.Command{ logger.Info("Collector finished") } + shutdown(ctx) wg.Wait() logger.Info("Shutdown complete") @@ -221,8 +234,16 @@ var githubCmd = &cobra.Command{ }, } -func validateGithubFlags(graphqlEndpoint, headerFile, githubMode, sbomName, workflowFileName, csubAddr string, csubTls, - csubTlsSkipVerify, useCsub, poll bool, queryVulnIngestion bool, queryLicenseIngestion bool, queryEOLIngestion bool, queryDepsDevOnIngestion bool, args []string) (githubOptions, error) { +func validateGithubFlags( + graphqlEndpoint, headerFile, githubMode, sbomName, workflowFileName, csubAddr string, + csubTls, csubTlsSkipVerify, useCsub, poll bool, + queryVulnIngestion bool, + queryLicenseIngestion bool, + queryEOLIngestion bool, + queryDepsDevOnIngestion bool, + enableOtel bool, + args []string, +) (githubOptions, error) { var opts githubOptions opts.graphqlEndpoint = graphqlEndpoint opts.headerFile = headerFile @@ -234,6 +255,7 @@ func validateGithubFlags(graphqlEndpoint, headerFile, githubMode, sbomName, work opts.queryLicenseOnIngestion = queryLicenseIngestion opts.queryEOLOnIngestion = queryEOLIngestion opts.queryDepsDevOnIngestion = queryDepsDevOnIngestion + opts.enableOtel = enableOtel if useCsub { csubOpts, err := csub_client.ValidateCsubClientFlags(csubAddr, csubTls, csubTlsSkipVerify) @@ -289,7 +311,14 @@ func validateGithubFlags(graphqlEndpoint, headerFile, githubMode, sbomName, work } func init() { - set, err := cli.BuildFlags([]string{githubMode, githubSbom, githubWorkflowFile, "use-csub", "poll"}) + set, err := cli.BuildFlags([]string{ + githubMode, + githubSbom, + githubWorkflowFile, + "use-csub", + "poll", + "enable-otel", + }) if err != nil { fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err) os.Exit(1) diff --git a/cmd/guacone/cmd/license.go b/cmd/guacone/cmd/license.go index 947702ae6c..d16c046f8b 100644 --- a/cmd/guacone/cmd/license.go +++ b/cmd/guacone/cmd/license.go @@ -37,6 +37,7 @@ import ( "github.com/guacsec/guac/pkg/handler/processor" "github.com/guacsec/guac/pkg/ingestor" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -61,7 +62,8 @@ type cdOptions struct { batchSize int // last time the scan was done in hours, if not set it will return // all packages to check - lastScan *int + lastScan *int + enableOtel bool } var cdCmd = &cobra.Command{ @@ -83,6 +85,7 @@ var cdCmd = &cobra.Command{ viper.GetString("certifier-latency"), viper.GetInt("certifier-batch-size"), viper.GetInt("last-scan"), + viper.GetBool("enable-otel"), ) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -94,6 +97,15 @@ var cdCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + if err := certify.RegisterCertifier(clearlydefined.NewClearlyDefinedCertifier, certifier.CertifierClearlyDefined); err != nil { logger.Fatalf("unable to register certifier: %v", err) } @@ -253,6 +265,7 @@ var cdCmd = &cobra.Command{ logger.Infof("All certifiers completed") } ingestionStop <- true + shutdown(ctx) wg.Wait() cf() @@ -278,6 +291,7 @@ func validateCDFlags( queryDepsDevIngestion bool, certifierLatencyStr string, batchSize int, lastScan int, + enableOtel bool, ) (cdOptions, error) { var opts cdOptions opts.graphqlEndpoint = graphqlEndpoint @@ -288,6 +302,7 @@ func validateCDFlags( return opts, err } opts.interval = i + opts.enableOtel = enableOtel if certifierLatencyStr != "" { addedLatency, err := time.ParseDuration(certifierLatencyStr) diff --git a/cmd/guacone/cmd/osv.go b/cmd/guacone/cmd/osv.go index f1129af827..1a29c8c424 100644 --- a/cmd/guacone/cmd/osv.go +++ b/cmd/guacone/cmd/osv.go @@ -37,6 +37,7 @@ import ( "github.com/guacsec/guac/pkg/handler/processor" "github.com/guacsec/guac/pkg/ingestor" "github.com/guacsec/guac/pkg/logging" + "github.com/guacsec/guac/pkg/metrics" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -64,6 +65,7 @@ type osvOptions struct { lastScan *int // addVulnMetadata enriches vulnerabilities with metadata during fetch addVulnMetadata bool + enableOtel bool } var osvCmd = &cobra.Command{ @@ -86,6 +88,7 @@ var osvCmd = &cobra.Command{ viper.GetInt("certifier-batch-size"), viper.GetInt("last-scan"), viper.GetBool("add-vuln-metadata"), + viper.GetBool("enable-otel"), ) if err != nil { fmt.Printf("unable to validate flags: %v\n", err) @@ -97,6 +100,15 @@ var osvCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) + var shutdown func(context.Context) error = func(context.Context) error { return nil } + if opts.enableOtel { + var err error + shutdown, err = metrics.SetupOTelSDK(ctx) + if err != nil { + logger.Fatalf("Error setting up Otel: %v", err) + } + } + if err := certify.RegisterCertifier(func() certifier.Certifier { certifierOpts := []osv.CertifierOpts{} if opts.addVulnMetadata { @@ -265,6 +277,7 @@ var osvCmd = &cobra.Command{ logger.Infof("All certifiers completed") } ingestionStop <- true + shutdown(ctx) wg.Wait() cf() @@ -291,6 +304,7 @@ func validateOSVFlags( certifierLatencyStr string, batchSize int, lastScan int, addVulnMetadata bool, + enableOtel bool, ) (osvOptions, error) { var opts osvOptions opts.graphqlEndpoint = graphqlEndpoint @@ -301,6 +315,7 @@ func validateOSVFlags( return opts, err } opts.interval = i + opts.enableOtel = enableOtel if certifierLatencyStr != "" { addedLatency, err := time.ParseDuration(certifierLatencyStr) diff --git a/go.mod b/go.mod index d5daef28a9..842130af1f 100644 --- a/go.mod +++ b/go.mod @@ -106,6 +106,7 @@ require ( github.com/bombsimon/logrusr/v2 v2.0.1 // indirect github.com/bradleyfalzon/ghinstallation/v2 v2.8.0 // indirect github.com/caarlos0/env/v6 v6.10.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect @@ -163,6 +164,7 @@ require ( github.com/google/s2a-go v0.1.8 // indirect github.com/google/wire v0.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -266,12 +268,13 @@ require ( go.etcd.io/etcd/client/v3 v3.5.12 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 + go.opentelemetry.io/otel v1.33.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect go.opentelemetry.io/otel/metric v1.33.0 // indirect - go.opentelemetry.io/otel/sdk v1.33.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.33.0 + go.opentelemetry.io/otel/sdk/metric v1.33.0 go.opentelemetry.io/otel/trace v1.33.0 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/mod v0.21.0 // indirect @@ -299,6 +302,7 @@ require ( github.com/Khan/genqlient v0.7.0 github.com/Masterminds/semver v1.5.0 github.com/ProtonMail/gluon v0.17.0 + github.com/XSAM/otelsql v0.36.0 github.com/arangodb/go-driver v1.6.4 github.com/aws/aws-sdk-go v1.55.5 github.com/aws/aws-sdk-go-v2 v1.32.7 @@ -348,6 +352,8 @@ require ( github.com/stretchr/testify v1.10.0 github.com/tikv/client-go/v2 v2.0.8-0.20231115083414-7c96dfd783fb github.com/vektah/gqlparser/v2 v2.5.21 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 go.uber.org/mock v0.5.0 go.uber.org/ratelimit v0.3.1 gocloud.dev v0.40.0 @@ -357,3 +363,5 @@ require ( golang.org/x/time v0.9.0 gopkg.in/yaml.v3 v3.0.1 ) + +require go.opentelemetry.io/proto/otlp v1.4.0 // indirect diff --git a/go.sum b/go.sum index d435eabf80..5eb29bc384 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,8 @@ github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azu github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0= github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/XSAM/otelsql v0.36.0 h1:SvrlOd/Hp0ttvI9Hu0FUWtISTTDNhQYwxe8WB4J5zxo= +github.com/XSAM/otelsql v0.36.0/go.mod h1:fo4M8MU+fCn/jDfu+JwTQ0n6myv4cZ+FU5VxrllIlxY= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= @@ -203,6 +205,8 @@ github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/I github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/cdevents/sdk-go v0.4.1 h1:Cr/iH/I51Z+slxKRx9AV7stn6hr2pjRHQ5wpPJhRLTU= github.com/cdevents/sdk-go v0.4.1/go.mod h1:3IhWLoY4vsyUEzv7XJbyr0BRQ0KPgvNx+wiD2hQGFNU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -468,6 +472,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -889,6 +895,12 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEj go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 h1:7F29RDmnlqk6B5d+sUqemt8TBfDqxryYW5gX6L74RFA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0/go.mod h1:ZiGDq7xwDMKmWDrN1XsXAj0iC7hns+2DhxBFSncNHSE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= @@ -899,6 +911,8 @@ go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCt go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= +go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/internal/client/depsdevclient/deps_dev_client.go b/internal/client/depsdevclient/deps_dev_client.go index 1073e4820f..291b0ba4db 100644 --- a/internal/client/depsdevclient/deps_dev_client.go +++ b/internal/client/depsdevclient/deps_dev_client.go @@ -31,7 +31,7 @@ import ( "github.com/guacsec/guac/pkg/version" pb "deps.dev/api/v3" - + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) @@ -97,6 +97,7 @@ func NewDepsClient(ctx context.Context) (*DepsClient, error) { grpc.WithUserAgent(version.UserAgent), // add the rate limit to the grpc client grpc.WithUnaryInterceptor(clients.UnaryClientInterceptor(clients.NewLimiter(rateLimit))), + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), ) if err != nil { return nil, fmt.Errorf("failed to connect to api.deps.dev: %w", err) diff --git a/pkg/assembler/backends/ent/backend/migrations.go b/pkg/assembler/backends/ent/backend/migrations.go index 73ad16d258..a6955773e0 100644 --- a/pkg/assembler/backends/ent/backend/migrations.go +++ b/pkg/assembler/backends/ent/backend/migrations.go @@ -17,17 +17,17 @@ package backend import ( "context" - "database/sql" "fmt" "time" "entgo.io/ent/dialect" + dialectsql "entgo.io/ent/dialect/sql" + "github.com/XSAM/otelsql" + "github.com/guacsec/guac/pkg/assembler/backends/ent" "github.com/guacsec/guac/pkg/assembler/backends/ent/hook" "github.com/guacsec/guac/pkg/assembler/backends/ent/migrate" "github.com/guacsec/guac/pkg/logging" - - dialectsql "entgo.io/ent/dialect/sql" ) type BackendOptions struct { @@ -67,10 +67,13 @@ func SetupBackend(ctx context.Context, options *BackendOptions) (*ent.Client, er return nil, fmt.Errorf("only postgres is supported at this time") } - db, err := sql.Open(driver, options.Address) + db, err := otelsql.Open(driver, options.Address) if err != nil { return nil, fmt.Errorf("error opening db: %w", err) } + if err := otelsql.RegisterDBStatsMetrics(db); err != nil { + return nil, fmt.Errorf("error registering db metrics: %w", err) + } if options.ConnectionMaxLifeTime != nil { // set timeout limit for connections diff --git a/pkg/certifier/eol/eol.go b/pkg/certifier/eol/eol.go index 8ee85201f7..7bc493085f 100644 --- a/pkg/certifier/eol/eol.go +++ b/pkg/certifier/eol/eol.go @@ -29,6 +29,7 @@ import ( "github.com/guacsec/guac/pkg/clients" "github.com/guacsec/guac/pkg/events" "github.com/guacsec/guac/pkg/handler/processor" + "github.com/guacsec/guac/pkg/version" attestationv1 "github.com/in-toto/attestation/go/v1" "golang.org/x/time/rate" ) @@ -175,7 +176,7 @@ type EOLData = []CycleData func NewEOLCertifier() certifier.Certifier { limiter := rate.NewLimiter(rate.Every(time.Second/time.Duration(rateLimit)), rateLimitBurst) client := &http.Client{ - Transport: clients.NewRateLimitedTransport(http.DefaultTransport, limiter), + Transport: clients.NewRateLimitedTransport(version.UATransport, limiter), } return &eolCertifier{client: client} } diff --git a/pkg/cli/store.go b/pkg/cli/store.go index 20745123bb..f9e2a52db5 100644 --- a/pkg/cli/store.go +++ b/pkg/cli/store.go @@ -95,9 +95,9 @@ func init() { set.Bool("retrieve-dependencies", true, "enable the deps.dev collector to retrieve package dependencies") - set.Bool("enable-prometheus", true, "enable prometheus metrics") - + set.Bool("enable-prometheus", false, "enable prometheus metrics") set.Int("prometheus-port", 9091, "port to listen to on prometheus server") + set.Bool("enable-otel", false, "enable otel metrics and tracing") set.StringP("interval", "i", "5m", "if polling set interval, m, h, s, etc.") diff --git a/pkg/metrics/README.md b/pkg/metrics/README.md index 52ead7ce2f..ca50673b0e 100644 --- a/pkg/metrics/README.md +++ b/pkg/metrics/README.md @@ -1,10 +1,51 @@ # Metrics -## Usage +## Otel metrics -This package provides a set of interfaces and implementations for collecting and exposing metrics in your application. The main interfaces are `MetricCollector`, `Observable`, and `Counter` which are defined in `metrics.go`. The `prometheus.go` file provides an implementation of these interfaces using the Prometheus monitoring system. +GUAC is using Otel instrumented libraries for the following parts: -This package is easy to test as it is based on interfaces. You can create mock implementations of the `MetricCollector`, `Observable`, and `Counter` interfaces for testing purposes. +- HTTP GQL server in `guacgql` +- SQL library underneath the Ent/Postgres backend +- HTTP client for: OSV, ClearlyDefined, GitHub, EoL +- GRPC client for Deps.dev. + +Any cli that runs one of the above will have the `enable-otel` cli option +available to setup the defult metric and trace providers. These are configured +to connect to an Otel collector over GRPC. Config uses the below defualt env +vars: + +- `OTEL_EXPORTER_OTLP_ENDPOINT`: Address of Otel collector to connect to +- `OTEL_EXPORTER_OTLP_INSECURE`: If true, don't use TLS (local collector). +- `OTEL_SERVICE_NAME`: Service name attached to metrics + +More details are available here: + +- https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc +- https://opentelemetry.io/docs/languages/sdk-configuration/general/#otel_traces_sampler + +> Note: GUAC is not set up to define and publish custom metrics to +> Otel. I.e. the `MetricCollector` interface defined in this package does not +> (yet) support Otel metrics. + +## Prometheus metrics + +Prometheus metrics are available on many cli tools using the +`enable-prometheus` option. This starts an http server (if not already started) +and serves metrics on the `/metrics` endpoint. Custom metrics are available for +those GUAC packages that are manaully instrumented. The instructions for adding +manual instrumentation to other GUAC packages is described below: + +### Usage + +This package provides a set of interfaces and implementations for collecting +and exposing metrics in your application. The main interfaces are +`MetricCollector`, `Observable`, and `Counter` which are defined in +`metrics.go`. The `prometheus.go` file provides an implementation of these +interfaces using the Prometheus monitoring system. + +This package is easy to test as it is based on interfaces. You can create mock +implementations of the `MetricCollector`, `Observable`, and `Counter` +interfaces for testing purposes. ### For New Packages diff --git a/pkg/metrics/otel.go b/pkg/metrics/otel.go new file mode 100644 index 0000000000..7366ae3b11 --- /dev/null +++ b/pkg/metrics/otel.go @@ -0,0 +1,113 @@ +// +// Copyright 2025 The GUAC Authors. +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package metrics + +import ( + "context" + "errors" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/trace" +) + +// SetupOTelSDK bootstraps the OpenTelemetry pipeline. +// If it does not return an error, make sure to call shutdown for proper cleanup. +func SetupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) { + var shutdownFuncs []func(context.Context) error + + // shutdown calls cleanup functions registered via shutdownFuncs. + // The errors from the calls are joined. + // Each registered cleanup will be invoked once. + shutdown = func(ctx context.Context) error { + var err error + for _, fn := range shutdownFuncs { + err = errors.Join(err, fn(ctx)) + } + shutdownFuncs = nil + return err + } + + // handleErr calls shutdown for cleanup and makes sure that all errors are returned. + handleErr := func(inErr error) { + err = errors.Join(inErr, shutdown(ctx)) + } + + // Set up propagator. + prop := newPropagator() + otel.SetTextMapPropagator(prop) + + // Set up trace provider. + tracerProvider, err := newTraceProvider(ctx) + if err != nil { + handleErr(err) + return + } + shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) + otel.SetTracerProvider(tracerProvider) + + // Set up meter provider. + meterProvider, err := newMeterProvider(ctx) + if err != nil { + handleErr(err) + return + } + shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) + otel.SetMeterProvider(meterProvider) + + return +} + +func newPropagator() propagation.TextMapPropagator { + return propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ) +} + +func newTraceProvider(ctx context.Context) (*trace.TracerProvider, error) { + traceExporter, err := otlptracegrpc.New(ctx) + if err != nil { + return nil, err + } + + traceProvider := trace.NewTracerProvider( + trace.WithBatcher(traceExporter, + // Default is 5s. Set to 1s for demonstrative purposes. + trace.WithBatchTimeout(time.Second)), + ) + return traceProvider, nil +} + +func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) { + metricExporter, err := otlpmetricgrpc.New(ctx) + if err != nil { + return nil, err + } + + meterProvider := metric.NewMeterProvider( + metric.WithReader(metric.NewPeriodicReader(metricExporter, + // Default is 1m. Set to 3s for demonstrative purposes. + metric.WithInterval(3*time.Second))), + ) + return meterProvider, nil +} diff --git a/pkg/version/version.go b/pkg/version/version.go index 7158bc3aac..673c6a10b6 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -18,6 +18,8 @@ package version import ( "fmt" "net/http" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) var ( @@ -33,7 +35,8 @@ type uat struct { func init() { UserAgent = fmt.Sprintf("GUAC/%s", Version) - UATransport = uat{tr: http.DefaultTransport} + otelTransport := otelhttp.NewTransport(http.DefaultTransport) + UATransport = uat{tr: otelTransport} } func (u uat) RoundTrip(r *http.Request) (*http.Response, error) { From 786c01350a7e580c12f7e76b12447c570b642b3c Mon Sep 17 00:00:00 2001 From: Jeff Mendoza Date: Thu, 16 Jan 2025 13:20:24 -0800 Subject: [PATCH 2/2] Update shutdown for otel on cli commands Signed-off-by: Jeff Mendoza --- cmd/guaccollect/cmd/deps_dev.go | 10 ++++++---- cmd/guaccollect/cmd/github.go | 10 ++++++---- cmd/guaccollect/cmd/license.go | 10 ++++++---- cmd/guaccollect/cmd/osv.go | 10 ++++++---- cmd/guacgql/cmd/server.go | 12 ++++++++---- cmd/guacingest/cmd/ingest.go | 13 ++++++++----- cmd/guacone/cmd/deps_dev.go | 14 ++++++++------ cmd/guacone/cmd/eol.go | 10 ++++++---- cmd/guacone/cmd/files.go | 14 ++++++++------ cmd/guacone/cmd/github.go | 31 +++++++++++++++++-------------- cmd/guacone/cmd/license.go | 12 +++++++----- cmd/guacone/cmd/osv.go | 12 +++++++----- 12 files changed, 93 insertions(+), 65 deletions(-) diff --git a/cmd/guaccollect/cmd/deps_dev.go b/cmd/guaccollect/cmd/deps_dev.go index f54634edb6..6d3a357ea9 100644 --- a/cmd/guaccollect/cmd/deps_dev.go +++ b/cmd/guaccollect/cmd/deps_dev.go @@ -102,13 +102,16 @@ you have access to read and write to the respective blob store.`, os.Exit(1) } - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } // Register collector @@ -131,7 +134,6 @@ you have access to read and write to the respective blob store.`, } initializeNATsandCollector(ctx, opts.pubsubAddr, opts.blobAddr, opts.publishToQueue) - shutdown(ctx) }, } diff --git a/cmd/guaccollect/cmd/github.go b/cmd/guaccollect/cmd/github.go index 9dc57984e0..bf8ed7d9ec 100644 --- a/cmd/guaccollect/cmd/github.go +++ b/cmd/guaccollect/cmd/github.go @@ -113,13 +113,16 @@ you have access to read and write to the respective blob store.`, os.Exit(1) } - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } // GITHUB_TOKEN is the default token name @@ -167,7 +170,6 @@ you have access to read and write to the respective blob store.`, } initializeNATsandCollector(ctx, opts.pubsubAddr, opts.blobAddr, opts.publishToQueue) - shutdown(ctx) }, } diff --git a/cmd/guaccollect/cmd/license.go b/cmd/guaccollect/cmd/license.go index 920274cc3b..9a67c07358 100644 --- a/cmd/guaccollect/cmd/license.go +++ b/cmd/guaccollect/cmd/license.go @@ -104,13 +104,16 @@ you have access to read and write to the respective blob store.`, ctx := logging.WithLogger(context.Background()) logger := logging.FromContext(ctx) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } if err := certify.RegisterCertifier(clearlydefined.NewClearlyDefinedCertifier, certifier.CertifierClearlyDefined); err != nil { @@ -128,7 +131,6 @@ you have access to read and write to the respective blob store.`, } initializeNATsandCertifier(ctx, opts.blobAddr, opts.pubsubAddr, opts.poll, opts.publishToQueue, opts.interval, packageQueryFunc()) - shutdown(ctx) }, } diff --git a/cmd/guaccollect/cmd/osv.go b/cmd/guaccollect/cmd/osv.go index 06d3a2dd31..9fadfc0a7f 100644 --- a/cmd/guaccollect/cmd/osv.go +++ b/cmd/guaccollect/cmd/osv.go @@ -115,13 +115,16 @@ you have access to read and write to the respective blob store.`, ctx := logging.WithLogger(context.Background()) logger := logging.FromContext(ctx) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } if err := certify.RegisterCertifier(func() certifier.Certifier { @@ -145,7 +148,6 @@ you have access to read and write to the respective blob store.`, } initializeNATsandCertifier(ctx, opts.blobAddr, opts.pubsubAddr, opts.poll, opts.publishToQueue, opts.interval, packageQueryFunc()) - shutdown(ctx) }, } diff --git a/cmd/guacgql/cmd/server.go b/cmd/guacgql/cmd/server.go index 19b9a531e6..48518e3c50 100644 --- a/cmd/guacgql/cmd/server.go +++ b/cmd/guacgql/cmd/server.go @@ -81,14 +81,19 @@ func startServer(cmd *cobra.Command) { srvHandler = srv } - var shutdown func(context.Context) error = func(context.Context) error { return nil } if flags.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + srvHandler = otelhttp.NewHandler(srvHandler, "/") + + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } if flags.tracegql { @@ -126,7 +131,6 @@ func startServer(cmd *cobra.Command) { ctx, cf := context.WithCancel(ctx) go func() { _ = server.Shutdown(ctx) - _ = shutdown(ctx) done <- true }() select { diff --git a/cmd/guacingest/cmd/ingest.go b/cmd/guacingest/cmd/ingest.go index 18db0d437b..bff6120905 100644 --- a/cmd/guacingest/cmd/ingest.go +++ b/cmd/guacingest/cmd/ingest.go @@ -73,17 +73,20 @@ func ingest(cmd *cobra.Command, args []string) { os.Exit(1) } - ctx, cf := context.WithCancel(logging.WithLogger(context.Background())) + ctx := logging.WithLogger(context.Background()) logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } if strings.HasPrefix(opts.pubsubAddr, "nats://") { @@ -113,6 +116,7 @@ func ingest(cmd *cobra.Command, args []string) { } defer csubClient.Close() + ctx, cf := context.WithCancel(ctx) emit := func(d *processor.Document) error { if _, err := ingestor.Ingest( ctx, @@ -150,7 +154,6 @@ func ingest(cmd *cobra.Command, args []string) { s := <-sigs logger.Infof("Signal received: %s, shutting down gracefully\n", s.String()) cf() - shutdown(ctx) wg.Wait() } diff --git a/cmd/guacone/cmd/deps_dev.go b/cmd/guacone/cmd/deps_dev.go index 054df42fe3..c96052f3ba 100644 --- a/cmd/guacone/cmd/deps_dev.go +++ b/cmd/guacone/cmd/deps_dev.go @@ -74,13 +74,16 @@ var depsDevCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } // Register collector @@ -137,7 +140,7 @@ var depsDevCmd = &cobra.Command{ go func() { defer wg.Done() if err := collector.Collect(ctx, emit, errHandler); err != nil { - logger.Fatal(err) + logger.Errorf("collector exited with error: %v", err) } done <- true }() @@ -151,11 +154,10 @@ var depsDevCmd = &cobra.Command{ logger.Infof("Deps dev collector completed") } cf() - shutdown(ctx) wg.Wait() if gotErr { - logger.Fatalf("completed ingestion with error, %v of %v were successful", totalSuccess, totalNum) + logger.Errorf("completed ingestion with error, %v of %v were successful", totalSuccess, totalNum) } else { logger.Infof("completed ingesting %v documents of %v", totalSuccess, totalNum) } diff --git a/cmd/guacone/cmd/eol.go b/cmd/guacone/cmd/eol.go index 896305bcd5..d2cddb380d 100644 --- a/cmd/guacone/cmd/eol.go +++ b/cmd/guacone/cmd/eol.go @@ -84,13 +84,16 @@ var eolCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } if err := certify.RegisterCertifier(eol.NewEOLCertifier, eol.EOLCollector); err != nil { @@ -214,7 +217,6 @@ var eolCmd = &cobra.Command{ logger.Infof("All certifiers completed") } ingestionStop <- true - shutdown(ctx) wg.Wait() cf() diff --git a/cmd/guacone/cmd/files.go b/cmd/guacone/cmd/files.go index 12a4bd146f..0675bd67e4 100644 --- a/cmd/guacone/cmd/files.go +++ b/cmd/guacone/cmd/files.go @@ -87,13 +87,16 @@ var filesCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } // Register Keystore @@ -175,16 +178,15 @@ var filesCmd = &cobra.Command{ } if err := collector.Collect(ctx, emit, errHandler); err != nil { - logger.Fatal(err) + logger.Errorf("collector exited with error: %v", err) } if gotErr { - logger.Fatalf("completed ingestion with error, %v of %v were successful - the following files did not ingest successfully: %v", + logger.Errorf("completed ingestion with error, %v of %v were successful - the following files did not ingest successfully: %v", totalSuccess, totalNum, strings.Join(filesWithErrors, " ")) } else { logger.Infof("completed ingesting %v documents of %v", totalSuccess, totalNum) } - shutdown(ctx) }, } diff --git a/cmd/guacone/cmd/github.go b/cmd/guacone/cmd/github.go index 5b48efeb5f..c917f8b0d1 100644 --- a/cmd/guacone/cmd/github.go +++ b/cmd/guacone/cmd/github.go @@ -109,19 +109,22 @@ var githubCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } // GITHUB_TOKEN is the default token name ghc, err := githubclient.NewGithubClient(ctx, os.Getenv("GITHUB_TOKEN")) if err != nil { - logger.Errorf("unable to create github client: %v", err) + logger.Fatalf("unable to create github client: %v", err) } // Register collector @@ -141,11 +144,11 @@ var githubCmd = &cobra.Command{ } if opts.ownerRepoName != "" { if !strings.Contains(opts.ownerRepoName, "/") { - logger.Errorf("owner-repo flag must be in the format /") + logger.Fatalf("owner-repo flag must be in the format /") } else { ownerRepoName := strings.Split(opts.ownerRepoName, "/") if len(ownerRepoName) != 2 { - logger.Errorf("owner-repo flag must be in the format /") + logger.Fatalf("owner-repo flag must be in the format /") } collectorOpts = append(collectorOpts, github.WithOwner(ownerRepoName[0])) collectorOpts = append(collectorOpts, github.WithRepo(ownerRepoName[1])) @@ -153,11 +156,11 @@ var githubCmd = &cobra.Command{ } githubCollector, err := github.NewGithubCollector(collectorOpts...) if err != nil { - logger.Errorf("unable to create Github collector: %v", err) + logger.Fatalf("unable to create Github collector: %v", err) } err = collector.RegisterDocumentCollector(githubCollector, github.GithubCollector) if err != nil { - logger.Errorf("unable to register Github collector: %v", err) + logger.Fatalf("unable to register Github collector: %v", err) } csubClient, err := csub_client.NewClient(opts.csubClientOptions) @@ -170,6 +173,9 @@ var githubCmd = &cobra.Command{ var errFound bool + ctx, cancel := context.WithCancel(ctx) + defer cancel() + emit := func(d *processor.Document) error { _, err := ingestor.Ingest( ctx, @@ -194,13 +200,11 @@ var githubCmd = &cobra.Command{ logger.Info("collector ended gracefully") return true } + errFound = true logger.Errorf("collector ended with error: %v", err) return false } - ctx, cancel := context.WithCancel(ctx) - defer cancel() - sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) @@ -210,7 +214,7 @@ var githubCmd = &cobra.Command{ go func() { defer wg.Done() if err := collector.Collect(ctx, emit, errHandler); err != nil { - logger.Fatal(err) + logger.Errorf("collector exited with error: %v", err) } }() @@ -222,12 +226,11 @@ var githubCmd = &cobra.Command{ logger.Info("Collector finished") } - shutdown(ctx) wg.Wait() logger.Info("Shutdown complete") if errFound { - logger.Fatalf("completed ingestion with error") + logger.Errorf("completed ingestion with error") } else { logger.Infof("completed ingestion") } diff --git a/cmd/guacone/cmd/license.go b/cmd/guacone/cmd/license.go index d16c046f8b..c803c32a01 100644 --- a/cmd/guacone/cmd/license.go +++ b/cmd/guacone/cmd/license.go @@ -97,13 +97,16 @@ var cdCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } if err := certify.RegisterCertifier(clearlydefined.NewClearlyDefinedCertifier, certifier.CertifierClearlyDefined); err != nil { @@ -128,6 +131,7 @@ var cdCmd = &cobra.Command{ ingestionStop := make(chan bool, 1) tickInterval := 30 * time.Second ticker := time.NewTicker(tickInterval) + ctx, cf := context.WithCancel(ctx) var gotErr int32 var wg sync.WaitGroup @@ -245,7 +249,6 @@ var cdCmd = &cobra.Command{ return true } - ctx, cf := context.WithCancel(ctx) done := make(chan bool, 1) wg.Add(1) go func() { @@ -265,7 +268,6 @@ var cdCmd = &cobra.Command{ logger.Infof("All certifiers completed") } ingestionStop <- true - shutdown(ctx) wg.Wait() cf() diff --git a/cmd/guacone/cmd/osv.go b/cmd/guacone/cmd/osv.go index 1a29c8c424..41e60ac609 100644 --- a/cmd/guacone/cmd/osv.go +++ b/cmd/guacone/cmd/osv.go @@ -100,13 +100,16 @@ var osvCmd = &cobra.Command{ logger := logging.FromContext(ctx) transport := cli.HTTPHeaderTransport(ctx, opts.headerFile, http.DefaultTransport) - var shutdown func(context.Context) error = func(context.Context) error { return nil } if opts.enableOtel { - var err error - shutdown, err = metrics.SetupOTelSDK(ctx) + shutdown, err := metrics.SetupOTelSDK(ctx) if err != nil { logger.Fatalf("Error setting up Otel: %v", err) } + defer func() { + if err := shutdown(ctx); err != nil { + logger.Errorf("Error on Otel shutdown: %v", err) + } + }() } if err := certify.RegisterCertifier(func() certifier.Certifier { @@ -137,6 +140,7 @@ var osvCmd = &cobra.Command{ ingestionStop := make(chan bool, 1) tickInterval := 30 * time.Second ticker := time.NewTicker(tickInterval) + ctx, cf := context.WithCancel(ctx) var gotErr int32 var wg sync.WaitGroup @@ -257,7 +261,6 @@ var osvCmd = &cobra.Command{ return true } - ctx, cf := context.WithCancel(ctx) done := make(chan bool, 1) wg.Add(1) go func() { @@ -277,7 +280,6 @@ var osvCmd = &cobra.Command{ logger.Infof("All certifiers completed") } ingestionStop <- true - shutdown(ctx) wg.Wait() cf()