Skip to content

Commit

Permalink
[monitor-opentelemetry-exporter] Serialize Span/LogRecord attributes (A…
Browse files Browse the repository at this point in the history
…zure#30495)

### Packages impacted by this PR
@azure/monitor-opentelemetry-exporter

### Issues associated with this PR
Azure#30205
  • Loading branch information
hectorhdzg authored Sep 6, 2024
1 parent f169f9e commit 4c7a677
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 33 deletions.
19 changes: 19 additions & 0 deletions sdk/monitor/monitor-opentelemetry-exporter/src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { KnownContextTagKeys, TelemetryItem as Envelope, MetricsData } from "../
import { Resource } from "@opentelemetry/resources";
import { Attributes, HrTime } from "@opentelemetry/api";
import { hrTimeToNanoseconds } from "@opentelemetry/core";
import { AnyValue } from "@opentelemetry/api-logs";

export function hrTimeToDate(hrTime: HrTime): Date {
return new Date(hrTimeToNanoseconds(hrTime) / 1000000);
Expand Down Expand Up @@ -255,6 +256,24 @@ export function createResourceMetricEnvelope(
return;
}

export function serializeAttribute(value: AnyValue): string {
if (typeof value === "object") {
if (value instanceof Uint8Array) {
return String(value);
} else {
try {
// Should handle Error objects as well
return JSON.stringify(value, Object.getOwnPropertyNames(value));
} catch (err: unknown) {
// Failed to serialize, return string cast
return String(value);
}
}
}
// Return scalar and undefined values
return String(value);
}

export function shouldCreateResourceMetric(): boolean {
return !(process.env.ENV_OPENTELEMETRY_RESOURCE_METRIC_DISABLED?.toLowerCase() === "true");
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
TelemetryExceptionData,
TelemetryExceptionDetails,
} from "../generated";
import { createTagsFromResource, hrTimeToDate } from "./common";
import { createTagsFromResource, hrTimeToDate, serializeAttribute } from "./common";
import { ReadableLogRecord } from "@opentelemetry/sdk-logs";
import {
SEMATTRS_EXCEPTION_MESSAGE,
Expand Down Expand Up @@ -135,7 +135,7 @@ function createPropertiesFromLog(log: ReadableLogRecord): [Properties, Measureme
key === SEMATTRS_EXCEPTION_STACKTRACE
)
) {
properties[key] = log.attributes[key] as string;
properties[key] = serializeAttribute(log.attributes[key]);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
getUrl,
hrTimeToDate,
isSqlDB,
serializeAttribute,
} from "./common";
import { Tags, Properties, MSLink, Measurements } from "../types";
import { parseEventHubSpan } from "./eventhub";
Expand Down Expand Up @@ -137,7 +138,7 @@ function createPropertiesFromSpanAttributes(attributes?: Attributes): {
key === SEMATTRS_EXCEPTION_STACKTRACE
)
) {
properties[key] = attributes[key] as string;
properties[key] = serializeAttribute(attributes[key]);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import os from "os";
import * as assert from "assert";
import { Resource } from "@opentelemetry/resources";
import { Tags } from "../../src/types";
import { createTagsFromResource } from "../../src/utils/common";
import { createTagsFromResource, serializeAttribute } from "../../src/utils/common";

describe("commonUtils.ts", () => {
describe("#createTagsFromResource", () => {
Expand Down Expand Up @@ -96,5 +96,21 @@ describe("commonUtils.ts", () => {
const tags: Tags = createTagsFromResource(resource);
assert.ok(tags["ai.cloud.role"].startsWith("unknown_service"), "wrong ai.cloud.role");
});

describe("#createProperties", () => {
it("should serialize attributes", () => {
let attr = serializeAttribute("test");
assert.strictEqual(attr, "test");
attr = serializeAttribute(false);
assert.strictEqual(attr, "false");
attr = serializeAttribute("123");
assert.strictEqual(attr, "123");
attr = serializeAttribute({ test: "value" });
assert.strictEqual(attr, '{"test":"value"}');
attr = serializeAttribute(new Error("testError") as any);
assert.ok(attr.includes('"stack":"Error: testError'));
assert.ok(attr.includes('"message":"testError"'));
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ export class AzureBatchLogRecordProcessor extends BatchLogRecordProcessor {
}
}
}
// Ensure nested log attributes are serialized
for (const [key, value] of Object.entries(logRecord.attributes)) {
if (typeof value === "object") {
logRecord.attributes[key] = JSON.stringify(value);
}
}
super.onEmit(logRecord);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,28 +78,5 @@ describe("AzureBatchLogRecordProcessor", () => {
span.end();
});
});

it("should serialize nested log attributes", () => {
const memoryLogExporter = new InMemoryLogRecordExporter();
const processor = new AzureBatchLogRecordProcessor(memoryLogExporter, {
enableTraceBasedSamplingForLogs: false,
});
const loggerProvider = new LoggerProvider();
loggerProvider.addLogRecordProcessor(processor);
const sampler = new ApplicationInsightsSampler(1);
const tracerProvider = new NodeTracerProvider({ sampler: sampler });
tracerProvider.getTracer("testTracere").startActiveSpan("test", async (span) => {
// Generate Log record
const logRecord: APILogRecord = {
attributes: { test: { nested: "value" } },
body: "testRecord",
};
loggerProvider.getLogger("testLoggere").emit(logRecord);
await loggerProvider.forceFlush();
span.end();
});
const logRecords = memoryLogExporter.getFinishedLogRecords();
assert.strictEqual(logRecords[0].attributes.test, '{"nested":"value"}');
});
});
});

0 comments on commit 4c7a677

Please sign in to comment.