{"id":15017952,"url":"https://github.com/nhatthm/otelsql","last_synced_at":"2025-04-04T10:04:21.443Z","repository":{"id":37407085,"uuid":"452392676","full_name":"nhatthm/otelsql","owner":"nhatthm","description":"OpenTelemetry SQL database driver wrapper for Go","archived":false,"fork":false,"pushed_at":"2025-03-25T16:06:44.000Z","size":1318,"stargazers_count":116,"open_issues_count":8,"forks_count":11,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T09:03:29.557Z","etag":null,"topics":["database","driver","go","golang","metrics","mysql","opencensus","opentelemetry","opentelemetry-contrib","opentelemetry-go","opentracing","otel","otelsql","postgres","sql","tracing","wrapper"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nhatthm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"nhatthm","custom":"donate.nhat.me"}},"created_at":"2022-01-26T18:27:43.000Z","updated_at":"2025-03-25T02:08:22.000Z","dependencies_parsed_at":"2023-02-04T07:31:39.637Z","dependency_job_id":"aab733b4-22f2-4f2e-b757-cddb19255817","html_url":"https://github.com/nhatthm/otelsql","commit_stats":{"total_commits":237,"total_committers":11,"mean_commits":"21.545454545454547","dds":0.4177215189873418,"last_synced_commit":"fa4741153f08af9b6d6d5f6ab6cf40442c31e741"},"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nhatthm%2Fotelsql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nhatthm%2Fotelsql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nhatthm%2Fotelsql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nhatthm%2Fotelsql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nhatthm","download_url":"https://codeload.github.com/nhatthm/otelsql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247152634,"owners_count":20892529,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","driver","go","golang","metrics","mysql","opencensus","opentelemetry","opentelemetry-contrib","opentelemetry-go","opentracing","otel","otelsql","postgres","sql","tracing","wrapper"],"created_at":"2024-09-24T19:51:14.726Z","updated_at":"2025-04-04T10:04:21.415Z","avatar_url":"https://github.com/nhatthm.png","language":"Go","readme":"# OpenTelemetry SQL database driver wrapper for Go\n\n[![GitHub Releases](https://img.shields.io/github/v/release/nhatthm/otelsql)](https://github.com/nhatthm/otelsql/releases/latest)\n[![Build Status](https://github.com/nhatthm/otelsql/actions/workflows/test-unit.yaml/badge.svg?branch=master)](https://github.com/nhatthm/otelsql/actions/workflows/test-unit.yaml)\n[![codecov](https://codecov.io/gh/nhatthm/otelsql/branch/master/graph/badge.svg?token=eTdAgDE2vR)](https://codecov.io/gh/nhatthm/otelsql)\n[![Go Report Card](https://goreportcard.com/badge/go.nhat.io/otelsql)](https://goreportcard.com/report/go.nhat.io/otelsql)\n[![GoDevDoc](https://img.shields.io/badge/dev-doc-00ADD8?logo=go)](https://pkg.go.dev/go.nhat.io/otelsql)\n[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](http://donate.nhat.me)\n\nAdd a OpenTelemetry wrapper to your existing database code to instrument the interactions with the database. The wrapper supports both traces and metrics.\n\n## Table of Contents\n\n- [Prerequisites](#prerequisites)\n- [Install](#install)\n- [Usage](#usage)\n    - [Options](#options)\n- [Extras](#extras)\n    - [Span Name Formatter](#span-name-formatter)\n    - [Convert Error to Span Status](#convert-error-to-span-status)\n    - [Trace Query](#trace-query)\n    - [AllowRoot() and Span Context](#allowroot-and-span-context)\n    - [`jmoiron/sqlx`](#jmoironsqlx)\n- [Metrics](#metrics)\n    - [Client](#client-metrics)\n    - [Database Connection](#database-connection-metrics)\n- [Traces](#traces)\n- [Migration from `ocsql`](#migration-from-ocsql)\n    - [Options](#options-1)\n    - [Metrics](#metrics-1)\n    - [Traces](#traces-1)\n- [Compatibility](#compatibility)\n\n## Prerequisites\n\n- `Go \u003e= 1.22`\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n## Install\n\n\u003e ⚠️ From `v0.5.0`, the project is rebranded to `go.nhat.io/otelsql`. `v0.4.x` is the last version with `github.com/nhatthm/otelsql`.\n\n```bash\ngo get go.nhat.io/otelsql\n```\n\nCompatibility\n\n|          `otelsql`          | `go.opentelemetry.io/otel/trace` | `go.opentelemetry.io/otel/metric` |\n|:---------------------------:|:--------------------------------:|:---------------------------------:|\n| `v0.15.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e |      `v1.33.0` ~\u003e `latest`       |       `v1.33.0` ~\u003e `latest`       |\n| `v0.14.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e |      `v1.30.0` ~\u003e `latest`       |       `v1.30.0` ~\u003e `latest`       |\n| `v0.13.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e |      `v1.24.0` ~\u003e `latest`       |       `v1.24.0` ~\u003e `latest`       |\n| `v0.12.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e |      `v1.17.0` ~\u003e `latest`       |       `v0.40.0` ~\u003e `latest`       |\n| `v0.11.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e |      `v1.16.0` ~\u003e `latest`       |       `v0.39.0` ~\u003e `latest`       |\n| `v0.10.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e |      `v1.15.0` ~\u003e `latest`       |             `v0.38.*`             |\n| `v0.9.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e  |      `v1.14.0` ~\u003e `latest`       |             `v0.37.*`             |\n| `v0.8.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e  |      `v1.12.0` ~\u003e `latest`       |      `v0.35.0` ~\u003e `v0.36.*`       |\n| `v0.7.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e  |      `v1.11.1` ~\u003e `latest`       |      `v0.33.0` ~\u003e `v0.34.*`       |\n| `v0.6.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e  |      `v1.10.0` ~\u003e `latest`       |             `v0.32.*`             |\n| `v0.5.*` \u003csup\u003e\u0026nbsp;\u003c/sup\u003e  |      `v1.10.0` ~\u003e `latest`       |             `v0.31.*`             |\n|    `v0.4.*` \u003csup\u003e1\u003c/sup\u003e    |       `v1.9.0` ~\u003e `latest`       |             `v0.31.*`             |\n|    `v0.3.*` \u003csup\u003e1\u003c/sup\u003e    |       `v1.7.0` ~\u003e `latest`       |      `v0.28.0` ~\u003e `v0.30.*`       |\n|    `v0.2.*` \u003csup\u003e1\u003c/sup\u003e    |       `v1.6.2` ~\u003e `latest`       |      `v0.28.0` ~\u003e `v0.30.*`       |\n|    `v0.1.*` \u003csup\u003e1\u003c/sup\u003e    |       `v1.4.1` ~\u003e `latest`       |      `v0.26.0` ~\u003e `v0.27.*`       |\n\n\u003csup\u003e1\u003c/sup\u003e Old versions were shipped under `github.com/nhatthm/otelsql`. Use `go get github.com/nhatthm/otelsql` instead.\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n## Usage\n\nTo use `otelsql` with your application, register the `otelsql` wrapper by using `otelsql.Register(driverName string, opts ...otelsql.DriverOption)`. For\nexample:\n\n```go\npackage example\n\nimport (\n\t\"database/sql\"\n\n\t\"go.nhat.io/otelsql\"\n\tsemconv \"go.opentelemetry.io/otel/semconv/v1.20.0\"\n)\n\nfunc openDB(dsn string) (*sql.DB, error) {\n\t// Register the otelsql wrapper for the provided postgres driver.\n\tdriverName, err := otelsql.Register(\"postgres\",\n\t\totelsql.AllowRoot(),\n\t\totelsql.TraceQueryWithoutArgs(),\n\t\totelsql.TraceRowsClose(),\n\t\totelsql.TraceRowsAffected(),\n\t\totelsql.WithDatabaseName(\"my_database\"),        // Optional.\n\t\totelsql.WithSystem(semconv.DBSystemPostgreSQL), // Optional.\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Connect to a Postgres database using the postgres driver wrapper.\n\treturn sql.Open(driverName, dsn)\n}\n```\n\nThe wrapper will automatically instrument the interactions with the database.\n\nOptionally, you could record [database connection metrics](#database-connection-metrics) using the `otelsql.RecordStats()`. For example:\n\n```go\npackage example\n\nimport (\n\t\"database/sql\"\n\n\t\"go.nhat.io/otelsql\"\n\tsemconv \"go.opentelemetry.io/otel/semconv/v1.20.0\"\n)\n\nfunc openDB(dsn string) (*sql.DB, error) {\n\t// Register the otelsql wrapper for the provided postgres driver.\n\tdriverName, err := otelsql.Register(\"postgres\",\n\t\totelsql.AllowRoot(),\n\t\totelsql.TraceQueryWithoutArgs(),\n\t\totelsql.TraceRowsClose(),\n\t\totelsql.TraceRowsAffected(),\n\t\totelsql.WithDatabaseName(\"my_database\"),        // Optional.\n\t\totelsql.WithSystem(semconv.DBSystemPostgreSQL), // Optional.\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Connect to a Postgres database using the postgres driver wrapper.\n\tdb, err := sql.Open(driverName, dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := otelsql.RecordStats(db); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn db, nil\n}\n```\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Options\n\n**Driver Options**\n\n| Option                                         | Description                                                                                                                                                                                                                                                                                       |\n|:-----------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `WithMeterProvider(metric.MeterProvider)`      | Specify a meter provider                                                                                                                                                                                                                                                                          |\n| `WithTracerProvider(trace.TracerProvider)`     | Specify a tracer provider                                                                                                                                                                                                                                                                         |\n| `WithDefaultAttributes(...attribute.KeyValue)` | Add extra attributes for the recorded spans and metrics                                                                                                                                                                                                                                           |\n| `WithInstanceName(string)`                     | Add an extra attribute for annotating the instance name                                                                                                                                                                                                                                           |\n| `WithSystem(attribute.KeyValue)`               | Add an extra attribute for annotating the type of database server.\u003cbr/\u003e The value is set by using the well-known identifiers in `semconv`. For example: `semconv.DBSystemPostgreSQL`. See [more](https://github.com/open-telemetry/opentelemetry-go/blob/main/semconv/v1.12.0/trace.go#L102-L107) |\n| `WithDatabaseName(string)`                     | Add an extra attribute for annotating the database name                                                                                                                                                                                                                                           |\n| `WithSpanNameFormatter(spanNameFormatter)`     | Set a custom [span name formatter](#span-name-formatter)                                                                                                                                                                                                                                          |\n| `ConvertErrorToSpanStatus(errorToSpanStatus)`  | Set a custom [converter for span status](#convert-error-to-span-status)                                                                                                                                                                                                                           |\n| `DisableErrSkip()`                             | `sql.ErrSkip` is considered as `OK` in span status                                                                                                                                                                                                                                                |\n| `TraceQuery()`                                 | Set a custom function for [tracing query](#trace-query)                                                                                                                                                                                                                                           |\n| `TraceQueryWithArgs()`                         | [Trace query](#trace-query) and all arguments                                                                                                                                                                                                                                                     |\n| `TraceQueryWithoutArgs()`                      | [Trace query](#trace-query) without the arguments                                                                                                                                                                                                                                                 |\n| `AllowRoot()`                                  | Create root spans in absence of existing spans or even context                                                                                                                                                                                                                                    |\n| `TracePing()`                                  | Enable the creation of spans on Ping requests                                                                                                                                                                                                                                                     |\n| `TraceRowsNext()`                              | Enable the creation of spans on RowsNext calls. (This can result in many spans)                                                                                                                                                                                                                   |\n| `TraceRowsClose()`                             | Enable the creation of spans on RowsClose calls                                                                                                                                                                                                                                                   |\n| `TraceRowsAffected()`                          | Enable the creation of spans on RowsAffected calls                                                                                                                                                                                                                                                |\n| `TraceLastInsertID()`                          | Enable the creation of spans on LastInsertId call                                                                                                                                                                                                                                                 |\n| `TraceAll()`                                   | Turn on all tracing options, including `AllowRoot()` and `TraceQueryWithArgs()`                                                                                                                                                                                                                   |\n\n**Record Stats Options**\n\n| Option                                          | Description                                                                                                                                                                                                                                                                                       |\n|:------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `WithMeterProvider(metric.MeterProvider)`       | Specify a meter provider                                                                                                                                                                                                                                                                          |\n| `WithMinimumReadDBStatsInterval(time.Duration)` | The minimum interval between calls to db.Stats(). Negative values are ignored.                                                                                                                                                                                                                    |\n| `WithDefaultAttributes(...attribute.KeyValue)`  | Add extra attributes for the recorded metrics                                                                                                                                                                                                                                                     |\n| `WithInstanceName(string)`                      | Add an extra attribute for annotating the instance name                                                                                                                                                                                                                                           |\n| `WithSystem(attribute.KeyValue)`                | Add an extra attribute for annotating the type of database server.\u003cbr/\u003e The value is set by using the well-known identifiers in `semconv`. For example: `semconv.DBSystemPostgreSQL`. See [more](https://github.com/open-telemetry/opentelemetry-go/blob/main/semconv/v1.12.0/trace.go#L102-L107) |\n| `WithDatabaseName(string)`                      | Add an extra attribute for annotating the database name                                                                                                                                                                                                                                           |\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n## Extras\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Span Name Formatter\n\nBy default, spans will be created with the `sql:METHOD` format, like `sql:exec` or `sql:query`. You could change this behavior by using\nthe `WithSpanNameFormatter()` option and set your own logic.\n\nFor example\n\n```go\npackage example\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\n\t\"go.nhat.io/otelsql\"\n)\n\nfunc openDB(dsn string) (*sql.DB, error) {\n\tdriverName, err := otelsql.Register(\"my-driver\",\n\t\totelsql.WithSpanNameFormatter(func(_ context.Context, op string) string {\n\t\t\treturn \"main-db:\" + op\n\t\t}),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sql.Open(driverName, dsn)\n}\n```\n\nWith traces of `ExecContext()` and `QueryContext()` (either `DB`, `Stmt`, or `Tx`), you could get the SQL query from the context\nusing `otelsql.QueryFromContext()`. For example:\n\n```go\npackage example\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\n\t\"go.nhat.io/otelsql\"\n)\n\nfunc openDB(dsn string) (*sql.DB, error) {\n\tdriverName, err := otelsql.Register(\"my-driver\",\n\t\totelsql.WithSpanNameFormatter(func(ctx context.Context, op string) string {\n\t\t\tif op != \"exec\" {\n\t\t\t\treturn \"main-db:\" + op\n\t\t\t}\n\n\t\t\tquery := otelsql.QueryFromContext(ctx)\n\n\t\t\t// Make span name from the query here and return.\n\t\t}),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sql.Open(driverName, dsn)\n}\n```\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Convert Error to Span Status\n\nBy default, all errors are considered as `ERROR` while setting span status, except `io.EOF` on RowsNext calls (which is `OK`). `otelsql` also provides an extra\noption `DisableErrSkip()` if you want to ignore the `sql.ErrSkip`.\n\nYou can write your own conversion by using the `ConvertErrorToSpanStatus()` option. For example\n\n```go\npackage example\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\n\t\"go.nhat.io/otelsql\"\n\t\"go.opentelemetry.io/otel/codes\"\n)\n\nfunc openDB(dsn string) (*sql.DB, error) {\n\tdriverName, err := otelsql.Register(\"my-driver\",\n\t\totelsql.ConvertErrorToSpanStatus(func(err error) (codes.Code, string) {\n\t\t\tif err == nil || errors.Is(err, ignoredError) {\n\t\t\t\treturn codes.Ok, \"\"\n\t\t\t}\n\n\t\t\treturn codes.Error, err.Error()\n\t\t}),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sql.Open(driverName, dsn)\n}\n```\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Trace Query\n\nBy default, `otelsql` does not trace query and arguments. When you use these options:\n\n- `TraceQueryWithArgs()`: Trace the query and all arguments.\n- `TraceQueryWithoutArgs()`: Trace only the query, without the arguments.\n\nThe traced query will be set in the `semconv.DBStatementKey` attribute (`db.statement`) and the arguments are set as follows:\n\n- `db.sql.args.NAME`: if the arguments are named.\n- `db.sql.args.ORDINAL`: Otherwise.\n\nExample #1:\n\n```sql\nSELECT *\nFROM data\nWHERE country = :country\n```\n\nThe argument attribute will be `db.sql.args.country`\n\nExample #2:\n\n```sql\nSELECT *\nFROM data\nWHERE country = $1\n```\n\nThe argument attribute will be `db.sql.args.1`\n\nYou can change this behavior for your own purpose (like, redaction or stripping out sensitive information) by using the `TraceQuery()` option. For example:\n\n```go\npackage example\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\n\t\"go.nhat.io/otelsql\"\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/codes\"\n\tsemconv \"go.opentelemetry.io/otel/semconv/v1.20.0\"\n)\n\nfunc openDB(dsn string) (*sql.DB, error) {\n\tdriverName, err := otelsql.Register(\"my-driver\",\n\t\totelsql.TraceQuery(func(_ context.Context, query string, args []driver.NamedValue) []attribute.KeyValue {\n\t\t\tattrs := make([]attribute.KeyValue, 0, 1+len(args))\n\n\t\t\tattrs = append(attrs, semconv.DBStatementKey.String(query))\n\n\t\t\t// Your redaction goes here.\n\n\t\t\treturn attrs\n\t\t}),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sql.Open(driverName, dsn)\n}\n```\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### AllowRoot() and Span Context\n\nTo fully take advantage of `otelsql`, all database calls should be made using the `*Context` methods. Failing to do so will result in many orphaned traces if\nthe `AllowRoot()` is used. By default, `AllowRoot()` is disabled and will result in `otelsql` not tracing the database calls if context or parent spans are\nmissing.\n\n| Old              | New                     |\n|:-----------------|:------------------------|\n| `*DB.Begin`      | `*DB.BeginTx`           |\n| `*DB.Exec`       | `*DB.ExecContext`       |\n| `*DB.Ping`       | `*DB.PingContext`       |\n| `*DB.Prepare`    | `*DB.PrepareContext`    |\n| `*DB.Query`      | `*DB.QueryContext`      |\n| `*DB.QueryRow`   | `*DB.QueryRowContext`   |\n|                  |                         |\n| `*Stmt.Exec`     | `*Stmt.ExecContext`     |\n| `*Stmt.Query`    | `*Stmt.QueryContext`    |\n| `*Stmt.QueryRow` | `*Stmt.QueryRowContext` |\n|                  |                         |\n| `*Tx.Exec`       | `*Tx.ExecContext`       |\n| `*Tx.Prepare`    | `*Tx.PrepareContext`    |\n| `*Tx.Query`      | `*Tx.QueryContext`      |\n| `*Tx.QueryRow`   | `*Tx.QueryRowContext`   |\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### `jmoiron/sqlx`\n\nIf using the `jmoiron/sqlx` library with named queries you will need to use the `sqlx.NewDb` function to wrap an existing `*sql.DB` connection. Do not use the\n`sqlx.Open` and `sqlx.Connect` methods. `jmoiron/sqlx` uses the driver name to figure out which database is being used. It uses this knowledge to convert named\nqueries to the correct bind type (dollar sign, question mark) if named queries are not supported natively by the database. Since `otelsql` creates a new driver\nname it will not be recognized by `jmoiron/sqlx` and named queries will fail.\n\nFor example:\n\n```go\npackage example\n\nimport (\n\t\"database/sql\"\n\n\t\"github.com/jmoiron/sqlx\"\n\t\"go.nhat.io/otelsql\"\n)\n\nfunc openDB(dsn string) (*sql.DB, error) {\n\tdriverName, err := otelsql.Register(\"my-driver\",\n\t\totelsql.AllowRoot(),\n\t\totelsql.TraceQueryWithoutArgs(),\n\t\totelsql.TraceRowsClose(),\n\t\totelsql.TraceRowsAffected(),\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdb, err := sql.Open(driverName, dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sqlx.NewDb(db, \"my-driver\"), nil\n}\n```\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n## Metrics\n\n**Attributes** *(applies to all the metrics below)*\n\n| Attribute       | Description             | Note                                                     |\n|:----------------|:------------------------|:---------------------------------------------------------|\n| `db_operation`  | The executed sql method | For example: `exec`, `query`, `prepare`                  |\n| `db_sql_status` | The execution status    | `OK` if no error, otherwise `ERROR`                      |\n| `db_sql_error`  | The error message       | When `status` is `ERROR`. The value is the error message |\n| `db_instance`   | The instance name       | Only when using `WithInstanceName()` option              |\n| `db_system`     | The system name         | Only when using `WithSystem()` option                    |\n| `db_name`       | The database name       | Only when using `WithDatabaseName()` option              |\n\n`WithDefaultAttributes(attrs ...attribute.KeyValue)` will also add the `attrs` to the recorded metrics.\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Client Metrics\n\n| Metric                                                                                      | Description                         |\n|:--------------------------------------------------------------------------------------------|:------------------------------------|\n| `db_sql_client_calls{db_instance,db_operation,db_sql_status,db_system,db_name}`             | Number of Calls (Counter)           |\n| `db_sql_client_latency_bucket{db_instance,db_operation,db_sql_status,db_system,db_name,le}` | Latency in milliseconds (Histogram) |\n| `db_sql_client_latency_sum{db_instance,db_operation,db_sql_status,db_system,db_name}`       |                                     |\n| `db_sql_client_latency_count{db_instance,db_operation,db_sql_status,db_system,db_name}`     |                                     |\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Database Connection Metrics\n\n| Metric                                                              | Description                                                |\n|:--------------------------------------------------------------------|:-----------------------------------------------------------|\n| `db_sql_connections_active{db_instance,db_system,db_name}`          | Number of active connections                               |\n| `db_sql_connections_idle{db_instance,db_system,db_name}`            | Number of idle connections                                 |\n| `db_sql_connections_idle_closed{db_instance,db_system,db_name}`     | Total number of closed connections by `SetMaxIdleConns`    |\n| `db_sql_connections_lifetime_closed{db_instance,db_system,db_name}` | Total number of closed connections by `SetConnMaxLifetime` |\n| `db_sql_connections_open{db_instance,db_system,db_name}`            | Number of open connections                                 |\n| `db_sql_connections_wait_count{db_instance,db_system,db_name}`      | Total number of connections waited for                     |\n| `db_sql_connections_wait_duration{db_instance,db_system,db_name}`   | Total time blocked waiting for new connections             |\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n## Traces\n\n| Operation               | Trace                                         |\n|:------------------------|:----------------------------------------------|\n| `*DB.BeginTx`           | Always                                        |\n| `*DB.ExecContext`       | Always                                        |\n| `*DB.PingContext`       | Disabled. Use `TracePing()` to enable         |\n| `*DB.PrepareContext`    | Always                                        |\n| `*DB.QueryContext`      | Always                                        |\n| `*DB.QueryRowContext`   | Always                                        |\n|                         |                                               |\n| `*Stmt.ExecContext`     | Always                                        |\n| `*Stmt.QueryContext`    | Always                                        |\n| `*Stmt.QueryRowContext` | Always                                        |\n|                         |                                               |\n| `*Tx.ExecContext`       | Always                                        |\n| `*Tx.PrepareContext`    | Always                                        |\n| `*Tx.QueryContext`      | Always                                        |\n| `*Tx.QueryRowContext`   | Always                                        |\n|                         |                                               |\n| `*Rows.Next`            | Disabled. Use `TraceRowsNext()` to enable     |\n| `*Rows.Close`           | Disabled. Use `TraceRowsClose()` to enable    |\n|                         |                                               |\n| `*Result.LastInsertID`  | Disabled. Use `TraceLastInsertID()` to enable |\n| `*Result.RowsAffected`  | Disabled. Use `TraceRowsAffected()` to enable |\n\n`ExecContext`, `QueryContext`, `QueryRowContext`, `PrepareContext` are always traced without query args unless using `TraceQuery()`, `TraceQueryWithArgs()`,\nor `TraceQueryWithoutArgs()` option.\n\nUsing `WithDefaultAttributes(...attribute.KeyValue)` will add extra attributes to the recorded spans.\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n## Migration from `ocsql`\n\nThe migration is easy because the behaviors of `otelsql` are the same as `ocsql`, and all options are almost similar.\n\n|                             | `ocsql`                                               | `otelsql`                                              |\n|:----------------------------|:------------------------------------------------------|:-------------------------------------------------------|\n| Register driver wrapper     | `Register(driverName string, options ...TraceOption)` | `Register(driverName string, options ...DriverOption)` |\n| Records database statistics | `RecordStats(db *sql.DB, interval time.Duration)`     | `RecordStats(db *sql.DB, opts ...StatsOption)`         |\n\nThe `interval` in `RecordStats()` is replaced with `WithMinimumReadDBStatsInterval(time.Duration)` option.\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Options\n\n| `ocsql`                                         | `otelsql`                                                             |\n|:------------------------------------------------|:----------------------------------------------------------------------|\n| `WithAllTraceOptions()`                         | `TraceAll()` \u003cbr/\u003e \u003csub\u003e`otelsql` always set to `true`\u003c/sub\u003e          |\n| `WithOptions(ocsql.TraceOptions)`               | *Dropped*                                                             |\n| `WithAllowRoot(bool)`                           | `AllowRoot()` \u003cbr/\u003e \u003csub\u003e`otelsql` always set to `true`\u003c/sub\u003e         |\n| `WithPing(bool)`                                | `TracePing()` \u003cbr/\u003e \u003csub\u003e`otelsql` always set to `true`\u003c/sub\u003e         |\n| `WithRowsNext(bool)`                            | `TraceRowsNext()` \u003cbr/\u003e \u003csub\u003e`otelsql` always set to `true`\u003c/sub\u003e     |\n| `WithRowsClose(bool)`                           | `TraceRowsClose()` \u003cbr/\u003e \u003csub\u003e`otelsql` always set to `true`\u003c/sub\u003e    |\n| `WithRowsAffected(bool)`                        | `TraceRowsAffected()` \u003cbr/\u003e \u003csub\u003e`otelsql` always set to `true`\u003c/sub\u003e |\n| `WithLastInsertID(bool)`                        | `TraceLastInsertID()` \u003cbr/\u003e \u003csub\u003e`otelsql` always set to `true`\u003c/sub\u003e |\n| `WithQuery(bool)` \u003cbr/\u003e `WithQueryParams(bool)` | `TraceQueryWithArgs()` \u003cbr/\u003e `TraceQueryWithoutArgs()`                |\n| `WithDefaultAttributes(...trace.Attribute)`     | `WithDefaultAttributes(...attribute.KeyValue)`                        |\n| `WithDisableErrSkip(bool)`                      | `DisableErrSkip()`                                                    |\n| `WithSampler(trace.Sampler)`                    | *Dropped*                                                             |\n| `WithInstanceName(string)`                      | `WithInstanceName(string)`                                            |\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Metrics\n\n**Attributes** *(applies to all the metrics below)*\n\n| `ocsql`           | `otelsql`       | Note                                        |\n|:------------------|:----------------|:--------------------------------------------|\n| `go_sql_instance` | `db_instance`   | Only when using `WithInstanceName()` option |\n| `go_sql_method`   | `db_operation`  |                                             |\n| `go_sql_status`   | `db_sql_status` |                                             |\n| n/a               | `db_system`     | Only when using `WithSystem()` option       |\n| n/a               | `db_name`       | Only when using `WithDatabaseName()` option |\n\n**Client Metrics**\n\n| `ocsql`                                                                        | `otelsql`                                                                                   |\n|:-------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------|\n| `go_sql_client_calls{go_sql_instance,go_sql_method,go_sql_status}`             | `db_sql_client_calls{db_instance,db_operation,db_sql_status,db_system,db_name}`             |\n| `go_sql_client_latency_bucket{go_sql_instance,go_sql_method,go_sql_status,le}` | `db_sql_client_latency_bucket{db_instance,db_operation,db_sql_status,db_system,db_name,le}` |\n| `go_sql_client_latency_sum{go_sql_instance,go_sql_method,go_sql_status}`       | `db_sql_client_latency_sum{db_instance,db_operation,db_sql_status,db_system,db_name}`       |\n| `go_sql_client_latency_count{go_sql_instance,go_sql_method,go_sql_status}`     | `db_sql_client_latency_count{db_instance,db_operation,db_sql_status,db_system,db_name}`     |\n\n**Connection Metrics**\n\n| `ocsql`                                                        | `otelsql`                                                                            |\n|:---------------------------------------------------------------|:-------------------------------------------------------------------------------------|\n| `go_sql_db_connections_active{go_sql_instance}`                | `db_sql_connections_active_ratio{db_instance,db_system,db_name}`                     |\n| `go_sql_db_connections_idle{go_sql_instance}`                  | `db_sql_connections_idle_ratio{db_instance,db_system,db_name}`                       |\n| `go_sql_db_connections_idle_closed_count{go_sql_instance}`     | `db_sql_connections_idle_closed_ratio_total{db_instance,db_system,db_name}`          |\n| `go_sql_db_connections_lifetime_closed_count{go_sql_instance}` | `db_sql_connections_lifetime_closed_ratio_total{db_instance,db_system,db_name}`      |\n| `go_sql_db_connections_open{go_sql_instance}`                  | `db_sql_connections_open_ratio{db_instance,db_system,db_name}`                       |\n| `go_sql_db_connections_wait_count{go_sql_instance}`            | `db_sql_connections_wait_count_ratio_total{db_instance,db_system,db_name}`           |\n| `go_sql_db_connections_wait_duration{go_sql_instance}`         | `db_sql_connections_wait_duration_milliseconds_total{db_instance,db_system,db_name}` |\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Traces\n\nThe traces are almost identical with some minor changes:\n\n1. Named arguments are not just recorder as `\u003cNAME\u003e` in the span. They are now `db.sql.args.\u003cNAME\u003e`.\n2. `sql.query` is now `db.statement`.\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n## Compatibility\n\n\u003ctable\u003e\n    \u003cthead\u003e\n        \u003ctr\u003e\n            \u003cth colspan=\"2\"\u003e\u003c/th\u003e\n            \u003cth colspan=\"6\"\u003eOS\u003c/th\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003cth rowspan=\"2\"\u003eDriver\u003c/th\u003e\n            \u003cth rowspan=\"2\"\u003eDatabase\u003c/th\u003e\n            \u003cth colspan=\"2\"\u003eUbuntu\u003c/th\u003e\n            \u003cth colspan=\"2\"\u003eMacOS\u003c/th\u003e\n            \u003cth colspan=\"2\"\u003eWindows\u003c/th\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003cth\u003ego 1.22\u003c/th\u003e\n            \u003cth\u003ego 1.23\u003c/th\u003e\n            \u003cth\u003ego 1.22\u003c/th\u003e\n            \u003cth\u003ego 1.23\u003c/th\u003e\n            \u003cth\u003ego 1.22\u003c/th\u003e\n            \u003cth\u003ego 1.23\u003c/th\u003e\n        \u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody\u003e\n        \u003ctr\u003e\n            \u003ctd colspan=\"2\"\u003e\n                \u003ccode style=\"white-space: nowrap\"\u003eDATA-DOG/go-sqlmock\u003c/code\u003e\n            \u003c/td\u003e\n            \u003ctd colspan=\"6\" align=\"center\"\u003e\n                \u003ca href=\"https://github.com/nhatthm/otelsql/actions/workflows/test-unit.yaml\"\u003e\n                    \u003cimg\n                        src=\"https://github.com/nhatthm/otelsql/actions/workflows/test-unit.yaml/badge.svg?branch=master\" alt=\"Build Status\"\n                        style=\"max-width: 100%;\"\u003e\n                \u003c/a\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd colspan=\"2\"\u003e\n                \u003ccode style=\"white-space: nowrap\"\u003ejmoiron/sqlx\u003c/code\u003e\n            \u003c/td\u003e\n            \u003ctd colspan=\"6\" align=\"center\"\u003e\n                \u003cimg src=\"https://img.shields.io/badge/manual%20test-passing-brightgreen?labelColor=3F4750\u0026logo=target\u0026logoWidth=10\u0026logoColor=959DA5\u0026color=31C754\" alt=\"Manually Tested\"\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e\n                \u003ccode style=\"white-space: nowrap\"\u003edenisenkom/go-mssqldb\u003c/code\u003e\n            \u003c/td\u003e\n            \u003ctd style=\"white-space: nowrap\"\u003e\n                SQL Server 2019\n            \u003c/td\u003e\n            \u003ctd colspan=\"6\" align=\"center\"\u003e\n                \u003ca href=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-mssql.yaml\"\u003e\n                    \u003cimg\n                        src=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-mssql.yaml/badge.svg?branch=master\" alt=\"Build Status\"\n                        style=\"max-width: 100%;\"\u003e\n                \u003c/a\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e\n                \u003ccode style=\"white-space: nowrap\"\u003ego-sql-driver/mysql\u003c/code\u003e\n            \u003c/td\u003e\n            \u003ctd style=\"white-space: nowrap\"\u003e\n                MySQL 8\n            \u003c/td\u003e\n            \u003ctd colspan=\"6\" align=\"center\"\u003e\n                \u003ca href=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-mysql.yaml\"\u003e\n                    \u003cimg\n                        src=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-mysql.yaml/badge.svg?branch=master\" alt=\"Build Status\"\n                        style=\"max-width: 100%;\"\u003e\n                \u003c/a\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e\n                \u003ccode style=\"white-space: nowrap\"\u003ejackc/pgx/v4/stdlib\u003c/code\u003e\n            \u003c/td\u003e\n            \u003ctd style=\"white-space: nowrap\"\u003e\n                Postgres 13, 14, 15, 16, 17\n            \u003c/td\u003e\n            \u003ctd colspan=\"6\" align=\"center\"\u003e\n                \u003ca href=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-pgx.yaml\"\u003e\n                    \u003cimg\n                        src=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-pgx.yaml/badge.svg?branch=master\" alt=\"Build Status\"\n                        style=\"max-width: 100%;\"\u003e\n                \u003c/a\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e\n                \u003ccode style=\"white-space: nowrap\"\u003ejackc/pgx/v5/stdlib\u003c/code\u003e\n            \u003c/td\u003e\n            \u003ctd style=\"white-space: nowrap\"\u003e\n                Postgres 13, 14, 15, 16, 17\n            \u003c/td\u003e\n            \u003ctd colspan=\"6\" align=\"center\"\u003e\n                \u003ca href=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-pgx.yaml\"\u003e\n                    \u003cimg\n                        src=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-pgx.yaml/badge.svg?branch=master\" alt=\"Build Status\"\n                        style=\"max-width: 100%;\"\u003e\n                \u003c/a\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003e\n                \u003ccode style=\"white-space: nowrap\"\u003elib/pq\u003c/code\u003e\n            \u003c/td\u003e\n            \u003ctd style=\"white-space: nowrap\"\u003e\n                Postgres 13, 14, 15, 16, 17\n            \u003c/td\u003e\n            \u003ctd colspan=\"6\" align=\"center\"\u003e\n                \u003ca href=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-libpq.yaml\"\u003e\n                    \u003cimg\n                        src=\"https://github.com/nhatthm/otelsql/actions/workflows/test-compatibility-libpq.yaml/badge.svg?branch=master\" alt=\"Build Status\"\n                        style=\"max-width: 100%;\"\u003e\n                \u003c/a\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n    \u003ctbody\u003e\n\u003c/table\u003e\n\n\u003csub\u003e*If you don't see a driver in the list, it doesn't mean the wrapper is incompatible. it's just not tested. Let me know if it works with your driver*\u003c/sub\u003e\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n## Donation\n\nIf this project help you reduce time to develop, you can give me a cup of coffee :)\n\n[\u003csub\u003e\u003csup\u003e[table of contents]\u003c/sup\u003e\u003c/sub\u003e](#table-of-contents)\n\n### Paypal donation\n\n[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](http://donate.nhat.me)\n\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;or scan this\n\n\u003cimg src=\"https://user-images.githubusercontent.com/1154587/113494222-ad8cb200-94e6-11eb-9ef3-eb883ada222a.png\" width=\"147px\" /\u003e\n","funding_links":["https://github.com/sponsors/nhatthm","donate.nhat.me"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnhatthm%2Fotelsql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnhatthm%2Fotelsql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnhatthm%2Fotelsql/lists"}