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'); + }); });