Skip to content

Commit 8bfb9b3

Browse files
authored
[format] print stack trace by default (#1023)
* [format] breaking: print stack trace when logging errors * add changeset * fix tests * don't print the `name` key when formatting an error
1 parent e8ad87a commit 8bfb9b3

File tree

4 files changed

+65
-13
lines changed

4 files changed

+65
-13
lines changed

.changeset/tasty-cats-eat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@edge-runtime/format': patch
3+
---
4+
5+
print stack trace by default when logging errors

packages/format/src/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ export function createFormat(opts: FormatterOptions = {}) {
4343
}
4444

4545
if (opts.formatError === undefined) {
46-
opts.formatError = (error: Error) =>
47-
`[${Error.prototype.toString.call(error)}]`
46+
opts.formatError = (error: Error) => {
47+
const stack = error.stack ?? Error.prototype.toString.call(error)
48+
return String(stack)
49+
}
4850
}
4951

5052
const { formatError, customInspectSymbol } = opts
@@ -255,10 +257,7 @@ export function createFormat(opts: FormatterOptions = {}) {
255257
base = ' ' + base
256258
} else if (isError(value)) {
257259
base = formatError(value)
258-
if (keys.length === 0) {
259-
return base
260-
}
261-
base = ' ' + base
260+
keys = keys.filter((x) => x !== 'name')
262261
} else if (hasCustomSymbol(value, ctx.customInspectSymbol)) {
263262
base = format(value[ctx.customInspectSymbol]({ format }))
264263
if (keys.length === 0) {

packages/format/tests/index.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ it('first argument', () => {
2929
expect(format({ [Symbol('a')]: 1 })).toBe('{ [Symbol(a)]: 1 }')
3030
expect(format(new Date(123))).toBe('1970-01-01T00:00:00.123Z')
3131
expect(format(new Date('asdf'))).toBe('Invalid Date')
32-
expect(format(new Error('oh no'))).toBe('[Error: oh no]')
32+
expect(format(new Error('oh no'))).toMatch(/^Error: oh no.+at Object\./ms)
3333
expect(
3434
format(
3535
(() => {
@@ -202,7 +202,7 @@ it('string (%s)', () => {
202202
expect(format('%s:%s', 'foo', 'bar')).toBe('foo:bar')
203203
expect(format('foo', 'bar', 'baz')).toBe('foo bar baz')
204204
expect(format('%s:%s', undefined)).toBe('undefined:%s')
205-
expect(format('%s', new Error('oh no'))).toBe('[Error: oh no]')
205+
expect(format('%s', new Error('oh no'))).toMatch(/^Error: oh no\n\s+at /)
206206
expect(format('%s:%s', 'foo', 'bar', 'baz')).toBe('foo:bar baz')
207207
expect(format('%s', function greetings() {})).toBe('function greetings() { }')
208208
;(() => {
@@ -213,8 +213,8 @@ it('string (%s)', () => {
213213
class CustomError extends Error {
214214
readonly name = 'CustomError'
215215
}
216-
expect(format(new CustomError('bar'))).toBe(
217-
"[CustomError: bar] { name: 'CustomError' }",
216+
expect(format(new CustomError('bar'))).toMatch(
217+
/^CustomError: bar.+at .+$/ms,
218218
)
219219
})()
220220
;(() => {
@@ -259,8 +259,8 @@ it('object generic (%O)', () => {
259259
expect(format('%O', /foo/g)).toBe('/foo/g')
260260
expect(format('%O', { foo: 'bar' })).toBe("{ foo: 'bar' }")
261261
expect(format('%O', [1, 2, 3])).toBe('[ 1, 2, 3 ]')
262-
expect(format('%O', { error: new Error('oh no') })).toBe(
263-
'{ error: [Error: oh no] }',
262+
expect(format('%O', { error: new Error('oh no') })).toMatch(
263+
/\{.+error: Error: oh no\n.+\}/ms,
264264
)
265265
expect(format('%O', { date: new Date(123) })).toBe(
266266
'{ date: 1970-01-01T00:00:00.123Z }',
@@ -281,7 +281,7 @@ it('object (%o)', () => {
281281
const error = new Error('mock error')
282282
delete error.stack
283283
expect(format('%o', error)).toBe(
284-
"[Error: mock error] { message: 'mock error' }",
284+
"Error: mock error { message: 'mock error' }",
285285
)
286286
})()
287287

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { EdgeVM } from '../../src'
2+
3+
test('Error maintains a stack trace', async () => {
4+
const log = jest.fn()
5+
console.log = log
6+
7+
function fn() {
8+
console.log(new Error('hello, world!'))
9+
}
10+
11+
const vm = new EdgeVM()
12+
vm.evaluate(`(${fn})()`)
13+
14+
expect(log).toHaveBeenCalledTimes(1)
15+
expect(log.mock.lastCall[0]).toMatch(/^Error: hello, world!\s+at fn/m)
16+
})
17+
18+
test('additional error properties', async () => {
19+
const log = jest.fn()
20+
console.log = log
21+
22+
function fn() {
23+
class CustomError extends Error {
24+
name = 'CustomError'
25+
constructor(
26+
message: string,
27+
private digest: string,
28+
private cause?: Error,
29+
) {
30+
super(message)
31+
}
32+
}
33+
console.log(new CustomError('without cause', 'digest1'))
34+
console.log(new CustomError('with cause', 'digest2', new Error('oh no')))
35+
}
36+
37+
const vm = new EdgeVM()
38+
vm.evaluate(`(${fn})()`)
39+
40+
expect(log).toHaveBeenCalledTimes(2)
41+
const [[withoutCause], [withCause]] = log.mock.calls
42+
expect(withoutCause).toMatch(
43+
/^CustomError: without cause\s+at fn.+\{.+digest: 'digest1',.+cause: undefined.+\}/ms,
44+
)
45+
expect(withCause).toMatch(
46+
/^CustomError: with cause\s+at fn.+\{.+digest: 'digest2',.+cause: Error: oh no.+.+\}/ms,
47+
)
48+
})

0 commit comments

Comments
 (0)