Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/clickhouse-observability-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ flowchart LR
graph TB
subgraph compose["docker compose"]
CH[("clickhouse<br/>25.6-alpine")]
OC["otel-collector<br/>0.136.0"]
OC["otel-collector<br/>0.150.1"]
API["api<br/>@kopai/api on Node.js"]
end

Expand Down Expand Up @@ -172,5 +172,5 @@ curl http://localhost:8000/signals/metrics/discover
| Component | Image |
| -------------- | ---------------------------------------------- |
| ClickHouse | `clickhouse/clickhouse-server:25.6-alpine` |
| OTEL Collector | `otel/opentelemetry-collector-contrib:0.136.0` |
| OTEL Collector | `otel/opentelemetry-collector-contrib:0.150.1` |
| Node.js | `node:24-slim` |
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
retries: 10

otel-collector:
image: otel/opentelemetry-collector-contrib:0.136.0
image: otel/opentelemetry-collector-contrib:0.150.1
ports:
- "4318:4318"
volumes:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- Migration: Add EventName column to otel_logs
-- Required when upgrading OTEL collector from v0.136.0 to v0.148.0
-- Run BEFORE starting the new collector version.
--
-- ClickHouse ALTER TABLE ADD COLUMN is online and non-blocking.
-- Existing rows get the default empty string value.
--
-- NOTE: This script assumes the correct database is selected in the client
-- context (e.g. via `USE otel_default` or the `--database` CLI flag).
-- If running from a different context, qualify the table name:
-- ALTER TABLE otel_default.otel_logs ADD COLUMN IF NOT EXISTS ...

ALTER TABLE otel_logs ADD COLUMN IF NOT EXISTS EventName String CODEC(ZSTD(1)) AFTER LogAttributes;
31 changes: 31 additions & 0 deletions packages/clickhouse-datasource/migrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ClickHouse Migrations

## 001 - Add EventName to otel_logs

**File:** `001_add_event_name_to_otel_logs.sql`

Adds the `EventName` column required by OTEL collector v0.148.0+.

### Running the migration

The SQL file assumes the correct database is selected in the client context. Use one of:

```bash
# Via clickhouse-client with --database flag
clickhouse-client --database=otel_default < 001_add_event_name_to_otel_logs.sql

# Or prefix the table name manually
ALTER TABLE otel_default.otel_logs ADD COLUMN IF NOT EXISTS EventName String CODEC(ZSTD(1)) AFTER LogAttributes;
```

### Post-migration deprecation

Once all production databases have been migrated and the new collector is running everywhere, the following backward-compatibility code can be removed:

| Item | File | Change |
| ------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------- |
| `EventName` optional → required | `packages/core/src/denormalized-signals-zod.ts` | Change `.optional()` to just `.string()` |
| `EventName` optional → required | `packages/clickhouse-datasource/src/ch-row-schemas.ts` | Change `chOptionalString` to `z.string()` |
| Migration test | `packages/clickhouse-datasource/src/migration.integration.test.ts` | Delete entirely |
| Old collector constant | `packages/clickhouse-datasource/src/test/constants.ts` | Remove `OTEL_COLLECTOR_VERSION_OLD` |
| This migration script | `packages/clickhouse-datasource/migrations/001_add_event_name_to_otel_logs.sql` | Archive (keep for history) |
1 change: 1 addition & 0 deletions packages/clickhouse-datasource/src/ch-row-schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ describe("chLogsRowSchema", () => {
SeverityText: "ERROR",
SeverityNumber: "17",
Body: "something failed",
EventName: "",
LogAttributes: { "log.source": "stderr" },
ResourceAttributes: { "service.name": "svc" },
ResourceSchemaUrl: "",
Expand Down
1 change: 1 addition & 0 deletions packages/clickhouse-datasource/src/ch-row-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const chLogsRowSchema = z.object({
SeverityText: chOptionalString,
SeverityNumber: chNumber,
Body: z.string().optional(),
EventName: chOptionalString,
LogAttributes: chAttributes,
ResourceAttributes: chAttributes,
ResourceSchemaUrl: chOptionalString,
Expand Down
34 changes: 33 additions & 1 deletion packages/clickhouse-datasource/src/datasource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ async function createOtelTables(client: ClickHouseClient) {
ScopeName String CODEC(ZSTD(1)),
ScopeVersion LowCardinality(String) CODEC(ZSTD(1)),
ScopeAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
LogAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1))
LogAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
EventName String CODEC(ZSTD(1))
) ENGINE = MergeTree()
ORDER BY (ServiceName, TimestampTime, Timestamp)
`,
Expand Down Expand Up @@ -622,6 +623,7 @@ async function seedLogs(client: ClickHouseClient) {
ScopeVersion: "",
ScopeAttributes: {},
LogAttributes: { "error.type": "ConnectionError" },
EventName: "db.connection.error",
},
{
Timestamp: "2024-01-01 00:00:03.000000000",
Expand Down Expand Up @@ -1446,6 +1448,36 @@ describe("ClickHouseReadDatasource", () => {
});
});

it("returns EventName when present", async () => {
const result = await ds.getLogs({
serviceName: "user-service",
severityText: "ERROR",
requestContext: requestContext(),
});

expect(firstRow(result.data).EventName).toBe("db.connection.error");
});

it("returns undefined EventName for logs without it", async () => {
const result = await ds.getLogs({
serviceName: "order-service",
requestContext: requestContext(),
});

// Empty string from ClickHouse default → converted to undefined by chOptionalString
expect(firstRow(result.data).EventName).toBeUndefined();
});

it("filters by eventName", async () => {
const result = await ds.getLogs({
eventName: "db.connection.error",
requestContext: requestContext(),
});

expect(result.data.length).toBe(1);
expect(firstRow(result.data).Body).toBe("Database connection failed");
});

it("supports cursor pagination", async () => {
const page1 = await ds.getLogs({
serviceName: "user-service",
Expand Down
13 changes: 13 additions & 0 deletions packages/clickhouse-datasource/src/e2e.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,19 @@ describe("E2E: OTEL Collector → ClickHouse → ReadDatasource", () => {
expect(log.ResourceAttributes?.["service.name"]).toBe(TEST_SERVICE_NAME);
});

it("has EventName field (empty for logs without eventName)", async () => {
const result = await ds.getLogs({
serviceName: TEST_SERVICE_NAME,
requestContext: requestContext(),
});

// Logs sent without eventName → ClickHouse stores empty string → parsed as undefined
expect(result.data.length).toBeGreaterThan(0);
for (const log of result.data) {
expect(log.EventName).toBeUndefined();
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it("filters by bodyContains", async () => {
const result = await ds.getLogs({
bodyContains: "error",
Expand Down
Loading