diff --git a/gcputil/gcputil.go b/gcputil/gcputil.go new file mode 100644 index 000000000..c2b4f80c7 --- /dev/null +++ b/gcputil/gcputil.go @@ -0,0 +1,104 @@ +// Package gcputil provides functions +// that interact with various GCP platforms +// such as getting GAE metadata and registering +// stack driver monitoring +package gcputil + +import ( + "os" + + "contrib.go.opencensus.io/exporter/stackdriver" + "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" + "go.opencensus.io/stats/view" + "go.opencensus.io/trace" +) + +// NewOpenCensusExporter will return the tracing and metrics through +// the stack driver exporter, if exists in the underlying platform. +// If exporter is registered, it returns the exporter so you can register +// it and ensure to call Flush on termination. +func NewOpenCensusExporter(projectID string, onErr func(error)) (*stackdriver.Exporter, error) { + svcName, svcVersion := "", "" + if IsGAE() { + _, svcName, svcVersion = GetGAEInfo() + } else if n, v := os.Getenv("SERVICE_NAME"), os.Getenv("SERVICE_VERSION"); n != "" { + svcName, svcVersion = n, v + } + opts := SDExporterOptions(projectID, svcName, svcVersion, onErr) + if opts == nil { + return nil, nil + } + return stackdriver.NewExporter(*opts) +} + +// GoogleProjectID returns the GCP Project ID +// that can be used to instantiate various +// GCP clients such as Stack Driver. +func GoogleProjectID() string { + return os.Getenv("GOOGLE_CLOUD_PROJECT") +} + +// IsGAE tells you whether your program is running +// within the App Engine platform. +func IsGAE() bool { + return os.Getenv("GAE_DEPLOYMENT_ID") != "" +} + +// GetGAEInfo returns the GCP Project ID, +// the service, and the version of the application. +func GetGAEInfo() (projectID, service, version string) { + return GoogleProjectID(), + os.Getenv("GAE_SERVICE"), + os.Getenv("GAE_VERSION") +} + +// SDExporterOptions returns Stack Driver Options that you can pass directly +// to the OpenCensus exporter or other libraries. +func SDExporterOptions(projectID, service, version string, onErr func(err error)) *stackdriver.Options { + var mr monitoredresource.Interface + if m := monitoredresource.Autodetect(); m != nil { + mr = m + } else if IsGAE() { + mr = gaeInterface{ + typ: "gae_app", + labels: map[string]string{ + "project_id": projectID, + }, + } + } + if mr == nil { + return nil + } + + return &stackdriver.Options{ + ProjectID: projectID, + MonitoredResource: mr, + OnError: onErr, + DefaultMonitoringLabels: &stackdriver.Labels{}, + DefaultTraceAttributes: map[string]interface{}{ + "service": service, + "version": version, + }, + } +} + +// InitSDExporter will initialize the OpenCensus tracing/metrics exporter +func InitSDExporter(opts stackdriver.Options) error { + exporter, err := stackdriver.NewExporter(opts) + if err != nil { + return err + } + trace.RegisterExporter(exporter) + view.RegisterExporter(exporter) + return nil +} + +// implements contrib.go.opencensus.io/exporter/stackdriver/monitoredresource.Interface +type gaeInterface struct { + typ string + labels map[string]string +} + +func (g gaeInterface) MonitoredResource() (string, map[string]string) { + return g.typ, g.labels +} diff --git a/ocexporters/ocexporters.go b/ocexporters/ocexporters.go new file mode 100644 index 000000000..77d62d621 --- /dev/null +++ b/ocexporters/ocexporters.go @@ -0,0 +1,111 @@ +package ocexporters + +import ( + "fmt" + "os" + + "contrib.go.opencensus.io/exporter/stackdriver" + "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" + "go.opencensus.io/exporter/prometheus" + "go.opencensus.io/stats/view" + "go.opencensus.io/trace" +) + +// NewOCExporters returns trace/client exporters +// based on the given environment. ProjectID +// is the GCP projectID for stackdriver (or the prometheus namespace) and backend +// specifies whether the backend is StackDriver or Prometheus +func NewOCExporters(projectID, backend string, onErr func(err error)) (trace.Exporter, view.Exporter, error) { + switch backend { + case "stackdriver": + return getSDExporter(projectID, onErr) + case "prometheus": + return getPrometheusExporter(projectID, onErr) + } + + return nil, nil, fmt.Errorf("unrecognized backend: %v", backend) +} + +func getPrometheusExporter(namespace string, onErr func(error)) (trace.Exporter, view.Exporter, error) { + pe, err := prometheus.NewExporter(prometheus.Options{ + Namespace: namespace, + OnError: onErr, + }) + return nil, pe, err +} + +func getSDExporter(projectID string, onErr func(error)) (trace.Exporter, view.Exporter, error) { + svcName, svcVersion := "", "" + if IsGAE() { + _, svcName, svcVersion = GetGAEInfo() + } else if n, v := os.Getenv("SERVICE_NAME"), os.Getenv("SERVICE_VERSION"); n != "" { + svcName, svcVersion = n, v + } + opts := getSDExporterOptions(projectID, svcName, svcVersion, onErr) + if opts == nil { + return nil, nil, nil + } + exp, err := stackdriver.NewExporter(*opts) + return exp, exp, err +} + +// getSDExporterOptions returns Stack Driver Options that you can pass directly +// to the OpenCensus exporter or other libraries. +func getSDExporterOptions(projectID, service, version string, onErr func(err error)) *stackdriver.Options { + var mr monitoredresource.Interface + if m := monitoredresource.Autodetect(); m != nil { + mr = m + } else if IsGAE() { + mr = gaeInterface{ + typ: "gae_app", + labels: map[string]string{ + "project_id": projectID, + }, + } + } + if mr == nil { + return nil + } + + return &stackdriver.Options{ + ProjectID: projectID, + MonitoredResource: mr, + OnError: onErr, + DefaultMonitoringLabels: &stackdriver.Labels{}, + DefaultTraceAttributes: map[string]interface{}{ + "service": service, + "version": version, + }, + } +} + +// implements contrib.go.opencensus.io/exporter/stackdriver/monitoredresource.Interface +type gaeInterface struct { + typ string + labels map[string]string +} + +func (g gaeInterface) MonitoredResource() (string, map[string]string) { + return g.typ, g.labels +} + +// GoogleProjectID returns the GCP Project ID +// that can be used to instantiate various +// GCP clients such as Stack Driver. +func GoogleProjectID() string { + return os.Getenv("GOOGLE_CLOUD_PROJECT") +} + +// IsGAE tells you whether your program is running +// within the App Engine platform. +func IsGAE() bool { + return os.Getenv("GAE_DEPLOYMENT_ID") != "" +} + +// GetGAEInfo returns the GCP Project ID, +// the service, and the version of the application. +func GetGAEInfo() (projectID, service, version string) { + return GoogleProjectID(), + os.Getenv("GAE_SERVICE"), + os.Getenv("GAE_VERSION") +} diff --git a/server/kit/kitserver.go b/server/kit/kitserver.go index cc40da300..d3e20b21c 100644 --- a/server/kit/kitserver.go +++ b/server/kit/kitserver.go @@ -13,6 +13,7 @@ import ( "cloud.google.com/go/errorreporting" sdpropagation "contrib.go.opencensus.io/exporter/stackdriver/propagation" + "github.com/NYTimes/gizmo/gcputil" "github.com/go-kit/kit/log" httptransport "github.com/go-kit/kit/transport/http" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" @@ -89,16 +90,20 @@ func NewServer(svc Service) *Server { propr propagation.HTTPFormat ) - projectID := googleProjectID() + projectID := gcputil.GoogleProjectID() var svcName, svcVersion string - if isGAE() { - _, svcName, svcVersion = getGAEInfo() + if gcputil.IsGAE() { + _, svcName, svcVersion = gcputil.GetGAEInfo() } else if n, v := os.Getenv("SERVICE_NAME"), os.Getenv("SERVICE_VERSION"); n != "" { svcName, svcVersion = n, v } - if opt := sdExporterOptions(projectID, svcName, svcVersion, lg); opt != nil { - err = initSDExporter(*opt) + onErr := func(err error) { + lg.Log("error", err, "message", "tracing client encountered an error") + } + + if opt := gcputil.SDExporterOptions(projectID, svcName, svcVersion, onErr); opt != nil { + err = gcputil.InitSDExporter(*opt) if err != nil { lg.Log("error", err, "message", "unable to initiate error tracing exporter") diff --git a/server/kit/log.go b/server/kit/log.go index da9fd2642..ceef14a74 100644 --- a/server/kit/log.go +++ b/server/kit/log.go @@ -4,6 +4,7 @@ import ( "context" "os" + "github.com/NYTimes/gizmo/gcputil" "github.com/go-kit/kit/log" "github.com/go-kit/kit/transport/http" "google.golang.org/grpc/metadata" @@ -23,7 +24,7 @@ import ( // If an empty string is provided, "gae_log" will be used in App Engine and "stdout" elsewhere. // For more information about to use of logID see the documentation here: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#FIELDS.log_name func NewLogger(ctx context.Context, logID string) (log.Logger, func() error, error) { - projectID, serviceID, svcVersion := getGAEInfo() + projectID, serviceID, svcVersion := gcputil.GetGAEInfo() lg, cl, err := newStackdriverLogger(ctx, logID, projectID, serviceID, svcVersion) // if Stackdriver logger was not able to find information about monitored resource it returns nil. if err != nil { diff --git a/server/kit/sd_log.go b/server/kit/sd_log.go index 4c15870e3..a48b91ad2 100644 --- a/server/kit/sd_log.go +++ b/server/kit/sd_log.go @@ -5,29 +5,18 @@ import ( "encoding" "encoding/json" "fmt" - "os" "reflect" "strings" "cloud.google.com/go/logging" "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" + "github.com/NYTimes/gizmo/gcputil" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/pkg/errors" "google.golang.org/genproto/googleapis/api/monitoredres" ) -// project, service, version -func getGAEInfo() (string, string, string) { - return googleProjectID(), - os.Getenv("GAE_SERVICE"), - os.Getenv("GAE_VERSION") -} - -func isGAE() bool { - return os.Getenv("GAE_DEPLOYMENT_ID") != "" -} - type sdLogger struct { project string monRes *monitoredres.MonitoredResource @@ -43,7 +32,7 @@ func newStackdriverLogger(ctx context.Context, logID, projectID, service, versio "version_id": version, }, } - if isGAE() { + if gcputil.IsGAE() { resource.Type = "gae_app" if logID == "" { logID = "app_logs" diff --git a/server/kit/stackdriver.go b/server/kit/stackdriver.go deleted file mode 100644 index 88161657b..000000000 --- a/server/kit/stackdriver.go +++ /dev/null @@ -1,66 +0,0 @@ -package kit - -import ( - "os" - - "contrib.go.opencensus.io/exporter/stackdriver" - "contrib.go.opencensus.io/exporter/stackdriver/monitoredresource" - "github.com/go-kit/kit/log" - "go.opencensus.io/stats/view" - "go.opencensus.io/trace" -) - -func sdExporterOptions(projectID, service, version string, lg log.Logger) *stackdriver.Options { - var mr monitoredresource.Interface - if m := monitoredresource.Autodetect(); m != nil { - mr = m - } else if isGAE() { - mr = gaeInterface{ - typ: "gae_app", - labels: map[string]string{ - "project_id": projectID, - }, - } - } - if mr == nil { - return nil - } - - return &stackdriver.Options{ - ProjectID: projectID, - MonitoredResource: mr, - OnError: func(err error) { - lg.Log("error", err, - "message", "tracing client encountered an error") - }, - DefaultMonitoringLabels: &stackdriver.Labels{}, - DefaultTraceAttributes: map[string]interface{}{ - "service": service, - "version": version, - }, - } -} - -func googleProjectID() string { - return os.Getenv("GOOGLE_CLOUD_PROJECT") -} - -func initSDExporter(opt stackdriver.Options) error { - exporter, err := stackdriver.NewExporter(opt) - if err != nil { - return err - } - trace.RegisterExporter(exporter) - view.RegisterExporter(exporter) - return nil -} - -// implements contrib.go.opencensus.io/exporter/stackdriver/monitoredresource.Interface -type gaeInterface struct { - typ string - labels map[string]string -} - -func (g gaeInterface) MonitoredResource() (string, map[string]string) { - return g.typ, g.labels -}