Отправка Nginx-логов в Google Analytics

    image

    С наших Download-серверов каждый день скачивается несколько миллионов драйверов (статичных .exe и .zip файлов). Для анализа поведения пользователей перед нами встала задача посчитать следующие параметры: когда, сколько, как часто и даже кто именно скачивает драйверы.

    Самым очевидным решением было бы использовать инструменты типа AWstat, GoAccess, ELK stack или Splunk, а в крайнем случае собирать логи Nginx.

    Но у каждого варианта есть свои минусы: неудобный интерфейс, скудность данных, сложность настройки и, самое главное, отсутствие возможности строить сегменты в пользовательских отчётах.

    И тогда мы решили заставить Nginx самостоятельно отправлять события в Google Analytics сразу же после скачивания файла. Мы также смогли передать в GA уникальный идентификатор пользователя ClientID.
    В результате мы получили аналитику по статичным файлам, к которым раньше невозможно было привязать счетчик GA.

    Под катом готовый конфиг и примеры работы нашей системы.

    В прошлом посте мы рассказали о том, как отправляем события из приложения DriverPack Online, используя Analytics Measurement Protocol.

    Ну а сегодня мы покажем, как трекаются загрузки статических файлов на наших Download-серверах.

    Информация по загрузкам приходит в режиме «реального времени».
    image

    Работает «из коробки»


    Теперь можно мониторить, сколько реальных скачиваний нашего продукта происходит с серверов. Причём подсчет уникальных загрузок производится в разы точнее, чем если делать это по IP-адресу, т.к. к каждому пользователю привязан уникальный идентификатор — ClientID.

    image

    Ошибки 404 и 500 отслеживаются через события (events).

    image

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

    image

    Отчётам «по поведениям» можно верить, так как пробрасывается реальный ClientID пользователя, что позволяет оценить:
    • число новых и вернувшихся пользователей,
    • периодичность посещений пользователем и время с его последнего посещения,
    • вовлечение пользователей,
    • по каким ключевым словам пользователь пришел к нам на сайт с самого начала.

    Nginx сам передаёт правильный User-Agent, что дает возможность построить отчеты по браузерам и ОС. В отчете можно встретить wget, которым наш DriverPack Online выкачивает драйверы, реальные браузеры, а также роботов и всяких парсеров.

    image

    Для кого-то окажется очень ценной информация о загрузках с мобильных устройств.

    image

    К большому сожалению, referrer передать в GA мы пока не можем, т.к. Nginx не поддерживает urlencode().
    Поэтому отчёты по каналам работать не будут (подробности в конце поста).

    image

    Как настроить так же? Инструкция


    1. Создаем отдельно счётчик GA, с которым будут работать все Download-серверы. Или используем номер существующего счетчика.

    2. В настройках счётчика “Пользовательские параметры” добавляем специальные параметры:
    • dimension1. Название “ClientID”, уровень “Пользователь”;
    • dimension2. Название “request_time”, уровень “Hit”;
    • dimension3. Название “body_bytes_sent”, уровень “Hit”.


    Это поможет нам рассчитать скорость закачки и даже процент оборванных скачиваний.

    image

    3. Создаём конфиг с названием «google-analytics» в директории "/etc/nginx/"

    # Номер нашего счетчика и основного домена
        set $gaID 'UA-XXXXXX-1';
        set $mainDomain 'example.com';
        
        # Пробрасываем ClientID через cookie "_ga_cid"
        # Важно, чтобы cookie была установлена для всех субдоменов
        set $cid $cookie__ga_cid;
        if ($cid = '') {
        	
    
    		set $cid "$request_time$request_length.$msec$connection";
    		    
        }
        add_header Set-Cookie "_ga_cid=$cid; path=/; domain=.$mainDomain" always;
    
    	
        set $postURI $uri;
        set $postRequestURI $request_uri;
        set $postIP $remote_addr;
        set $postHOST $host;
        
        
        # Отправка события pageview
        location @GAlog {
          resolver 8.8.8.8 ipv6=off
          internal;
          
          proxy_ignore_client_abort on;
          proxy_next_upstream timeout;
          
          proxy_pass http://google-analytics.com/collect?v=1&dh=$postHOST&dt=$postHOST&tid=$gaID&cid=$cid&cd1=$cid&uip=$postIP&cd2=$request_time&cd3=$body_bytes_sent&t=pageview&dp=$postURI;
        }
        
        # Отправка события 404
        location @GAlog404 {
          resolver 8.8.8.8 ipv6=off;
          internal;
          
          proxy_ignore_client_abort on;
          proxy_next_upstream timeout;
          
          proxy_pass http://google-analytics.com/collect?v=1&tid=$gaID&cid=$cid&cd1=$cid&uip=$postIP&t=event&el=nginx&ec=404&ea=$postHOST$postRequestURI;
        }
        
        # Отправка события 500
        location @GAlog500 {
          resolver 8.8.8.8 ipv6=off;
          internal;
          
          proxy_ignore_client_abort on;
          proxy_next_upstream timeout;
          
          proxy_pass http://google-analytics.com/collect?v=1&tid=$gaID&cid=$cid&cd1=$cid&uip=$postIP&t=event&el=nginx&ec=500&ea=$postHOST$postRequestURI;
        }
    


    4. Получаем конфигурационный файл “/etc/nginx/conf.d/default.config” (на примере нашего)

    server {
        include google-analytics;
    
        listen       80;
        server_name  localhost;
        
        autoindex on;
        autoindex_exact_size off;
    
        access_log  off;
    
        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
            
            post_action @GAlog;
        }
        
        
        error_page  404              /404.html;
        location = /404.html {
            root   /usr/share/nginx/html;
            post_action @GAlog404;
        }
    
    
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
            post_action @GAlog500;
        }
    	
    }
    


    5. Перезапускаем nginx

    $ sudo service nginx reload
    


    6. Настраиваем счетчик на сайте таким образом, чтобы ClientID сохранялся в Cookie (не забудьте подставить номер своего счетчика и имя главного домена).

    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
    
      ga('create', 'UA-XXXXXX-1', 'auto');
    
      ga(function(tracker) {
        var clientId = tracker.get('clientId'); // Get clientId from Google Analytics
        document.cookie = "_ga_cid=" + clientId + "; path=/; domain=.<ИМЯ ОСНОВНОГО ДОМЕНА>"; // Write cookie for web-server
        ga('set', 'dimension1', clientId); // Write clientId in custom dimension
      });
    
      ga('require', 'displayfeatures')
      ga('send', 'pageview');
    
    </script>
    


    Напоследок о точности


    Точность статистики до 99%! Мы проанализировали несколько файлов и сравнили данные GA с данными из логов.

    image

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

    Недостатки


    Скрипт работает отлично и на 100% соответствует нашим требованиям, но в него можно добавить несколько улучшений:
    1. Скрипт немного грузит сервер, но для нас это совсем не критично.
    2. Nginx не поддерживает urlencode(), поэтому ссылки вида example.com/?SomeOptions будут биться. Одним из способов решения данной проблемы является использование lua-скрипта.
    3. Не передаётся referrer в параметр GA (также необходимо использование urlencode()).
    4. В Nginx 1.8 не работает переменная $content_length, поэтому мы не можем передать в GA размер файла. Этот параметр позволил бы делать отчёты, содержащие информацию о проценте недокачанных файлов.
    5. Отправлять служебную информацию из Nginx. Например, количество коннектов и т.п.
    6. Можно было бы отправлять время закачки напрямую в Google Analytics, используя параметр &plt, но Nginx возвращает время в секундах, а GA такой формат не устраивает (ожидаются миллисекунды). Поэтому приходится отправлять эти данные в dimension2.
    7. Скрипт использует недокументированную функцию post_action. Существует риск, что в новых версиях эта функция будет устранена.


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

    Ну и заранее – спасибо!
    Веб-аналитика на Nginx+GA? Это:

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

    DriverPack Solution 26,38
    Компания
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Похожие публикации
    Комментарии 14
    • +1
      А вы отправляйте не нгинксом напрямую а чем то другим, что умеет логи читать, преобразовывать их и знает urlencode.

      Proxy-pass здесь по-мойму лишний, хотя сходу красиво смотрится.
      • +2
        Вот именно proxy_pass и перенаправляет запрос на GA. Можно там прописать не прямую ссылку на GA, а свой php скрипт и в нём уже порешать эти проблемы. Но это показалось лишним звеном в системе…
      • +3
        Вся соль в использовании опции post_action — она дергает указанный путь по окончании обработки запроса.
        wiki.nginx.org/HttpCoreModule#post_action (почему-то её нет на обычной страничке с директивами в документации)
        • +5
          А почему бы не воспользоваться Lua?
          Некоторое назад писал уже про пример использования: habrahabr.ru/post/215295
          • +1
            Как раз хотел предложить автору использовать Lua-модуль или встроенный перл, котрые бы как раз помогли бы решить 146% описанных в конце статьи проблем :)
            • +1
              В стате есть упомянание про lua. Но хотелось использовать только стандартные модули Nginx. Этот конфиг можно использовать даже на shared-хостингах.

              Если кто-то возьмётся за доработку конфига на lua, то будет здорово!
              • +1
                Ну, как я заметил, «встроенный Perl» таки является стандартным модулем который можно использовать везде :) Правда, Perl я знаю несколько хуже, чем Lua, увы ;)
                • +1
                  Тогда я голосую за Perl-реализацию :))

                  А на lua уже есть пример, мне его прислал Николай Шардин (сотрудник Nginx в кремниевой долине):

                  location / {
                  set_by_lua $urlencode '
                  local str = ngx.var.uri
                  str = string.gsub (str, "([^%w ])",
                  function © return string.format ("%%%02X", string.byte©) end)
                  str = string.gsub (str, " ", "+")
                  return str';
                  return 200 «your URI is $uri
                  encoded to $urlencode»;
                  }

                  # curl -v 10.2.2.65/привет
                  * Hostname was NOT found in DNS cache
                  * Trying 10.2.2.65…
                  * Connected to 10.2.2.65 (10.2.2.65) port 80 (#0)
                  > GET /привет HTTP/1.1
                  > User-Agent: curl/7.35.0
                  > Host: 10.2.2.65
                  > Accept: */*
                  >
                  < HTTP/1.1 200 OK
                  * Server nginx/1.7.11 is not blacklisted
                  < Server: nginx/1.7.11
                  < Date: Fri, 05 Jun 2015 08:40:21 GMT
                  < Content-Type: text/html
                  < Content-Length: 82
                  < Connection: keep-alive
                  <
                  * Connection #0 to host 10.2.2.65 left intact
                  your URI is /привет
                  encoded to %2F%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82
                  • +1
                    А на lua уже есть пример, мне его прислал Николай Шардин (сотрудник Nginx в кремниевой долине):


                    * Сори, опечатка: Николай Шадрин
          • +5
            nginx поддерживает urlencode — с помощью модуля set_misc, github.com/openresty/set-misc-nginx-module#set_escape_uri
            кстати добавить свою функцию к nginx намного проще, чем многие думают.
            • +3
              По-моему с post_action есть определенного рода проблемы — nginx не закрывает соединение с клиентом до завершения подзапроса, и были еще какие-то нюансы с кодом ответа. Механизм неплох, позволяет очень многое, но недокументирован он, как мне кажется, не зря.
              • 0
                Верно. Поэтому его не следует использовать для других решений кроме как для скачиваний, где коннект рвется сразу после окончания. Если у тебя keep-alive, то процесс nginx при этом будет блокироваться.
                Хотя это и не сильно большая проблема, так как GA очень быстро отвечает на запросы, хоть и не мгновенно.
              • +1
                Смотрю на скриншот с юзер-агентами.
                А почему в целых 86.73% случаев значение (not set)? Данные где-то теряются?
                • +1
                  Нет, это наш клиент выкачивает без указания UserAgent. Данные вообще не теряются, это очень здорово! :)
                  Не один раз сверяли данные, всё 1 в 1.
                  Даже делали серьезное нагрузочное тестирование и всё отлично!

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

                Самое читаемое