Files
cairnobs/ingest/internal/clickhousewriter/writer.go
T
jcoffey-dev b6b092c912 Scaffold Phase 0: agent -> Redpanda -> ingest -> ClickHouse -> api -> web
End-to-end log pipeline for Linux hosts, per /docs/architecture.md:

- proto: shared gRPC contract (agent <-> ingest), Go bindings checked in
- agent: Rust, musl-targeted, journald/file sourcing, RFC5424 parser,
  mTLS gRPC client, no required config for the common case
- ingest: Go, single binary with --mode server|consumer|all; gRPC front
  end forwards to Redpanda unchanged, consumer normalizes and
  batch-writes to ClickHouse with at-least-once delivery
- storage: ClickHouse schema + a plain SQL-file migration runner
- api: minimal SELECT-only query endpoint, plain REST (not gRPC+gateway
  yet -- see api/README.md)
- web: SvelteKit static SPA, one query page
- transport: Redpanda compose + topic provisioning
- cli: sentryctl ping stub
- hack/dev-certs: throwaway CA + cert generation for local mTLS
- root docker-compose.yml + docs/phase-0-runbook.md tie it together

Not yet run end-to-end against real Docker/ClickHouse/Redpanda -- see the
runbook's caveats section before relying on this working as-is.
2026-08-13 08:25:19 -07:00

61 lines
1.6 KiB
Go

// Package clickhousewriter batch-inserts normalized log rows into
// ClickHouse using the native protocol driver's batch API.
package clickhousewriter
import (
"context"
"fmt"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/sentry/sentry/ingest/internal/config"
"github.com/sentry/sentry/ingest/internal/normalize"
logsv1 "github.com/sentry/sentry/proto/sentry/logs/v1"
)
type Writer struct {
conn driver.Conn
}
func New(ctx context.Context, cfg config.ClickHouseConfig) (*Writer, error) {
conn, err := clickhouse.Open(&clickhouse.Options{
Addr: []string{cfg.Addr},
Auth: clickhouse.Auth{
Database: cfg.Database,
Username: cfg.Username,
Password: cfg.Password,
},
})
if err != nil {
return nil, fmt.Errorf("opening clickhouse connection: %w", err)
}
if err := conn.Ping(ctx); err != nil {
return nil, fmt.Errorf("pinging clickhouse: %w", err)
}
return &Writer{conn: conn}, nil
}
func (w *Writer) Close() error {
return w.conn.Close()
}
func (w *Writer) WriteBatch(ctx context.Context, records []*logsv1.LogRecord) error {
batch, err := w.conn.PrepareBatch(ctx, "INSERT INTO logs (timestamp, host, service, severity, message, attributes)")
if err != nil {
return fmt.Errorf("preparing batch: %w", err)
}
for _, rec := range records {
row := normalize.ToRow(rec)
if err := batch.Append(row.Timestamp, row.Host, row.Service, row.Severity, row.Message, row.Attributes); err != nil {
return fmt.Errorf("appending row to batch: %w", err)
}
}
if err := batch.Send(); err != nil {
return fmt.Errorf("sending batch: %w", err)
}
return nil
}