Подключаем логгер к проектам на JavaScript

Подключаем логгер к проектам на JavaScript

Следим за каждым шагом программы

В прошлый раз мы разобрали, что такое логгеры и зачем они нужны. Вот короткая версия:

  • Логгер — это часть программы, которая реагирует на нужные нам события и записывает их, как бы ведёт летопись работы программы.
  • Эти записи называются логами. Они могут появляться в консоли, записываться в файл или отправлять в другую программу.
  • Логи нужны, когда надо посмотреть, что в программе пошло не так, или если надо восстановить хронологию событий.
  • Есть несколько уровней логирования: сообщения об ошибках, предупреждения, штатные срабатывания и разовые ситуации.
  • Если проект сложный, лучше использовать готовый логгер, чем делать свой.

Сегодня мы попробуем логгер в деле — подключим его к цветному арканоиду на JavaScript и посмотрим, что будет в логах после игры.

js-Logger — простой логгер для браузеров

Если нам нужен простой логгер для консоли браузера с делением на уровни сообщений, то проще всего использовать js-Logger. Он подключается как обычный скрипт на HTML-странице:   

<script src="https://github.com/jonnyreeves/js-logger/blob/master/src/logger.js"></script>

Сразу после этого его можно использовать для логирования разных сообщений:

// ставим настройки по умолчанию
Logger.useDefaults();

// и выводим тестовые сообщения разных уровней
Logger.debug("Это обычное сообщение о каком-то событии, например для отладки");
Logger.info("Информационное сообщение, его можно привязать к разовому событию");
Logger.warn("А вот это уже серьёзно, тут логгер нас о чём-то предупреждает. Ничего критичного, но присмотреться стоит");
Logger.error("Ошибка — что-то в программе пошло не так");
Подключаем логгер к проектам на JavaScript

Смотрим уровни логирования

В браузере Chrome можно настроить в консоли типы сообщений, которые мы хотим видеть. По умолчанию мы видим все события:

Подключаем логгер к проектам на JavaScript

Если нам нужны только предупреждения и ошибки, то ставим галочку на Warnings (предупреждения) и Errors (ошибки). Браузер отфильтрует эти события и скроет всё остальное, но при этом сами логи никуда не исчезнут — мы просто перестанем их видеть. Это работает с любым логгером, который поддерживает разные уровни сообщений:

Подключаем логгер к проектам на JavaScript

Добавляем логгер в проект

Теперь расставим сообщения логгера в разных местах в коде арканоида и посмотрим, что будет в консоли, если мы немного в неё поиграем. Мы не будем комментировать каждый шаг, а добавим вызов логгера в ключевых местах: при касании платформы, нажатии игроком на кнопки, потере или добавлении жизней и при вылете шарика за пределы поля. Чтобы было интереснее, сделаем события на разных уровнях. Единственное, чего у нас не будет, — сообщений об ошибках, потому что мы не обрабатывали их в исключениях.

Поначалу кажется, что всё нормально и с игрой всё в порядке: мы видим каждое нажатие клавиши и реакцию игры на все события:

Подключаем логгер к проектам на JavaScript

Но как только мы потратим последнюю жизнь, то увидим странное: начнётся бесконечный повтор событий «Шарик улетел за границы поля» → «Потеряли жизнь». При этом внешне игра остановится штатно: появится чёрный экран с надписью «Game over»:

Подключаем логгер к проектам на JavaScript

Оказывается, программа вошла в бесконечный цикл: ей кажется, что шарик всё время улетает за границы поля и это нужно обработать. Без логгера мы этого бы не заметили: кажется, что игра просто остановилась, но внутри продолжает кипеть работа. Эту ошибку легко поправить: перед завершением игры нужно виртуально вернуть шарик на поле. Попробуйте сделать это самостоятельно и проверьте себя, заглянув в консоль.

<!-- Лицензия CC0 1.0 Universal-->
<!-- оригинал — https://gist.github.com/straker/98a2aed6a7686d26c04810f08bfaf66b -->
<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
  <title>Arkanoid</title>
  <style>
	/* стили для всей страницы */
	html, body {
	  height: 100%;
	  margin: 0;
	}
	/* отдельные параметры для фона */
	body {
	  background: black;
	  display: flex;
	  align-items: center;
	  justify-content: center;
	}
  </style>
</head>

<body>
  <!-- рисуем игровое поле -->
  <canvas width="400" height="500" id="game"></canvas>
  <!-- сам скрипт с игрой -->
   <script src="https://github.com/jonnyreeves/js-logger/blob/master/src/logger.js"></script>


  <script>

  // ставим настройки по умолчанию
  Logger.useDefaults();

  // и выводим тестовые сообщения разных уровней
  Logger.debug("Это обычное сообщение о каком-то событии, например для отладки");
	Logger.info("Информационное сообщение, его можно привязать к разовому событию");
	Logger.warn("А вот это уже серьёзно, тут логгер нас о чём-то предупреждает. Ничего критичного, но присмотреться стоит");
	Logger.error("Ошибка — что-то в программе пошло не так");


  // переменная для работы с холстом, на котором будет нарисована игра
  const canvas = document.getElementById('game');
  const context = canvas.getContext('2d');

  // каждый ряд состоит из 14 кирпичей. На уровне будут 6 пустых рядов, а затем 8 рядов с кирпичами
  // цвета кирпичей: красный, оранжевый, зелёный и жёлтый
  // буква в массиве означает цвет кирпича
  const level1 = [
		[],
		[],
		[],
		[],
		[],
		[],
		['R','R','R','R','R','R','R','R','R','R','R','R','R','R'],
		['R','R','R','R','R','R','R','R','R','R','R','R','R','R'],
		['O','O','O','O','O','O','O','O','O','O','O','O','O','O'],
		['O','O','O','O','O','O','O','O','O','O','O','O','O','O'],
		['G','G','G','G','G','G','G','G','G','G','G','G','G','G'],
		['G','G','G','G','G','G','G','G','G','G','G','G','G','G'],
		['Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y'],
		['Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y']
  ];

  // сопоставляем буквы (R, O, G, Y) с цветами
  const colorMap = {
		'R': 'red',
		'O': 'orange',
		'G': 'green',
		'Y': 'yellow'
  };

  // делаем зазор в 2 пикселя между кирпичами, чтобы отделить их друг от друга
  const brickGap = 2;
  // размеры каждого кирпича
  const brickWidth = 25;
  const brickHeight = 12;

  // ширина стены должна занимать оставшееся место на холсте с каждой стороны
  // у нас 14 кирпичей по 25 пикселей и 13 промежутков по 2 пикселя, а общая ширина холста — 400 пикселей
  // получаем общую ширину стен: 400 - (14 * 25 + 2 * 13) = 24px. 
  // разделим пополам, чтобы получить ширину каждой стены, и получим  12px
  const wallSize = 12;
  // основной массив для игры
  const bricks = [];

  // количество набранных очков за одну попытку
  score = 0;

  // количество жизней на старте
  lives = 3;

  Logger.info("Объявили все переменные")

  // создадим уровень так: обработем весь массив level1
  // и те места, которые обозначены каким-либо цветом, поместим в игровой массив.
  // там же будем хранить координаты начала каждого кирпича и его цвет

// в эту функцию мы поместим всё, что связано с касанием кирпичей
function touchdown(t_brick) {
	Logger.debug('Коснулись кирпича')

	// начисляем очки в зависимости от цвета кирпича
	switch(t_brick.color) {
		case "yellow" : score += 1; break;
		case "green"  : score += 2; break;
		case "orange" : score += 3; break;
		case "red"    : score += 4; 
	}

	// за каждые 25 очков — увеличиваем размер платформы на 2 пикселя
	if (score % 25 == 0) {
		paddle.width += 2;
	}

	// а за каждые 100 очков в одной попытке — прибавляем ещё одну жизнь
	if (score % 100 == 0){
		lives += 1;
		Logger.info('Получили ещё одну жизнь')
		// и усложняем игру — увеличиваем скорость шарика
		ball.speed += 1;
	}

}

  function lost() {
  	// уменьшаем количество жизней
  	lives = lives - 1;
  	Logger.info('Потеряли жизнь')

  	// обнуляем набранные очки
  	score = 0;

  }

  Logger.debug('Переходим к обработке массива с уровнем')
  // пока у нас есть необработанные элементы в массиве с уровнем — обрабатываем их
  for (let row = 0; row < level1.length; row++) {
		for (let col = 0; col < level1[row].length; col++) {

		  // находим цвет кирпича
		  const colorCode = level1[row][col];

		  // создаём новый элемент игрового массива — с координатами кирпича, цветом, шириной и высотой кирпича
		  bricks.push({
				x: wallSize + (brickWidth + brickGap) * col,
				y: wallSize + (brickHeight + brickGap) * row,
				color: colorMap[colorCode],
				width: brickWidth,
				height: brickHeight
		  });
		}
  }

  // платформа, которой управляет игрок
  const paddle = {
		// ставим её внизу по центру поля
		x: canvas.width / 2 - brickWidth / 2,
		y: 440,
		// делаем её размером с кирпич
		width: brickWidth,
		height: brickHeight,

		// пока платформа никуда не движется, поэтому направление движения равно нулю
		dx: 0
  };

  // шарик, который отскакивает от платформы и уничтожает кирпичи
  ball = {
		// стартовые координаты
		x: 130,
		y: 260,
		// высота и ширина (для простоты это будет квадратный шарик)
		width: 5,
		height: 5,

		// скорость шарика по обоим координатам
		speed: 2,

		// на старте шарик пока никуда не смещается
		dx: 0,
		dy: 0
  };

	// проверка на пересечение объектов
	// взяли отсюда: https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
	function collides(obj1, obj2) {
	  return obj1.x < obj2.x + obj2.width &&
			 obj1.x + obj1.width > obj2.x &&
			 obj1.y < obj2.y + obj2.height &&
			 obj1.y + obj1.height > obj2.y;
	}

	Logger.info('Входим в главный цикл игры')
  // главный цикл игры
  function loop() {
		// на каждом кадре — очищаем поле и рисуем всё заново
		requestAnimationFrame(loop);
		context.clearRect(0,0,canvas.width,canvas.height);

		// двигаем платформу с нужной скоростью 
		paddle.x += paddle.dx;

		// при этом смотрим, чтобы она не уехала за стены
		if (paddle.x < wallSize) {
		  paddle.x = wallSize
		}
		else if (paddle.x + paddle.width > canvas.width - wallSize) {
		  paddle.x = canvas.width - wallSize - paddle.width;
		}

		// шарик тоже двигается со своей скоростью
		ball.x += ball.dx;
		ball.y += ball.dy;

		// и его тоже нужно постоянно проверять, чтобы он не улетел за границы стен
		// смотрим левую и правую стенки
		if (ball.x < wallSize) {
		  ball.x = wallSize;
		  ball.dx *= -1;
		}
		else if (ball.x + ball.width > canvas.width - wallSize) {
		  ball.x = canvas.width - wallSize - ball.width;
		  ball.dx *= -1;
		}
		// проверяем верхнюю границу
		if (ball.y < wallSize) {
		  ball.y = wallSize;
		  ball.dy *= -1;
		}

		// перезагружаем шарик, если он улетел вниз, за край игрового поля
		if (ball.y > canvas.height) {
			Logger.warn('Шарик улетел за границы поля')
			// обрабатываем падение шарика
			lost();

			if (lives <= 0){ 
				// рисуем чёрный прямоугольник посередине поля
				context.fillStyle = 'black';
				context.globalAlpha = 0.75;
				context.fillRect(0, canvas.height / 2 - 30, canvas.width, 60);
				// пишем надпись белым моноширинным шрифтом по центру
				context.globalAlpha = 1;
				context.fillStyle = 'white';
				context.font = '36px monospace';
				context.textAlign = 'center';
				context.textBaseline = 'middle';
				context.fillText('GAME OVER!', canvas.width / 2, canvas.height / 2);

				return; };
			console.log(lives);

		  ball.x = 130;
		  ball.y = 260;
		  ball.dx = 0;
		  ball.dy = 0;
		}

		// проверяем, коснулся ли шарик платформы, которой управляет игрок. Если коснулся — меняем направление движения по y на противоположное
		if (collides(ball, paddle)) {
		  ball.dy *= -1;

		  // сдвигаем шарик выше платформы, чтобы на следующем кадре это снова не засчиталось за столкновение
		  ball.y = paddle.y - ball.height;
		}

		// проверяем, коснулся ли шарик цветного кирпича 
		// если коснулся — меняем направление движения шарика в зависимости от стенки касания
		// для этого в цикле проверяем каждый кирпич на касание
		for (let i = 0; i < bricks.length; i++) {
		  // берём очередной кирпич
		  const brick = bricks[i];

		  // если было касание
		  if (collides(ball, brick)) {
		  	Logger.debug('Касание')

				touchdown(brick);

				// убираем кирпич из массива
				bricks.splice(i, 1);

				// если шарик коснулся кирпича сверху или снизу — меняем направление движения шарика по y
				if (ball.y + ball.height - ball.speed <= brick.y ||
					ball.y >= brick.y + brick.height - ball.speed) {
				  ball.dy *= -1;
				}
				// в противном случае меняем направление движения шарика по x
				else {
				  ball.dx *= -1;
				}
				// как нашли касание — сразу выходим из цикла проверки
				break;
		  }
		}

		// рисуем стены
		context.fillStyle = 'lightgrey';
		context.fillRect(0, 0, canvas.width, wallSize);
		context.fillRect(0, 0, wallSize, canvas.height);
		context.fillRect(canvas.width - wallSize, 0, wallSize, canvas.height);

		// если шарик в движении — рисуем его
		if (ball.dx || ball.dy) {
		  context.fillRect(ball.x, ball.y, ball.width, ball.height);
		}

		// рисуем кирпичи
		bricks.forEach(function(brick) {
		  context.fillStyle = brick.color;
		  context.fillRect(brick.x, brick.y, brick.width, brick.height);
		});

		// рисуем платформу
		context.fillStyle = 'cyan';
		context.fillRect(paddle.x, paddle.y, paddle.width, paddle.height);

		// Цвет текста — серый    
		context.fillStyle = "#777777";    
		// Задаём размер и шрифт    
		context.font = "20pt monospace";    
		// Сначала выводим рекорд    
		context.fillText('Очки: ' + score, 50, 490);    
		// Затем — набранные очки    
		context.fillText('Жизни:'+ lives, 250, 490);
  }

  // отслеживаем нажатия игрока на клавиши
  document.addEventListener('keydown', function(e) {
  	Logger.info('Игрок нажал на кнопку')
		// стрелка влево
		if (e.which === 37) {
		  paddle.dx = -3;
		}
		// стрелка вправо
		else if (e.which === 39) {
		  paddle.dx = 3;
		}

		// обрабатываем нажатие на пробел
		// если шарик не запущен — запускаем его из начальной точки, сверху вниз
		if (ball.dx === 0 && ball.dy === 0 && e.which === 32) {
		  ball.dx = ball.speed;
		  ball.dy = ball.speed;
		}
  });

  // как только игрок перестал нажимать клавиши со стрелками — останавливаем платформу
  document.addEventListener('keyup', function(e) {
		if (e.which === 37 || e.which === 39) {
		  paddle.dx = 0;
		}
  });

  // запускаем игру
  requestAnimationFrame(loop);
  </script>
</body>
</html>

log4javascript — продвинутый логгер для сложных задач

Если понадобится логгер посложнее, можно посмотреть на log4javascript — он также подключается как внешний скрипт и не требует установленной платформы Node.js (как это делают многие логгеры).

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

Подключаем логгер к проектам на JavaScript

Чтобы подключить логгер, его нужно скачать себе на компьютер, а потом указать полный путь к файлу, например так:

<script type="text/javascript" src="/Users/mike/Downloads/log4javascript-1.4.13/log4javascript.js"></script>

Добавим логгер в проект — для этого скопируем из руководства пример подключения логгера с выводом во внешнее окно:

// настраиваем лог на вывод сообщений в отдельное окно
	var log = log4javascript.getLogger();
var popUpAppender = new log4javascript.PopUpAppender();
var popUpLayout = new log4javascript.PatternLayout("%d{HH:mm:ss} %-5p - %m%n");
popUpAppender.setLayout(popUpLayout);
log.addAppender(popUpAppender);
var ajaxAppender = new log4javascript.AjaxAppender("myloggingservlet.do");
ajaxAppender.setThreshold(log4javascript.Level.ERROR);
log.addAppender(ajaxAppender);
// выводим тестовые сообщения
log.debug('Простое сообщение отладчика');
log.info('Информационное сообщение');
log.warn('Предупреждение, на которое нужно обратить внимание')
log.error('Ошибка! В программе что-то случилось.');

Теперь мы можем делать то же самое, что и с предыдущим логгером: отслеживать все события, смотреть логи и делать выводы о том, как работает игра. Если нужно сохранить лог в отдельный файл, можно добавить специальный модуль — он сохранит весь вывод с момента запуска.

Что дальше

В следующий раз добавим логгер в проект на Python — там больше возможностей для отладки и настройки таких сообщений.

Текст:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Виталий Вебер

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
Играем со шрифтами на главной странице
Играем со шрифтами на главной странице

Делаем красивый эффект на случайном фоне

easy
Создаём статичный сайт на Hugo
Создаём статичный сайт на Hugo

Превращаем простой текст в полноценный сайт.

medium
Шифр Вернама на JavaScript
Шифр Вернама на JavaScript

Невзламываемый шифр за 4 строчки кода.

medium
Большой разбор: ИИ научился играть в динозаврика из Chrome

Тот редкий случай, когда хочешь остаться без интернета.

medium
Что означает ошибка SyntaxError: Unexpected token '{'. import call expects exactly one argument
Что означает ошибка SyntaxError: Unexpected token '{'. import call expects exactly one argument
easy
Что означает предел в математике
Что означает предел в математике

Сага о погрешностях при участии слова lim

medium
Пишем свой генератор паролей
Пишем свой генератор паролей

Готовый код с уникальным алгоритмом шифрования. Возьми и сделай.

medium
Чат-бот
Делаем своего первого чат-бота

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

easy
Подключаем логгер к проектам на JavaScript
Подключаем логгер к проектам на JavaScript

Следим за каждым шагом программы

medium
Свой текстовый редактор: делаем красиво
Собственный текстовый редактор: делаем красиво

Самое простое и полезное введение в CSS.

medium
medium