Логирование хитов (payload) Google Analytics в Google Sheets через Google Tag Manager

Задача: определить какие события превышают payload size Google Analytics
Решение: логирование хитов Google Analytics (включая payload size) в Google Sheets при помощи Google Tag Manager, без участия разработчиков

Суть проблемы


Если вам доводилось имплементировать Enhanced Ecommerce для Google Analytics (GA) через Google Tag Manager (GTM) и затем дебажить это дело с помощью Google Analytics debugger, то вероятно вы сталкивались с тем, что некоторые события «почему-то» не доходят в GA и появляется ошибка: Payload size is to large (9000). Max allowed is 8192



Почему так происходит?


Дело в том, что библиотека analytics.js не принимает хиты более 8192 байт. Если размер хита больше, то в GA он не дойдет и в отчетах по событиям будет пусто.

Пример ситуации:

Веб аналитик просит разработчика запихинуть (или сам запихивает) все product impressions в один хит. В результате хит не доходит т.к. в листинге на одной странице более 50 товаров. Или в корзину добавляется 50+ различных товаров, в результате возникают проблемы с событиями checkout step и transaction.

Что нужно делать


Старайтесь всегда оптимизировать структуру данных хита (т.е. например, отправлять хиты по мере появления товара в поле зрения юзера, не пихать в хит ненужные переменные (variant, category, brand), не использовать длинных названий товаров и т.п.) — это, во-первых, увеличит скорость отправки хита; во-вторых, позволит избежать проблем с payload size.

Если оптимизировать невозможно, то существует несколько способов обойти эти ограничения:


Подготовка к действиям


Прежде чем приступить к нарезке хитов и оптимизации контента, определим, какие именно события превышают payload size.

Шаг 1. Настройка Google Sheets


Создаем новую таблицу.

В хедер (1 строка), записываем имена параметров, которые хотим извлечь из хита (будьте внимательны, названия указываете в точности такие же, которые затем будете использовать в JS скрипте в GTM). В качестве примера извлечем такие данные:

  • timestamp
  • payLoadLength
  • tid (tracking id)
  • cid (client id)
  • uid (user id)
  • t (type of hit)
  • pa (product action)
  • ni (non interaction)
  • dl (document location)
  • dp (document path)
  • dt (document title)
  • ec (event category)
  • ea (event action)
  • el (event label)
  • ti (transaction id)
  • tr (transaction revenue)

image
Пример мапинга колонок для логирования payload в Google Sheet

Порядок параметров в колонках неважен (кроме timestamp — она должен быть первым). Колонки с параметрами cid и ti отформатируйте как Plain text (Format > Number > Plain text), во избежание ошибок с автоформатированием.
При желании можете добавить/убрать необходимые параметры, (не забудьте затем изменить список переменных в JS скрипте в GTM). Список возможных полей и параметров analytics.js

Далее, открываем script editor и добавляем код (оригинал скрипта принадлежит Martin Hawksey https://gist.github.com/mhawksey/1276293):

function doGet(e){
  return handleResponse(e);
}
 
function doPost(e){
  return handleResponse(e);
}
 
function handleResponse(e) {
  var lock = LockService.getPublicLock();
  lock.waitLock(30000);  // wait 30 seconds before conceding defeat.
   
  try {
    // next set where we write the data - you could write to multiple/alternate destinations
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = ss.getSheets()[0];     
    // we'll assume header is in row 1 but you can override with header_row in GET/POST data
    var headRow = e.parameter.header_row || 1;
    var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    var nextRow = sheet.getLastRow()+1; // get next row
    var row = []; 
    // loop through the header columns
    for (i in headers){
      if (headers[i] == "timestamp"){ // special case if you include a 'timestamp' column
        row.push(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MMM d yyyy HH:mm:ss"));
      } else { // else use header name to get data
        row.push(e.parameter[headers[i]]);
      }
    }
    // more efficient to set values as [][] array than individually
    sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
    // return json success results
    return ContentService
          .createTextOutput(JSON.stringify({"result":"success", "row": nextRow}))
          .setMimeType(ContentService.MimeType.JSON);
  } catch(e){
    // if error return this
    return ContentService
          .createTextOutput(JSON.stringify({"result":"error", "error": e}))
          .setMimeType(ContentService.MimeType.JSON);
  } finally { //release lock
    lock.releaseLock();
  }
}

Разворачиваем скрипт как веб приложение (Publish > Deploy as web app...). Права доступа — anyone, even anonymous. Публикуем.


Настройки Google Script editor

В дальнейшем нам будет нужен URL нашего веб приложения (Current web app URL), поэтому вкладку пока не закрывайте.

Шаг 2. Настройка GTM


Создадим 2 custom JavaScript variables:

  1. v_EE_timestamp
  2. v_EE_mimic GA payload


Пример JavaScript variable в GTM

Первая JS variable, для определения времени отправки хита (timestamp). Эта переменная будет использоваться во второй JS variable.

function() {
    // Get local time as ISO string with offset at the end
    var now = new Date();
    var tzo = -now.getTimezoneOffset();
    var dif = tzo >= 0 ? ' Timezone: +' : ' Timezone: -';
    var pad = function(num) {
        var norm = Math.abs(Math.floor(num));
        return (norm < 10 ? '0' : '') + norm;
    };
    return now.getFullYear() 
        + '-' + pad(now.getMonth()+1)
        + '-' + pad(now.getDate())
        + ' Time' + pad(now.getHours())
        + ':' + pad(now.getMinutes()) 
        + ':' + pad(now.getSeconds())
        + dif + pad(tzo / 60) 
        + ':' + pad(tzo % 60);
}

Вторая JS variable, для отлова нужных хитов и передачи их в Google Sheet (за основу взят код из этого материала).

function sendHitTask(){
  return function(model) {
    var payLoad = model.get('hitPayload');                     
    var trackingBaseUrls = ['https://www.google-analytics.com/collect', 'https://script.google.com/macros/s/AKfycbxJLy3eYBLpPu_S_eNccxzn_GwHXkZWr-93feMuBaAZelk3fj01yB/exec'];
 
    for (i = 0; i < trackingBaseUrls.length; i++) { var baseUrl = trackingBaseUrls[i]; if (trackingBaseUrls[i].indexOf('collect') > -1) {
          var req = new XMLHttpRequest();
          req.open('POST', baseUrl, true);
          req.send(payLoad);
      } else if (payLoad.length > 7500){
        var payLoadExtract = payLoad.split('&');
        var payLoadArray = {};
        // Push values to array for later access
          for (i = 0; i < payLoadExtract.length; i++){
                  var splitArray = payLoadExtract[i].split('=');
                  payLoadArray[splitArray[0].trim()] = splitArray[1].trim();
          }
            // Specify values to be sent to Google Sheets from array
            var tid = 'tid=' + payLoadArray.tid,
            cid = '&cid=' + payLoadArray.cid,
            uid = '&uid=' + payLoadArray.uid,
            t = '&t=' + payLoadArray.t,  
            pa = '&pa=' + payLoadArray.pa,
            ni = '&ni=' + payLoadArray.ni,
            dl = '&dl=' + payLoadArray.dl,
            dp = '&dp=' + payLoadArray.dp,
            dt = '&dt=' + payLoadArray.dt,                                      
            ec = '&ec=' + payLoadArray.ec,                                      
            ea = '&ea=' + payLoadArray.ea,                                      
            el = '&el=' + payLoadArray.el,                                      
            ti = '&ti=' + payLoadArray.ti,                                      
            tr = '&tr=' + payLoadArray.tr,                                      
            timestamp = '&timestamp=' + {{v_EE_timestamp}},
            payLoadLength = '&payLoadLength=' + payLoad.length;
            var collectPayLoad = tid + cid + uid + t + pa + ni + dl + dp + dt + ec + ea + el + ti + tr + timestamp + payLoadLength;
        // Send Values to Google Sheets
        var collectUrl = baseUrl +'?'+ collectPayLoad;
        var myImage = new Image();
        myImage.src = collectUrl;
      }
    }
  }
}

Что необходимо настроить:

  • В переменную trackingBaseUrls вставляем url вашего веб приложения (созданного в шаге 1) и url google analytics.
  • В payLoad.length определяем размер хита, который необходимо ловить. В примере установлено >7500 т.е. ловим всё что потенциально может превысить порог 8192 байт. Можно поставить меньше, если интересен весь лог.
  • В req.open определяется метод отправки хита (может быть POST или GET, в зависимости от размера хита; Google рекомендует использовать POST т.к. это позволяет отправлять более крупный payload). По умолчанию события в GA отправляются через GET. Для Enhanced Ecommerce рекомендую изменить способ отправки на POST (делается путем добавления в тег Enhance Ecommerce в Fields to set: transport — beakon)

Теперь, находим в своем контейнере тег(теги) отвечающие за отправку событий Enhanced Ecommerce в GA и создаем их копию с привязкой к соответствующим триггерам.
В копии тегов изменяем GA ID на любой (тестовый/фейковый) и добавляем в Fields to Set поле sendHitTask и название JS variable (v_EE_mimic GA payload).
sendHitTask, в данном случае, модифицируется т.е. мы отправляем данные о хите в тестовый GA (можно и не отправлять, для этого удалите в коде значение 'https://www.google-analytics.com/collect',) и в Google Sheet (если он превышает заданный payload size).
Копия тегов с тестовым GA ID нужна для подстраховки, чтобы не трогать оригинальный Enhanced Ecommerce тег. Можно модифицировать sendHitTask и в оригинальном теге (без создания копий), но тогда нельзя будет использовать customTask (его придется модифицировать интегрируя в него sendHitTask) и появляется риск, что хит не дойдет в GA (у меня такого не встречалось, но на всякий случай лучше подстраховаться).


Настройки тега Enhanced Ecommerce в GTM

Сохраняем тег, публикуем новую версию GTM контейнера.

Итог


Теперь, при срабатывании тега Enhance Ecommerce, также будет отрабатывать скрипт v_EE_mimic GA payload. Если при заданных настройках payload превышает свои значения, произойдет запись этого хита в Google Sheet.

Собирая логи по хитам, можно определить какое конкретно событие не дошло в GA, где это произошло, в каком браузере и т.д. (см. список возможных полей и параметров analytics.js).

Спасибо за внимание!

Надеюсь, данный материал вам пригодится и облегчит жизнь с дебагом Google Analytics.

P.S. Кто умеет/знает как автоматизировать проверку GTM тегов (автотесты для тегов), пожалуйста отзовитесь! Когда-то давно Симо написал статью про это www.simoahava.com/analytics/automated-tests-for-google-tag-managers-datalayer но разобраться в ней не смог, может кто пробовал/знает другие способы. Буду очень благодарен советам и помощи в этом вопросе.
Поделиться публикацией

Комментарии 2

    0
    payload — это все параметры, которые уходят запросом в GA.
    почему тогда в таблицу не собирать их все?

    Я тут habr.com/post/353836 описывал упрощенный скрипт по отправке всех параметров.
    Не хватает только if (payLoad.length > 7500) под ваше решение.
      0
      Спасибо за дополнение, интересный опыт. При проблеме с семплированием пробовал выгружать в BigQuery через OWOX, но вот через GTM не доводилось такое проделывать. Хороший вариант если есть свое хранилище и отдавать деньги в BigQuery, OWOX не хочется.

      почему тогда в таблицу не собирать их все?
      В данном случае, хотел сконцентрироваться конкретно на дебаге Enhance Ecommerce т.е. выгружать все параметры не вижу смысла.

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

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