Логирование событий

MapLibre GL JS строится вокруг событийной модели, в которой практически любое изменение состояния карты, источников данных, слоёв или пользовательского взаимодействия может быть зафиксировано через систему событий. Логирование этих событий используется для отладки, мониторинга производительности, диагностики ошибок и построения аналитики поведения карты в реальном времени.

События в MapLibre GL JS реализованы через паттерн EventEmitter: объект карты подписывается на события, а обработчики получают структурированные данные о происходящем.


Базовая модель логирования через события карты

Основной механизм регистрации событий — метод map.on. Он позволяет привязать обработчик к конкретному типу события.

map.on('load', (e) => {
  console.log('Карта загружена', e);
});

Каждое событие передаёт объект события, содержащий контекст: время, источник, координаты (если применимо), состояние карты.

Типовая структура логирования строится вокруг универсальной функции:

function logEvent(type, payload) {
  console.log(`[Map Event] ${type}`, {
    time: new Date().toISOString(),
    payload
  });
}

И применение:

map.on('load', (e) => logEvent('load', e));
map.on('error', (e) => logEvent('error', e));

События жизненного цикла карты

Жизненный цикл карты формирует основу наблюдаемого поведения.

load

Срабатывает после полной инициализации стиля и ресурсов.

map.on('load', () => {
  logEvent('load');
});

Используется для фиксации момента готовности карты к работе.


idle

Сигнализирует о завершении всех загрузок и рендеринга.

map.on('idle', () => {
  logEvent('idle');
});

Часто применяется для измерения времени полной стабилизации карты.


remove

Фиксирует уничтожение экземпляра карты.

map.on('remove', () => {
  logEvent('remove');
});

Критично для отслеживания утечек памяти.


События рендеринга и производительности

render

Срабатывает при каждом кадре отрисовки.

map.on('render', () => {
  logEvent('render');
});

Используется осторожно из-за высокой частоты вызовов.


data

Фиксирует изменения данных источников или стиля.

map.on('data', (e) => {
  logEvent('data', {
    dataType: e.dataType,
    sourceId: e.sourceId,
    isSourceLoaded: e.isSourceLoaded
  });
});

Позволяет отслеживать загрузку тайлов и обновления GeoJSON.


error

Ключевое событие для диагностики.

map.on('error', (e) => {
  logEvent('error', e.error || e);
});

В продакшене часто перенаправляется в систему мониторинга.


Логирование пользовательских взаимодействий

MapLibre GL JS предоставляет события взаимодействия с картой.

Перемещение карты

map.on('move', () => logEvent('move'));
map.on('movestart', () => logEvent('movestart'));
map.on('moveend', () => logEvent('moveend'));

Масштабирование

map.on('zoom', () => logEvent('zoom', { zoom: map.getZoom() }));
map.on('zoomstart', () => logEvent('zoomstart'));
map.on('zoomend', () => logEvent('zoomend'));

Поворот

map.on('rotate', () => logEvent('rotate', { bearing: map.getBearing() }));

Наклон

map.on('pitch', () => logEvent('pitch', { pitch: map.getPitch() }));

Логирование событий слоёв

Слои могут генерировать события при взаимодействии или изменении состояния.

map.on('click', 'points-layer', (e) => {
  logEvent('layer-click', {
    features: e.features,
    coordinates: e.lngLat
  });
});

Регистрация событий наведения:

map.on('mouseenter', 'points-layer', () => {
  logEvent('mouseenter-layer');
  map.getCanvas().style.cursor = 'pointer';
});

map.on('mouseleave', 'points-layer', () => {
  logEvent('mouseleave-layer');
  map.getCanvas().style.cursor = '';
});

Логирование источников данных

Источники данных (sources) являются ключевым объектом наблюдения.

map.on('sourcedata', (e) => {
  logEvent('sourcedata', {
    sourceId: e.sourceId,
    sourceDataType: e.sourceDataType,
    isSourceLoaded: e.isSourceLoaded
  });
});

Отдельный контроль загрузки тайлов:

map.on('data', (e) => {
  if (e.dataType === 'source' && e.sourceId === 'cities') {
    logEvent('cities-source-update', e);
  }
});

Структурированное логирование

При сложных системах обычный console.log заменяется на структурированные журналы.

const logger = {
  info: (event, data) => {
    console.info(JSON.stringify({
      level: 'info',
      event,
      data,
      time: Date.now()
    }));
  },
  error: (event, data) => {
    console.error(JSON.stringify({
      level: 'error',
      event,
      data,
      time: Date.now()
    }));
  }
};

Применение:

map.on('load', () => logger.info('map_load'));
map.on('error', (e) => logger.error('map_error', e));

Измерение производительности через события

Комбинация событий позволяет вычислять метрики.

Время загрузки карты

const start = performance.now();

map.on('load', () => {
  const duration = performance.now() - start;
  logger.info('map_load_time', { duration });
});

Время до полной стабилизации

const startIdle = performance.now();

map.on('idle', () => {
  const duration = performance.now() - startIdle;
  logger.info('map_idle_time', { duration });
});

Подписка и отписка от событий

Долгоживущие приложения требуют управления подписками.

function onMove() {
  logEvent('move');
}

map.on('move', onMove);

Удаление обработчика:

map.off('move', onMove);

Глобальная очистка:

map.remove();

Логирование событий с фильтрацией

При высокой частоте событий используется фильтрация.

let lastLog = 0;

map.on('render', () => {
  const now = Date.now();
  if (now - lastLog > 1000) {
    logEvent('render_throttled');
    lastLog = now;
  }
});

Группировка событий

Для анализа последовательностей событий применяется группировка.

console.group('Map interaction');

map.on('movestart', () => console.log('start'));
map.on('moveend', () => console.log('end'));

console.groupEnd();

Интеграция с внешними системами аналитики

События карты часто отправляются в внешние системы.

map.on('click', (e) => {
  fetch('/analytics', {
    method: 'POST',
    body: JSON.stringify({
      event: 'map_click',
      coordinates: e.lngLat,
      zoom: map.getZoom()
    })
  });
});

Отладка источников ошибок рендеринга

Ошибки рендеринга часто связаны с отсутствующими ресурсами или неверной конфигурацией стиля.

map.on('error', (e) => {
  if (e.error && e.error.status === 404) {
    logEvent('missing_resource', e);
  }
});

Логирование пользовательских сценариев

Комбинация событий позволяет восстанавливать поведение пользователя.

map.on('movestart', () => logEvent('user_navigation_start'));
map.on('moveend', () => logEvent('user_navigation_end'));

map.on('zoomstart', () => logEvent('user_zoom_start'));
map.on('zoomend', () => logEvent('user_zoom_end'));

Контроль избыточного логирования

Высокочастотные события требуют ограничения:

const debounce = (fn, delay) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
};

map.on('move', debounce(() => {
  logEvent('move_debounced');
}, 200));

Отладочные режимы

Условное логирование позволяет разделять dev и prod окружения.

const DEBUG = true;

function debugLog(event, data) {
  if (!DEBUG) return;
  console.log(event, data);
}

map.on('load', () => debugLog('load'));

Системное наблюдение за состоянием карты

Регулярный опрос состояния карты дополняет событийную модель.

setInterval(() => {
  logEvent('state_snapshot', {
    zoom: map.getZoom(),
    center: map.getCenter(),
    bearing: map.getBearing(),
    pitch: map.getPitch()
  });
}, 5000);

Логирование ошибок источников тайлов

map.on('data', (e) => {
  if (e.tile && e.tile.state === 'errored') {
    logEvent('tile_error', {
      source: e.sourceId,
      tile: e.tile
    });
  }
});

Расширенные сценарии аудита событий

Полный аудит взаимодействия включает захват всех ключевых точек:

const events = [
  'load',
  'idle',
  'error',
  'move',
  'zoom',
  'rotate',
  'pitch',
  'data',
  'render'
];

events.forEach((event) => {
  map.on(event, (e) => {
    logEvent(`audit_${event}`, e);
  });
});

Корреляция событий во времени

Для анализа последовательностей вводится идентификатор сессии:

const sessionId = crypto.randomUUID();

function log(event, data) {
  console.log({
    sessionId,
    event,
    data,
    timestamp: performance.now()
  });
}

Логирование взаимодействия с источниками GeoJSON

map.on('data', (e) => {
  if (e.sourceId === 'geojson-points') {
    logEvent('geojson_update', {
      loaded: e.isSourceLoaded,
      type: e.dataType
    });
  }
});

Наблюдение за динамическими слоями

map.on('styledata', () => {
  logEvent('style_updated');
});

Системная модель централизованного логирования

class MapLogger {
  constructor(map) {
    this.map = map;
    this.bindEvents();
  }

  bindEvents() {
    this.map.on('load', (e) => this.log('load', e));
    this.map.on('error', (e) => this.log('error', e));
    this.map.on('move', () => this.log('move'));
    this.map.on('zoom', () => this.log('zoom', this.map.getZoom()));
  }

  log(event, data) {
    console.log(JSON.stringify({
      event,
      data,
      time: Date.now()
    }));
  }
}