Статьи

Думай вперед, думай

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

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

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

Что значит иметь хорошие логи?

На мой взгляд, хорошими бревнами являются те, которые удовлетворяют следующим критериям:

  • Все необработанные ошибки записываются
  • Журнальные записи являются полными и понятными
  • Журналы легко доступны
  • Если регистрируется критическая ошибка, разработчики должны быть уведомлены

лесоруб

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

var util = require('util');
var colors = require('colors');
var moment = require('moment');
var logger = {
  colorsMap: {
      'success': 'green',
      'warning': 'yellow',
      'err': 'red',
      'info': 'grey'
  },
  success: function (message) {
      this.log('success', message);
  },
  warning: function (message) {
      this.log('warning', message);
  },
  error: function (message) {
      this.log('err', message);
  },
  info: function (message) {
      this.log('info', message);
  },
  log: function (type, message) {
      var record = this.timestamptMessage(util.format('%s: %s', type.toUpperCase(), this.formatMessage(message)));
      console.log(record[this.colorsMap[type]]);
  },
  formatMessage: function (message) {
      return typeof message === 'string' ? message : JSON.stringify(message);
  },
  timestamptMessage: function (message) {
      return util.format('[%s] %s', moment(), message);
  }
};
module.exports = logger;

Logger может использоваться везде, где вам нужно, чтобы получить некоторую информацию. Но наша конечная цель — знать обо всех ошибках, которые могут появиться в приложении.

Дополнение приложения Express.js журналами

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

Обработка «необработанных» ошибок

Мы можем прослушивать событие процесса uncaughtException. Он просто помещен в  app.js файл. Лучшее место — сразу после секции require и перед использованием любого объекта.

process.on('uncaughtException', function (err) {
  logger.error({msg:'Uncaught exception', error:err, stack:err.stack});
});

Из официальных документов,

Испускается, когда исключение пузырится полностью до цикла событий. Если для этого исключения добавлен прослушиватель, действие по умолчанию (то есть печать трассировки стека и выход) не будет выполнено.

Итак, мы просто перенаправили эту ошибку в логгер. Кроме того, документы говорят следующее:

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

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

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

Регистрация неудачных HTTP-запросов

Функция Power.js — это  промежуточное ПО . Можно сделать много интересных вещей, основанных на функциях промежуточного программного обеспечения. Мы будем использовать эту функцию для создания функции промежуточного программного обеспечения, которая позволит регистрировать все неудачные HTTP-запросы.

Первый,

// have to be injected as last middlware function for all routes
function logErrors () {
  return function logErrors(err, req, res, next) {
      req.unhandledError = err;
      next(err);
  };
}

Второй,

function logHttpErrors () {
  return function logHttpErrors (req, res, next) {
      var end = res.end;
      res.end = function (data, encoding) {
          var status = res.statusCode;
          var message = {
              url: res.req.url,
              headers: res.req.headers,
              status: status,
              body: req.body,
              params: req.params
          };
          if (req.unhandledError) {
              message.error = req.unhandledError;
          }
          if (warning(status)) {
              logger.warning(message);
          }
          if (error(status)) {
              logger.error(message);
          }
          end.call (res, data, encoding);
      };
      next();
  };
  function warning (status) {
      return status >= 400 && status < 500;
  }
  function error (status) {
      return status >= 500;
  }
}

Посмотрите немного поближе:  logError() создает функцию промежуточного программного обеспечения, которая должна быть последней в цепочке, и если предыдущая функция исправила ошибку, она сохраняет этот объект ошибки в запросах. logHttpErrors() создает функцию промежуточного программного обеспечения, которая переопределяет .end() функцию ответа  и регистрирует предупреждение или ошибку, в зависимости от кода состояния ответа.

Давайте интегрировать в приложение.

logHttpErrors() может быть введен в действие  app.configure() ,

app.configure(function(){
  app.set('port', process.env.VCAP_APP_PORT || 3001);
  // ...
  app.use(middleware.errors.logHttpErrors());
  // ...
});

С logError() функцией немного сложнее  . Как я уже говорил выше, это должен быть последний обратный вызов в цепочке.

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

Я пришел к следующему решению,

require('./source/api')(app);
require('./source/router')(app);
// here .logError() will be added to end of chain
applyErrorLogging(app);
http.createServer(app).listen(app.get('port'), function() {
  var env = process.env.NODE_ENV || 'development';
  logger.info('Likeastore app listening on port ' + app.get('port') + ' ' + env + ' mongo: ' + config.connection);
});

И  applyErrorLogging() функция,

var middleware = require('../middleware');
function applyErrorLogging(app) {
  for (var verb in app.routes) {
      var routes = app.routes[verb];
      routes.forEach(patchRoute);
  }
  function patchRoute (route) {
      route.callbacks.push(middleware.errors.logErrors());
  }
}
module.exports = applyErrorLogging;

Теперь все на месте, поэтому все  4xx регистрируются как предупреждения, все  5xx регистрируются как ошибки.

Переместите ваши журналы в облако

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

Есть несколько таких услуг. Один из недавно я зацепил с в  Logentries .

Logentries дает вам API для предоставления информации там + панель инструментов, там журналы можно просматривать, искать и анализировать.

приборная панель logentries

Установить  node-logentries клиент,

$ npm install node-logentries --save

И теперь нам нужно обновить регистратор, чтобы не только  console.log отправить его в Logentries.

Создаст клиент Logentries,

var log = logentries.logger({
  token:process.env.LOGENTRIES_TOKEN
});
log.level('info');

Расширит существующий регистратор и отменит текущую  .log() функцию:

var logentriesLogger = (function (_super) {
  var child = {
      log: function (type, message) {
          _super.log(type, message);
          log.log(type, message);
      }
  };
  return _.extend(Object.create(_super), child);
})(logger);
module.exports = logentriesLogger;

Оформить заказ этой  сути ,  где вы можете увидеть все вместе взятое.

Таким образом, теперь везде, где  logger используется, журналы будут отображаться на экране (что здорово для разработки) и отправляться в Logentries (что здорово для производства).

Настройка уведомлений о критических ошибках

Если ошибка возникла на производстве, внимание разработчиков должно быть там. Без хорошей системы уведомлений слишком легко пропустить момент, когда возникает ошибка.

Опять же, это легко сделать с Logentries. Просто зайдите в  Alerts раздел и настройте шаблоны ошибок, которые вас интересуют, и адреса электронной почты для уведомлений.

настройка оповещений logentries

Электронная почта — это не только один вариант, вы можете настроить SMS или webhook для своего приложения. Таким образом, в любое время появляется сообщение об ошибке или предупреждение, и вы будете предупреждены об этом.

Выводы

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

Принимая во внимание, что вышеупомянутый подход является очень универсальным и легко принять, сегодня.