diff --git a/config.go b/config.go index 884a878..e26ba87 100644 --- a/config.go +++ b/config.go @@ -34,14 +34,15 @@ var ( queryMethodKey = attribute.Key("method") ) -// SpanNameFormatter supports formatting span names. +// SpanNameFormatter помогает иницилизировать имена для spans. type SpanNameFormatter func(ctx context.Context, method Method, query string) string -// AttributesGetter provides additional attributes on spans creation. +// AttributesGetter помогает иницилизировать spans при их создании. type AttributesGetter func(ctx context.Context, method Method, query string, args []driver.NamedValue) []attribute.KeyValue type SpanFilter func(ctx context.Context, method Method, query string, args []driver.NamedValue) bool +// config структура, содержащее все необходимое для трассировки базы данных. type config struct { TracerProvider trace.TracerProvider Tracer trace.Tracer @@ -53,71 +54,68 @@ type config struct { SpanOptions SpanOptions - // Attributes will be set to each span. + // Attributes, которые будут добавлены во все spans. Attributes []attribute.KeyValue - // SpanNameFormatter will be called to produce span's name. - // Default use method as span name + // SpanNameFormatter будет вызван для присваивания имени span. + // По умолчанию использует название метод как имя span. SpanNameFormatter SpanNameFormatter - // SQLCommenterEnabled enables context propagation for database - // by injecting a comment into SQL statements. + // SQLCommenterEnabled включает проброс context для database + // при помощи включения комментария в SQL statements. // - // Experimental + // Эксперементально! // - // Notice: This config is EXPERIMENTAL and may be changed or removed in a - // later release. + // Notice: Эта опция ЭКСПЕРЕМЕНТАЛЬНА и, возможно, будет изменена или удалена в + // более поздник релизах. SQLCommenterEnabled bool SQLCommenter *commenter - // AttributesGetter will be called to produce additional attributes while creating spans. - // Default returns nil + // AttributesGetter функция, которая будет вызвана для инициализации дополнительных attributes + // во время создания spans. + // По умолчанию возвращает nil AttributesGetter AttributesGetter } -// SpanOptions holds configuration of tracing span to decide -// whether to enable some features. -// By default all options are set to false intentionally when creating a wrapped -// driver and provide the most sensible default with both performance and -// security in mind. +// SpanOptions структура, содержащая некоторые опции для тонко настройки tracing spans. +// По умолчанию все опции отключены. type SpanOptions struct { - // Ping, if set to true, will enable the creation of spans on Ping requests. + // Ping, если выставлено значение true, включит создание spans по Ping requests. Ping bool - // RowsNext, if set to true, will enable the creation of events in spans on RowsNext - // calls. This can result in many events. + // RowsNext, если высталено значение true, включит создание events в spans на вызов RowsNext RowsNext bool - // DisableErrSkip, if set to true, will suppress driver.ErrSkip errors in spans. + // DisableErrSkip,если выставлено значение true, будет подавлять driver.ErrSkip errors в spans. DisableErrSkip bool - // DisableQuery if set to true, will suppress db.statement in spans. + // DisableQuery если выставлено значение true, будет подавлено db.statement в spans. DisableQuery bool - // RecordError, if set, will be invoked with the current error, and if the func returns true - // the record will be recorded on the current span. + // RecordError, если включено, будет вызвана с текущей ошибкой, если функция возвращает true + // то запись будет записана в текущий span. // - // If this is not set it will default to record all errors (possible not ErrSkip, see option + // В противном случае будет записывать все ошибки в текущий span (possible not ErrSkip, see option // DisableErrSkip). RecordError func(err error) bool - // OmitConnResetSession if set to true will suppress sql.conn.reset_session spans + // OmitConnResetSession, если выставлено значение true, будет подавлять sql.conn.reset_session spans OmitConnResetSession bool - // OmitConnPrepare if set to true will suppress sql.conn.prepare spans + // OmitConnPrepare, если выставлено true, будет подавлять sql.conn.prepare spans OmitConnPrepare bool - // OmitConnQuery if set to true will suppress sql.conn.query spans + // OmitConnQuery, если выставлено true, будет подавлять sql.conn.query spans OmitConnQuery bool - // OmitRows if set to true will suppress sql.rows spans + // OmitRows, если выставлено true, будет подавлять sql.rows spans OmitRows bool - // OmitConnectorConnect if set to true will suppress sql.connector.connect spans + // OmitConnectorConnect, если выставлено true, будет подавлять sql.connector.connect spans OmitConnectorConnect bool - // SpanFilter, if set, will be invoked before each call to create a span. If it returns - // false, the span will not be created. + // SpanFilter, функция, которая будет вызвана перед каждым вызовом span. Если функция возвращает + // false, span will не будет создан. SpanFilter SpanFilter } @@ -125,7 +123,7 @@ func defaultSpanNameFormatter(_ context.Context, method Method, _ string) string return string(method) } -// newConfig returns a config with all Options set. +// newConfig функция, возвращающая config, иницилизированный переданными опциями options. func newConfig(options ...Option) config { cfg := config{ TracerProvider: otel.GetTracerProvider(), diff --git a/connector.go b/connector.go index bc84db9..b511b8e 100644 --- a/connector.go +++ b/connector.go @@ -25,12 +25,16 @@ import ( var _ driver.Connector = (*otConnector)(nil) var _ io.Closer = (*otConnector)(nil) +// otConnector структура, описывающая connector +// для database/sql connector type otConnector struct { driver.Connector otDriver *otDriver cfg config } +// newConnector иницилизирует otCollector с переданными настройками cfg, +// и драйвером otDriver. func newConnector(connector driver.Connector, otDriver *otDriver) *otConnector { return &otConnector{ Connector: connector, @@ -39,6 +43,8 @@ func newConnector(connector driver.Connector, otDriver *otDriver) *otConnector { } } +// Connect метод структуры otConnector, осуществляющий подключение +// и возращающий интерфейс driver.Conn и ошибку func (c *otConnector) Connect(ctx context.Context) (connection driver.Conn, err error) { method := MethodConnectorConnect onDefer := recordMetric(ctx, c.cfg.Instruments, c.cfg.Attributes, method) @@ -60,29 +66,34 @@ func (c *otConnector) Connect(ctx context.Context) (connection driver.Conn, err return newConn(connection, c.cfg), nil } +// метод otConnector Driver возвращает +// поле структуры otConnector otDriver func (c *otConnector) Driver() driver.Driver { return c.otDriver } func (c *otConnector) Close() error { - // database/sql uses a type assertion to check if connectors implement io.Closer. - // The type assertion does not pass through to otConnector.Connector, so we explicitly implement it here. + // database/sql использует type assertion для проверки удоволетворяет ли connector io.Closer. if closer, ok := c.Connector.(io.Closer); ok { return closer.Close() } return nil } -// dsnConnector is copied from sql.dsnConnector. +// dsnConnector сорпирован с sql.dsnConnector. type dsnConnector struct { dsn string driver driver.Driver } +// Connect метод структуры dsnConnector, осуществленяющий подклчение +// и возвращающий driver.Conn и ошибку. func (t dsnConnector) Connect(_ context.Context) (driver.Conn, error) { return t.driver.Open(t.dsn) } +// Driver метод структуры dsnConnector возвращает +// драйвер, хранящийся в структуре dsnConnector func (t dsnConnector) Driver() driver.Driver { return t.driver } diff --git a/instruments.go b/instruments.go index 5ec1652..8cf8674 100644 --- a/instruments.go +++ b/instruments.go @@ -36,10 +36,12 @@ type dbStatsInstruments struct { } type instruments struct { - // The latency of calls in milliseconds + // latency(задержка) вызовов в миллисекундах latency metric.Float64Histogram } +// newInstuments возвращает структура insruments с иницилизированным +// полем latency, который считает задрежку в миллисекундах func newInstruments(meter metric.Meter) (*instruments, error) { var instruments instruments var err error @@ -54,6 +56,7 @@ func newInstruments(meter metric.Meter) (*instruments, error) { return &instruments, nil } +// newDBStatsInstruments возвращает иницилизированную структуру dbStatsInstruments func newDBStatsInstruments(meter metric.Meter) (*dbStatsInstruments, error) { var instruments dbStatsInstruments var err error diff --git a/methods.go b/methods.go index a28d4b8..a64c6eb 100644 --- a/methods.go +++ b/methods.go @@ -14,10 +14,10 @@ package otelsql -// Method specifics operation in the database/sql package. +// Method описывает операцию в database/sql package. type Method string -// Event specifics events in the database/sql package. +// Event описывает событие в database/sql package. type Event string const ( diff --git a/myexample/otel-collector/.null-ls_399798_main.go b/myexample/otel-collector/.null-ls_399798_main.go new file mode 100644 index 0000000..7c3aaab --- /dev/null +++ b/myexample/otel-collector/.null-ls_399798_main.go @@ -0,0 +1,241 @@ +// 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" + _ "github.com/lib/pq" + "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("andyfilya-example") // +var mysqlDSN = "root:otel_password@tcp(mysql)/db?parseTime=true" +var postgresqlDSN = "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable" + +// 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) + } + }() + + db := connectDB() + postdb := connectpostgresDB() + defer postdb.Close() + defer db.Close() + + err = runSQLQuery(ctx, db) + if err != nil { + log.Fatal(err) + } + + err = runSQLQuery(ctx, postdb) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Example finished") +} + +func connectpostgresDB() *sql.DB { + db, err := otelsql.Open("postgres", postgresqlDSN, otelsql.WithAttributes( + semconv.DBSystemPostgreSQL, + )) + if err != nil { + log.Fatal(err) + } + err = otelsql.RegisterDBStatsMetrics(db, otelsql.WithAttributes( + semconv.DBSystemPostgreSQL, + )) + if err != nil { + log.Fatal(err) + } + return db +} +func connectDB() *sql.DB { + // Connect to database + db, err := otelsql.Open("mysql", mysqlDSN, otelsql.WithAttributes( + semconv.DBSystemMySQL, + )) + if err != nil { + log.Fatal(err) + } + + // Register DB stats to meter + err = otelsql.RegisterDBStatsMetrics(db, otelsql.WithAttributes( + semconv.DBSystemMySQL, + )) + if err != nil { + log.Fatal(err) + } + return db +} + +func runSQLQuery(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/myexample/otel-collector/Dockerfile b/myexample/otel-collector/Dockerfile new file mode 100644 index 0000000..da5d2c0 --- /dev/null +++ b/myexample/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/myexample/otel-collector + +FROM base +RUN go install main.go +CMD ["/go/bin/main"] diff --git a/myexample/otel-collector/README.md b/myexample/otel-collector/README.md new file mode 100644 index 0000000..094fca9 --- /dev/null +++ b/myexample/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/myexample/otel-collector/docker-compose.yaml b/myexample/otel-collector/docker-compose.yaml new file mode 100644 index 0000000..c37a79f --- /dev/null +++ b/myexample/otel-collector/docker-compose.yaml @@ -0,0 +1,63 @@ +# 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:8.3 + 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 + postgres: + image: postgres:latest + environment: + POSTGRES_USER: "postgres" + POSTGRES_PASSWORD: "postgres" + POSTGRES_DB: "postgres" + ports: + - 5432:5432 + 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/myexample/otel-collector/go.mod b/myexample/otel-collector/go.mod new file mode 100644 index 0000000..e59924e --- /dev/null +++ b/myexample/otel-collector/go.mod @@ -0,0 +1,35 @@ +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 + github.com/lib/pq v1.10.9 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/otel/sdk/metric v1.24.0 + google.golang.org/grpc v1.62.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.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) diff --git a/myexample/otel-collector/go.sum b/myexample/otel-collector/go.sum new file mode 100644 index 0000000..45a385b --- /dev/null +++ b/myexample/otel-collector/go.sum @@ -0,0 +1,59 @@ +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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0 h1:f2jriWfOdldanBwS9jNBdeOKAQN7b4ugAMaNu1/1k9g= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0/go.mod h1:B+bcQI1yTY+N0vqMpoZbEN7+XU4tNM0DmUiOwebFJWI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= +go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.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-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +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/myexample/otel-collector/main.go b/myexample/otel-collector/main.go new file mode 100644 index 0000000..02b6292 --- /dev/null +++ b/myexample/otel-collector/main.go @@ -0,0 +1,233 @@ +// 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" + _ "github.com/lib/pq" + "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("andyfilya-example") // название сервера +var mysqlDSN = "root:otel_password@tcp(mysql)/db?parseTime=true" // адрес для обработки запросов MySQL +var postgresqlDSN = "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable" // адрес для обработки запросов PostgreSQL + +// Инициализируем подключение к otel-collector, которое будет использоваться +// tracer provider и meter provider. +func initConn(ctx context.Context) (*grpc.ClientConn, error) { + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + conn, err := grpc.DialContext(ctx, "otel-collector:4317", + grpc.WithTransportCredentials(insecure.NewCredentials()), // Не используется протокол TLS для упрощения задачи (в production необходимо использовать это!) + grpc.WithBlock(), + ) + if err != nil { + return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err) + } + + return conn, err +} + +// Инициализируем tracer provider для экспорта данных в otlp-collector и последующей обработки. +func initTracerProvider(ctx context.Context, conn *grpc.ClientConn) (func(context.Context) error, error) { + res, err := resource.New(ctx, + resource.WithAttributes( + // имя сервиса, которое будет отображаться в backends. + serviceName, + ), + ) + if err != nil { + return nil, fmt.Errorf("failed to create resource: %w", err) + } + + // создание tracer exporter + traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn)) + if err != nil { + return nil, fmt.Errorf("failed to create trace exporter: %w", err) + } + + // Регистрируем batch span proccor для обработки + // spans перед отправкой. + bsp := sdktrace.NewBatchSpanProcessor(traceExporter) + tracerProvider := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithResource(res), + sdktrace.WithSpanProcessor(bsp), + ) + otel.SetTracerProvider(tracerProvider) + otel.SetTextMapPropagator(propagation.TraceContext{}) + + // возвращаем sutdown функцию для tracer provider, которая будет корректно завершать работу tracerProvider + return tracerProvider.Shutdown, nil +} + +// Инициализация OTLP экспортера, и настройка meter provider +func initMeterProvider(ctx context.Context, conn *grpc.ClientConn) (func(context.Context) error, error) { + res, err := resource.New(ctx, + resource.WithAttributes( + 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) + } + }() + + db := connectDB() + postdb := connectpostgresDB() + defer postdb.Close() + defer db.Close() + + err = runSQLQuery(ctx, db) + if err != nil { + log.Fatal(err) + } + + err = runSQLQuery(ctx, postdb) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Example finished") +} + +func connectpostgresDB() *sql.DB { + db, err := otelsql.Open("postgres", postgresqlDSN, otelsql.WithAttributes( + semconv.DBSystemPostgreSQL, + )) + if err != nil { + log.Fatal(err) + } + err = otelsql.RegisterDBStatsMetrics(db, otelsql.WithAttributes( + semconv.DBSystemPostgreSQL, + )) + if err != nil { + log.Fatal(err) + } + return db +} +func connectDB() *sql.DB { + // Connect to database + db, err := otelsql.Open("mysql", mysqlDSN, otelsql.WithAttributes( + semconv.DBSystemMySQL, + )) + if err != nil { + log.Fatal(err) + } + + // Register DB stats to meter + err = otelsql.RegisterDBStatsMetrics(db, otelsql.WithAttributes( + semconv.DBSystemMySQL, + )) + if err != nil { + log.Fatal(err) + } + return db +} + +func runSQLQuery(ctx context.Context, db *sql.DB) error { + 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 { + 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/myexample/otel-collector/otel-collector.yaml b/myexample/otel-collector/otel-collector.yaml new file mode 100644 index 0000000..0ef4efe --- /dev/null +++ b/myexample/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/myexample/otel-collector/prometheus.yaml b/myexample/otel-collector/prometheus.yaml new file mode 100644 index 0000000..4ba4666 --- /dev/null +++ b/myexample/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/option.go b/option.go index 119b21d..8c119c6 100644 --- a/option.go +++ b/option.go @@ -20,61 +20,61 @@ import ( "go.opentelemetry.io/otel/trace" ) -// Option is the interface that applies a configuration option. +// Option это интерфейс, который инициализирует опции. type Option interface { - // Apply sets the Option value of a config. + // Apply выставяет значение Option необходимых config. Apply(*config) } var _ Option = OptionFunc(nil) -// OptionFunc implements the Option interface. +// OptionFunc функция, удоволетворяющая интерфейсу Option. type OptionFunc func(*config) func (f OptionFunc) Apply(c *config) { f(c) } -// WithTracerProvider specifies a tracer provider to use for creating a tracer. -// If none is specified, the global provider is used. +// WithTracerProvider инициализирует tracer provider для создания tracer. +// По умолчанию используется global provider. func WithTracerProvider(provider trace.TracerProvider) Option { return OptionFunc(func(cfg *config) { cfg.TracerProvider = provider }) } -// WithAttributes specifies attributes that will be set to each span. +// WithAttributes инициализирует attributes, которые будут применены к каждому span. func WithAttributes(attributes ...attribute.KeyValue) Option { return OptionFunc(func(cfg *config) { cfg.Attributes = attributes }) } -// WithSpanNameFormatter takes an interface that will be called on every -// operation and the returned string will become the span name. +// WithSpanNameFormatter принимает функцию, которая будет вызвана на всякую +// со span и строка, которая вернётся, станет именем span. func WithSpanNameFormatter(spanNameFormatter SpanNameFormatter) Option { return OptionFunc(func(cfg *config) { cfg.SpanNameFormatter = spanNameFormatter }) } -// WithSpanOptions specifies configuration for span to decide whether to enable some features. +// WithSpanOptions иницилизирует некоторые опции для span. func WithSpanOptions(opts SpanOptions) Option { return OptionFunc(func(cfg *config) { cfg.SpanOptions = opts }) } -// WithMeterProvider specifies a tracer provider to use for creating a tracer. -// If none is specified, the global provider is used. +// WithMeterProvider инициализирует tracer provider для создания tracer. +// По умолчанию используется global provider. func WithMeterProvider(provider metric.MeterProvider) Option { return OptionFunc(func(cfg *config) { cfg.MeterProvider = provider }) } -// WithSQLCommenter will enable or disable context propagation for database -// by injecting a comment into SQL statements. +// WithSQLCommenter включает или отключает проброс context для database +// посредством включения комментария в SQL statements. // // e.g., a SQL query // @@ -84,18 +84,18 @@ func WithMeterProvider(provider metric.MeterProvider) Option { // // SELECT * from FOO /*traceparent='00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',tracestate='congo%3Dt61rcWkgMzE%2Crojo%3D00f067aa0ba902b7'*/ // -// This option defaults to disable. +// Данная опцияя по умолчанию отключена. // -// Notice: This option is EXPERIMENTAL and may be changed or removed in a -// later release. +// Notice: Эта опция ЭКСПЕРЕМЕНТАЛЬНА и, возможно, будет изменена или удалена +// в более поздних релизах. func WithSQLCommenter(enabled bool) Option { return OptionFunc(func(cfg *config) { cfg.SQLCommenterEnabled = enabled }) } -// WithAttributesGetter takes AttributesGetter that will be called on every -// span creations. +// WithAttributesGetter принимает AttributesGetter которая будет вызвана при +// создании span. func WithAttributesGetter(attributesGetter AttributesGetter) Option { return OptionFunc(func(cfg *config) { cfg.AttributesGetter = attributesGetter diff --git a/rows.go b/rows.go index d018b10..0664e08 100644 --- a/rows.go +++ b/rows.go @@ -57,9 +57,9 @@ func newRows(ctx context.Context, rows driver.Rows, cfg config) *otRows { } } -// HasNextResultSet calls the implements the driver.RowsNextResultSet for otRows. -// It returns the the underlying result of HasNextResultSet from the otRows.parent -// if the parent implements driver.RowsNextResultSet. +// HasNextResultSet вызывает реализацию driver.RowsNextResultSet для otRows. +// Возвращает результат HasNextResultSet от otRows.parent +// если parent реализует driver.RowsNextResultSet. func (r otRows) HasNextResultSet() bool { if v, ok := r.Rows.(driver.RowsNextResultSet); ok { return v.HasNextResultSet() @@ -68,9 +68,9 @@ func (r otRows) HasNextResultSet() bool { return false } -// NextResultSet calls the implements the driver.RowsNextResultSet for otRows. -// It returns the the underlying result of NextResultSet from the otRows.parent -// if the parent implements driver.RowsNextResultSet. +// NextResultSet вызывает реализацию driver.RowsNextResultSet для otRows. +// Возвращает NextResultSet от otRows.parent +// если parent удовоетворяет driver.RowsNextResultSet. func (r otRows) NextResultSet() error { if v, ok := r.Rows.(driver.RowsNextResultSet); ok { return v.NextResultSet() @@ -79,9 +79,9 @@ func (r otRows) NextResultSet() error { return io.EOF } -// ColumnTypeDatabaseTypeName calls the implements the driver.RowsColumnTypeDatabaseTypeName for otRows. -// It returns the the underlying result of ColumnTypeDatabaseTypeName from the otRows.Rows -// if the Rows implements driver.RowsColumnTypeDatabaseTypeName. +// ColumnTypeDatabaseTypeName вызывает реализацию driver.RowsColumnTypeDatabaseTypeName для otRows. +// Возвращает результат ColumnTypeDatabaseTypeName от otRows.Rows +// если Rows реализует driver.RowsColumnTypeDatabaseTypeName. func (r otRows) ColumnTypeDatabaseTypeName(index int) string { if v, ok := r.Rows.(driver.RowsColumnTypeDatabaseTypeName); ok { return v.ColumnTypeDatabaseTypeName(index) @@ -90,9 +90,9 @@ func (r otRows) ColumnTypeDatabaseTypeName(index int) string { return "" } -// ColumnTypeLength calls the implements the driver.RowsColumnTypeLength for otRows. -// It returns the the underlying result of ColumnTypeLength from the otRows.Rows -// if the Rows implements driver.RowsColumnTypeLength. +// ColumnTypeLength вызывает реализацию driver.RowsColumnTypeLength для otRows. +// Возвращает результат ColumnTypeLength от otRows.Rows +// если Rows реализует driver.RowsColumnTypeLength. func (r otRows) ColumnTypeLength(index int) (length int64, ok bool) { if v, ok := r.Rows.(driver.RowsColumnTypeLength); ok { return v.ColumnTypeLength(index) @@ -101,9 +101,9 @@ func (r otRows) ColumnTypeLength(index int) (length int64, ok bool) { return 0, false } -// ColumnTypeNullable calls the implements the driver.RowsColumnTypeNullable for otRows. -// It returns the the underlying result of ColumnTypeNullable from the otRows.Rows -// if the Rows implements driver.RowsColumnTypeNullable. +// ColumnTypeNullable вызывает реализацию driver.RowsColumnTypeNullable для otRows. +// Возвращает результат ColumnTypeNullable от otRows.Rows +// если Rows реализует driver.RowsColumnTypeNullable. func (r otRows) ColumnTypeNullable(index int) (nullable, ok bool) { if v, ok := r.Rows.(driver.RowsColumnTypeNullable); ok { return v.ColumnTypeNullable(index) @@ -112,9 +112,9 @@ func (r otRows) ColumnTypeNullable(index int) (nullable, ok bool) { return false, false } -// ColumnTypePrecisionScale calls the implements the driver.RowsColumnTypePrecisionScale for otRows. -// It returns the the underlying result of ColumnTypePrecisionScale from the otRows.Rows -// if the Rows implements driver.RowsColumnTypePrecisionScale. +// ColumnTypePrecisionScale вызывает реализацию driver.RowsColumnTypePrecisionScale для otRows. +// Возвращает результат ColumnTypePrecisionScale от otRows.Rows +// если Rows реализует driver.RowsColumnTypePrecisionScale. func (r otRows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) { if v, ok := r.Rows.(driver.RowsColumnTypePrecisionScale); ok { return v.ColumnTypePrecisionScale(index) @@ -144,7 +144,7 @@ func (r otRows) Next(dest []driver.Value) (err error) { } err = r.Rows.Next(dest) - // io.EOF is not an error. It is expected to happen during iteration. + // io.EOF это не ошибка. Это непредвиденное обстоятельство во время итерации. if err != nil && err != io.EOF { recordSpanError(r.span, r.cfg.SpanOptions, err) } diff --git a/sql.go b/sql.go index 465f288..fe929c9 100644 --- a/sql.go +++ b/sql.go @@ -29,12 +29,12 @@ var registerLock sync.Mutex var maxDriverSlot = 1000 -// Register initializes and registers OTel wrapped database driver -// identified by its driverName, using provided Option. -// It is possible to register multiple wrappers for the same database driver if -// needing different Option for different connections. +// Register инициализирует и регистрирует OTel обёрнутый database driver +// идентефициронный с помощью driverName,используя дополнительные настройки Option. +// Возможно заргестрировать multiple wrappers для одинаковых database driver если +// вы нуждаетесь в разных дополнительных настройках Option. func Register(driverName string, options ...Option) (string, error) { - // Retrieve the driver implementation we need to wrap with instrumentation + // Извлечение реализации driver для последующей обёртки с помощью инстрементария. db, err := sql.Open(driverName, "") if err != nil { return "", err @@ -47,9 +47,9 @@ func Register(driverName string, options ...Option) (string, error) { registerLock.Lock() defer registerLock.Unlock() - // Since we might want to register multiple OTel drivers to have different - // configurations, but potentially the same underlying database driver, we - // cycle through to find available driver names. + // Если вы завели multiple OTel drivers с разными + // конфигурациями, но имеем одинаковые database driver, мы + // проходимся циклом, чтобы найти доступный driver. driverName = driverName + "-otelsql-" for i := 0; i < maxDriverSlot; i++ { var ( @@ -69,14 +69,14 @@ func Register(driverName string, options ...Option) (string, error) { return "", errors.New("unable to register driver, all slots have been taken") } -// WrapDriver takes a SQL driver and wraps it with OTel instrumentation. +// WrapDriver принимает SQL driver и обороачивает с помощью инструментария OTel. func WrapDriver(dri driver.Driver, options ...Option) driver.Driver { return newDriver(dri, newConfig(options...)) } -// Open is a wrapper over sql.Open with OTel instrumentation. +// Open это обёртка над sql.Open, реализованная с помощью инструментария OTel. func Open(driverName, dataSourceName string, options ...Option) (*sql.DB, error) { - // Retrieve the driver implementation we need to wrap with instrumentation + // Извлечение реализации driver для последующей обёртки с помощью инстрементария. db, err := sql.Open(driverName, "") if err != nil { return nil, err @@ -99,7 +99,7 @@ func Open(driverName, dataSourceName string, options ...Option) (*sql.DB, error) return sql.OpenDB(dsnConnector{dsn: dataSourceName, driver: otDriver}), nil } -// OpenDB is a wrapper over sql.OpenDB with OTel instrumentation. +// OpenDB это обёртка над sql.OpenDB, реализованная с помощью инструментария OTel. func OpenDB(c driver.Connector, options ...Option) *sql.DB { d := newOtDriver(c.Driver(), newConfig(options...)) connector := newConnector(c, d) @@ -107,7 +107,7 @@ func OpenDB(c driver.Connector, options ...Option) *sql.DB { return sql.OpenDB(connector) } -// RegisterDBStatsMetrics register sql.DBStats metrics with OTel instrumentation. +// RegisterDBStatsMetrics регистрирует sql.DBStats metrics с помощью инструментария OTel. func RegisterDBStatsMetrics(db *sql.DB, opts ...Option) error { cfg := newConfig(opts...) meter := cfg.Meter diff --git a/stmt.go b/stmt.go index 32f0f00..6acbf57 100644 --- a/stmt.go +++ b/stmt.go @@ -64,7 +64,7 @@ func (s *otStmt) ExecContext( return execer.ExecContext(ctx, args) } - // StmtExecContext.ExecContext is not permitted to return ErrSkip. fall back to Exec. + // StmtExecContext.ExecContext не разрешено возвращать ErrSkip. вернуться к Exec. var dargs []driver.Value if dargs, err = namedValueToValue(args); err != nil { return nil, err @@ -103,7 +103,7 @@ func (s *otStmt) QueryContext( return nil, err } } else { - // StmtQueryContext.QueryContext is not permitted to return ErrSkip. fall back to Query. + // StmtQueryContext.QueryContext не разрешено возвращать ErrSkip. вернуться to Query. var dargs []driver.Value if dargs, err = namedValueToValue(args); err != nil { return nil, err