Sentry-javascript: [@sentry/node] Soporte para AWS Lambda y otras soluciones sin servidor

Creado en 28 jul. 2018  ·  77Comentarios  ·  Fuente: getsentry/sentry-javascript

  • @sentry/nodo versión 4.0.0-beta.11
  • Estoy usando Sentry alojado

¿Cuál es el comportamiento actual?

Estoy usando @sentry/node para capturar la excepción en la función lambda de AWS.

    .catch(err => {
      Sentry.captureException(err)
      context.fail()
    })

Sin embargo, mata el proceso cuando se llama a context.fail() y la excepción no termina en Sentry.

Podría hacer una solución como:

    .catch(err => {
      Sentry.captureException(err)
      setTimeout(() => context.fail(), 1000)
    })

¿Cuál es el comportamiento esperado?

Sería bueno si puedo hacer algo como:

    .catch(err => {
      Sentry.captureException(err, () => context.fail())
    })

O algo que maneja globalmente la devolución de llamada.

Comentario más útil

@LinusU , lo más probable es que creemos un paquete sin servidor específico para este escenario. Solo necesitamos encontrar algo de tiempo, ya que es el final del año y las cosas se están poniendo abarrotadas ahora. ¡Te mantendré informado!

Todos 77 comentarios

Supongo que esto puede ayudar https://blog.sentry.io/2018/06/20/how-droplr-uses-sentry-to-debug-serverless (está usando la versión antigua de Raven, que tenía una devolución de llamada, pero estoy principalmente apuntando a una bandera callbackWaitsForEmptyEventLoop .

Todavía no hay una forma oficial, ya que todavía estamos probando cosas en la versión beta, pero es factible con este código:

import { init, getDefaultHub } from '@sentry/node';

init({
  dsn: 'https://my-dsn.com/1337'
});

exports.myHandler = async function(event, context) {
  // your code

  await getDefaultHub().getClient().captureException(error, getDefaultHub().getScope());
  context.fail();
}

@kamilogorek Gracias por la indicación. Lo intentaré y reproduciré los aprendizajes.

@kamilogorek Su sugerencia funciona. Espero con ansias una forma más oficial.

@vietbui
En 4.0.0-rc.1 presentamos una función en el cliente llamada close , la llamas así:

import { getCurrentHub } from '@sentry/node';

getCurrentHub().getClient().close(2000).then(result => {
      if (!result) {
        console.log('We reached the timeout for emptying the request buffer, still exiting now!');
      }
      global.process.exit(1);
})

close esperará hasta que se envíen todas las solicitudes, siempre se resolverá (resultado = se alcanzó un tiempo de espera falso), hasta que se alcance el tiempo de espera (en este ejemplo, 2000 ms).
Esta es nuestra API oficial.
Si bien el enfoque anterior seguirá funcionando, el método close funciona para todos los casos.

@HazAT Buen día. Gracias por todo el trabajo duro.

En 4.0.3 lo llamo así en mi función lambda:

try {
  ...
} catch (err) {
  await getCurrentHub().getClient().captureException(err, getCurrentHub().getScope())
  throw err
}

getDefaultHub() ya no está disponible.

@vietbui ahora se llama getCurrentHub , ya que tuvimos que unificar nuestra API con SDK de otros idiomas.

@kamilogorek Gracias por la aclaración. Hay un problema con el enfoque de getCurrentHub ya que, de alguna manera, el alcance que configuré no terminó en Sentry.

Al final, tomé un enfoque diferente según lo sugerido por @HazAT para capturar la excepción en mis funciones lambda:

try {
  ...
} catch (err) {
  Sentry.captureException(err)
  await new Promise(resolve => Sentry.getCurrentHub().getClient().close(2000).then(resolve))
  throw err
}

Y funciona perfectamente.

¿Es esta la forma recomendada de esperar/obligar a Sentry a enviar eventos?

@albinekb sí – https://docs.sentry.io/learn/draining/?platform=browser

Esta solución no funciona para mí por alguna razón. Solo funciona la primera vez en producción cuando hay un tiempo de arranque en frío y no funciona después de eso. aquí hay un código de ejemplo

'use strict'

const Sentry =  require('@sentry/node')
Sentry.init({
  dsn: 'xxx',
  environment: process.env.STAGE
});

module.exports.createPlaylist = async (event, context, callback) => {
  context.callbackWaitsForEmptyEventLoop = false
  if(!event.body) {
    Sentry.captureException(error)
    await new Promise(resolve => Sentry.getCurrentHub().getClient().close(2000).then(resolve))
    return {
      statusCode: 500,
      headers: { 'Content-Type': 'text/plain' },
      body: 'Missing body parameters'
    }
  }
  return {
    statusCode: 200,
  }
};

@ Andriy-Kulak Eso también se indica en los documentos:

After shutdown the client cannot be used any more so make sure to only do that right before you shut down the application.

Así que no sé cómo podemos manejar esto en lambda donde no sabemos cuándo se cerrará la aplicación. ¿Lo mejor sería drenar centinela por solicitud como podíamos hacer con la antigua API?

@HazAT , ¿podríamos reabrir esto, por favor? Creo que es importante tener una forma de trabajar con Lambda, que se está convirtiendo en un objetivo cada vez más común para implementar.

Esto actualmente me impide actualizar a la última versión...

Personalmente, preferiría poder obtener una Promesa/devolución de llamada al informar un error. Tener una forma de drenar la cola sin cerrarla después sería la siguiente mejor opción...

¿Cuál fue la razón para eliminar la devolución de llamada de captureException ?

@albinekb no funciona en absoluto si elimino la siguiente línea

await new Promise(resolve => Sentry.getCurrentHub().getClient().close(2000).then(resolve))

@LinusU, ¿cuál es la solución y la solución centinela o cuervo que está utilizando?

Para mí, básicamente, lo siguiente funciona con sentry/node @4.3.0 , pero tengo que hacer que lambda espere manualmente un período de tiempo (en este caso puse 2 segundos) para que Sentry haga lo que debe hacer. No estoy seguro de por qué debe estar allí porque estamos esperando que Sentry termine la solicitud captureException . Si no tengo el período de espera posterior, entonces Sentry no parece enviar el error.

'use strict'

const Sentry =  require('@sentry/node')
Sentry.init({
  dsn: 'xxx',
  environment: process.env.STAGE
});

module.exports.createPlaylist = async (event, context, callback) => {
  context.callbackWaitsForEmptyEventLoop = false
  if(!event.body) {
    const error = new Error('Missing body parameters in createPlaylist')
    await Sentry.captureException(error)
    await new Promise(resolve => {setTimeout(resolve, 2000)})
    return {
      statusCode: 500,
      headers: { 'Content-Type': 'text/plain' },
      body: 'Missing body parameters'
    }
  }
  return {
    statusCode: 200,
  }
};

También nos estamos quemando con esto en Lambda. Comenzamos con las nuevas librerías y estamos totalmente descartados, considerando volver a Raven. Estamos escribiendo pruebas en este momento para intentar cerrar el concentrador y luego reiniciarlo, lo que sería una solución viable si retiene agua. Pero aún hacky / es probable que cause problemas bajo carga.

Personalmente, prefiero algún tipo de flush() que devuelva una promesa; es difícil encontrar una desventaja. ¿Crees que alguna vez sucederá?

¿Cuál es la solución y la solución Sentry o Raven que estás usando?

Estoy usando el siguiente controlador de error express:

app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  let status = (err.status || err.statusCode || 500) as number

  if (process.env.NODE_ENV === 'test') {
    return next(err)
  }

  if (status < 400 || status >= 500) {
    Raven.captureException(err, () => next(err))
  } else {
    next(err)
  }
})

Luego estoy usando Scandium para implementar la aplicación Express en Lambda.

editar: esto es con Raven "raven": "^2.6.3",

La API soñada sería algo así 😍

Sentry.captureException(err: Error): Promise<void>

@LinusU https://github.com/getsentry/sentry-javascript/blob/master/packages/core/src/baseclient.ts#L145 -L152 🙂

Sin embargo, debe usar la instancia del cliente directamente para obtenerlo. La razón de esto es que decidió que el escenario principal es un tipo de comportamiento de "disparar y olvidar", por lo tanto, no es un método asíncrono. Sin embargo, internamente, tenemos una API asíncrona que usamos nosotros mismos.

Parece que lo que realmente quiero es algo más como:

const backend = client.getBackend()
const event = await backend.eventFromException(error)
await client.processEvent(event, finalEvent => backend.sendEvent(finalEvent))

Para omitir todas las colas y el almacenamiento en búfer...

Entiendo que el diseño está diseñado para "disparar y olvidar", y para ejecutarse en un servidor de ejecución prolongada, y probablemente sea bastante bueno en eso, ya que hace mucho almacenamiento en búfer, etc. El problema es que esto es exactamente lo contrario. que desea para Lambda, App Engine y otras arquitecturas "sin servidor", que son cada vez más comunes.

¿Sería posible tener un método especial que envíe el evento lo más rápido posible y devuelva un Promise que podemos await ? ¡Eso sería perfecto para los escenarios sin servidor!

class Sentry {
  // ...

  async unbufferedCaptureException(err: Error): Promise<void> {
    const backend = this.client.getBackend()
    const event = await backend.eventFromException(error)
    await this.client.processEvent(event, finalEvent => backend.sendEvent(finalEvent))
  }

  // ...
}

@LinusU , lo más probable es que creemos un paquete sin servidor específico para este escenario. Solo necesitamos encontrar algo de tiempo, ya que es el final del año y las cosas se están poniendo abarrotadas ahora. ¡Te mantendré informado!

lo más probable es que creemos un paquete sin servidor específico para este escenario

¡Eso sería sorprendente! 😍

@mtford90

¿Cuándo exactamente usaría esta mejor solución? Hasta donde yo sé, no es posible saber cuándo se apagará la lambda; además, parece una tontería esperar una cantidad de tiempo arbitraria para que el apagado permita que Sentry haga lo suyo, especialmente en costosas funciones lambda de alta memoria/cpu.

(hablando de drenaje)

Está destinado a ser utilizado como lo último antes de cerrar el proceso del servidor. El tiempo de espera en el método drain es el tiempo máximo que esperaremos antes de cerrar el proceso, lo que no significa que siempre usaremos ese tiempo. Si el servidor responde completamente, enviará todos los eventos restantes de inmediato.

No hay forma de saber esto per se, pero hay una manera de decirle a la lambda cuándo debe apagarse usando el argumento de devolución de llamada del controlador.

También @LinusU , volví a leer tu comentario anterior, específicamente esta parte:

¿Sería posible tener un método especial que envíe el evento lo más rápido posible y devuelva una Promesa que podamos esperar? ¡Eso sería perfecto para los escenarios sin servidor!

Así es como implementamos nuestro búfer. Cada llamada captureX en el cliente, lo agregará al búfer, eso es correcto, pero no está en cola de ninguna manera, se ejecuta de inmediato y este patrón solo se usa para que podamos obtener la información si todo fue enviado con éxito a través de Sentry.

https://github.com/getsentry/sentry-javascript/blob/0f0dc37a4276aa2b832da451307bc4cd5413b34d/packages/core/src/requestbuffer.ts#L12 -L18

Esto significa que si hace algo como esto en AWS Lambda (asumiendo que desea usar el cliente predeterminado, que es el caso más simple):

import * as Sentry from '@sentry/browser';

Sentry.init({ dsn: '__YOUR_DSN__' });

exports.handler = (event, context, callback) => {
    try {
      // do something
    catch (err) {
      Sentry.getCurrentHub()
        .getClient()
        .captureException(err)
        .then((status) => {
          // request status
          callback(null, 'Hello from Lambda');
        })
    }
};

Puede estar seguro de que se envió de inmediato y no hubo sobrecarga de tiempo/procesamiento.

@kamilogorek
¿Significa esto que algo como esto debería funcionar en un controlador asíncrono/en espera (donde no usa la devolución de llamada)?

import * as Sentry from '@sentry/node';

Sentry.init({ dsn: '__YOUR_DSN__' });

exports.handler = async (event, context) => {
    try {
      // do something

      return 'Hello from Lambda';
    catch (err) {
      await Sentry.getCurrentHub().getClient().captureException(err);
      return 'Hello from Lambda with error';
    }
};

@jviolas totalmente! :)

Parece que los siguientes cambios funcionarían para mí entonces ☺️

-import Raven = require('raven')
+import * as Sentry from '@sentry/node'

 // ...

-Raven.config(config.SENTRY_DSN)
+Sentry.init({ dsn: config.SENTRY_DSN })

 // ...

 app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
   let status = (err.status || err.statusCode || 500) as number

   if (process.env.NODE_ENV === 'test') {
     return next(err)
   }

   if (status < 400 || status >= 500) {
-    Raven.captureException(err, () => next(err))
+    Sentry.getCurrentHub().getClient().captureException(err).then(() => next(err))
   } else {
     next(err)
   }
 })

Para ser honesto, cada línea se volvió un poco más fea 😆 pero supongo que es mejor debajo del capó...

@kamilogorek No pude encontrar getCurrentHub() en los documentos de su sitio web, ¿se garantiza que esta API no se romperá sin un gran golpe? ❤️

@kamilogorek No pude encontrar getCurrentHub() en los documentos de su sitio web, ¿se garantiza que esta API no se romperá sin un gran golpe? ❤️

Sí, está garantizado. Es la parte del paquete @sentry/hub que se describe aquí: https://docs.sentry.io/enriching-error-data/scopes/?platform=browser

Estamos hablando de "usos avanzados" aquí en este hilo y aún no hemos llegado al punto de documentarlos. Eventualmente haremos esto :)

Claramente, lo que nos falta aquí es algo de documentación y buenas prácticas en este tipo de casos de uso avanzado. Será realmente bueno cuando esté documentado o incluso una publicación de blog puede ser un buen comienzo.
De lo contrario, el nuevo SDK es realmente simple de usar y la unificación es realmente agradable.

@kamilogorek
¿Significa esto que algo como esto debería funcionar en un controlador asíncrono/en espera (donde no usa la devolución de llamada)?

import * as Sentry from '@sentry/node';

Sentry.init({ dsn: '__YOUR_DSN__' });

exports.handler = async (event, context) => {
    try {
      // do something

      return 'Hello from Lambda';
    catch (err) {
      await Sentry.getCurrentHub().getClient().captureException(err);
      return 'Hello from Lambda with error';
    }
};

Hacer algo como se sugirió anteriormente funciona, excepto que no puedo agregar contexto adicional. Por ejemplo, si hago:

Sentry.configureScope(scope => {
   scope.setExtra('someExtraInformation', information);
});
await Sentry.getCurrentHub().getClient().captureException(err);

En realidad, no veré 'someExtraInformation' en Sentry.

Alguien sugirió un método alternativo en la parte superior de este hilo, y eso funciona, pero parece extraño (forzando un tiempo de espera).

Sentry.configureScope(scope => {
  scope.setExtra('someExtraInformation', information);
});
Sentry.captureException(error);
await new Promise(resolve => Sentry.getCurrentHub().getClient().close(2000).then(resolve));

@kamilogorek @jviolas

import * as Sentry from '@sentry/node';

Sentry.init({ dsn: '__YOUR_DSN__' });

exports.handler = async (event, context) => {
   try {
     // do something

     return 'Hello from Lambda';
   catch (err) {
     await Sentry.getCurrentHub().getClient().captureException(err);
     return 'Hello from Lambda with error';
   }
};

¿Se puede aplicar esto también a las _excepciones no detectadas_? Parece que modificar la integración Sentry.Integrations.OnUncaughtException es la forma oficial de hacerlo, pero la documentación es bastante pobre en este momento.

+1 por esto. Al menos tener algo documentado oficialmente sería bueno. Serverless está creciendo rápidamente a partir de 2019, realmente quiero ver el soporte oficial de Sentry. Una de las ideas que leí aquí y que realmente me gustó fue tener algo como Sentry.flush() para enviar todos los eventos que están en cola.

@rdsedmundo ¿Puede explicar por qué este enfoque no funciona para usted?

import * as Sentry from '@sentry/node';

Sentry.getCurrentHub().getClient().close(2000).then(result => {
      if (!result) {
        console.log('We reached the timeout for emptying the request buffer, still exiting now!');
      }
      global.process.exit(1);
})

Este es nuestro enfoque oficial y básicamente Sentry.flush() .
referencia: https://docs.sentry.io/error-reporting/configuration/draining/?platform=javascript

@HazAT El problema con eso surge cuando piensas en la reutilización de contenedores de AWS Lambda. Lo que en términos TL; DR significa que un proceso que acaba de atender una solicitud puede atender una nueva marca si se realiza en un período de tiempo corto. Si cierro la conexión con este fragmento que proporcionó y el contenedor se reutiliza, necesitaría crear un nuevo centro para la nueva solicitud. Puedo ver fácilmente que esto se está complicando. Es por eso que un simple await Sentry.flush() sería una mejor solución:

import Sentry from './sentry'; // this calls Sentry.init under the hood

export const handler = async (event, context) => {
  try {
    ...
  } catch (error) {
    Sentry.captureException(error);
    await Sentry.flush(); // could even be called on the finally block

    return formatError(error);
  }
}

@rdsedmundo No estoy seguro si tal vez estoy malinterpretando algo, pero si lo hace

import Sentry from './sentry'; // this calls Sentry.init under the hood

export const handler = async (event, context) => {
  try {
    ...
  } catch (error) {
    Sentry.captureException(error);
    await Sentry.getCurrentHub().getClient().close(2000);

    return formatError(error);
  }
}

Es exactamente como await Sentry.flush solo que usted define el tiempo de espera.

La promesa se resuelve después de 2000 ms con seguridad con false si todavía había cosas en la cola.
De lo contrario close se resolverá con true si la cola se ha vaciado antes de que se alcance el tiempo de espera.

¿O se reutilizará el contenedor antes de que se resuelvan todas las promesas? (No puedo imaginar eso)

@HazAT ¿no es el problema que close(...) evitará que el cliente se vuelva a usar? Lambda reutiliza el mismo proceso de Nodo, por lo que las llamadas serían algo como esto, que supongo que dejará de funcionar después de la primera llamada a close .

  • Sentry.init()
  • Sentry.captureException()
  • Sentry.getCurrentHub().getClient().close()
  • Sentry.captureException()
  • Sentry.getCurrentHub().getClient().close()
  • Sentry.captureException()
  • Sentry.getCurrentHub().getClient().close()
  • Sentry.captureException()
  • Sentry.getCurrentHub().getClient().close()
  • ...

No, close no elimina al cliente, solo está aquí para vaciar la cola de transporte.
Estoy de acuerdo en que el nombre close en este contexto puede ser engañoso, pero al menos en JS/Node close no hace nada con el cliente y está perfectamente bien usarlo después.

Editar: si ese fue realmente el "problema", actualizaré los documentos para aclararlo.

Frio. Pero la documentación es incorrecta entonces:

After shutdown the client cannot be used any more so make sure to only do that right before you shut down the application.

Bien, acabamos de discutir este asunto internamente en el equipo.
Tenían razón y aunque JavaScript en este momento no se comporta como lo documentamos 🙈, presentaremos una función flush que hará exactamente lo que espera.

Entonces, en este momento puede usar close sin ningún problema (no estoy seguro de si lo cambiaremos para desechar/deshabilitar el cliente en el futuro).
Pero habrá una función flush que está ahí para _solo_ flush la cola.

Actualizaré este problema una vez que la característica aterrice.

Ya que me perdí un poco en todos estos comentarios, ¿es así como debería verse el controlador de errores Express (imitando al de este repositorio)?

function getStatusCodeFromResponse(error) {
    const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode);
    return statusCode ? parseInt(statusCode, 10) : 500;
}

app.use(async (err, req, res, next) => {
    const status = getStatusCodeFromResponse(err);

    if (status >= 500) {
        Sentry.captureException(err)

        await Sentry.getCurrentHub().getClient().close(2000)
    }

    next(err)
})

Parece que funciona y no pierde datos adicionales como en el código de @rreynier .

personalmente siento que

await Sentry.getCurrentHub().getClient().captureException(err)

es más limpio que:

Sentry.captureException(err)
await Sentry.getCurrentHub().getClient().close(2000)

close realmente se lee como si cerrara el cliente...

Ejemplo completo:

import * as Sentry from '@sentry/node'

// ...

Sentry.init({ dsn: config.SENTRY_DSN })

// ...

app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  let status = (err.status || err.statusCode || 500) as number

  if (process.env.NODE_ENV === 'test') {
    return next(err)
  }

  if (status < 400 || status >= 500) {
    Sentry.getCurrentHub().getClient().captureException(err).then(() => next(err))
  } else {
    next(err)
  }
})

@LinusU Lo intenté y, por alguna razón, no envía datos adicionales junto con el seguimiento de la pila. Básicamente, solo envía el seguimiento de la pila. No hay información sobre el usuario, el sistema operativo ni nada.

Ajá, eso no es nada bueno 😞

Mientras esperamos flush , como una solución alternativa más confiable que las dos opciones anteriores, puede informar y esperar el resultado, _e_ incluir el alcance, usando el siguiente fragmento:

const scope = Sentry.getCurrentHub().getScope();
await Sentry.getCurrentHub().getClient().captureException(error, scope);

Estoy usando esto, y parece funcionar de manera confiable para mí, con errores informados que incluyen todo lo que esperaría.

De hecho, estoy usando todo esto con Netlify Functions, pero la teoría es la misma con Lambda, etc. He escrito una publicación con los detalles completos de cómo hacer que esto funcione, si alguien está interesado: https://httptoolkit. tech/blog/netlify-function-error-reporting-with-sentry/

Uso este ayudante en todas mis lambdas actualmente.

@pimterry ¿No es esta básicamente la misma solución que sugirió @LinusU ? Lo he probado y tampoco envía datos extra.

Este enfoque me ha funcionado hasta ahora @ondrowan

@ondrowan es lo mismo, pero tomando manualmente e incluyendo el alcance actual. Creo que eso debería ser suficiente para que trabajes excepciones. Con la versión anterior, recibía eventos sin etiquetar, y ahora, con este cambio, mis excepciones vienen con todos los detalles extra normales.

@vietbui @albinekb @Andriy-Kulak @LinusU @dwelch2344 @jviolas @rreynier @guillaumekh @rdsedmundo @ondrowan @pimterry @zeusdeux no estoy seguro de quién sigue interesado en este caso de uso, así que disculpe si no debería llamarlo.

A partir 4.6.0 , no hay más baile client/hub . Simplemente puede llamar a cualquiera de nuestros métodos captureX y luego usar Sentry.flush() para esperar la respuesta una vez que todo se haya enviado al servidor. Todos los datos de alcance/adicionales deben conservarse sin ninguna interacción con el desarrollador.

Este es un ejemplo con solicitudes correctas o con tiempo de espera agotado.

image

¡Espero eso ayude! :)

¡Bonito!

¿Todavía hay planes para hacer un paquete mínimo solo para capturar excepciones de Lambda y otras soluciones sin servidor? Creo que eso sería una muy buena adición ❤️

@LinusU esperemos que sí, pero ahora mismo estamos inundados con SDK de otros idiomas 😅

Gracias a todos por todas las soluciones posibles, tl; dr por todos los que vienen aquí

Use: await Sentry.flush() para enviar todas las solicitudes pendientes, esto se introdujo en 4.6.x .

Cerrando esto, siéntase libre de abrir un nuevo problema en caso de que falte algo (pero este hilo ya es muy largo).

Saludos 👍 🎉

@kamilogorek ¡Hola! Para tu información, estoy usando Sentry.flush en mi aplicación en lugar de la solución anterior y no se informa ninguno de los errores. Actualmente estoy volviendo a la solución anterior del método flush actualizado .

@zeusdeux , ¿hay alguna forma de que pueda proporcionar información de depuración/caso de reproducción para esto?
Está anulando el método captureException que agrega el evento al búfer, y luego debe await en el valor devuelto flush . ¿Has intentado usarlo de la "manera normal"?

@kamilogorek Ojalá tuviera información de depuración, pero no hay nada en los registros. Siempre hice await en el captureException . Por la forma habitual, ¿quieres decir sin anular captureException ?

@zeusdeux exactamente, simplemente llame a nuestro nativo Sentry.captureException(error) sin anulaciones.

Así que tu ayudante será:

import * as Sentry from '@sentry/node'

export function init({ host, method, lambda, deployment }) {
  const environment = host === process.env.PRODUCTION_URL ? 'production' : host

  Sentry.init({
    dsn: process.env.SENTRY_DSN,
    environment,
    beforeSend(event, hint) {
      if (hint && hint.originalException) {
        // eslint-disable-next-line
        console.log('Error:', hint.originalException);
      }
      return event;
    }
  })

  Sentry.configureScope(scope => {
    scope.setTag('deployment', deployment)
    scope.setTag('lambda', lambda)
    scope.setTag('method', method)
  })
}

y en el código lo llamas:

import * as Sentry from '@sentry/node'

try {
  // ...
} catch (err) {
  Sentry.captureException(err);
  await Sentry.flush(2000);
  return respondWithError('Something went wrong', 500);
}

@kamilogorek Lo intentaré e informaré. Además, gracias por el consejo sobre beforeSend ^_^

await Sentry.flush(2000);

tampoco ~no~ funciona para mí.

@tanduong , ¿puede proporcionar un caso de reproducción? Solo decir que no funciona no es muy útil 😅

@kamilogorek en realidad, me acabo de enterar de que

await Sentry.getCurrentHub().getClient().close(2000)

tampoco funciona para mí porque mi función lambda está adjunta a VPC.

lo confirmo

await Sentry.flush(2000);

en realidad está funcionando.

Por cierto, ¿cómo lidiarías con lambda en VPC? ¿Adjuntar a una puerta de enlace NAT? Solo quiero Sentry pero no el Internet público.

@tanduong Sentry está en la Internet pública, así que sí, necesita tener una puerta de enlace NAT si su lambda se ejecuta dentro de su VPC. De lo contrario, tendría que explorar la opción Sentry alojada.

¿Qué está haciendo realmente el flush(2000) ? Tenía este código funcionando bien en su mayoría, pero ahora tengo un par de llamadas captureMessage al mismo tiempo, ¡se agota el tiempo cada vez!

Vaciar la cola interna de mensajes a través del cable

Ok, eso tiene mucho sentido. Creo que mi problema es que esta promesa nunca regresa cuando no hay nada más que tirar. Cada vez que ejecuto mi captureException fn envuelto al mismo tiempo, se agota el tiempo de espera de mi controlador.

export const captureMessage = async (
  message: string,
  extras?: any,
): Promise<boolean> =>
  new Promise((resolve) => {
    Sentry.withScope(async (scope) => {
      if (typeof extras !== 'undefined') {
        scope.setExtras(extras)
      }
      Sentry.captureMessage(message)
      await Sentry.flush(2000)
      resolve(true)
    })
  })

await Sentry.flush() realmente no termina después de la primera llamada de captureMessage.

Tengo lo que creo que es un problema similar al de @enapupe. Si llama a await client.flush(2000); en paralelo, solo se resuelve la primera promesa. Esto puede suceder en contextos de AWS lambda donde el cliente se reutiliza entre varias llamadas simultáneas al controlador.

Estoy usando un código como este:

 let client = Sentry.getCurrentHub().getClient();
  if (client) {
    // flush the sentry client if it has any events to send
    log('begin flushing sentry client');
    try {
      await client.flush(2000);
    } catch (err) {
      console.error('sentry client flush error:', err);
    }
    log('end flushing sentry client');
  }

Pero cuando hago dos llamadas a mi función lambda en rápida sucesión, obtengo:

  app begin flushing sentry client +2ms
  app begin flushing sentry client +0ms
  app end flushing sentry client +2ms

Puedes ver que la segunda promesa nunca se resuelve.

@esetnik He presentado un problema al respecto: https://github.com/getsentry/sentry-javascript/issues/2131
Mi solución actual es un contenedor de descarga fn que siempre se resuelve (basado en un tiempo de espera):

const resolveAfter = (ms: number) =>
  new Promise((resolve) => setTimeout(resolve, ms))

const flush = (timeout: number) =>
  Promise.race([resolveAfter(timeout), Sentry.flush(timeout)])

@enapupe Agregué una nota sobre su solución en #2131. Creo que causará una regresión de rendimiento en el vaciado simultáneo.

Por si alguien tiene algún problema.
esto funciona muy bien

@SarasArya @HazAT
En primer lugar... ¡Gracias por compartir tu solución! :)
Hay una devolución de llamada del método configureScope que supongo que se debe llamar antes de captureException pero no se está haciendo en el mismo "hilo".
¿No podría esto conducir a la aparición de condiciones de carrera?

@cibergarri No lo creo, me parece sincrónico, en caso de que tenga un método asíncrono allí, entonces habría condiciones de carrera.
Considere que es como .map of array's que sucede lo mismo aquí. En caso de que tenga problemas para entenderlo. Espero que eso ayude.

Sí, está totalmente bien hacer eso.

Actualización: Sentry ahora admite la captura automática de errores para entornos Node/Lambda: https://docs.sentry.io/platforms/node/guides/aws-lambda/

Estoy usando @sentry/serverless así:

const Sentry = require("@sentry/serverless");
Sentry.AWSLambda.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
  environment: appEnv
});

exports.main = Sentry.AWSLambda.wrapHandler(async (event, context) => {
     try{
           //my code
     }catch(error){
          Sentry.captureException(error);
          await Sentry.flush(3000);
     }

});

No funciona en lambda.
En mi entorno de prueba, estaba funcionando, pero en producción, donde hay muchas ejecuciones simultáneas y los contenedores se reutilizan, está registrando solo alrededor del 10% de la cantidad total.

¿Algún consejo?

@armando25723

Indique cómo midió que pierde los eventos de excepción. ¿Tiene una muestra de código de cómo se lanzó tal excepción perdida? Necesita más contexto.

const Sentry = require("@sentry/serverless"); // "version": "5.27.3"
Sentry.AWSLambda.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 1.0,
  environment: appEnv
});
exports.main = Sentry.AWSLambda.wrapHandler(async (event, context) => {
     try{
           throw new Error('Test Error');
     }catch(error){
          Sentry.captureException(error);
          await Sentry.flush(3000);
     }
});

¿Lo que está sucediendo?
Si la función se invoca varias veces con un breve intervalo entre invocaciones, el evento solo se registra una vez.
Si el intervalo de tiempo entre invocaciones es mayor, se registran todos los eventos.

Supongo que el problema es cuando la invocación es sobre un contenedor reutilizado.

he probado tambien
await Sentry.captureException(error);
y:
await Sentry.flush();
y sin rubor
mismo resultado

@ marshall-lee ¿qué me recomiendas? Si debo crear un problema, estoy atrapado aquí.

@armando25723 Parece que el servidor responde con 429 (demasiadas excepciones) mientras envía estos eventos. Lanzamos eso en caso de escenarios de límite de cuota/tasa. ¿Sabes si estás enviando errores de forma secuencial o por encima de la cuota? Podemos depurar aún más si cree que estos son eventos de error reales que se descartan y está por debajo de nuestro límite de 5k para el nivel gratuito.

@ajjindal todos los demás proyectos funcionan bien con Sentry. El slug de la organización es "alegra", el nombre del proyecto es mail-dispatch-serverless bajo #mail-micros. Hemos estado usando centinela durante mucho tiempo, pero la primera vez sin servidor. Este no es un nivel gratuito, no puedo decirte exactamente qué plan estamos usando, pero es de pago.
Sería bueno si pudieras ayudarme a depurar más.
Gracias por responder : )

PD: es Plan de equipo

¿Fue útil esta página
0 / 5 - 0 calificaciones