- Bun v1.1.5 agrega el reportero de fallos
bun.report, que permite transmitir información de stack de Zig/C++ usando solo una URL de unos 150 bytes sin datos personales, incluso ante un crash o panic - Los reporteros de fallos del sistema operativo y los core dumps tradicionales tienen costos altos en símbolos de depuración, rendimiento, privacidad y tamaño del ejecutable, lo que dificulta aplicarlos a una herramienta CLI como Bun
- El nuevo enfoque convierte las direcciones cuyo significado queda difuminado por ASLR en direcciones relativas al módulo, y el servidor restaura los nombres de funciones usando símbolos de depuración correspondientes al commit SHA y la plataforma
- La URL incluye la plataforma, el subcomando, el commit SHA, feature flags, direcciones del stack, el tipo de fallo y el mensaje; las direcciones del stack se codifican de forma compacta con base64 VLQ
- No se envía código fuente JavaScript/TypeScript ni variables de entorno; el equipo de Bun solo transmite información de stack de Zig/C++ y algunos metadatos necesarios para el diagnóstico
Por qué Bun creó su propio reportero de fallos
- Al momento de escribir esto, Bun tiene más de 2,600 issues abiertos en GitHub, y algunos son especialmente difíciles de reproducir y depurar
- Los servicios de reporte de fallos como Sentry son adecuados para apps y productos SaaS, pero si una herramienta CLI como Bun sube core dumps, aumentan los problemas de privacidad, rendimiento y tamaño del ejecutable
- Bun v1.1.5 introduce un nuevo formato pequeño para reportes de fallos de Zig y C++
- El reporte de fallo cabe en una URL de unos 150 bytes
- No incluye datos personales
Por qué los reporteros de fallos del sistema operativo no alcanzan
- Algunos sistemas operativos, como macOS, incluyen reporteros de fallos integrados, pero para aprovecharlos correctamente normalmente hay que distribuir símbolos de depuración junto con la aplicación
- Los símbolos de depuración aumentan mucho el tamaño de distribución de Bun
- Los símbolos de depuración de Linux pesan unos 30MB
- Los símbolos de depuración de macOS pesan unos 9MB
- Los archivos
.pdbde Windows pesan más de 250MB
- En un ejemplo del ejecutable de Bun, el tamaño baja de
60Ma51Mdespués de ejecutarllvm-strip - Si ocurre un fallo sin símbolos de depuración, el stack trace queda con solo
???y direcciones, por lo que resulta poco útil - Debido a ASLR (Address space layout randomization), las direcciones de funciones incluyen un desplazamiento aleatorio y no se pueden usar tal cual para restaurar los nombres de funciones
Cómo funciona bun.report
- Cuando ocurre un crash o panic en Bun v1.1.5, Bun imprime un enlace de
bun.reportjunto con la versión, la plataforma, los argumentos de ejecución, el uso de memoria y el mensaje del fallo - Cuando el usuario abre el enlace, se lo redirige a un formulario de issue de GitHub prellenado
- Dentro de la URL se codifica un stack trace remapeado
- El servidor restaura las direcciones del stack con base en la información incluida en la URL y las convierte en un reporte de fallo legible para el equipo de Bun
Procedimiento para convertir direcciones en un stack trace legible
- Una dirección de función es un puntero que señala dónde se cargó en memoria el código de la aplicación, e incluye un desplazamiento aleatorio por seguridad
- La idea básica es restar la dirección base (base address) del binario a la dirección cruda para obtener una dirección relativa
- La implementación real es más compleja por las diferencias entre APIs de cada plataforma
- En Windows se usa la bandera
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESSconGetModuleHandleExW, y se toma el puntero del módulo como dirección base - En Linux se recorren los módulos cargados con
dl_iterate_phdr, y se usa como dirección base eldl_phdr_info.dlpi_addrdel módulo que contiene la dirección - En macOS se recorren los módulos con
_dyld_image_county_dyld_get_image_header, y se obtiene el slide de ASLR con_dyld_get_image_vmaddr_slide- Las direcciones resultantes en macOS conservan el desplazamiento de imagen, que en el caso de Bun es
0x100000000 - Para acortar la URL se elimina este desplazamiento, pero hay que volver a sumarlo antes de remapear con
llvm-symbolizer
- Las direcciones resultantes en macOS conservan el desplazamiento de imagen, que en el caso de Bun es
- En Windows se usa la bandera
- En Linux y macOS, el primer módulo apunta al binario principal de la aplicación
- En Windows, se puede determinar si es el binario principal comparando el nombre del módulo con
peb.ProcessParameters.ImagePathName - Bun no descarga ni parsea símbolos de depuración localmente, sino que delega la demangling al servidor
- El servidor puede cachear símbolos de depuración
- Puede hacer demangling del stack trace en pocos segundos
- A la vez funciona como enlace para abrir un nuevo issue de GitHub
Estructura de la URL de bun.report
- En una URL de
bun.reportse codifica la siguiente información- Platform: una letra que representa la plataforma. Por ejemplo,
wes x86_64 Windows yMes aarch64 macOS - Subcommand: una letra que representa subcomandos como
bun test,bun installobun run - Commit SHA: el commit SHA de la versión actual de Bun; se usa luego para obtener los símbolos de depuración
- Feature Flags: marcas que indican las APIs y funciones usadas antes del fallo
- Stack Trace Addresses: las direcciones calculadas en el paso anterior
- Crash Type: una letra que representa el tipo de fallo
- Crash Message: un mensaje cuyo formato varía según el tipo de fallo
- Platform: una letra que representa la plataforma. Por ejemplo,
- El número de versión incluido en la URL es una indicación para lectura humana, más que para el procesamiento real
- Incluso solo con esta información se pueden identificar manualmente algunas características de un fallo
- Al ver el identificador
w, se puede saber rápidamente que es un crash en Windows - Al ver
A2al final de la cadena, se puede identificar una falla de segmentación
- Al ver el identificador
Codificación VLQ para URLs cortas
- Las direcciones del stack trace se codifican como números base64 Variable Length Quantity (VLQ) para mantener corta la URL
- VLQ puede representar números pequeños con menos caracteres y también codificar números grandes
- La misma técnica se usa en source maps de JavaScript para almacenar números de línea
- El servidor decodifica los valores VLQ nuevamente a direcciones relativas, usa el hash del commit y la plataforma para descargar los símbolos de depuración, y luego aplica demangling a los nombres de funciones con
llvm-symbolizer - En el crash de ejemplo se revela que falló una aserción en
dirInfoCachedMaybeLog, parte del código de resolución de módulos en Windows
Codificación de feature flags
- La URL también codifica un entero de 64 bits, donde cada bit corresponde al uso de una función específica de Bun
- Estas flags dan pistas sobre qué APIs y sistemas pudieron haber influido en el fallo
- Si un archivo
.envse carga automáticamente, se activa la funcióndotenv - Si se usa
fetch(), se activa la funciónfetch
- Si un archivo
- Bun rastrea el uso de funciones con un contenedor de variables globales, y dentro de cada API incrementa ese número para marcar su uso
- Mediante metaprogramación en tiempo de compilación de Zig, se recorre la lista de funciones y se genera dinámicamente un packed struct que usa 1 bit por función
- Con
inline forse puede recorrer la lista de funciones en tiempo de compilación, mientras que el ajuste real de los bits se realiza en runtime - Al agregar una nueva función al struct
Featuresexistente, el reportero de fallos también la maneja sin escribir lógica repetida - Lo mismo podría hacerse con macros de C o Rust, pero en la implementación de Bun,
comptimede Zig se usa como una opción más simple y legible
Diferencias frente a los core dumps
- Los core dumps contienen mucha más información, pero son grandes, solo son útiles con símbolos de depuración y pueden incluir mucha información sensible o confidencial
- El nuevo enfoque de reporte de Bun evita enviar código fuente JavaScript/TypeScript, variables de entorno u otra información sensible
- En lugar de enviar todo de forma predeterminada, solo envía el stack trace de Zig/C++ y algunos detalles que probablemente sean necesarios para diagnosticar el problema
- Si se necesita información adicional, se puede pedir al usuario por separado
- Para el equipo de Bun, esto facilita diagnosticar fallos en comparación con la situación anterior, donde solo quedaban direcciones sin mapear
Demo
- Hay una pequeña webapp para probar el reportero de fallos disponible en bun.report
- Si se agrega
/viewal final de cualquier URL de reporte de fallo, se abre la pantalla de esa webapp
1 comentarios
Opiniones de Hacker News
Si la razón para usar este método en vez de un seguimiento de pila normal es evitar distribuir varios MB de símbolos de depuración, parece que se ignoró una mejor opción: incluir solo los nombres de funciones en las tablas de depuración.
Es una solución mucho mejor que tener que usar un servicio web para ver el seguimiento de pila, y no es solo teoría: ya está implementada en LLVM: https://clang.llvm.org/docs/UsersManual.html#cmdoption-gline...
Si una sola URL autocompleta casi todo lo necesario, se vuelve lo bastante fácil, y así los desarrolladores sí envían reportes de crash. El tamaño también importa, porque querían que no hubiera desventajas para el usuario, pero lo central es hacer que todo el proceso sea muy fácil.
En este caso de uso, tener que ver el seguimiento de pila mediante un servicio web no es una gran desventaja. Es casi lo mismo que ofuscar/minificar bundles de JavaScript de frontend, subir los source maps a Sentry y luego usar Sentry para reconstruir los seguimientos de pila que vienen del navegador del usuario. El usuario de todos modos no va a ver ese seguimiento de pila, y a mí no me molesta verlo con Sentry. Si no fuera por eso, directamente no podría verlo.
Hay muchas formas de proponer alternativas.
En esas plataformas, así es como la simbolización puede encontrar nombres de funciones de las bibliotecas del sistema sin tener todos los símbolos de depuración.
Excelente y muy creativo. Ojalá muchos proyectos copien este enfoque. La clave es dejar el seguimiento de pila usando contadores de programa relativos al ejecutable/objeto compartido.
Tengo entendido que Bun está enlazado estáticamente, pero en un sistema con enlace dinámico habría que anteponer a cada contador de programa normalizado un ID pequeño, numérico, del objeto compartido.
Por ejemplo, desde hace años el crash reporter de Unreal Engine puede enviar un formato simple como este y reconstruir con bastante precisión la función y el número de línea de cada frame de la pila. Aunque normalmente se prefieren los minidumps, porque si también se tienen las variables de la pila se obtienen pistas adicionales sobre lo que ocurrió.
Microsoft es realmente muy buena en esto. En SQL Server usaban minidumps sin datos personales, que eran muy pequeños y extremadamente útiles.
Incluso en ese entonces, hace 15 años, un volcado completo de un SQL Server en producción era un archivo tan enorme que resultaba difícil de mover.
Llevo años siguiendo Bun desde que vi el primer tuit relacionado con Zig, y empecé a usarlo hace poco. Simplemente funciona bien, sin mayor complicación.
Bun es bastante atractivo. Lo probé en algunos proyectos pequeños de ejemplo y es rápido; me gusta que combine gestión de paquetes y runtime de JavaScript.
Sin embargo, uso Dependabot en la mayoría de los proyectos serios. Tengo entendido que el soporte de Dependabot para Bun está en desarrollo, o al menos se está discutiendo en algunos issues de repositorios, así que estoy postergando su uso hasta que ese soporte esté disponible.
No nos arrepentimos en absoluto. Los ahorros acumulados por las mejoras de velocidad y la gran mejora en la experiencia de desarrollo valen tanto como esperábamos.
No mucha gente notará cuánta atención pusieron en algo como esto. Me gusta porque muestra cuánto se preocupa el equipo de Bun por su craft.
Bun es sorprendente, pero hace poco intenté crear un servidor HTTP/2 con Fastify y no funcionó.
Apareció el error
node:http2 createServer is not yet implemented in Bun, y el issue al que apunta el mensaje en realidad trata sobre soporte de cliente HTTP/2. El soporte de cliente ya se lanzó en v1.0.13: https://bun.sh/blog/bun-v1.0.13#http2-client-supportEl mensaje de
NotImplementedErrordebería cambiarse para apuntar al issue del lado servidor: https://github.com/oven-sh/bun/issues/8823El soporte para servidor HTTP/2 es una de las solicitudes de funcionalidades más populares: https://github.com/oven-sh/bun/issues?q=is%3Aissue+is%3Aopen...
Cuando salga esta función, parece que mucha más gente podrá pasarse a Bun.
Bun todavía está en una etapa demasiado temprana de su ciclo de vida. Aun así, tengo muchas expectativas para el proyecto.
Me pregunto si hay gente usando Bun de verdad. ¿Es tan bueno como promete?
Configurar ts-node, ts-jest, soporte ESM, top-level await, etc. en un entorno TypeScript con Node es más engorroso de lo necesario. Las versiones recientes de Node redujeron algunas molestias, pero no es tan simple como
bun init. También estoy disfrutando la API de bun shell: https://bun.sh/blog/the-bun-shellLos mensajes de error también son bastante peores que los de Node. Lo usé durante un tiempo, pero hoy Node con
—loader tsxhace todo lo que quiero y sin esas desventajas. Para un servidor simple, por ejemplo con WebSocket, y si tienes la certeza de que no necesitas módulos nativos, vale la pena considerar Bun. De hecho, tengo varios servicios así en operación.La velocidad de arranque instantánea todavía me sorprende.
Todavía le faltan cosas, pero para mí ya es mejor que Node.
Este artículo también me parece un excelente caso de estudio de Zig. Interesante.
Bun tiene que descargar 37 paquetes antes de poder usar el REPL. Sin internet, ni siquiera puedes usar el REPL.
Al ejecutar
bun repl, aparece un error diciendo que falló la descarga del manifiesto del paquetebun-repl. No es un gran problema, pero esperaba que con solo poner un ejecutable único en el PATH funcionara de inmediato, sin instalación, y me hacía bastante ilusión.Internamente,
bun replhace lo mismo quebunx bun-repl.