OpenTelemetry Go Ent monitoring [otelent]

Vladimir Mihailenco
March 25, 2026
7 min read

OpenTelemetry enables comprehensive monitoring of Ent applications by capturing database query execution times, connection pool metrics, and query patterns. By integrating OpenTelemetry with Ent, you can trace database operations across your distributed system and identify performance bottlenecks.

Quick Setup

StepActionCode/Command
1. InstallInstall otelsql packagego get github.com/uptrace/opentelemetry-go-extra/otelsql
2. ImportImport otelsqlimport "github.com/uptrace/opentelemetry-go-extra/otelsql"
3. Open DBOpen database with otelsql instead of sqldb, err := otelsql.Open("postgres", dsn, otelsql.WithDBName("mydb"))
4. Use driverCreate Ent client from instrumented DBdrv := entsql.OpenDB(dialect.Postgres, db)
5. VerifyCheck your observability backend for tracesTraces collected automatically

Minimal working example:

go
import (
    "log"

    "entgo.io/ent/dialect"
    entsql "entgo.io/ent/dialect/sql"
    "github.com/uptrace/opentelemetry-go-extra/otelsql"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"

    _ "github.com/lib/pq"
    "myapp/ent"
)

func main() {
    db, err := otelsql.Open("postgres", "host=localhost dbname=mydb sslmode=disable",
        otelsql.WithAttributes(semconv.DBSystemPostgreSQL),
        otelsql.WithDBName("mydb"),
    )
    if err != nil {
        log.Fatal(err)
    }

    drv := entsql.OpenDB(dialect.Postgres, db)
    client := ent.NewClient(ent.Driver(drv))
    defer client.Close()
}

This approach works because Ent uses database/sql under the hood — otelsql wraps the SQL driver to automatically capture every query as a span.

What is Ent?

Ent is a powerful entity framework for Go developed by Facebook. It provides a graph-based approach to modeling and querying data, generating type-safe Go code from schema definitions.

Key features of Ent include:

  • Schema as code - Define your data model using Go structs with full type safety
  • Code generation - Automatically generates CRUD operations, queries, and migrations
  • Graph traversal - Query relationships naturally using edge definitions
  • Database agnostic - Supports PostgreSQL, MySQL, SQLite, and Gremlin-based databases
  • Migration support - Built-in schema migration and versioning

Ent is commonly used in production systems that require complex data relationships, type safety, and high performance.

What is OpenTelemetry?

OpenTelemetry is an open-source observability framework that aims to standardize and simplify the collection, processing, and export of telemetry data from applications and systems.

OpenTelemetry supports multiple programming languages and platforms, making it suitable for a wide range of applications and environments.

OpenTelemetry enables developers to instrument their code and collect telemetry data, which can then be exported to various OpenTelemetry backends or observability platforms for analysis and visualization. The OpenTelemetry architecture provides a comprehensive framework with APIs, SDKs, and instrumentation libraries that integrate seamlessly with various application frameworks.

Configuration can be managed through OpenTelemetry environment variables, providing a standardized way to configure exporters, resource attributes, and sampling behavior across environments.

Ent instrumentation

Because Ent works on top of database/sql, you can use otelsql instrumentation with Ent:

shell
go get github.com/uptrace/opentelemetry-go-extra/otelsql

Basic usage

To instrument Ent database client, use OpenTelemetry database/sql instrumentation to patch the database driver you are using.

SQLite example

go
import (
    "context"
    "log"

    "entgo.io/ent/dialect"
    entsql "entgo.io/ent/dialect/sql"
    "github.com/uptrace/opentelemetry-go-extra/otelsql"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"

    "myapp/ent"
)

func NewClient() (*ent.Client, error) {
    // Open database with OpenTelemetry instrumentation
    db, err := otelsql.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1",
        otelsql.WithAttributes(semconv.DBSystemSqlite),
        otelsql.WithDBName("mydb"),
    )
    if err != nil {
        return nil, err
    }

    // Create Ent driver from instrumented database
    drv := entsql.OpenDB(dialect.SQLite, db)
    client := ent.NewClient(ent.Driver(drv))

    return client, nil
}

PostgreSQL example

go
import (
    "entgo.io/ent/dialect"
    entsql "entgo.io/ent/dialect/sql"
    "github.com/uptrace/opentelemetry-go-extra/otelsql"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"

    _ "github.com/lib/pq"
    "myapp/ent"
)

func NewClient() (*ent.Client, error) {
    dsn := "host=localhost port=5432 user=postgres dbname=mydb sslmode=disable"

    db, err := otelsql.Open("postgres", dsn,
        otelsql.WithAttributes(semconv.DBSystemPostgreSQL),
        otelsql.WithDBName("mydb"),
    )
    if err != nil {
        return nil, err
    }

    drv := entsql.OpenDB(dialect.Postgres, db)
    client := ent.NewClient(ent.Driver(drv))

    return client, nil
}

MySQL example

go
import (
    "entgo.io/ent/dialect"
    entsql "entgo.io/ent/dialect/sql"
    "github.com/uptrace/opentelemetry-go-extra/otelsql"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"

    _ "github.com/go-sql-driver/mysql"
    "myapp/ent"
)

func NewClient() (*ent.Client, error) {
    dsn := "user:password@tcp(localhost:3306)/mydb?parseTime=true"

    db, err := otelsql.Open("mysql", dsn,
        otelsql.WithAttributes(semconv.DBSystemMySQL),
        otelsql.WithDBName("mydb"),
    )
    if err != nil {
        return nil, err
    }

    drv := entsql.OpenDB(dialect.MySQL, db)
    client := ent.NewClient(ent.Driver(drv))

    return client, nil
}

otelsql configuration options

The otelsql instrumentation supports several configuration options:

OptionDescription
WithAttributesSet default attributes on all spans (e.g., semconv.DBSystem*)
WithDBNameSet the database name attribute on spans
WithDBSystemSet the database system attribute (alternative to semconv)
WithoutQueryVariablesExclude query parameters from span attributes (for sensitive data)
WithoutMetricsDisable automatic database metrics collection
WithTracerProviderUse a custom TracerProvider instead of the global one
WithMeterProviderUse a custom MeterProvider instead of the global one

Filtering sensitive data

If your queries contain sensitive data, strip query variables from spans:

go
db, err := otelsql.Open("postgres", dsn,
    otelsql.WithAttributes(semconv.DBSystemPostgreSQL),
    otelsql.WithDBName("mydb"),
    otelsql.WithoutQueryVariables(),
)

Adding custom spans

Add custom tracing to your Ent operations for finer-grained observability:

go
import (
    "context"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"

    "myapp/ent"
    "myapp/ent/user"
)

var tracer = otel.Tracer("myapp/repository")

func GetUserByEmail(ctx context.Context, client *ent.Client, email string) (*ent.User, error) {
    ctx, span := tracer.Start(ctx, "GetUserByEmail")
    defer span.End()

    span.SetAttributes(attribute.String("user.email", email))

    u, err := client.User.
        Query().
        Where(user.EmailEQ(email)).
        Only(ctx)

    if err != nil {
        span.RecordError(err)
        return nil, err
    }

    span.SetAttributes(attribute.Int("user.id", u.ID))
    return u, nil
}

Tracing Ent hooks

Use Ent hooks to add tracing around mutations:

go
import (
    "context"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"

    "myapp/ent"
    "myapp/ent/hook"
)

var tracer = otel.Tracer("myapp/ent")

func AddTracingHooks(client *ent.Client) {
    // Add hook for all mutations
    client.Use(func(next ent.Mutator) ent.Mutator {
        return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
            ctx, span := tracer.Start(ctx, "ent."+m.Type()+"."+m.Op().String())
            defer span.End()

            span.SetAttributes(
                attribute.String("ent.type", m.Type()),
                attribute.String("ent.operation", m.Op().String()),
            )

            v, err := next.Mutate(ctx, m)
            if err != nil {
                span.RecordError(err)
            }

            return v, err
        })
    })
}

Connection pool metrics

Monitor database connection pool health. otelsql can report database/sql connection pool statistics as OpenTelemetry metrics:

MetricDescription
db.sql.connections.openNumber of open connections
db.sql.connections.idleNumber of idle connections
db.sql.connections.activeNumber of active connections
db.sql.connections.wait_countTotal number of connections waited for
db.sql.connections.wait_durationTotal time blocked waiting for a connection
go
import (
    "time"

    "entgo.io/ent/dialect"
    entsql "entgo.io/ent/dialect/sql"
    "github.com/uptrace/opentelemetry-go-extra/otelsql"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"

    _ "github.com/lib/pq"
    "myapp/ent"
)

func NewClientWithMetrics() (*ent.Client, error) {
    db, err := otelsql.Open("postgres", dsn,
        otelsql.WithAttributes(semconv.DBSystemPostgreSQL),
        otelsql.WithDBName("mydb"),
    )
    if err != nil {
        return nil, err
    }

    // Configure connection pool
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)

    // Report connection pool metrics
    if err := otelsql.ReportDBStatsMetrics(db, otelsql.WithAttributes(
        semconv.DBSystemPostgreSQL,
    )); err != nil {
        return nil, err
    }

    drv := entsql.OpenDB(dialect.Postgres, db)
    return ent.NewClient(ent.Driver(drv)), nil
}

What is Uptrace?

Uptrace is an OpenTelemetry APM that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace Overview

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.

Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.

In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.

FAQ

Why use otelsql instead of native Ent tracing? Ent doesn't have built-in OpenTelemetry support, but since it uses database/sql under the hood, otelsql provides comprehensive query tracing without requiring changes to your Ent code. This also means you get the same instrumentation regardless of which Ent features you use — queries, mutations, graph traversals, and migrations are all captured.

What information is captured in traces? otelsql captures query text, execution duration, database system type, database name, and any errors. You can add custom attributes for business context. Each SQL query becomes a span with attributes like db.statement, db.system, and db.name.

How do I trace graph traversals? Graph traversals in Ent generate multiple SQL queries. Each query is traced individually by otelsql. To group related queries, wrap your Ent operations in a custom span (see the "Adding custom spans" section). This gives you a parent span that shows the total duration with child spans for each SQL query.

Does this work with Ent migrations? Yes, schema migrations executed through Ent are also traced since they use the same instrumented database connection. This is useful for monitoring migration duration and identifying slow schema changes.

How do I filter sensitive data from traces? Ent uses parameterized queries by default, so actual values appear as placeholders ($1, $2). For additional protection, use otelsql.WithoutQueryVariables() to strip all query variables from spans.

Can I use otelsql with Ent's transaction support? Yes, transactions work seamlessly. otelsql traces each query within the transaction individually. Wrap the entire transaction in a custom span to see the full transaction duration and all queries it contains.

What's the performance overhead? otelsql adds minimal overhead — typically a few microseconds per query for span creation. The connection pool metrics collection uses Go's built-in sql.DBStats and has negligible impact.

What's next?

Ent instrumentation enables you to trace database schema operations and query execution with OpenTelemetry.

Next steps to enhance your observability: