Sentry-javascript: [@sentry/node] Поддержка AWS Lambda и других бессерверных решений

Созданный на 28 июл. 2018  ·  77Комментарии  ·  Источник: getsentry/sentry-javascript

  • @sentry/node версии 4.0.0-beta.11
  • Я использую размещенный Sentry

Каково текущее поведение?

Я использую @sentry/node для захвата исключений в лямбда-функции AWS.

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

Однако он убивает процесс, когда вызывается context.fail() , и исключение не попадает в Sentry.

Я мог бы сделать обходной путь, например:

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

Каково ожидаемое поведение?

Было бы неплохо, если бы я мог сделать что-то вроде:

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

Или что-то глобально обрабатывать обратный вызов.

Самый полезный комментарий

@LinusU , мы, скорее всего, создадим специальный бессерверный пакет для этого сценария. Нам просто нужно найти время, так как сейчас конец года, и людей становится все больше. Буду держать вас в курсе!

Все 77 Комментарий

Это может помочь, я думаю, https://blog.sentry.io/2018/06/20/how-droplr-uses-sentry-to-debug-serverless (используется старая версия ворона, у которой был обратный вызов, но я в основном указывая на флаг callbackWaitsForEmptyEventLoop .

Официального способа пока нет , так как мы все еще пробуем что-то в бета-версии, но это выполнимо с помощью этого кода:

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 Спасибо за указатель. Я попробую и воспроизведу уроки.

@kamilogorek Ваше предложение работает. Жду более официального пути.

@vietbui
В 4.0.0-rc.1 мы ввели функцию на клиенте с именем close , вы вызываете ее так:

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 будет ждать, пока не будут отправлены все запросы, он всегда разрешается (результат = ложный тайм-аут) до тех пор, пока не будет достигнут тайм-аут (в этом примере 2000 мс).
Это наш официальный API.
В то время как предыдущий подход по-прежнему будет работать, метод close работает во всех случаях.

@HazAT Хороший. Спасибо за всю тяжелую работу.

В 4.0.3 я называю это так в моей лямбда-функции:

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

getDefaultHub() больше не доступен.

@vietbui теперь называется getCurrentHub , так как нам пришлось унифицировать наш API с SDK для других языков.

@kamilogorek Спасибо за разъяснения. Существует проблема с подходом getCurrentHub , так как каким-то образом установленная область не попала в Sentry.

В конце концов, я выбрал другой подход, предложенный @HazAT , для захвата исключений в моих лямбда-функциях:

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

И это работает отлично.

Это рекомендуемый способ ожидания/заставить часового отправлять события?

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

Это решение не работает для меня по какой-то причине. Он работает только первый раз в производстве, когда есть время холодного запуска, и не работает после этого. вот пример кода

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

@ Андрей-Кулак Это также указано в документах:

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

Поэтому я не знаю, как мы можем справиться с этим в лямбде, где мы не знаем, когда приложение будет убито. Лучше всего было бы истощать часовых за запрос, как мы могли бы со старым API?

@HazAT , мы можем снова открыть это, пожалуйста? Я думаю, важно иметь способ работать с этим на Lambda, который становится все более распространенной целью для развертывания.

Это в настоящее время блокирует меня от обновления до последней версии ...

Лично я бы предпочел иметь возможность получить обещание/обратный вызов при сообщении об ошибке. Следующей лучшей вещью было бы иметь способ слить очередь, не закрывая ее после этого...

Каково было обоснование удаления обратного вызова из captureException ?

@albinekb вообще не работает, если я удалю следующую строку

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

@LinusU , какое решение и решение для сторожевого или ворона вы используете?

Для меня в основном работает следующее с sentry/node @4.3.0 , но я должен заставить лямбду вручную ждать некоторый период времени (в этом случае я ставлю 2 секунды), чтобы часовой сделал то, что ему нужно. Что я не уверен, почему он должен быть там, потому что мы ждем, пока часовой завершит запрос captureException . Если после этого у меня нет периода ожидания, то часовой, похоже, не отправляет ошибку.

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

Мы также обожглись на этом на Lambda. Мы начали с новых библиотек и полностью разобрались, думая вернуться к Raven. Мы пишем тесты прямо сейчас, чтобы попытаться закрыть концентратор, а затем повторно инициализировать, что было бы рабочим обходным путем, если он выдерживает критику. Но все же хакерский / может вызвать проблемы под нагрузкой.

Лично я бы предпочел какой-то flush() , который возвращает обещание — трудно найти недостаток. Думаешь, это когда-нибудь случится?

какое решение и решение часового или ворона вы используете?

Я использую следующий экспресс-обработчик ошибок:

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

Затем я использую скандий для развертывания приложения Express в Lambda.

редактировать: это с Raven "raven": "^2.6.3",

API мечты будет примерно таким 😍

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

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

Вы должны использовать экземпляр клиента напрямую, чтобы получить его. Причина этого в том, что было решено, что основной сценарий — это поведение типа «запустил и забыл», поэтому это не асинхронный метод. Однако внутри у нас есть асинхронный API, который мы используем сами.

Кажется, что я на самом деле хочу что-то вроде:

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

Чтобы пропустить всю очередь и буферизацию...

Я понимаю, что дизайн предназначен для «выстрелил и забыл» и для работы на долго работающем сервере, и, вероятно, он неплохо справляется с этим, поскольку он выполняет много буферизации и т. Д. Проблема в том, что это полная противоположность которые вам нужны для Lambda, App Engine и других «бессерверных» архитектур, которые становятся все более распространенными.

Возможно ли иметь специальный метод, который отправляет событие как можно быстрее и возвращает Promise , который мы можем await ? Это было бы идеально для бессерверных сценариев!

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 , мы, скорее всего, создадим специальный бессерверный пакет для этого сценария. Нам просто нужно найти время, так как сейчас конец года, и людей становится все больше. Буду держать вас в курсе!

мы, скорее всего, создадим специальный бессерверный пакет для этого сценария.

Это было бы потрясающе! 😍

@mtford90

когда именно я буду использовать это лучшее решение? Насколько я знаю, невозможно узнать, когда лямбда будет выключена, плюс кажется глупым ждать произвольного периода времени для выключения, чтобы часовой мог делать свое дело, особенно на дорогостоящих лямбда-функциях с высокой памятью / процессором.

(речь о сливе)

Он предназначен для использования в последнюю очередь перед закрытием серверного процесса. Тайм-аут в методе drain — это максимальное время ожидания перед завершением процесса, что не означает, что мы всегда будем использовать это время. Если сервер полностью отвечает, он сразу отправит все оставшиеся события.

Невозможно узнать это само по себе, но есть способ сообщить лямбде, когда ее следует закрыть, используя аргумент обратного вызова обработчика.

Также @LinusU , я перечитал ваш предыдущий комментарий, особенно эту часть:

Возможно ли иметь специальный метод, который отправляет событие как можно быстрее и возвращает обещание, которое мы можем ожидать? Это было бы идеально для бессерверных сценариев!

Вот как мы реализовали наш буфер. Каждый вызов captureX на клиенте будет добавлять его в буфер, это правильно, но он никак не ставится в очередь, он выполняется сразу, и этот шаблон используется только для того, чтобы мы могли получить информацию, если все было успешно отправлено в Sentry.

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

Это означает, что если вы делаете что-то подобное в AWS Lambda (при условии, что вы хотите использовать клиент по умолчанию, что является самым простым случаем):

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

Вы можете быть уверены, что он был отправлен сразу же и не было затрат времени/обработки.

@камилогорек
Означает ли это, что что-то подобное должно работать в обработчике async/await (где вы не используете обратный вызов)?

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 полностью! :)

Похоже, что следующие изменения будут работать для меня тогда ☺️

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

Честно говоря, каждая строчка становилась немного уродливее 😆, но я думаю, что под капотом лучше...

@kamilogorek Я не смог найти getCurrentHub() в документах на вашем веб-сайте, гарантировано ли, что этот API не сломается без серьезного удара? ❤️

@kamilogorek Я не смог найти getCurrentHub() в документации на вашем веб-сайте, гарантированно ли, что этот API не сломается без серьезного удара? ❤️

Да, это гарантировано. Это часть пакета @sentry/hub , который описан здесь — https://docs.sentry.io/enriching-error-data/scopes/?platform=browser .

Мы обсуждаем своего рода «расширенное использование» здесь, в этой теме, и мы еще не дошли до их документирования. Мы сделаем это в конце концов :)

Очевидно, что здесь нам не хватает некоторой документации и передовой практики в таких продвинутых случаях использования. Будет очень хорошо, если это будет задокументировано, или даже запись в блоге может стать хорошим началом.
В остальном новый SDK очень прост в использовании, а унификация действительно хороша.

@камилогорек
Означает ли это, что что-то подобное должно работать в обработчике async/await (где вы не используете обратный вызов)?

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

Выполнение того, что было предложено выше, действительно работает, за исключением того, что я не могу добавить дополнительный контекст. Например, если я делаю:

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

На самом деле я не увижу «someExtraInformation» в Sentry.

Кто-то предложил альтернативный метод в верхней части этой темы, и он работает, но кажется хакерским (принудительный тайм-аут).

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

@камилогорек @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';
   }
};

Можно ли это также применить к _неперехваченным исключениям_? Кажется, изменение интеграции Sentry.Integrations.OnUncaughtException является официальным способом сделать это, но документация сейчас довольно скудная.

+1 за это. По крайней мере, иметь что-то официально задокументированное было бы хорошо. Serverless быстро развивается с 2019 года, я очень хочу увидеть официальную поддержку от Sentry. Одна из идей, которые я прочитал здесь, и которая мне очень понравилась, заключалась в том, чтобы иметь что-то вроде Sentry.flush() для отправки всех событий, находящихся в очереди.

@rdsedmundo Можете ли вы объяснить, почему этот подход не работает для вас?

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

Это наш официальный подход и в основном Sentry.flush() .
ссылка: https://docs.sentry.io/error-reporting/configuration/draining/?platform=javascript

@HazAT Проблема с этим возникает, когда вы думаете о повторном использовании контейнера AWS Lambda. Что в терминах TL;DR означает, что процесс, который только что обслуживал запрос, может обслуживать новый бренд, если он сделан в течение короткого промежутка времени. Если я закрою соединение с этим фрагментом, который вы дали, и контейнер будет повторно использован, мне нужно будет создать новый концентратор для нового запроса. Я легко вижу, что это становится сложным. Вот почему простой await Sentry.flush() был бы лучшим решением:

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 Я не уверен, что, возможно, я что-то не так понял, но если вы это сделаете

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

Это точно так же, как await Sentry.flush , только вы определяете время ожидания.

Обещание разрешается через 2000 мс наверняка с помощью false , если в очереди все еще есть что-то.
В противном случае close разрешается с помощью true , если очередь была очищена до истечения времени ожидания.

Или контейнер будет повторно использован до того, как все промисы будут разрешены? (Я не могу себе этого представить)

@HazAT , разве проблема не в том, что close(...) предотвратит повторное использование клиента? Lambda повторно использует один и тот же процесс Node, поэтому вызовы будут примерно такими, которые, я думаю, перестанут работать после первого вызова 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()
  • ...

Нет, close не удаляет клиента, а просто очищает транспортную очередь.
Я согласен с тем, что имя close в этом контексте может вводить в заблуждение, но, по крайней мере, в JS/Node close ничего не делает с клиентом, и вполне нормально использовать его впоследствии.

Изменить: если это действительно была «проблема», я обновлю документы, чтобы прояснить это.

Прохладный. Но тогда документация неверна:

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

Хорошо, мы только что обсудили этот вопрос внутри команды.
Вы, ребята, были правы, и хотя сейчас JavaScript ведет себя не так, как мы его задокументировали 🙈 мы представим функцию flush , которая будет делать именно то, что вы ожидаете.

Так что прямо сейчас вы можете использовать close без каких-либо проблем (не уверен, что мы собираемся изменить его, чтобы удалить/отключить клиент в будущем).
Но будет функция flush , которая _just_ flush очереди.

Я обновлю эту проблему, как только функция появится.

Поскольку я немного запутался во всех этих комментариях, так ли должен выглядеть обработчик ошибок Express (имитирующий обработчик из этого репо)?

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

Похоже, он работает и не теряет лишние данные, как в коде @rreynier .

Лично я чувствую, что

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

чище, чем:

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

close действительно читается так, будто закроет клиент...

Полный пример:

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 Я попробовал это, и по какой-то причине он не отправляет дополнительные данные вместе с трассировкой стека. В основном он отправляет только трассировку стека. Нет информации о пользователе, ОС или чем-то еще.

Ага, это совсем не хорошо 😞

Пока мы ждем flush , в качестве более надежного обходного пути, чем оба вышеперечисленных варианта, вы можете сообщить и дождаться результата, _и_ включить область действия, используя приведенный ниже фрагмент:

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

Я использую это, и, кажется, он работает надежно для меня, с сообщениями об ошибках, включая все, что я ожидал.

На самом деле я использую все это с функциями Netlify, но теория такая же, как с Lambda и т. д. Я написал пост с полной информацией о том, как заставить это работать, если кому-то интересно: https://httptoolkit. технология/блог/netlify-функция-сообщения об ошибках-с-сентри/

В настоящее время я использую этого помощника во всех своих лямбдах.

@pimterry Разве это не то же самое решение, которое предложил @LinusU ? Я пробовал, и он также не отправляет дополнительные данные.

Этот подход сработал для меня до сих пор @ondrowan

@ondrowan это то же самое, но вручную захватывается и включает текущую область. Хотя я думаю, этого должно быть достаточно, чтобы заставить вас работать с исключениями. В предыдущей версии я получал события без меток, а теперь, с этим изменением, мои исключения отображаются со всеми дополнительными нормальными деталями.

@vietbui @albinekb @Andriy- Kulak @LinusU @dwelch2344 @jviolas @rreynier @guillaumekh @rdsedmundo @ondrowan @pimterry @zeusdeux не уверен, кого еще интересует этот вариант использования, так что извините, если я не должен вам звонить.

Начиная с 4.6.0 client/hub больше нет. Вы можете просто вызвать любой наш метод captureX , а затем использовать Sentry.flush() для ожидания ответа после того, как все будет отправлено на сервер. Все данные области/дополнительные данные должны быть сохранены без какого-либо взаимодействия с разработчиком.

Вот пример с успешно завершенными запросами/запросами с истекшим временем ожидания.

image

Надеюсь, поможет! :)

Хороший!

Есть ли еще планы по созданию минимального пакета только для захвата исключений из Lambda и других бессерверных решений? Я думаю, что это все еще было бы очень хорошим дополнением ❤️

@LinusU , надеюсь, да, но сейчас мы завалены SDK для других языков 😅

Спасибо всем за все возможные решения, спасибо всем, кто приходит сюда.

Используйте: await Sentry.flush() для отправки всех ожидающих запросов, это было введено в 4.6.x .

Закрывая это, пожалуйста, не стесняйтесь открывать новый вопрос, если что-то упущено (но эта тема уже очень длинная).

Здоровья 👍 🎉

@kamilogorek Привет! К сведению, я использую Sentry.flush в своем приложении вместо старого обходного пути, и ни об одной из ошибок не сообщается. В настоящее время я возвращаюсь к старому обходному пути из обновленного метода flush .

@zeusdeux есть ли способ предоставить для этого некоторую отладочную информацию / репродукцию?
Вы переопределяете метод captureException , который добавляет событие в буфер, а затем вы должны await flush . Вы пытались использовать его "обычным способом"?

@kamilogorek Хотел бы я иметь отладочную информацию, но в журналах ничего нет. Я всегда делал await на переопределенном captureException . Под обычным способом вы имеете в виду без переопределения captureException ?

Точно @zeusdeux , просто вызовите наш родной Sentry.captureException(error) без каких-либо переопределений.

Итак, вашим помощником будет:

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

и в коде вы его называете:

import * as Sentry from '@sentry/node'

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

@kamilogorek Я попробую и отчитаюсь. Кроме того, спасибо за подсказку о beforeSend ^_^

await Sentry.flush(2000);

у меня тоже ~не~ работает.

@tanduong , можете ли вы предоставить репродукцию? Просто заявить, что это не работает, не слишком полезно 😅

@kamilogorek на самом деле, я только что узнал, что

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

у меня тоже не работает, потому что моя лямбда-функция подключена к VPC.

я подтверждаю это

await Sentry.flush(2000);

на самом деле работает.

Кстати, а как бы вы поступили с лямбдой в VPC? Подключиться к шлюзу NAT? Я просто хочу Sentry, но не публичный интернет.

@tanduong Sentry находится в общедоступном Интернете, поэтому да, вам нужен шлюз NAT, если ваша лямбда работает в вашем VPC. В противном случае вам придется изучить вариант размещения Sentry.

Что на самом деле делает flush(2000) ? У меня этот код работал в основном нормально, но теперь у меня есть пара вызовов captureMessage , происходящих одновременно, и каждый раз истекает время ожидания!

Сброс внутренней очереди сообщений по сети

Хорошо, это имеет смысл. Я думаю, моя проблема в том, что это обещание никогда не возвращается, когда больше нечего сбрасывать? Всякий раз, когда я одновременно запускаю обернутую captureException fn, время ожидания моего обработчика истекает.

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() на самом деле не завершается после первого вызова captureMessage.

У меня есть проблема, похожая на @enapupe. Если вы вызываете await client.flush(2000); параллельно, разрешается только первое промис. Это может произойти в лямбда-контекстах AWS, где клиент повторно используется среди нескольких одновременных вызовов обработчика.

Я использую такой код:

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

Но когда я делаю два вызова моей лямбда-функции в быстрой последовательности, я получаю:

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

Вы можете видеть, что второе обещание никогда не разрешается.

@esetnik Я подал заявку на это: https://github.com/getsentry/sentry-javascript/issues/2131
Мой текущий обходной путь — это сброс оболочки fn, который всегда разрешается (на основе тайм-аута):

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

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

@enapupe Я добавил примечание о вашем обходном пути в # 2131. Я считаю, что это приведет к снижению производительности при параллельной очистке.

На случай, если у кого-то возникнут проблемы.
Это прекрасно работает

@СарасАрья @HazAT
Прежде всего... Спасибо, что поделились своим решением! :)
Существует один обратный вызов метода configureScope, который, как я полагаю, должен вызываться до CaptureException, но он не выполняется в том же «потоке».
Не могло ли это привести к возникновению условий гонки?

@cibergarri Я так не думаю, мне кажется синхронным, если у вас есть асинхронный метод, тогда будут условия гонки.
Учтите, что это похоже на .map массива, здесь происходит то же самое. На случай, если у вас возникнут проблемы с обдумыванием этого. Надеюсь, это поможет.

Да, это совершенно нормально.

Обновление: Sentry теперь поддерживает автоматический захват ошибок для сред Node/Lambda: https://docs.sentry.io/platforms/node/guides/aws-lambda/

Я использую @sentry/serverless следующим образом:

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

});

На лямбде не работает.
В моем тестировании env это работало, но в prod, где много одновременных выполнений и контейнеры используются повторно, он регистрирует около 10% от общей суммы.

Любой совет?

@армандо25723

Расскажите, пожалуйста, как вы измеряли, что он теряет события исключения? У вас есть пример кода того, как было выброшено такое потерянное исключение? Нужно больше контекста.

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

Что случилось?
Если функция вызывается несколько раз с небольшим интервалом между вызовами, событие регистрируется только один раз.
Если интервал времени между вызовами больше, все события регистрируются.

Я предполагаю, что проблема заключается в том, что вызов выполняется через повторно используемый контейнер.

я тоже пробовал
await Sentry.captureException(error);
и:
await Sentry.flush();
и без промывки
тот же результат

@marshall-lee что посоветуете? Если я создам проблему, я застрял здесь.

@ armando25723 armando25723 Похоже, сервер отвечает 429 (слишком много исключений) при отправке этих событий. Мы выбрасываем это в случае превышения квоты/ограничения скорости. Знаете ли вы, последовательно ли вы отправляете ошибки или превышаете квоту? Мы можем продолжить отладку, если вы считаете, что это настоящие события ошибок, которые отбрасываются, и вы находитесь ниже нашего ограничения в 5 КБ для уровня бесплатного пользования.

@ajjindal все остальные проекты отлично работают с часовым. Слаг организации — «alegra», название проекта — mail-dispatch-serverless под #mail-micros. Мы давно пользуемся sentry, но с serverless впервые. Это не бесплатный уровень, я не могу точно сказать, какой план мы используем, но он платный.
Было бы неплохо, если бы вы помогли мне отлаживать дальше.
Спасибо за ответ : )

PD: это план команды

Была ли эта страница полезной?
0 / 5 - 0 рейтинги