Durante los últimos meses estuve trabajando en una aplicación con una arquitectura de microservicios. Tuve que debuggear mensajes de erroes cómo “Something went wront with your request” con stack traces escasos y no descriptivos.
En el proceso de buscar esos errores aprendí algunas cosas.

Stack esto, stack aquello

El stack es la parte de la memoria que mantiene registro en todo momento de las funciones invocadas hasta un momento específico en tu programa. Pensá de un stack trace como un grupo de entradas con un orden de precedencia, generalmente de arriba a abajo de funciones más recientes a menos recientes respectivamente.

Hagamos unas llamadas a funciones anidadas con la ayuda de un poco de recursión y eval:

1
2
3
4
5
function fn(n){
if(n === 0) return Error().stack;
return eval(`(function fn${n}(){ return fn(n-1)})()`);
}
console.log(fn(10));

Al ejecutar el ejemplo anterior en Chrome (y Node.js o cualquier runtime basado en V8) te mostrará algo cómo lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
Error
at Error (native)
at fn (<anonymous>:2:24)
at fn1 (eval at fn (unknown source), <anonymous>:1:25)
at eval (eval at fn (unknown source), <anonymous>:1:34)
at fn (<anonymous>:3:12)
at fn2 (eval at fn (unknown source), <anonymous>:1:25)
at eval (eval at fn (unknown source), <anonymous>:1:34)
at fn (<anonymous>:3:12)
at fn3 (eval at fn (unknown source), <anonymous>:1:25)
at eval (eval at fn (unknown source), <anonymous>:1:34)

Está siendo cortado, Chrome por defecto limita a 10 stack traces. Para cambiar el límite en Chrome,
Node o Edge podés ejecutar Error.stackTraceLimit = Infinity.

En Firefox, el límite es de 128 (al menos una potencia de 2, verdad?) y parece ser que está hardcodeado. En Safari no está limitado en lo absoluto.

Long stack traces

Los stack traces asincrónicos, también llamados long stack traces, son algo con lo que vas a tener que tratar si escribís JavaScript. Primero, si no viste la charla de Philip Roberts “What the Heck is the Event Loop anyway” andá y mirala ahora, yo espero.

Hecho? Ok, continuemos.

Por la naturaleza de cómo las cosas aparecen en el event loop, cualquier operación asincrónica (timers o I/O) tendrán un stack trace local. Miremos la documentación de Node.js sobre errores:

Los stack traces extienden únicamente a bien (a) el principio de ejecución de código sincrónica, o (b) el número de frames presente en la propiedad Error.stackTraceLimit, cualquiera que sea más pequeño.

Cuando se debuggea es común querer toda la granularidad que se pueda tener para ser capaz de identificar el path a la línea de código que tiene el error. Sin importar si pasó por operaciones asincrónicas para ser ejecutada.

Es cuando los long stack traces entran en juego: son una extensión de los stack traces en un intento de llevar registro del origen de las operaciones asincrónicas.

Longjohn es un módulo muy útil de npm que llevará registro de todas las operaciones asincrónicas y te proveerá de stack traces completos (internamente reemplaza todas las operaciones asincrónicas con su propio wrapper para hacerlo).

Así es cómo podés obtener long stack traces con Longjohn y Bluebird (resulta ser la que yo uso, podría ser cualquier implementación de Promises):

1
2
3
4
5
6
7
require('longjohn');
const Promise = require('bluebird');
function example(){
Promise.resolve().then(() => console.log(Error().stack));
}

example();

Tené en cuenta que usar long stack traces tendrá cierto hit en la performance.

Nota para usuarios de Bluebird: el feature de long stack traces sólo funcionará cuando se haga throw. Ejemplo:

1
2
3
4
5
6
7
8
9
var Promise = require('bluebird');
Promise.longStackTraces();
function example(){
Promise.resolve().then(() => {
console.log(Error().stack); // no appereance of the function name
});
}

example();

Lo cual no es bueno para las herramientas de tracing que usan el estado del stack pero no hacen throw.

¿Qué es lo siguiente?

Es un problema no muy conocido el hecho de que no podamos tener promesas nativas (Node v4 o posterior) con stack traces asincrónicos funcionando. Si tenés un error de unhandledRejection en tu aplicación y estás usando promesas nativas, el único workaround para obtener un long stack trace es cambiar a otra implementación de promesas y usar un módulo cómo longjohn.

Por el momento tampoco está claro cómo se va a resolver el problema de long stack traces con promesas nativas, ya sea para Node o en browsers. AsyncHooks podría ser una solución factible para Node. Por el momento, sólo sé que voy a estar evitando usar promesas nativas por un tiempo.

¿Estás usando otras herramientas? ¿Te encontraste con otros problemas? Contamelo en los comentarios.