Регистрация и анализ ваших данных

Использование архитектуры AWS

В предыдущей статье мы узнали, как создать лямбда-функцию Node.js и как подключить ее к шлюзу HTTP API. Наша лямбда выглядела примерно так:

Если вы только присоединяетесь к нам, просто создайте базовую лямбду и подключите ее к новому шлюзу HTTP API или следуйте этому руководству.

Шаг 1. Использование CloudWatch

Скорее всего, вы уже посетили конечную точку HTTP, которая запускает функцию лямбда. Но если вы этого не сделали, просто нажмите ссылку на свою конечную точку API. После этого для вас будет автоматически создан журнал. В AWS замечательно то, что многие из этих вещей сразу работают на вас. Чтобы просмотреть журнал этого сделанного запроса, перейдите в сервис CloudWatch на панели управления AWS (вы можете найти CloudWatch с помощью кнопки служб в заголовке), а затем щелкните подраздел «Группы журналов» в разделе «Журналы» на левой панели. После того, как был сделан запрос к вашей конечной точке API, вы должны увидеть имя вашей лямбда-функции, появившееся в списке. Щелкните по нему, и вы попадете в несколько групп журналов. Если у вас небольшое количество, это означает, что у вас было небольшое количество запросов. Вот что происходит в моем случае. Вы можете увидеть здесь:

Чтобы выполнить поиск по всем этим сразу, нажмите кнопку Search Log Group. Теперь вы можете увидеть логи вашего запроса:

Это замечательно то, что вы можете видеть, сколько вам будет выставлен счет. AWS предоставляет нам 1 миллион бесплатных запросов в месяц и 400 000 ГБ-секунд вычислительного времени в месяц, поэтому мы его используем. Но если вы превысили этот лимит, вы можете точно узнать, сколько денег вы заплатите за типичный запрос, посмотрев на ваш оплачиваемый срок и сверив его с их схемой ценообразования.

Шаг 2: настраиваемый вывод журнала

Вся эта регистрируемая информация хороша, но было бы здорово, если бы мы могли регистрировать нашу собственную информацию (например, регистрировать информацию о наших пользователях анонимно, чтобы мы могли использовать эту информацию для улучшения нашего продукта или для регистрации, если запрос был успешным. или не)? Давайте реализуем базовую версию этого. Для этого вернемся к коду нашей функции Lambda, выполнив поиск по запросу Lambda в разделе Services и выбрав соответствующую Lambda. Мы можем регистрировать любую информацию, которая нам нужна, просто используя console.log из Node.js. Я реализовал очень простую версию этого:

После того, как вы отредактировали свою лямбду, чтобы включить в нее требуемый вывод журнала, не забудьте нажать кнопку «Сохранить», чтобы изменения можно было развернуть. После сохранения давайте сделаем еще один запрос, щелкнув URL-адрес конечной точки API, а затем вернемся в CloudWatch, чтобы увидеть, как теперь выглядят журналы:

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

Шаг 3. Регистрация HTTP-ответов

Итак, чтобы регистрировать более сложную информацию, давайте также усложним нашу лямбду, чтобы выводимые данные были более значимыми. Давайте воспользуемся нашей Lambda для вызова внешнего HTTPS API и получения необходимых нам данных. Для этого воспользуемся бесплатным API под названием PokeAPI, который предоставляет нам информацию о покемонах. Выполнить HTTPS-вызов в Node.js невероятно просто. Это можно сделать с помощью https основного модуля:

const https = require('https');

let dataString = '';

const req = https.get("https://pokeapi.co/api/v2/pokemon/ditto", function(res) {
  res.on('data', chunk => {
    dataString += chunk;
  });
  res.on('end', () => {
    console.log(dataString);
  });
});

req.on('error', (e) => {
  console.error(e);
});

Давайте использовать аналогичный код в нашей лямбда-функции. Для этого воспользуемся функцией ES7 Async-Await. После вызова PokeAPI в нашей лямбде в ожидании его ответа мы хотим вернуть ответ нашим пользователям. Мы можем легко сделать это, написав лямбда-код, который выглядит так:

const https = require('https');
exports.handler = async (event) => {
    let dataString = '';
    
    const response = await new Promise((resolve, reject) => {
        const req = https.get("https://pokeapi.co/api/v2/pokemon/ditto", function(res) {
          res.on('data', chunk => {
            dataString += chunk;
          });
          res.on('end', () => {
            resolve({
                statusCode: 200,
                body: JSON.stringify(JSON.parse(dataString), null, 4)
            });
          });
        });
        
        req.on('error', (e) => {
          reject({
              statusCode: 500,
              body: 'Something went wrong!'
          });
        });
    });
    
    return response;
};

Обратите внимание, что мы анализируем JSON из строки, а затем снова преобразуем его в строку просто для эстетических целей (чтобы дать четыре пробела между каждой переменной). На нашем экране AWS это выглядит так:

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

Шаг 4: принятие пользовательского ввода

Как вы могли заметить, функция-обработчик получает аргумент с именем event. Давайте зарегистрируем этот объект, чтобы увидеть, есть ли в нем что-то, что мы можем использовать. Мы можем просто добавить console.log(event);, прежде чем делать что-либо еще в лямбда-функции. Теперь код выглядит так:

Сохраняя эти изменения и делая еще один запрос к URL-адресу конечной точки API, мы видим, что получаем весь объект события в нашем последнем журнале CloudWatch. В этом объекте также есть поле queryStringParameters, которое в данном случае имеет значение NULL (очевидно, потому что мы не отправляли никаких параметров запроса в нашем запросе). Вот как это выглядит для меня:

Теперь давайте сделаем еще один вызов URL-адреса конечной точки нашего API через браузер, скопировав URL-адрес, который мы находим в разделе «Триггеры»:

Но вместо того, чтобы нажимать Enter, давайте добавим в наш запрос параметр запроса. Очевидно, мы можем называть этот параметр запроса как угодно, но давайте остановимся на кое-что значимом. Я выбрал name и присвоил ему значение pikachu. Итак, мой URL-адрес в браузере выглядел так: https://evdiwllvsf.execute-api.eu-central-1.amazonaws.com/default/KanyeWestFunction?name=pikachu

Давай нажимаем ввод. Очевидно, что на этом этапе мы по-прежнему получаем тот же ответ покемона ditto вместо pikachu, потому что мы не писали никакой логики внутри нашей Lambda для изменения попадания API на основе имени запрошенной сущности, но давайте все же посмотрим на наш последний Журнал CloudWatch:

Теперь мы видим, что queryStringParameters более значим, и мы действительно видим параметр запроса name, который мы добавили в наш запрос. Поскольку теперь мы можем получить к нему доступ, мы также можем использовать его для запроса понравившегося нам покемона с помощью PokeAPI. Давайте изменим код нашей лямбда-функции на этот:

const https = require('https');
exports.handler = async (event) => {
    console.log(event);
    let dataString = '';
    
    const response = await new Promise((resolve, reject) => {
        const { queryStringParameters } = event;
        if (!queryStringParameters || !queryStringParameters.name) {
            resolve({
                statusCode: 400,
                body: 'Please provide a Pokemon name!'
            })
        }
        const req = https.get(`https://pokeapi.co/api/v2/pokemon/${queryStringParameters.name}`, function(res) {
          res.on('data', chunk => {
            dataString += chunk;
          });
          res.on('end', () => {
            resolve({
                statusCode: 200,
                body: JSON.stringify(JSON.parse(dataString), null, 4)
            });
          });
        });
        
        req.on('error', (e) => {
          reject({
              statusCode: 500,
              body: 'Something went wrong!'
          });
        });
    });
    
    return response;
};

Давайте сохраним эти изменения и сделаем еще один вызов нашего URL-адреса API с ключом параметра строки запроса name и значением pikachu. Оно работает! На этот раз мы получаем успешный ответ с данными классического покемона Пикачу:

И если мы удалим параметр запроса name, мы получим ответ, который мы определили:

Шаг 5: Регистрация значимых данных

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

const https = require('https');
exports.handler = async (event) => {
    let dataString = '';
    
    const response = await new Promise((resolve, reject) => {
        const { queryStringParameters } = event;
        if (!queryStringParameters || !queryStringParameters.name) {
            console.log('BadRequest');
            resolve({
                statusCode: 400,
                body: 'Please provide a Pokemon name!'
            })
        }
        const req = https.get(`https://pokeapi.co/api/v2/pokemon/${queryStringParameters.name}`, function(res) {
            res.on('data', chunk => {
                dataString += chunk;
            });
            res.on('end', () => {
                console.log('OK');
                console.log(JSON.stringify({ name: queryStringParameters.name }));
                resolve({
                    statusCode: 200,
                    body: JSON.stringify(JSON.parse(dataString), null, 4)
            });
          });
        });
        
        req.on('error', (e) => {
            console.log('ServerError');
          reject({
              statusCode: 500,
              body: 'Something went wrong!'
          });
        });
    });
    
    return response;
};

В этих трех различных случаях мы регистрируем значимый вывод:

  1. Если этот пользователь сделал неверный запрос (без имени покемона)
  2. Если ответ был успешным и какой покемон запросил пользователь
  3. Ответ был неудачным

Я сохранил этот код, а затем сделал несколько запросов к нашему API, чтобы в CloudWatch было много информации о журналах. Мои журналы теперь выглядят так:

Шаг 6: Анализ наших данных

Теперь, чтобы проанализировать ситуацию, мы можем просто поискать в журналах. Например, я поискал BadRequest и обнаружил, что один пользователь сделал неверный запрос (без имени покемона). Хм? Интересно, кто это? В нашем случае, очевидно, единственный пользователь - это мы (вы!), Поэтому мы получаем только одну запись в журнале:

Но давайте представим, что наше приложение имело огромный успех и было несколько пользователей. Видеть кучу логов было бы совершенно бесполезно. Таким образом, нам следует перейти в подраздел Insights в подразделе Log groups на левой панели страницы. На этой странице давайте выберем имя нашей лямбда-функции из поля ввода поиска Select Log Group (s) вверху. Если вы затем нажмете «Выполнить запрос», не изменяя запрос по умолчанию, ответ должен выглядеть примерно так:

Мы можем изменить наш запрос на что-нибудь значимое; например, чтобы увидеть, сколько положительных ответов они получили. Теперь запрос будет выглядеть так:

fields @timestamp, @message
| filter @message like /OK/
| sort @timestamp desc
| limit 20

Результатом этого является:

Обратите внимание, что все ответы «ОК» появились совсем недавно. Это потому, что мы не регистрировали ОК внутри нашей лямбда-функции до нашей последней итерации кода. Давайте сделаем еще несколько запросов API для разных покемонов и повторим несколько. Затем дайте ему несколько минут на регистрацию. Теперь выполнение того же запроса вернет несколько результатов. Мы даже можем изменить лимит с 20 до 40, если захотим, или снять ограничение на количество результатов. И вместо того, чтобы запрашивать данные за последний 1 час, мы можем переключить его на 6 часов или на любой другой срок в правом верхнем углу ввода. Теперь, поскольку в нашем коде функции Lambda мы также регистрируем имя как строковый объект JSON, выполнив:

console.log(JSON.stringify({ name: queryStringParameters.name }));

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

fields @timestamp, name
| filter  ispresent(name)
| sort @timestamp desc

Результаты для этого гораздо более информативны:

Теперь мы можем увидеть разных покемонов, которых просили, и их имена.

Идти вперед

Мы также можем использовать этот инструмент Insights от AWS для создания визуализаций для нас, но это более сложно, и я чувствую, что в этом руководстве уже достаточно. Оставим это на другой день, и мы будем создавать диаграммы и визуализации на основе этих данных.