From a8fa6a5a1f35220cf661aaa65270b9ae2aa3a90f Mon Sep 17 00:00:00 2001 From: Deploy Bot Date: Mon, 2 Feb 2026 15:48:59 +0530 Subject: [PATCH] fix(core): escape dotted keys in flattenAttributes to correct log rendering (#1510) --- .changeset/fix-dotted-keys-logging.md | 5 +++ .../core/src/v3/utils/flattenAttributes.ts | 37 +++++++++++++++++-- packages/core/test/flattenAttributes.test.ts | 15 ++++---- 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 .changeset/fix-dotted-keys-logging.md diff --git a/.changeset/fix-dotted-keys-logging.md b/.changeset/fix-dotted-keys-logging.md new file mode 100644 index 0000000000..dafa8061ab --- /dev/null +++ b/.changeset/fix-dotted-keys-logging.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Fix issue where logging objects with keys containing dots resulted in incorrect nested object structure in logs (#1510) diff --git a/packages/core/src/v3/utils/flattenAttributes.ts b/packages/core/src/v3/utils/flattenAttributes.ts index 83d1a14f2c..cab6ad5b32 100644 --- a/packages/core/src/v3/utils/flattenAttributes.ts +++ b/packages/core/src/v3/utils/flattenAttributes.ts @@ -5,6 +5,36 @@ export const CIRCULAR_REFERENCE_SENTINEL = "$@circular(("; const DEFAULT_MAX_DEPTH = 128; +function escapeKey(key: string) { + return key.replace(/\\/g, "\\\\").replace(/\./g, "\\."); +} + +function splitPath(path: string): string[] { + const parts: string[] = []; + let current = ""; + let isEscaped = false; + + for (let i = 0; i < path.length; i++) { + const char = path[i]; + + if (isEscaped) { + current += char; + isEscaped = false; + } else if (char === "\\") { + isEscaped = true; + } else if (char === ".") { + parts.push(current); + current = ""; + } else { + current += char; + } + } + + parts.push(current); + + return parts; +} + export function flattenAttributes( obj: unknown, prefix?: string, @@ -24,7 +54,7 @@ class AttributeFlattener { constructor( private maxAttributeCount?: number, private maxDepth: number = DEFAULT_MAX_DEPTH - ) {} + ) { } get attributes(): Attributes { return this.result; @@ -200,7 +230,8 @@ class AttributeFlattener { break; } - const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : key}`; + const newPrefix = `${prefix ? `${prefix}.` : ""}${Array.isArray(obj) ? `[${key}]` : escapeKey(key) + }`; if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { @@ -278,7 +309,7 @@ export function unflattenAttributes( continue; } - const parts = key.split(".").reduce( + const parts = splitPath(key).reduce( (acc, part) => { if (part.startsWith("[") && part.endsWith("]")) { // Handle array indices more precisely diff --git a/packages/core/test/flattenAttributes.test.ts b/packages/core/test/flattenAttributes.test.ts index 28f137deaf..b15b250d51 100644 --- a/packages/core/test/flattenAttributes.test.ts +++ b/packages/core/test/flattenAttributes.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from "vitest"; import { flattenAttributes, unflattenAttributes } from "../src/v3/utils/flattenAttributes.js"; describe("flattenAttributes", () => { @@ -297,9 +298,9 @@ describe("flattenAttributes", () => { }); it("handles function values correctly", () => { - function namedFunction() {} - const anonymousFunction = function () {}; - const arrowFunction = () => {}; + function namedFunction() { } + const anonymousFunction = function () { }; + const arrowFunction = () => { }; const result = flattenAttributes({ named: namedFunction, @@ -317,7 +318,7 @@ describe("flattenAttributes", () => { it("handles mixed problematic types", () => { const complexObj = { error: new Error("Mixed error"), - func: function testFunc() {}, + func: function testFunc() { }, date: new Date("2023-01-01"), normal: "string", number: 42, @@ -415,10 +416,10 @@ describe("flattenAttributes", () => { it("handles Promise objects correctly", () => { const resolvedPromise = Promise.resolve("value"); const rejectedPromise = Promise.reject(new Error("failed")); - const pendingPromise = new Promise(() => {}); // Never resolves + const pendingPromise = new Promise(() => { }); // Never resolves // Catch the rejection to avoid unhandled promise rejection warnings - rejectedPromise.catch(() => {}); + rejectedPromise.catch(() => { }); const result = flattenAttributes({ resolved: resolvedPromise, @@ -481,7 +482,7 @@ describe("flattenAttributes", () => { it("handles complex mixed object with all special types", () => { const complexObj = { error: new Error("Test error"), - func: function testFunc() {}, + func: function testFunc() { }, date: new Date("2023-01-01"), mySet: new Set([1, 2, 3]), myMap: new Map([["key", "value"]]),