From 599bbe93f99d15820076254d3e6089c3ebefef73 Mon Sep 17 00:00:00 2001 From: breken-ai <312387581+breken-ai@users.noreply.github.com> Date: Fri, 25 Sep 2026 19:46:44 -0700 Subject: [PATCH] fix(core): Resolve escape sequences in `fmt` / `parameterize` messages `parameterize` built the message with `String.raw`, so an escape such as `\n` or `` \` `` stayed as a backslash sequence in the log body or event message, while the template attribute used the cooked strings. A string with an invalid escape sequence (e.g. a Windows path) has no cooked value, so its text dropped out of the template entirely. Build both from the cooked strings, falling back to the raw string where there is no cooked value. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/core/src/utils/parameterize.ts | 7 +++++-- .../core/test/lib/utils/parameterize.test.ts | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/core/src/utils/parameterize.ts b/packages/core/src/utils/parameterize.ts index d6e7a5e853bf..278bda490c2e 100644 --- a/packages/core/src/utils/parameterize.ts +++ b/packages/core/src/utils/parameterize.ts @@ -12,8 +12,11 @@ import type { ParameterizedString } from '../types/parameterize'; * @returns A `ParameterizedString` object that can be passed into `captureMessage` or Sentry.logger.X methods. */ export function parameterize(strings: TemplateStringsArray, ...values: unknown[]): ParameterizedString { - const formatted = new String(String.raw(strings, ...values)) as ParameterizedString; - formatted.__sentry_template_string__ = strings.join('\x00').replace(/%/g, '%%').replace(/\0/g, '%s'); + // `String.raw` would keep escape sequences such as `\n` as typed, while the template uses the cooked strings. + // A string with an invalid escape sequence has no cooked value, so fall back to its raw form. + const cooked = strings.map((str, i) => str ?? strings.raw[i]); + const formatted = new String(String.raw({ raw: cooked }, ...values)) as ParameterizedString; + formatted.__sentry_template_string__ = cooked.join('\x00').replace(/%/g, '%%').replace(/\0/g, '%s'); formatted.__sentry_template_values__ = values; return formatted; } diff --git a/packages/core/test/lib/utils/parameterize.test.ts b/packages/core/test/lib/utils/parameterize.test.ts index 725fec5d3944..17f72df8e948 100644 --- a/packages/core/test/lib/utils/parameterize.test.ts +++ b/packages/core/test/lib/utils/parameterize.test.ts @@ -24,4 +24,20 @@ describe('parameterize()', () => { expect(formatted.__sentry_template_string__).toEqual(string.__sentry_template_string__); expect(formatted.__sentry_template_values__).toEqual(string.__sentry_template_values__); }); + + test('keeps escape sequences the same in the message and the template', () => { + const x = 'first'; + const formatted = parameterize`Line one\nline two with ${x} → \`done\``; + + expect(String(formatted)).toBe('Line one\nline two with first → `done`'); + expect(formatted.__sentry_template_string__).toBe('Line one\nline two with %s → `done`'); + }); + + test('keeps the raw text of a string with an invalid escape sequence', () => { + const file = 'app.log'; + const formatted = parameterize`Reading C:\users ${file}`; + + expect(String(formatted)).toBe('Reading C:\\users app.log'); + expect(formatted.__sentry_template_string__).toBe('Reading C:\\users %s'); + }); });