#баг дня
Преамбула
Некий сервис, назовём его Doodle Lampolytics, заставил всех своих клиентов переползти с модели подсчёта просмотров страниц на модель подсчёта событий.
Суть проблемы сервиса очевидна: постраничная модель слабо подходила для серьёзной аналитики данных в веб-приложениях. Даже элементарные лендинги стали гораздо сложнее, чем просто переходы по ссылкам.
И если раньше мы в нагрузку к «просмотру» передавали так называемые индивидуальные размерности и метрики, привязанные к сессии, то теперь — каждое событие может нести индивидуальный их набор.
Не в последнюю очередь это связано с ужесточением запретов на слежку за посетителями сайтов.
Поскольку в типичном проекте зачастую присутствует не одна сотня событий/страниц, то переезд сопровождается знатным перетряхиванием всей аналитики.
Суть
Суд да дело, приходим мы к идее передачи события с неким контекстом:
trackEvent('event_name', context) {}
В одном из
постов выше я уже показывал, что этот самый контекст (context) обогащает собой общие для всех событий параметры, превращая событийную модель в некое подобие сессионной.
Передаём туда, например, productId и orderId — и получаем трекинг истории конкретного продукта или заказа. Удобно, когда продукт сильно кастомизированный. Можно накинуть туда optionId и следить, какие продукты какими опциями нашпиговывают.
Таким образом, мы отсылаем объекты вида:
productId,
optionId,
orderId,
locale,
}
Потом объект из camelCase конвертируется в snake_case, как того требует документация, и дёргается некий API. Запомните этот момент.
И всё, поехали в продакшен. События уходят, счастье. Заодно, те же самые события, за некоторым исключением, можно пихать в сервисы вроде Sentry.
И всё бы ничего, но интерфейс Doodle Lampolytics настолько проклят, что оказалось намного проще выгружать данные оттуда сразу в Doodle LargeQuery и SQL-ем их запрашивать.
Но в какой-то момент времени мне это радикально надоело. Мне нравилась простота старого интерфейса, и я решил разобраться в новом. Проблема была в том, что я там не мог найти не только половину событий, но и их параметры.
Я почитал документацию, нашёл где создавать свои размерности и метрики на основе передаваемых параметров — в админке — захожу туда и наблюдаю следующую картину:
product_ıd,
option_id,
option_ıd
— Какого хера?! — заорал я, я же был в офисе.
Но параметры с ı вместо i исчислялись десятками, в то время как остальные — десятками тысяч. И я зачем-то успокоился. Подумал, что это косяк ламполитики, с которым можно разобраться попозже.
Но сегодня я зашёл в наш прекрасный чат, зачем-то ткнул в аватарку одного из подписчиков и увидел у него в истории некую песню с названием Bakı.
И тут до меня резко дошло, какой я идиот.
Прежде чем читать дальше, вы можете посмотреть на код функции, которой мы преобразуем camelCase в snake_case: https://github.com/jonschlinkert/snakecase/blob/master/index.js
Особо внимательные, наверное, уже догадались, в чём проблема. А ведь мы даже тесты написали.
Поскольку не все пройдут по ссылке, я приведу кусочек:
const lowercase = (input = '', options) => input.toLocaleLowerCase(options?.locale);
Ничего не смущает? Я подожду.
...
Да, правильно. Если забыть передать локаль (язык клиента, сервера в нашем случае), то будет взята переменная среды.
И для кого-то из наших клиентов в этой переменной оказался турецкий (или узбекский) язык.
В которых нет буквы i, вместо неё — ı.
Вы все правильно поняли, мы не полностью контролируем среду выполнения. Это так называемый Doodle AppsScript. Он исполняется на языке, в котором открыт офисный инструмент посетителя.
Естественно, мы локаль передать забыли. Хороший вопрос, почему мы вообще не проверили это поведение изначально... но тут как есть.
Стыдно? Стыдно. Зря я винил Doodle Lampolytics.
#бородач