Сайт: посетители по логам nginx
Дашборд «Сайт: посетители» считает посещаемость сайта по access-логу nginx (или apache): люди, просмотры, источники, реклама, воронка до заявки, битые ссылки. Счётчика на страницах не нужно, данные не уходят третьим лицам и хранятся у вас.
Что нужно
- Access-лог в стандартном формате combined (по умолчанию у nginx и apache) должен попадать в Voltir:
- nginx в Docker на хосте с агентом — уже собирается (логи контейнеров);
- nginx или apache из пакета — портал → «Файловые логи» → кнопка nginx или apache, формат «nginx», см. Журналы.
- Если перед сайтом стоит прокси или балансировщик, nginx сайта должен видеть настоящий адрес посетителя, иначе все посетители превратятся в один IP прокси:
set_real_ip_from 10.0.0.5; # адрес вашего прокси real_ip_header X-Forwarded-For; # или X-Real-IP — что передаёт прокси
Необязательно, но полезно — дописать в конец log_format имя сайта и время ответа. Тогда можно разделить несколько сайтов одного nginx и увидеть медленные страницы:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'rt=$request_time vhost=$host';
access_log /var/log/nginx/access.log main;
Поля после стандартной строки агент 1.6.1+ разбирает в любом порядке.
Как считаются цифры
- Люди — уникальные IP, которые за период открыли страницу сайта и загрузили её стили, скрипт или шрифт. Боты, сканеры и мониторинги так не делают и не считаются, даже если представляются браузером.
- Просмотры — загрузки страниц этими людьми.
- Из рекламы — заходы по ссылкам с
utm_medium=cpc; кампании, фразы и объявления берутся из метокutm_campaign,utm_term,utm_content. - Заявки — отправки формы (POST на адрес формы), принятые сайтом с ответом 2xx; отклонённые показаны отдельно.
Настройка дашборда
Все параметры — переменные вверху дашборда. Значения по умолчанию подходят большинству сайтов; поменяйте нужные и сохраните дашборд или ссылку на него.
| Переменная | По умолчанию | Что задаёт |
|---|---|---|
| Хост с nginx сайта | первый в списке | сервер, где работает nginx сайта |
| Сайт (регэксп) | пусто — все сайты | имя сайта из поля vhost=, точку писать как [.]: ^(www[.])?example[.]ru$ |
| Источник логов | nginx в Docker и файлы *access* | сузить: container:nginx или file:access.log |
| Что считать страницей | /, пути на /, .html, .php и одно слово без точки (/about) | регэксп пути страницы — не картинки и не API |
| Адрес формы | /feedback, /contact, /order, /lead, /callback, /form, /send (и то же под /api/) | куда форма отправляет заявку |
| Шаг 2 и шаг 3 воронки | демо или цены; контакты | страницы промежуточных шагов и их подписи на панели «Воронка» |
Что на дашборде
- Посещаемость, Форма заявки, Люди и просмотры по дням;
- Воронка — открыли сайт → шаг 2 → шаг 3 → отправили форму → заявка принята;
- Устройства — телефоны, планшеты, компьютеры;
- Время на сайте и Длительность визитов — по маячку страниц (ниже);
- свёрнутые разделы: Страницы и откуда пришли (с временем ответа), Время на страницах, Реклама: кампании и фразы, Форма и битые ссылки (ответы 4xx/5xx по путям).
Свёрнутые разделы не запрашивают данные, пока их не раскроют, — дашборд открывается быстро даже на большом сайте.
Время на сайте: маячок страниц
По одному access-логу нельзя понять, сколько человек читал страницу. Для этого на страницы добавляется маленький скрипт: он считает активное время (вкладка видна и человек что-то делал за последнюю минуту) и раз в 15 секунд сообщает его запросом /e?…. Запрос попадает в тот же access-лог — дашборд считает по нему визиты, медиану и среднее время, отказы (визит из одной страницы короче 15 секунд) и глубину просмотра, как в отчёте Метрики «Время на сайте».
1. nginx — ответить на маячок пустым ответом (в server сайта):
location = /e {
expires -1;
return 204; # запрос пишется в access-лог сайта, его и читает агент
}
2. Скрипт — перед </body> на всех страницах:
<script>
(function () {
if (navigator.webdriver) return;
var now = function () { return Date.now(); };
var rnd = function () { return Math.random().toString(36).slice(2, 10); };
var visit;
try {
var s = JSON.parse(sessionStorage.getItem('vt_visit') || 'null');
visit = s && s.id && now() - s.at < 1800000 ? s.id : rnd();
} catch (e) { visit = rnd(); }
function touch() { try { sessionStorage.setItem('vt_visit', JSON.stringify({ id: visit, at: now() })); } catch (e) {} }
touch();
var view = rnd(), path = location.pathname, active = 0, lastInput = now(), tick = now(), sent = -1, closed = false;
function engaged() { return document.visibilityState === 'visible' && now() - lastInput < 60000; }
function account() { var t = now(); if (engaged()) active += t - tick; tick = t; }
function send(ev) {
var a = Math.round(active / 1000);
if (ev === 'ping' && a === sent) return;
sent = a;
var url = '/e?v=' + visit + '&p=' + view + '&e=' + ev + '&a=' + a + '&u=' + encodeURIComponent(path);
try {
if (!(navigator.sendBeacon && navigator.sendBeacon(url)))
fetch(url, { method: 'POST', keepalive: true, credentials: 'omit' })['catch'](function () {});
} catch (e) {}
}
['pointermove', 'pointerdown', 'keydown', 'scroll', 'wheel', 'touchstart'].forEach(function (t) {
addEventListener(t, function () { if (!engaged()) tick = now(); lastInput = now(); touch(); }, { passive: true });
});
send('load');
var timer = setInterval(function () {
account();
if (active > 1800000) { clearInterval(timer); return; } // 30 минут на странице — дальше это забытая вкладка
if (engaged()) send('ping');
}, 15000);
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'hidden') { account(); send('hide'); } else { tick = now(); lastInput = now(); }
});
addEventListener('pagehide', function () { if (closed) return; closed = true; account(); send('leave'); });
})();
</script>
sessionStorage вкладки, обнуляется после 30 минут бездействия), случайный номер просмотра, адрес страницы и число секунд. Упомяните его в политике конфиденциальности сайта рядом с серверными логами. Если на сайте строгая CSP, разрешите скрипт так же, как остальные свои скрипты.