diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6d8559f..8c7dc0a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,7 +25,17 @@ updates: - dependency-name: "go.opentelemetry.io/otel/*" - package-ecosystem: gomod - directory: /example + directory: /example/stdout + labels: + - dependencies + schedule: + day: sunday + interval: weekly + ignore: + - dependency-name: "go.opentelemetry.io/*" + - + package-ecosystem: gomod + directory: /example/otel-collector labels: - dependencies schedule: diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 17ed4fa..6f5c4a1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,7 +40,7 @@ jobs: - name: Run linters run: make license-check lint - name: Build - run: make examples build + run: make build - name: Check clean repository run: make check-clean-work-tree diff --git a/Makefile b/Makefile index 5d55a66..b6f881f 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -EXAMPLES := ./example TOOLS_MOD_DIR := ./internal/tools # All directories with go.mod files related to opentelemetry library. Used for building, testing and linting. @@ -25,7 +24,7 @@ TIMEOUT = 60 .DEFAULT_GOAL := precommit .PHONY: precommit ci -precommit: license-check lint build examples test-default +precommit: license-check lint build test-default ci: precommit check-clean-work-tree test-coverage # Tools @@ -47,13 +46,7 @@ tools: $(GOLANGCI_LINT) # Build -.PHONY: examples generate build -examples: - @set -e; for dir in $(EXAMPLES); do \ - echo "$(GO) build $${dir}/..."; \ - (cd "$${dir}" && \ - $(GO) build -o ./bin/main .); \ - done +.PHONY: generate build generate: $(STRINGER) set -e; for dir in $(ALL_GO_MOD_DIRS); do \ diff --git a/README.md b/README.md index c8d6009..dae3954 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,14 @@ if err != nil { Check [Option](https://pkg.go.dev/github.com/XSAM/otelsql#Option) for more features like adding context propagation to SQL queries when enabling [`WithSQLCommenter`](https://pkg.go.dev/github.com/XSAM/otelsql#WithSQLCommenter). -See [godoc](https://pkg.go.dev/mod/github.com/XSAM/otelsql) and [a docker-compose example](./example/README.md) for details. +See [godoc](https://pkg.go.dev/mod/github.com/XSAM/otelsql) for details. + +## Examples + +This project provides two docker-compose examples to show how to use it. + +- [The stdout example](example/stdout) is a simple example to show how to use it with a MySQL database. It prints the trace data to stdout and serves metrics data via prometheus client. +- [The otel-collector example](example/otel-collector) is a more complex example to show how to use it with a MySQL database and an OpenTelemetry Collector. It sends the trace data and metrics data to an OpenTelemetry Collector. Then, it shows data visually on Jaeger and Prometheus servers. ## Trace Instruments diff --git a/example/README.md b/example/README.md deleted file mode 100644 index fa8c92b..0000000 --- a/example/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# database/sql instrumentation example - -A MySQL client using database/sql with instrumentation. - -These instructions expect you have -[docker-compose](https://docs.docker.com/compose/) installed. - -Bring up the `Mysql` services to run the -example: - -```sh -docker-compose up -d mysql -``` - -Then up the `client` service to make request with `MySQL`: - -```sh -docker-compose up client -``` - -Shut down the services when you are finished with the example: - -```sh -docker-compose down -``` diff --git a/example/otel-collector/Dockerfile b/example/otel-collector/Dockerfile new file mode 100644 index 0000000..0cedb52 --- /dev/null +++ b/example/otel-collector/Dockerfile @@ -0,0 +1,20 @@ +# Copyright Sam Xie +# +# 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. +FROM golang:1.22-alpine AS base +COPY .. /src/otelsql +WORKDIR /src/otelsql/example/otel-collector + +FROM base +RUN go install main.go +CMD ["/go/bin/main"] diff --git a/example/otel-collector/README.md b/example/otel-collector/README.md new file mode 100644 index 0000000..094fca9 --- /dev/null +++ b/example/otel-collector/README.md @@ -0,0 +1,37 @@ +# database/sql instrumentation OpenTelemetry Collector example + +> This is an adapted example of https://github.com/open-telemetry/opentelemetry-go/tree/main/example/otel-collector to provide a one-stop place to play with this instrumentation and see the results visually. + +A MySQL client using database/sql with instrumentation. This example shows the trace data on Jaeger and the metrics data on Prometheus server. + +The complete data flow is: + +``` + -----> Jaeger (trace) +MySQL client ---> OpenTelemetry Collector ---| + -----> Prometheus (metrics) +``` + +These instructions expect you have +[Docker Compose V2](https://docs.docker.com/compose/) installed. + +Bring up all services to run the +example: + +```sh +docker compose up -d +``` + +Then check the logs of `client` service to make ensure it is finished: + +```sh +docker compose logs client +``` + +Access the Jaeger UI at http://localhost:16686 and the Prometheus UI at http://localhost:9090 to see the results. + +Shut down the services when you are finished with the example: + +```sh +docker compose down +``` diff --git a/example/otel-collector/docker-compose.yaml b/example/otel-collector/docker-compose.yaml new file mode 100644 index 0000000..e8afad2 --- /dev/null +++ b/example/otel-collector/docker-compose.yaml @@ -0,0 +1,56 @@ +# Copyright Sam Xie +# +# 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. +version: "3.9" +services: + mysql: + image: mysql:5.7 + environment: + - MYSQL_ROOT_PASSWORD=otel_password + - MYSQL_DATABASE=db + healthcheck: + test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD + start_period: 5s + interval: 5s + timeout: 5s + retries: 10 + + otel-collector: + image: otel/opentelemetry-collector-contrib:0.91.0 + command: ["--config=/etc/otel-collector.yaml"] + volumes: + - ./otel-collector.yaml:/etc/otel-collector.yaml + depends_on: + - jaeger + + prometheus: + image: prom/prometheus:v2.45.2 + volumes: + - ./prometheus.yaml:/etc/prometheus/prometheus.yml + ports: + - 9090:9090 + depends_on: + - otel-collector + + jaeger: + image: jaegertracing/all-in-one:1.52 + ports: + - 16686:16686 + + client: + build: + dockerfile: $PWD/Dockerfile + context: ../.. + depends_on: + mysql: + condition: service_healthy diff --git a/example/otel-collector/go.mod b/example/otel-collector/go.mod new file mode 100644 index 0000000..e4a5791 --- /dev/null +++ b/example/otel-collector/go.mod @@ -0,0 +1,34 @@ +module github.com/XSAM/otelsql/example/otel-collector + +go 1.20 + +replace github.com/XSAM/otelsql => ../../ + +require ( + github.com/XSAM/otelsql v0.0.0 + github.com/go-sql-driver/mysql v1.7.1 + go.opentelemetry.io/otel v1.23.1 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 + go.opentelemetry.io/otel/sdk v1.23.1 + go.opentelemetry.io/otel/sdk/metric v1.23.1 + google.golang.org/grpc v1.61.0 +) + +require ( + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect + go.opentelemetry.io/otel/trace v1.23.1 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) diff --git a/example/otel-collector/go.sum b/example/otel-collector/go.sum new file mode 100644 index 0000000..a027af5 --- /dev/null +++ b/example/otel-collector/go.sum @@ -0,0 +1,57 @@ +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1 h1:ZqRWZJGHXV/1yCcEEVJ6/Uz2JtM79DNS8OZYa3vVY/A= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1/go.mod h1:D7ynngPWlGJrqyGSDOdscuv7uqttfCE3jcBvffDv9y4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 h1:p3A5+f5l9e/kuEBwLOrnpkIDHQFlHmbiVxMURWRK6gQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1/go.mod h1:OClrnXUjBqQbInvjJFjYSnMxBSCXBF8r3b34WqjiIrQ= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= +go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= +go.opentelemetry.io/otel/sdk/metric v1.23.1 h1:T9/8WsYg+ZqIpMWwdISVVrlGb/N0Jr1OHjR/alpKwzg= +go.opentelemetry.io/otel/sdk/metric v1.23.1/go.mod h1:8WX6WnNtHCgUruJ4TJ+UssQjMtpxkpX0zveQC8JG/E0= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/example/otel-collector/main.go b/example/otel-collector/main.go new file mode 100644 index 0000000..7b469aa --- /dev/null +++ b/example/otel-collector/main.go @@ -0,0 +1,215 @@ +// Copyright Sam Xie +// +// 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 main + +import ( + "context" + "database/sql" + "fmt" + "log" + "os" + "os/signal" + "time" + + _ "github.com/go-sql-driver/mysql" + "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" + 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.18.0" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/XSAM/otelsql" +) + +const instrumentationName = "github.com/XSAM/otelsql/example/otel-collector" + +var serviceName = semconv.ServiceNameKey.String("otesql-example") + +var mysqlDSN = "root:otel_password@tcp(mysql)/db?parseTime=true" + +// Initialize a gRPC connection to be used by both the tracer and meter +// providers. +func initConn(ctx context.Context) (*grpc.ClientConn, error) { + // Make a gRPC connection with otel collector. + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + conn, err := grpc.DialContext(ctx, "otel-collector:4317", + // Note the use of insecure transport here. TLS is recommended in production. + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + ) + if err != nil { + return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err) + } + + return conn, err +} + +// Initializes an OTLP exporter, and configures the corresponding trace providers. +func initTracerProvider(ctx context.Context, conn *grpc.ClientConn) (func(context.Context) error, error) { + res, err := resource.New(ctx, + resource.WithAttributes( + // the service name used to display traces in backends + serviceName, + ), + ) + if err != nil { + return nil, fmt.Errorf("failed to create resource: %w", err) + } + + // Set up a trace exporter + traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn)) + if err != nil { + return nil, fmt.Errorf("failed to create trace exporter: %w", err) + } + + // Register the trace exporter with a TracerProvider, using a batch + // span processor to aggregate spans before export. + bsp := sdktrace.NewBatchSpanProcessor(traceExporter) + tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithResource(res), + sdktrace.WithSpanProcessor(bsp), + ) + otel.SetTracerProvider(tracerProvider) + + // set global propagator to tracecontext (the default is no-op). + otel.SetTextMapPropagator(propagation.TraceContext{}) + + // Shutdown will flush any remaining spans and shut down the exporter. + return tracerProvider.Shutdown, nil +} + +// Initializes an OTLP exporter, and configures the corresponding meter +// provider. +func initMeterProvider(ctx context.Context, conn *grpc.ClientConn) (func(context.Context) error, error) { + res, err := resource.New(ctx, + resource.WithAttributes( + // the service name used to display traces in backends + serviceName, + ), + ) + if err != nil { + return nil, fmt.Errorf("failed to create resource: %w", err) + } + + metricExporter, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithGRPCConn(conn)) + if err != nil { + return nil, fmt.Errorf("failed to create metrics exporter: %w", err) + } + + meterProvider := sdkmetric.NewMeterProvider( + sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter)), + sdkmetric.WithResource(res), + ) + otel.SetMeterProvider(meterProvider) + + return meterProvider.Shutdown, nil +} + +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + conn, err := initConn(ctx) + if err != nil { + log.Fatal(err) + } + + shutdownTracerProvider, err := initTracerProvider(ctx, conn) + if err != nil { + log.Fatal(err) + } + defer func() { + if err := shutdownTracerProvider(ctx); err != nil { + log.Fatalf("failed to shutdown TracerProvider: %s", err) + } + }() + + shutdownMeterProvider, err := initMeterProvider(ctx, conn) + if err != nil { + log.Fatal(err) + } + defer func() { + if err := shutdownMeterProvider(ctx); err != nil { + log.Fatalf("failed to shutdown MeterProvider: %s", err) + } + }() + + runSQLQuery(ctx) + + fmt.Println("Example finished") +} + +func runSQLQuery(ctx context.Context) { + // Connect to database + db, err := otelsql.Open("mysql", mysqlDSN, otelsql.WithAttributes( + semconv.DBSystemMySQL, + )) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + err = otelsql.RegisterDBStatsMetrics(db, otelsql.WithAttributes( + semconv.DBSystemMySQL, + )) + if err != nil { + log.Fatal(err) + } + + err = run(ctx, db) + if err != nil { + log.Fatal(err) + } +} + +func run(ctx context.Context, db *sql.DB) error { + // Create a parent span (Optional) + tracer := otel.GetTracerProvider() + ctx, span := tracer.Tracer(instrumentationName).Start(ctx, "example") + defer span.End() + + err := query(ctx, db) + if err != nil { + span.RecordError(err) + return err + } + return nil +} + +func query(ctx context.Context, db *sql.DB) error { + // Make a query + rows, err := db.QueryContext(ctx, `SELECT CURRENT_TIMESTAMP`) + if err != nil { + return err + } + defer rows.Close() + + var currentTime time.Time + for rows.Next() { + err = rows.Scan(¤tTime) + if err != nil { + return err + } + } + fmt.Println(currentTime) + return nil +} diff --git a/example/otel-collector/otel-collector.yaml b/example/otel-collector/otel-collector.yaml new file mode 100644 index 0000000..0ef4efe --- /dev/null +++ b/example/otel-collector/otel-collector.yaml @@ -0,0 +1,31 @@ +receivers: + # Make sure to add the otlp receiver. + # This will open up the receiver on port 4317 + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" +processors: +extensions: + health_check: {} +exporters: + otlp: + endpoint: "jaeger:4317" + tls: + insecure: true + prometheus: + endpoint: 0.0.0.0:9090 + logging: + +service: + extensions: [health_check] + pipelines: + traces: + receivers: [otlp] + processors: [] + exporters: [otlp] + + metrics: + receivers: [otlp] + processors: [] + exporters: [prometheus, logging] \ No newline at end of file diff --git a/example/otel-collector/prometheus.yaml b/example/otel-collector/prometheus.yaml new file mode 100644 index 0000000..4ba4666 --- /dev/null +++ b/example/otel-collector/prometheus.yaml @@ -0,0 +1,5 @@ +scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 5s + static_configs: + - targets: ['otel-collector:9090'] diff --git a/example/Dockerfile b/example/stdout/Dockerfile similarity index 87% rename from example/Dockerfile rename to example/stdout/Dockerfile index 371de03..e0e4ac6 100644 --- a/example/Dockerfile +++ b/example/stdout/Dockerfile @@ -11,9 +11,9 @@ # 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. -FROM golang:alpine AS base -COPY . /src/otelsql -WORKDIR /src/otelsql/example +FROM golang:1.22-alpine AS base +COPY .. /src/otelsql +WORKDIR /src/otelsql/example/stdout FROM base RUN go install main.go diff --git a/example/stdout/README.md b/example/stdout/README.md new file mode 100644 index 0000000..ba930d2 --- /dev/null +++ b/example/stdout/README.md @@ -0,0 +1,25 @@ +# database/sql instrumentation stdout example + +A MySQL client using database/sql with instrumentation. Then, the client prints the trace data to stdout and serves metrics data via prometheus client. + +These instructions expect you have +[Docker Compose V2](https://docs.docker.com/compose/) installed. + +Bring up all services to run the +example: + +```sh +docker compose up -d +``` + +Then check the logs of `client` service to see the results: + +```sh +docker compose logs client +``` + +Shut down the services when you are finished with the example: + +```sh +docker compose down +``` diff --git a/example/docker-compose.yml b/example/stdout/docker-compose.yml similarity index 75% rename from example/docker-compose.yml rename to example/stdout/docker-compose.yml index 7be162c..4f452a6 100644 --- a/example/docker-compose.yml +++ b/example/stdout/docker-compose.yml @@ -11,7 +11,7 @@ # 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. -version: "3.7" +version: "3.9" services: mysql: image: mysql:5.7 @@ -20,12 +20,19 @@ services: environment: - MYSQL_ROOT_PASSWORD=otel_password - MYSQL_DATABASE=db + healthcheck: + test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD + start_period: 5s + interval: 5s + timeout: 5s + retries: 10 client: build: dockerfile: $PWD/Dockerfile - context: .. + context: ../.. ports: - 2222:2222 depends_on: - - mysql + mysql: + condition: service_healthy diff --git a/example/go.mod b/example/stdout/go.mod similarity index 91% rename from example/go.mod rename to example/stdout/go.mod index 43ccca7..3181bcb 100644 --- a/example/go.mod +++ b/example/stdout/go.mod @@ -1,8 +1,8 @@ -module github.com/XSAM/otelsql/example +module github.com/XSAM/otelsql/example/stdout go 1.20 -replace github.com/XSAM/otelsql => ../ +replace github.com/XSAM/otelsql => ../../ require ( github.com/XSAM/otelsql v0.0.0 diff --git a/example/go.sum b/example/stdout/go.sum similarity index 100% rename from example/go.sum rename to example/stdout/go.sum diff --git a/example/main.go b/example/stdout/main.go similarity index 95% rename from example/main.go rename to example/stdout/main.go index 735ed48..6ee51c8 100644 --- a/example/main.go +++ b/example/stdout/main.go @@ -35,7 +35,7 @@ import ( "github.com/XSAM/otelsql" ) -const instrumentationName = "github.com/XSAM/otelsql/example" +const instrumentationName = "github.com/XSAM/otelsql/example/stdout" var serviceName = semconv.ServiceNameKey.String("otesql-example") @@ -107,7 +107,7 @@ func main() { panic(err) } - fmt.Println("Example finished updating, please visit :2222") + fmt.Println("Example finished updating, please visit localhost:2222/metrics") select {} }