Skip to content
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ tmp-*
chicken.*
.vscode
.idea
e2e.*
e2e.*
.claude/.planning/
2 changes: 1 addition & 1 deletion jest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"**/*.spec.ts"
],
"transform": {
"^.+\\.ts$": "ts-jest"
"^.+\\.ts$": ["ts-jest", { "tsconfig": "tsconfig-base.json", "isolatedModules": true }]
},
"transformIgnorePatterns": [
"/node_modules/(?!(axios)/)"
Expand Down
1 change: 1 addition & 0 deletions packages/logging/src/otelProvider/internal-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface EventAttributesException {
type?: string;
message?: string;
stacktrace?: string;
cause?: EventAttributesException | string;
}

export enum OtelSeverity {
Expand Down
75 changes: 70 additions & 5 deletions packages/logging/src/otelProvider/otelProvider.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { OtelProviderError, otelProviderFactory } from "./otelProvider";
import { DvelopLogEvent, DbRequest, HttpResponse, IncomingHttpRequest, OutgoingHttpRequest, DvelopLogLevel } from "../logger/log-event";
import { OtelEvent, OtelSeverity } from "./internal-types";
import { EventAttributesException, OtelEvent, OtelSeverity } from "./internal-types";
import { DvelopContext, TraceContext } from "@dvelop-sdk/core";

describe("otel provider", () => {
Expand Down Expand Up @@ -587,10 +587,11 @@ describe("otel provider", () => {
appName: "test", transports: [async otelMessage => {
const json: OtelEvent = JSON.parse(otelMessage);
expect(json.attr).toBeDefined();
expect(json.attr.exception).toBeDefined();
expect(json.attr.exception.type).toEqual("Error");
expect(json.attr.exception.message).toEqual("some error");
expect(json.attr.exception.stacktrace).toBeDefined();
expect(json.attr?.exception).toBeDefined();
expect(json.attr?.exception?.type).toEqual("Error");
expect(json.attr?.exception?.message).toEqual("some error");
expect(json.attr?.exception?.stacktrace).toBeDefined();
expect(json.attr?.exception?.cause).toBeUndefined();
done();
}]
});
Expand All @@ -603,6 +604,70 @@ describe("otel provider", () => {
});
});

test("should set exception cause when cause is an Error", () => {
return new Promise<void>(done => {
const provider = otelProviderFactory({
appName: "test", transports: [async otelMessage => {
const json: OtelEvent = JSON.parse(otelMessage);
expect(json.attr?.exception?.cause).toBeDefined();
expect((json.attr?.exception?.cause as EventAttributesException).type).toEqual("TypeError");
expect((json.attr?.exception?.cause as EventAttributesException).message).toEqual("root cause");
expect((json.attr?.exception?.cause as EventAttributesException).stacktrace).toBeDefined();
done();
}]
});

const cause = new TypeError("root cause");
const event: DvelopLogEvent = {
error: Object.assign(new Error("wrapper error"), { cause })
};

provider({}, event, "debug");
});
});

test("should set exception cause recursively for nested Error causes", () => {
return new Promise<void>(done => {
const provider = otelProviderFactory({
appName: "test", transports: [async otelMessage => {
const json: OtelEvent = JSON.parse(otelMessage);
const outerCause = json.attr?.exception?.cause as EventAttributesException;
expect(outerCause.message).toEqual("middle error");
expect(outerCause.cause).toBeDefined();
expect((outerCause.cause as EventAttributesException).message).toEqual("root cause");
done();
}]
});

const root = new Error("root cause");
const middle = Object.assign(new Error("middle error"), { cause: root });
const event: DvelopLogEvent = {
error: Object.assign(new Error("outer error"), { cause: middle })
};

provider({}, event, "debug");
});
});

test("should JSON-stringify exception cause when cause is not an Error", () => {
return new Promise<void>(done => {
const provider = otelProviderFactory({
appName: "test", transports: [async otelMessage => {
const json: OtelEvent = JSON.parse(otelMessage);
expect(typeof json.attr?.exception?.cause).toEqual("string");
expect(json.attr?.exception?.cause).toEqual(JSON.stringify({ code: 42 }));
done();
}]
});

const event: DvelopLogEvent = {
error: Object.assign(new Error("some error"), { cause: { code: 42 } })
};

provider({}, event, "debug");
});
});

});

});
17 changes: 11 additions & 6 deletions packages/logging/src/otelProvider/otelProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { LoggingError } from "../error";
* @category Error
*/
export class OtelProviderError extends LoggingError {

constructor(message: string, originalError?: Error) {
super(message, originalError);
Object.setPrototypeOf(this, OtelProviderError.prototype);
Expand Down Expand Up @@ -170,13 +170,18 @@ function mapDbAttribute(event: DvelopLogEvent): { db?: EventAttributesDb } {
}

function mapExceptionAttribute(event: DvelopLogEvent): { exception?: EventAttributesException } {
const mapErrorToAttributes = (error: Error): EventAttributesException => {
return {
message: error.message,
type: error.name,
stacktrace: error.stack,
cause: error.cause ? (error.cause instanceof Error ? mapErrorToAttributes(error.cause) : JSON.stringify(error.cause)) : undefined
};
};

if (event.error) {
return {
exception: {
message: event.error.message,
type: event.error.name,
stacktrace: event.error.stack
}
exception: mapErrorToAttributes(event.error)
};
} else {
return {};
Expand Down