OpenTelemetry Go Ent monitoring [otelent]

Vladimir Mihailenco
February 04, 2026
5 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.

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.

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
}

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:

go
import (
    "github.com/uptrace/opentelemetry-go-extra/otelsql"
)

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.

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.

How do I trace graph traversals? Graph traversals in Ent generate multiple SQL queries. Each query is traced individually by otelsql. Use custom spans around your Ent operations to group related queries.

Does this work with Ent migrations? Yes, schema migrations executed through Ent are also traced since they use the same database connection.

How do I filter sensitive data from traces? otelsql captures query text by default. For sensitive data, use parameterized queries (which Ent does automatically) and consider using the otelsql.WithoutQueryVariables() option.

What's next?

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

Next steps to enhance your observability: