JM
JordanMarcelino
jordan@deck:~/projects/sentinel-go$cat README.md
Reading project details...
codeGo

Sentinel Go

An opinionated, production-ready wrapper that makes OpenTelemetry instrumentation in Go effectively “drop-in” for common libraries, starting with SQL (database/sql and sqlx).

Jordan Marcelino
Jordan MarcelinoSoftware Engineer
Sentinel Go

Most Go services don’t fail because they can’t do work they fail because you can’t see what work they’re doing under load, in prod, at 2 a.m.

Sentinel-Go is a Go library that aims to remove the “optional” nature of observability by making OpenTelemetry instrumentation the default path: you keep using familiar interfaces, but you get consistent spans and metrics without having to hand-roll wrappers across every codebase.

Overview

  • What it is: An opinionated OpenTelemetry wrapper for Go, designed to be a drop-in replacement for standard library usage (starting with SQL).
  • Who it’s for: Backend engineers and platform teams who want consistent tracing/metrics across services with minimal integration friction.
  • Primary value: Standardized telemetry with best-practice defaults (including query sanitization controls) while keeping standard interfaces intact.

At a glance

  • Drop-in wrappers for:
    • database/sql via github.com/kroma-labs/sentinel-go/sql
    • jmoiron/sqlx via github.com/kroma-labs/sentinel-go/sqlx
  • Focus areas:
    • Trace spans following DB semantic conventions (e.g., db.system, db.operation)
    • Metrics for query latency and connection pool behavior
    • Guardrails to reduce accidental leakage of sensitive query data

Background

OpenTelemetry is the standard for vendor-neutral telemetry, but teams repeatedly hit the same operational bottleneck: the SDK is straightforward, while instrumenting every subsystem consistently is not. SQL is a classic example teams end up with partial tracing, inconsistent attributes, missing context propagation, and “quick fixes” that leak query text into spans.

Sentinel-Go’s stance is explicit: instrumentation should be easy to adopt, hard to misconfigure, and consistent across services.

The Problem

Typical symptoms Sentinel-Go targets:

  • Inconsistent telemetry: Each service (or team) wraps libraries differently, creating incompatible dashboards and traces.
  • Context propagation gaps: Traces break because context isn’t passed consistently (especially around DB calls).
  • Risky defaults: Query text and parameters can land in telemetry systems unexpectedly.
  • High integration cost: “We’ll add OTel later” turns into “we never added OTel” because the work is repetitive and easy to deprioritize.

Constraints implied by the repo:

  • Must remain compatible with standard interfaces (e.g., *sql.DB) to reduce migration cost.
  • Must provide modular adoption (import only what’s needed).
  • Must include production-grade defaults rather than sample-only patterns.

The Solution

Sentinel-Go provides wrapper modules that:

  1. Keep the same public surface area you already use (sql.Open-like entry points, *sql.DB return types).
  2. Instrument operations with structured spans and metrics that follow semantic conventions.
  3. Offer functional options to control critical behavior (e.g., database system/name attributes, query recording behavior, query sanitization).

This approach aims to shift observability from an “implementation detail” into an enforced baseline.

Key Features

Core

  • Drop-in SQL wrapper (database/sql) that returns a standard *sql.DB.
  • Drop-in SQLX wrapper (jmoiron/sqlx) for teams using struct scanning and sqlx APIs.
  • Rich telemetry
    • Tracing with DB semantic attributes such as db.system and operation naming.
    • Metrics including query latency histograms and connection pool gauges.
  • Controls for query recording and sanitization
    • Disable query text recording entirely.
    • Provide a custom query sanitizer (or rely on built-in behavior).

Architecture

At a high level, Sentinel-Go sits between your application code and the underlying libraries, emitting OpenTelemetry signals through the configured SDK/exporters.

Loading diagram...

How data flows

  • Your code executes DB operations via Sentinel-Go’s wrapper entry points.
  • Sentinel-Go creates spans and records metrics around each operation.
  • Signals flow through the OpenTelemetry SDK to your configured exporters (e.g., OTLP, Prometheus, vendor agents configuration depends on your application).

Tech Stack

  • Language: Go
  • Observability: OpenTelemetry (tracing + metrics), via wrapper modules
  • Data/DB: Focused on SQL integrations (database/sql) and sqlx
  • Dev tooling: Repository includes common quality automation files (e.g., .golangci.yaml, .pre-commit-config.yaml, .mockery.yaml) and a Makefile

Getting Started

Installation

  1. Add the dependency:

    go get github.com/kroma-labs/sentinel-go
    
  2. Import the module you need:

    • github.com/kroma-labs/sentinel-go/sql
    • github.com/kroma-labs/sentinel-go/sqlx

Configuration

Sentinel-Go exposes functional options for SQL modules:

  • WithDBSystem: Sets db.system (required by OTel semantic conventions).
  • WithDBName: Sets db.name.
  • WithInstanceName: Distinguishes instances (e.g., primary vs replica).
  • WithDisableQuery: Disables recording SQL query text in spans.
  • WithQuerySanitizer: Provide a custom sanitizer function.

Run

There is no standalone binary. This is a library you import into your service.

A typical workflow is:

go test ./...

Usage

Instrument database/sql

Use the Sentinel SQL module as a replacement for sql.Open, while keeping the returned type as a standard *sql.DB.

import (
  "context"

  sentinelsql "github.com/kroma-labs/sentinel-go/sql"
  _ "github.com/lib/pq"
)

func main() {
  ctx := context.Background()

  db, err := sentinelsql.Open("postgres", "postgres://...",
    sentinelsql.WithDBSystem("postgresql"),
    sentinelsql.WithDBName("main_db"),
  )
  if err != nil {
    panic(err)
  }
  defer db.Close()

  // Use standard *sql.DB methods; context is required for trace propagation.
  _, _ = db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", 123)
}

Instrument sqlx

Use the Sentinel SQLX module if you rely on sqlx helpers such as GetContext and SelectContext.

import (
  "context"

  sentinelsqlx "github.com/kroma-labs/sentinel-go/sqlx"
  _ "github.com/lib/pq"
)

type User struct {
  ID   int    `db:"id"`
  Name string `db:"name"`
}

func main() {
  ctx := context.Background()

  db, err := sentinelsqlx.Open("postgres", "postgres://...",
    sentinelsqlx.WithDBSystem("postgresql"),
    sentinelsqlx.WithDBName("main_db"),
  )
  if err != nil {
    panic(err)
  }
  defer db.Close()

  var user User
  _ = db.GetContext(ctx, &user, "SELECT * FROM users WHERE id = $1", 1)
}

Suggested operational patterns

  1. Initialize OpenTelemetry SDK/exporters in your service startup (outside Sentinel-Go).

  2. Ensure every DB call uses context.Context to preserve trace continuity.

  3. Decide your policy for query recording:

    • Disable query text in spans for high-sensitivity environments.
    • Use a sanitizer to reduce leakage risk while retaining diagnostic value.