Skip to content

Commit

Permalink
OTEL and Go 1.20 (#29)
Browse files Browse the repository at this point in the history
* Added OTEL Support
* Upgraded to go 1.20
  • Loading branch information
eeisegn authored Nov 20, 2023
1 parent 7701c16 commit c37ae23
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.20.x

- name: Unit Test
run: make unit_test
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.20.x

- name: golangci-lint
uses: golangci/golangci-lint-action@v3
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.20.x

- name: Unit Test
run: make unit_test
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Upcoming changes...

## [0.2.0] - 2023-11-20
### Added
- Added Open Telemetry (OTEL) export support
- Upgraded to Go 1.20


## [0.1.0] - 2023-03-08
### Added
Expand All @@ -18,3 +23,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added HTTP/REST gateway helper

[0.1.0]: https://github.com/scanoss/go-grpc-helper/compare/v0.0.0...v0.1.0
[0.2.0]: https://github.com/scanoss/go-grpc-helper/compare/v0.1.0...v0.2.0
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ test: unit_test ## Run package tests
lint_local: ## Run local instance of linting across the code base
golangci-lint run ./...

lint_local_fix: ## Run local instance of linting across the code base including auto-fixing
golangci-lint run --fix ./...

lint_docker: ## Run docker instance of linting across the code base
docker run --rm -v $(PWD):/app -v ~/.cache/golangci-lint/v1.50.1:/root/.cache -w /app golangci/golangci-lint:v1.50.1 golangci-lint run ./...
38 changes: 25 additions & 13 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
module github.com/scanoss/go-grpc-helper

go 1.19
go 1.20

require (
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1
github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.17
github.com/mattn/go-sqlite3 v1.14.18
github.com/scanoss/ipfilter/v2 v2.0.2
github.com/scanoss/zap-logging-helper v0.2.1
github.com/scanoss/zap-logging-helper v0.3.0
github.com/stretchr/testify v1.8.4
golang.org/x/net v0.15.0
google.golang.org/grpc v1.58.2
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1
go.opentelemetry.io/otel v1.21.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
go.opentelemetry.io/otel/sdk v1.21.0
go.opentelemetry.io/otel/sdk/metric v1.21.0
golang.org/x/net v0.18.0
google.golang.org/grpc v1.59.0
)

//replace github.com/scanoss/ipfilter => ../../../oss/ipfilter

require (
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/phuslu/iploc v1.0.20230201 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.25.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
89 changes: 60 additions & 29 deletions go.sum

Large diffs are not rendered by default.

128 changes: 128 additions & 0 deletions pkg/grpc/otel/telemetry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (c) 2023, SCANOSS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

// Package otel provides functions to configure, start and shutdown gRPC open telemetry
package otel

import (
"context"
"strings"
"time"

zlog "github.com/scanoss/zap-logging-helper/pkg/logger"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"

"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

// InitTelemetryProviders sets up the OLTP Meter and Trace providers and the OLTP gRPC exporter.
func InitTelemetryProviders(serviceName, serviceNamespace, version, oltpExporter string, traceSampler sdktrace.Sampler) (func(), error) {
zlog.L.Info("Setting up Open Telemetry providers.")
// Setup resource for the providers
ctx := context.Background()
res, err := resource.New(ctx,
resource.WithFromEnv(),
resource.WithProcess(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithAttributes(
// the service name & version used to display traces in backends
semconv.ServiceName(serviceName),
semconv.ServiceNamespace(serviceNamespace),
semconv.ServiceVersion(strings.TrimSpace(version)),
),
)
if err != nil {
zlog.S.Errorf("Failed to create oltp resource: %v", err)
return nil, err
}
// Setup meter provider & exporter
metricExp, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithInsecure(),
otlpmetricgrpc.WithEndpoint(oltpExporter),
)
if err != nil {
zlog.S.Errorf("Failed to setup oltp metric grpc: %v", err)
return nil, err
}
meterProvider := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(
sdkmetric.NewPeriodicReader(
metricExp,
sdkmetric.WithInterval(2*time.Second),
),
),
)
otel.SetMeterProvider(meterProvider)
// Setup trace provider & exporter
traceClient := otlptracegrpc.NewClient(
otlptracegrpc.WithInsecure(),
otlptracegrpc.WithEndpoint(oltpExporter),
)
traceExp, err := otlptrace.New(ctx, traceClient)
if err != nil {
zlog.S.Errorf("Failed to create collector trace exporter: %v", err)
return nil, err
}
bsp := sdktrace.NewBatchSpanProcessor(traceExp)
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithSampler(traceSampler),
sdktrace.WithResource(res),
sdktrace.WithSpanProcessor(bsp),
)
// set global propagator to trace context (the default is no-op).
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
otel.SetTracerProvider(tracerProvider)
// Return the function use to shut down the collector before exiting
return func() {
cxt, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := traceExp.Shutdown(cxt); err != nil {
otel.Handle(err)
}
// pushes any last exports to the receiver
if err := meterProvider.Shutdown(cxt); err != nil {
otel.Handle(err)
}
}, nil
}

// GetTraceSampler determines what level of trace sampling to run.
func GetTraceSampler(mode string) sdktrace.Sampler {
switch mode {
case "dev":
return sdktrace.AlwaysSample()
case "prod":
return sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.5))
default:
return sdktrace.AlwaysSample()
}
}
27 changes: 27 additions & 0 deletions pkg/grpc/otel/telemetry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package otel

import (
"testing"

zlog "github.com/scanoss/zap-logging-helper/pkg/logger"
)

func TestSetupTelemetry(t *testing.T) {
err := zlog.NewSugaredDevLogger()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a sugared logger", err)
}
defer zlog.SyncZap()

oltpShutdown, err := InitTelemetryProviders("go-grpc-helper", "go-grpc", "0.0.1", "0.0.0.0:4317", GetTraceSampler("dev"))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
oltpShutdown()
}

func TestSampleMode(t *testing.T) {
GetTraceSampler("dev")
GetTraceSampler("prod")
GetTraceSampler("unknown")
}
7 changes: 6 additions & 1 deletion pkg/grpc/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import (
"syscall"
"time"

"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"

grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
"github.com/scanoss/go-grpc-helper/pkg/grpc/utils"
Expand All @@ -46,7 +48,7 @@ import (

// SetupGrpcServer configures the port, filtering & logging interceptors for a gRPC Server.
func SetupGrpcServer(port, tlsCertFile, tlsKeyFile string, allowedIPs, deniedIPs []string, startTLS, blockedByDefault,
trustProxy bool) (net.Listener, *grpc.Server, error) {
trustProxy, telemetry bool) (net.Listener, *grpc.Server, error) {
port = utils.SetupPort(port)
listen, err := net.Listen("tcp", port)
if err != nil {
Expand All @@ -71,6 +73,9 @@ func SetupGrpcServer(port, tlsCertFile, tlsKeyFile string, allowedIPs, deniedIPs
}
opts = append(opts, grpc.Creds(creds))
}
if telemetry {
opts = append(opts, grpc.StatsHandler(otelgrpc.NewServerHandler()))
}
opts = append(opts, grpc.UnaryInterceptor(grpcmiddleware.ChainUnaryServer(interceptors...)))
// register service
server := grpc.NewServer(opts...)
Expand Down
35 changes: 32 additions & 3 deletions pkg/grpc/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"testing"
"time"

"github.com/scanoss/go-grpc-helper/pkg/grpc/otel"

"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
zlog "github.com/scanoss/zap-logging-helper/pkg/logger"
)
Expand All @@ -43,7 +45,7 @@ func TestSetupGrpcServerNoTLS(t *testing.T) {
allowedIPs := []string{"127.0.0.1"}
deniedIPs := []string{"192.168.0.1"}

listen, server, err := SetupGrpcServer(":0", "", "", allowedIPs, deniedIPs, false, true, false)
listen, server, err := SetupGrpcServer(":0", "", "", allowedIPs, deniedIPs, false, true, false, false)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
Expand All @@ -64,7 +66,7 @@ func TestSetupGrpcServerWithTLS(t *testing.T) {
defer zlog.SyncZap()
allowedIPs := []string{"127.0.0.1"}
deniedIPs := []string{"192.168.0.1"}
listen, server, err := SetupGrpcServer(":0", "../../../tests/server.crt", "../../../tests/server.key", allowedIPs, deniedIPs, true, true, false)
listen, server, err := SetupGrpcServer(":0", "../../../tests/server.crt", "../../../tests/server.key", allowedIPs, deniedIPs, true, true, false, false)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
Expand All @@ -84,7 +86,7 @@ func TestSetupGrpcServerNoTLSShutdown(t *testing.T) {
defer zlog.SyncZap()
allowedIPs := []string{"127.0.0.1"}
deniedIPs := []string{"192.168.0.1"}
listen, server, err := SetupGrpcServer(":0", "", "", allowedIPs, deniedIPs, false, true, false)
listen, server, err := SetupGrpcServer(":0", "", "", allowedIPs, deniedIPs, false, true, false, false)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
Expand Down Expand Up @@ -118,3 +120,30 @@ func TestSetupGrpcServerNoTLSShutdown(t *testing.T) {
t.Errorf("Unexpected error: %v", err)
}
}

func TestSetupGrpcServerNoTLSTelemetry(t *testing.T) {
err := zlog.NewSugaredDevLogger()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a sugared logger", err)
}
defer zlog.SyncZap()
allowedIPs := []string{"127.0.0.1"}
deniedIPs := []string{"192.168.0.1"}

otelShutdown, err := otel.InitTelemetryProviders("go-grpc-helper", "go-grpc", "0.0.1", "0.0.0.0:4317", otel.GetTraceSampler("dev"))
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
listen, server, err := SetupGrpcServer(":0", "", "", allowedIPs, deniedIPs, false, true, false, true)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
fmt.Printf("Listening on %v\n", listen.Addr().String())

go func() {
time.Sleep(3 * time.Second)
server.GracefulStop()
}()
StartGrpcServer(listen, server, false)
otelShutdown()
}

0 comments on commit c37ae23

Please sign in to comment.