diff --git a/.changeset/hungry-islands-accept.md b/.changeset/hungry-islands-accept.md new file mode 100644 index 0000000..856b46d --- /dev/null +++ b/.changeset/hungry-islands-accept.md @@ -0,0 +1,6 @@ +--- +"@kopai/sqlite-datasource": minor +"@kopai/core": minor +--- + +Extended support for attribute values to include arrays of strings, numbers, and booleans alongside primitive values, broadening the allowed data types for OpenTelemetry resource and span attributes. diff --git a/packages/core/src/denormalized-signals-zod.ts b/packages/core/src/denormalized-signals-zod.ts index 816a980..a9b9d0e 100644 --- a/packages/core/src/denormalized-signals-zod.ts +++ b/packages/core/src/denormalized-signals-zod.ts @@ -1,6 +1,13 @@ import { z } from "zod"; -const attributeValue = z.union([z.string(), z.number(), z.boolean()]); +const attributeValue = z.union([ + z.string(), + z.number(), + z.boolean(), + z.array(z.string()), + z.array(z.number()), + z.array(z.boolean()), +]); export const otelTracesSchema = z.object({ // Required fields diff --git a/packages/sqlite-datasource/src/datasource-read.test.ts b/packages/sqlite-datasource/src/datasource-read.test.ts index 07ab385..5841fb8 100644 --- a/packages/sqlite-datasource/src/datasource-read.test.ts +++ b/packages/sqlite-datasource/src/datasource-read.test.ts @@ -1,7 +1,7 @@ /// import { DatabaseSync } from "node:sqlite"; import { NodeSqliteTelemetryDatasource } from "./datasource.js"; -import { otlp, type datasource } from "@kopai/core"; +import { otlp, denormalizedSignals, type datasource } from "@kopai/core"; import { initializeDatabase } from "./initialize-database.js"; import { SqliteDatasourceQueryError } from "./sqlite-datasource-error.js"; @@ -515,6 +515,66 @@ describe("NodeSqliteTelemetryDatasource", () => { }); }); + it("returns array attribute values in ResourceAttributes", async () => { + // OTel attributes can be arrays (e.g. process.command_args) + await ds.writeTraces({ + resourceSpans: [ + { + resource: { + attributes: [ + { + key: "process.command_args", + value: { + arrayValue: { + values: [ + { stringValue: "node" }, + { stringValue: "server.js" }, + { stringValue: "--port=3000" }, + ], + }, + }, + }, + { + key: "service.name", + value: { stringValue: "test-service" }, + }, + ], + }, + scopeSpans: [ + { + scope: { name: "test-scope" }, + spans: [ + { + traceId: "trace-with-array-attr", + spanId: "span1", + name: "test-span", + startTimeUnixNano: "1000000000000000", + endTimeUnixNano: "1001000000000000", + }, + ], + }, + ], + }, + ], + }); + + const result = await readDs.getTraces({ + traceId: "trace-with-array-attr", + }); + + expect(result.data).toHaveLength(1); + const row = result.data[0]; + assertDefined(row); + expect(row.ResourceAttributes).toEqual({ + "process.command_args": ["node", "server.js", "--port=3000"], + "service.name": "test-service", + }); + + // Validate schema accepts array attribute values (this is what fastify validates) + const parseResult = denormalizedSignals.otelTracesSchema.safeParse(row); + expect(parseResult.success).toBe(true); + }); + it("parses Events and Links fields as arrays", async () => { await insertSpan({ traceId: "trace-with-events-links",