Sentry-javascript: [@sentry/node] Suporte ao AWS Lambda e outras soluções Serverless

Criado em 28 jul. 2018  ·  77Comentários  ·  Fonte: getsentry/sentry-javascript

  • @sentry/node versão 4.0.0-beta.11
  • Estou usando o Sentinela hospedado

Qual é o comportamento atual?

Estou usando @sentry/node para capturar exceção na função lambda da AWS.

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

No entanto, ele mata o processo quando context.fail() é chamado e a exceção não termina no Sentry.

Eu poderia fazer uma solução alternativa como:

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

Qual é o comportamento esperado?

Seria bom se eu pudesse fazer algo como:

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

Ou algo globalmente lidar com retorno de chamada.

Comentários muito úteis

@LinusU , provavelmente criaremos um pacote sem servidor específico para esse cenário. Nós só precisamos encontrar algum tempo, já que é final de ano e as coisas estão ficando lotadas agora. Vai mantê-lo informado!

Todos 77 comentários

Isso pode ajudar, acho que https://blog.sentry.io/2018/06/20/how-droplr-uses-sentry-to-debug-serverless (está usando a versão antiga do raven, que tinha um retorno de chamada, mas estou principalmente apontando para um sinalizador callbackWaitsForEmptyEventLoop .

Ainda não há uma maneira oficial, pois ainda estamos testando as coisas na versão beta, mas é possível com 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 Obrigado pela indicação. Vou tentar e reproduzir os aprendizados.

@kamilogorek Sua sugestão funciona. Estou ansioso para uma forma mais oficial.

@vietbui
Em 4.0.0-rc.1 nós introduzimos uma função no cliente chamada close , você a chama assim:

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 irá esperar até que todas as requisições sejam enviadas, sempre irá resolver (resultado = false timeout foi atingido), até que o timeout seja atingido (neste exemplo 2000ms).
Esta é a nossa API oficial.
Embora a abordagem anterior ainda funcione, o método close funciona para todos os casos.

@HazAT Legal. Obrigado por todo o trabalho duro.

Em 4.0.3 eu chamo assim na minha função lambda:

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

getDefaultHub() não está mais disponível.

@vietbui agora se chama getCurrentHub , pois tivemos que unificar nossa API com SDKs de outras linguagens.

@kamilogorek Obrigado pelo esclarecimento. Há um problema com a abordagem getCurrentHub , pois de alguma forma o escopo que eu configurei não acabou no Sentry.

No final, tomei uma abordagem diferente, conforme sugerido por @HazAT para capturar a exceção em minhas funções lambda:

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

E funciona perfeitamente.

Esta é a maneira recomendada de esperar/forçar o sentinela a enviar eventos?

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

Esta solução não funciona para mim por algum motivo. Ele só funciona na primeira vez em produção quando há um tempo de inicialização a frio e não funciona depois disso. aqui está o código de exemplo

'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 Isso também afirmou nos documentos:

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

Portanto, não sei como podemos lidar com isso em lambda, onde não sabemos quando o aplicativo será morto. O melhor seria drenar o sentry por solicitação, como poderíamos com a API antiga?

@HazAT podemos reabrir isso, por favor? Acho importante ter uma maneira de trabalhar com isso no Lambda, que está se tornando um alvo cada vez mais comum para implantação.

No momento, isso está me impedindo de atualizar para a versão mais recente ...

Pessoalmente, eu preferiria receber uma promessa/retorno de chamada ao relatar um erro. Ter uma maneira de drenar a fila sem realmente fechá-la depois seria a próxima melhor coisa...

Qual foi o motivo de remover o retorno de chamada de captureException ?

@albinekb não funciona se eu remover a seguinte linha

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

@LinusU qual é a solução e a solução sentry ou raven que você está usando?

Para mim basicamente o seguinte funciona com sentry/node @4.3.0 , mas eu tenho que fazer lambda manualmente esperar algum período de tempo (neste caso eu coloco 2 segundos) para sentry fazer o que precisa fazer. O que eu não sei por que ele precisa estar lá, porque estamos aguardando a sentinela terminar o pedido captureException . Se eu não tiver o período de espera depois, o sentry não parece enviar o erro.

'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,
  }
};

Também estamos ficando queimados com isso no Lambda. Começamos com as novas libs e estamos totalmente encaixotados, pensando em voltar para Raven. Estamos escrevendo testes agora para tentar fechar o hub e reinicializar, o que seria uma solução viável se ele retivesse a água. Mas ainda hacky / susceptível de causar problemas sob carga.

Pessoalmente, prefiro algum tipo de flush() que retorne uma promessa – difícil encontrar uma desvantagem. Acha que isso aconteceria?

qual é a solução e a solução sentry ou raven que você está usando?

Estou usando o seguinte manipulador de erro expresso:

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)
  }
})

Estou usando o scandium para implantar o aplicativo Express no Lambda

edit: isso é com Raven "raven": "^2.6.3",

A API dos sonhos seria algo assim 😍

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

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

Você precisa usar a instância do cliente diretamente para obtê-lo. A razão para isso é que decidiu que o cenário principal é um tipo de comportamento "dispare e esqueça", portanto, não é um método assíncrono. Internamente, no entanto, temos API assíncrona que usamos.

Parece que o que eu realmente quero é algo mais como:

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

Para pular todo o enfileiramento e armazenamento em buffer ...

Entendo que o design é adaptado para "disparar e esquecer" e para rodar em um servidor de longa duração, e provavelmente é muito bom nisso, pois faz muito buffer, etc. O problema é que isso é exatamente o oposto que você deseja para Lambda, App Engine e outras arquiteturas "sem servidor", que estão se tornando cada vez mais comuns.

Seria possível ter um método especial que enviasse o evento o mais rápido possível, e retornasse um Promise que podemos await ? Isso seria perfeito para os cenários sem 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 , provavelmente criaremos um pacote sem servidor específico para esse cenário. Nós só precisamos encontrar algum tempo, já que é final de ano e as coisas estão ficando lotadas agora. Vai mantê-lo informado!

provavelmente criaremos um pacote sem servidor específico para este cenário

Isso seria incrível! 😍

@mtford90

quando exatamente eu usaria essa solução melhor? Até onde eu sei, não é possível saber quando o lambda será desligado - além disso, parece bobo esperar por uma quantidade arbitrária de tempo para o desligamento para permitir que o sentry faça sua coisa - especialmente em funções caras de alta memória / cpu lambda.

(falando sobre drenagem)

Ele deve ser usado como a última coisa antes de encerrar o processo do servidor. O tempo limite no método drain é o tempo máximo que esperaremos antes de encerrar o processo, o que não significa que sempre usaremos esse tempo. Se o servidor for totalmente responsivo, ele enviará todos os eventos restantes imediatamente.

Não há como saber isso em si, mas há uma maneira de dizer ao lambda quando ele deve ser desligado usando o argumento de retorno de chamada do manipulador.

Também @LinusU , reli seu comentário anterior, especificamente esta parte:

Seria possível ter um método especial que enviasse o evento o mais rápido possível e retornasse um Promise que podemos aguardar? Isso seria perfeito para os cenários sem servidor!

Foi assim que implementamos nosso buffer. Cada chamada captureX no cliente, vai adicionar no buffer, isso mesmo, mas não é enfileirado de forma alguma, é executado imediatamente e esse padrão é usado apenas para que possamos obter as informações se tudo foi enviado com sucesso para o Sentinela.

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

Isso significa que, se você fizer algo assim no AWS Lambda (supondo que deseja usar o cliente padrão, que é o caso mais simples):

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

Você pode ter certeza de que foi enviado imediatamente e não houve sobrecarga de tempo/processamento.

@kamilogorek
Isso significa que algo assim deve funcionar em um manipulador async/await (onde você não usa o retorno de chamada)?

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 as seguintes mudanças funcionariam para mim então ☺️

-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 linha ficou um pouco mais feia 😆 mas acho que é melhor sob o capô ...

@kamilogorek Não consegui encontrar getCurrentHub() nos documentos do seu site, essa API é garantida para não quebrar sem um grande problema? ❤️

@kamilogorek Não consegui encontrar getCurrentHub() nos documentos do seu site, essa API é garantida para não quebrar sem um grande impacto semver? ❤️

Sim, é garantido. É a parte do pacote @sentry/hub que é descrito aqui - https://docs.sentry.io/enriching-error-data/scopes/?platform=browser

Estamos discutindo meio que "usos avançados" aqui neste tópico e ainda não chegamos ao ponto de documentá-los. Faremos isso eventualmente :)

Claramente o que está faltando aqui é alguma documentação e boas práticas neste tipo de casos de uso avançado. Vai ser muito bom quando for documentado ou até mesmo um post no blog pode ser um bom começo.
Caso contrário, o novo SDK é muito simples de usar e a unificação é muito boa.

@kamilogorek
Isso significa que algo assim deve funcionar em um manipulador async/await (onde você não usa o retorno de chamada)?

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

Fazer algo como sugerido acima funciona, exceto que não consigo adicionar contexto extra. Por exemplo, se eu fizer:

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

Na verdade, eu não vou ver 'someExtraInformation' no Sentry.

Alguém sugeriu um método alternativo no topo deste tópico, e isso funciona, mas parece hacky (forçando um tempo limite).

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

Isso também pode ser aplicado a _exceções não capturadas_? Parece que modificar a integração Sentry.Integrations.OnUncaughtException é a maneira oficial de fazer isso, mas a documentação é muito ruim no momento.

+1 para isso. Pelo menos ter algo oficialmente documentado seria bom. O Serverless está crescendo rapidamente a partir de 2019, eu realmente quero ver o suporte oficial do Sentry nele. Uma das ideias que li aqui e gostei muito foi ter algo como Sentry.flush() para enviar todos os eventos que estão na fila.

@rdsedmundo Você pode explicar por que essa abordagem não está funcionando para você?

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

Esta é a nossa abordagem oficial e basicamente Sentry.flush() .
ref: https://docs.sentry.io/error-reporting/configuration/draining/?platform=javascript

@HazAT O problema com isso vem quando você pensa na reutilização de contêineres do AWS Lambda. O que em termos de TL;DR significa que um processo que acabou de atender a uma solicitação pode atender a uma nova marca se for feito em um curto período de tempo. Se eu fechar a conexão com esse snippet que você forneceu e o container for reutilizado, precisarei conseguir criar um novo hub para a nova solicitação. Eu posso facilmente ver isso ficando complicado. É por isso que um simples await Sentry.flush() seria uma solução melhor:

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 Não tenho certeza se estou entendendo mal alguma coisa, mas se você entender

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

É exatamente como await Sentry.flush só que você define o tempo limite.

A promessa resolve após 2000ms com certeza com false se ainda houver coisas na fila.
Caso contrário close resolverá com true se a fila tiver sido drenada antes que o tempo limite seja atingido.

Ou o contêiner será reutilizado antes que todas as promessas sejam resolvidas? (não consigo imaginar isso)

@HazAT não é o problema que close(...) impedirá que o cliente seja usado novamente? O Lambda reutiliza o mesmo processo do Node para que as chamadas sejam algo assim, que eu acho que parará de funcionar após a primeira chamada para 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()
  • ...

Não, close não descarta o cliente, está aqui apenas para esvaziar a fila de transporte.
Eu concordo que o nome close neste contexto pode ser enganoso, mas pelo menos em JS/Node close não faz nada com o cliente e é perfeitamente normal ainda usá-lo depois.

Edit: Se esse foi realmente o "problema", atualizarei os documentos para deixar isso claro.

Legal. Mas a documentação está errada então:

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

OK, acabamos de discutir esse assunto internamente na equipe.
Vocês estavam certos e, embora o JavaScript agora não se comporte da maneira que documentamos 🙈, apresentaremos uma função flush que fará exatamente o que você espera.

Então agora você pode usar close sem problemas (não tenho certeza se vamos alterá-lo para descartar/desativar o cliente no futuro).
Mas haverá uma função flush que está lá para _just_ flush a fila.

Atualizarei esse problema assim que o recurso for lançado.

Como me perdi um pouco em todos esses comentários, é assim que o manipulador de erros do Express (imitando o deste repositório) deve se parecer?

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 está funcionando e não perde dados extras como no código do @rreynier .

Pessoalmente sinto que

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

é mais limpo do que:

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

close realmente parece que vai fechar o cliente...

Exemplo 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 Eu tentei isso e, por algum motivo, ele não envia dados extras junto com o rastreamento de pilha. Ele basicamente envia apenas rastreamento de pilha. Nenhuma informação sobre usuário, sistema operacional ou qualquer coisa.

Aha, isso não é nada bom 😞

Enquanto esperamos por flush , como uma solução alternativa mais confiável do que as duas opções acima, você pode relatar e aguardar o resultado, _and_ inclua o escopo, usando o snippet abaixo:

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

Estou usando isso e parece funcionar de maneira confiável para mim, com erros relatados, incluindo tudo o que eu esperava.

Na verdade, estou usando tudo isso com Netlify Functions, mas a teoria é a mesma com Lambda etc. Eu escrevi um post com todos os detalhes de como fazer isso funcionar, se alguém estiver interessado: https://httptoolkit. tech/blog/netlify-function-error-reporting-with-sentry/

Eu uso este auxiliar em todos os meus lambdas atualmente.

@pimterry Não é basicamente a mesma solução que o @LinusU sugeriu? Eu tentei e não envia dados extras também.

Essa abordagem funcionou para mim até agora @ondrowan

@ondrowan é o mesmo, mas pegando manualmente e incluindo o escopo atual. Isso deve ser suficiente para você trabalhar com exceções, eu acho. Com a versão anterior, eu estava recebendo eventos não rotulados e agora, com essa alteração, minhas exceções aparecem com todos os detalhes extras normais.

@vietbui @albinekb @Andriy-Kulak @LinusU @dwelch2344 @jviolas @rreynier @guillaumekh @rdsedmundo @ondrowan @pimterry @zeusdeux não tenho certeza de quem ainda está interessado neste caso de uso, então me desculpe se não devo ligar para você.

A partir de 4.6.0 , não há mais dança client/hub . Você pode simplesmente chamar qualquer nosso método captureX e então usar Sentry.flush() para aguardar a resposta assim que tudo for enviado ao servidor. Todos os dados de escopo/extras devem ser preservados sem qualquer interação do desenvolvedor.

Aqui está um exemplo com solicitações bem-sucedidas/expiradas.

image

Espero que ajude! :)

Agradável!

Ainda há planos de fazer um pacote mínimo apenas para capturar exceções do Lambda e outras soluções sem servidor? Eu acho que isso ainda seria uma adição muito legal ❤️

@LinusU espero que sim, mas estamos inundados com SDKs de outras linguagens agora 😅

Obrigado a todos por todas as soluções possíveis, tl;dr por todos que vêm aqui

Use: await Sentry.flush() para enviar todas as solicitações pendentes, isso foi introduzido em 4.6.x .

Fechando isso, sinta-se à vontade para abrir um novo problema caso esteja faltando alguma coisa (mas este tópico já é super longo).

Felicidades 👍 🎉

@kamilogorek Oi! Uma informação rápida, estou usando Sentry.flush no meu aplicativo no lugar da solução alternativa antiga e nenhum dos erros está sendo relatado. No momento, estou voltando para a solução alternativa antiga do método flush atualizado .

@zeusdeux existe alguma maneira de fornecer algumas informações de depuração/caso de reprodução para isso?
Você está substituindo o método captureException que adiciona o evento ao buffer, e então você deve await no valor de retorno flush . Você já tentou usá-lo da "maneira regular"?

@kamilogorek Eu gostaria de ter informações de depuração, mas não há nada nos logs. Eu sempre fiz await no captureException substituído . Pela maneira normal, você quer dizer sem substituir captureException ?

@zeusdeux exatamente, basta chamar nosso Sentry.captureException(error) nativo sem nenhuma substituição.

Então seu ajudante 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)
  })
}

e no código você chama:

import * as Sentry from '@sentry/node'

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

@kamilogorek Vou tentar e volto a contar. Além disso, obrigado pela dica em beforeSend ^_^

await Sentry.flush(2000);

também ~não~ está funcionando para mim.

@tanduong você pode fornecer caso de reprodução? Apenas afirmar que não funciona não ajuda muito 😅

@kamilogorek na verdade, acabei de descobrir que

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

também não funciona para mim porque minha função lambda está anexada à VPC.

eu confirmo isso

await Sentry.flush(2000);

está realmente funcionando.

BTW, então como você lidaria com lambda no VPC? Anexar a um gateway NAT? Eu só quero o Sentinela, mas não a internet pública.

@tanduong Sentry está na internet pública, então sim, você precisa ter um gateway NAT se seu lambda estiver sendo executado em sua VPC. Caso contrário, você teria que explorar a opção Sentry hospedada.

O que o flush(2000) está realmente fazendo? Eu tinha esse código funcionando muito bem, mas agora tenho algumas chamadas captureMessage acontecendo simultaneamente, está expirando todas as vezes!

Liberando a fila interna de mensagens pela rede

Ok, isso faz todo o sentido. Acho que meu problema é que essa promessa nunca retorna quando não há mais nada para liberar? Sempre que eu executo meu captureException fn encapsulado simultaneamente, ele expira meu manipulador.

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 não termina após a primeira chamada captureMessage.

Eu tenho o que acredito ser um problema semelhante ao @enapupe. Se você chamar await client.flush(2000); em paralelo, apenas a primeira promessa será resolvida. Isso pode acontecer em contextos lambda da AWS em que o cliente é reutilizado entre várias chamadas simultâneas para o manipulador.

Estou usando um código assim:

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

Mas quando faço duas chamadas para minha função lambda em rápida sucessão, recebo:

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

Você pode ver que a segunda promessa nunca é resolvida.

@esetnik Eu registrei um problema sobre isso: https://github.com/getsentry/sentry-javascript/issues/2131
Minha solução atual é um wrapper flush fn que sempre resolve (com base em um tempo limite):

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

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

@enapupe , adicionei uma nota sobre sua solução alternativa em #2131. Acredito que isso causará uma regressão de desempenho na liberação simultânea.

Caso alguém tenha algum problema.
Isso funciona lindamente

@SarasArya @HazAT
Em primeiro lugar... Obrigado por compartilhar sua solução! :)
Há um retorno de chamada do método configureScope que eu acho que deveria ser chamado antes de captureException, mas não está sendo feito no mesmo "thread".
Isso não poderia levar ao aparecimento de condições de corrida?

@cibergarri Acho que não, parece síncrono para mim, caso você tenha um método assíncrono lá, haveria condições de corrida.
Considere como .map de arrays que a mesma coisa está acontecendo aqui. No caso de você ter problemas envolvendo sua cabeça em torno dele. Espero que isso ajude.

Sim, é totalmente bom fazer isso

Atualização: o Sentry agora suporta captura automática de erros para ambientes Node/Lambda: https://docs.sentry.io/platforms/node/guides/aws-lambda/

Estou usando @sentry/serverless assim:

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

});

Não funciona em lambda.
No meu ambiente de teste estava funcionando, mas no prod onde há muitas execuções simultâneas e os contêineres são reutilizados, está registrando apenas cerca de 10% do valor total.

Algum conselho?

@armando25723

Por favor, diga como sua medida que perde os eventos de exceção? Você tem um exemplo de código de como essa exceção perdida foi lançada? Precisa de mais 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);
     }
});

O que está acontecendo?
Se a função for invocada várias vezes com curto intervalo entre as invocações, o evento será registrado apenas uma vez.
Se o intervalo de tempo entre as invocações for maior, todos os eventos serão registrados.

Presumo que o problema é quando a invocação é sobre um contêiner reutilizado.

ja tentei tambem
await Sentry.captureException(error);
e:
await Sentry.flush();
e sem rubor
mesmo resultado

@marshall-lee o que você recomenda? Se eu criar um problema, estou preso aqui.

@armando25723 Parece que o servidor está respondendo com 429 (muitas exceções) ao enviar esses eventos. Lançamos isso em caso de cenários de limitação de cota/taxa. Você sabe se está enviando erros sequencialmente ou acima da cota? Podemos depurar ainda mais se você achar que esses são eventos de erro reais sendo descartados e você está abaixo do nosso limite de 5k para o nível gratuito.

@ajjindal todos os outros projetos estão funcionando bem com sentry. O slug da organização é "alegra", o nome do projeto é mail-dispatch-serverless em #mail-micros. Estamos usando sentry há muito tempo, mas pela primeira vez com serverless. Este não é o nível gratuito, não posso dizer exatamente qual plano estamos usando, mas é pago.
Seria bom se você pudesse me ajudar a depurar ainda mais.
Obrigado pela resposta : )

PD: é o Plano de Equipe

Esta página foi útil?
0 / 5 - 0 avaliações