Существует ряд средств для отладки веб-приложений, которые выводят сообщения сформированные на стороне сервера в консоль браузера.
Рассмотрим как работает одно из таких средств - ChromeLogger (https://craig.is/writing/chrome-logger/techspecs).
На стороне сервера в процессе формирования ответа добавляется заголовок X-ChromeLogger-Data. Значение заголовка формируется так:
1. Формируем json строку вида:
data = {
"version": "0.2", // версия модуля, который формирует отладочную информацию
"request_uri": "/", // ресурс
"columns": ["label", "log", "backtrace", "type"], // перечень колонок
"rows": [ // массив строк, соответствующих колонкам
[
'метка',
'сообщение/описание/представление объекта',
'стек вызова',
'тип отладочного сообщения'
]
]
}
2. Кодируем строку в UTF8
3. Кодируем строку в Base64
На стороне клиента в браузере Google Chrome установленное расширение Chrome Logger разбирает данный заголовок и выводит информацию в консоль.
А теперь реализуем это для 1С
Разработка производится на 8.3.10.2505, УНФ 1.6.12.4. Модули входят в состав расширения. Конфигурация с поддержки не снята.
Нам необходимо где-то хранить информацию, я выбрал ХранилищеОбщихНастроек. Может быть есть лучше вариант?
функция Отладка(Данные, Метка="", Трассировка="", Тип="") экспорт
ТекущиеДанные = ОбщегоНазначения.ХранилищеОбщихНастроекЗагрузить("ChromeLogger", "ChromeLogger");
если НЕ ЗначениеЗаполнено(ТекущиеДанные) тогда
ТекущиеДанные = новый ТаблицаЗначений();
ТекущиеДанные.Колонки.Добавить("log");
ТекущиеДанные.Колонки.Добавить("label");
ТекущиеДанные.Колонки.Добавить("backtrace");
ТекущиеДанные.Колонки.Добавить("type");
конецесли;
Запись = ТекущиеДанные.Добавить();
Запись.log = Данные;
Запись.label = Метка;
Запись.backtrace = Трассировка;
Запись.type = Тип;
ОбщегоНазначения.ХранилищеОбщихНастроекСохранить("ChromeLogger", "ChromeLogger", ТекущиеДанные);
конецфункции
Теперь сформируем заголовок.
Формируем json строку.
функция СформироватьChromeLoggerJson()
Таблица = ОбщегоНазначения.ХранилищеОбщихНастроекЗагрузить("ChromeLogger", "ChromeLogger");
если НЕ ЗначениеЗаполнено(Таблица) ИЛИ Таблица.Количество() = 0 тогда
возврат неопределено;
конецесли;
Запись = новый ЗаписьJSON();
Запись.УстановитьСтроку();
Структура = Новый Структура();
Структура.Вставить("version", "4.0");
Колонки = Новый Массив();
Колонки.Добавить("label");
Колонки.Добавить("log");
Колонки.Добавить("backtrace");
Колонки.Добавить("type");
Структура.Вставить("columns", Колонки);
Строки = новый Массив();
для каждого строка из Таблица цикл
Значения = Новый Массив();
Значения.Добавить(КодироватьВUnicodeEscape(строка.label));
//Значения.Добавить(строка.log);
Значения.Добавить(КодироватьВUnicodeEscape(строка.log));
Значения.Добавить(КодироватьВUnicodeEscape(строка.backtrace));
Значения.Добавить(КодироватьВUnicodeEscape(строка.type));
Строки.Добавить(Значения);
конеццикла;
Структура.Вставить("rows", Строки);
Структура.Вставить("request_uri", "/");
ЗаписатьJSON(Запись, Структура);
Таблица.Очистить();
возврат Запись.Закрыть();
конецфункции
Все национальные символы должны быть кодированы с помощью escape-последовательностей \uhhhh, где hhhh — шестнадцатеричное число из четырех цифр. Для этого была написана, как быстрое решение, функция КодироватьВUnicodeEscape. Можно ли это сделать "красивее", и желательно по максимум штатными средствами?
функция КодироватьВUnicodeEscape(Строка)
Уникод = новый Массив();
Уникод.Добавить("\u0410");
Уникод.Добавить("\u0430");
Уникод.Добавить("\u0411");
Уникод.Добавить("\u0431");
Уникод.Добавить("\u0412");
Уникод.Добавить("\u0432");
Уникод.Добавить("\u0413");
Уникод.Добавить("\u0433");
Уникод.Добавить("\u0414");
Уникод.Добавить("\u0434");
Уникод.Добавить("\u0415");
Уникод.Добавить("\u0435");
Уникод.Добавить("\u0401");
Уникод.Добавить("\u0451");
Уникод.Добавить("\u0416");
Уникод.Добавить("\u0436");
Уникод.Добавить("\u0417");
Уникод.Добавить("\u0437");
Уникод.Добавить("\u0418");
Уникод.Добавить("\u0438");
Уникод.Добавить("\u0419");
Уникод.Добавить("\u0439");
Уникод.Добавить("\u041a");
Уникод.Добавить("\u043a");
Уникод.Добавить("\u041b");
Уникод.Добавить("\u043b");
Уникод.Добавить("\u041c");
Уникод.Добавить("\u043c");
Уникод.Добавить("\u041d");
Уникод.Добавить("\u043d");
Уникод.Добавить("\u041e");
Уникод.Добавить("\u043e");
Уникод.Добавить("\u041f");
Уникод.Добавить("\u043f");
Уникод.Добавить("\u0420");
Уникод.Добавить("\u0440");
Уникод.Добавить("\u0421");
Уникод.Добавить("\u0441");
Уникод.Добавить("\u0422");
Уникод.Добавить("\u0442");
Уникод.Добавить("\u0423");
Уникод.Добавить("\u0443");
Уникод.Добавить("\u0424");
Уникод.Добавить("\u0444");
Уникод.Добавить("\u0425");
Уникод.Добавить("\u0445");
Уникод.Добавить("\u0426");
Уникод.Добавить("\u0446");
Уникод.Добавить("\u0427");
Уникод.Добавить("\u0447");
Уникод.Добавить("\u0428");
Уникод.Добавить("\u0448");
Уникод.Добавить("\u0429");
Уникод.Добавить("\u0449");
Уникод.Добавить("\u042a");
Уникод.Добавить("\u044a");
Уникод.Добавить("\u042b");
Уникод.Добавить("\u044b");
Уникод.Добавить("\u042c");
Уникод.Добавить("\u044c");
Уникод.Добавить("\u042d");
Уникод.Добавить("\u044d");
Уникод.Добавить("\u042e");
Уникод.Добавить("\u044e");
Уникод.Добавить("\u042f");
Уникод.Добавить("\u044f");
РусскиеБуквы = Новый Массив();
РусскиеБуквы.Добавить("А");
РусскиеБуквы.Добавить("а");
РусскиеБуквы.Добавить("Б");
РусскиеБуквы.Добавить("б");
РусскиеБуквы.Добавить("В");
РусскиеБуквы.Добавить("в");
РусскиеБуквы.Добавить("Г");
РусскиеБуквы.Добавить("г");
РусскиеБуквы.Добавить("Д");
РусскиеБуквы.Добавить("д");
РусскиеБуквы.Добавить("Е");
РусскиеБуквы.Добавить("е");
РусскиеБуквы.Добавить("Ё");
РусскиеБуквы.Добавить("ё");
РусскиеБуквы.Добавить("Ж");
РусскиеБуквы.Добавить("ж");
РусскиеБуквы.Добавить("З");
РусскиеБуквы.Добавить("з");
РусскиеБуквы.Добавить("И");
РусскиеБуквы.Добавить("и");
РусскиеБуквы.Добавить("Й");
РусскиеБуквы.Добавить("й");
РусскиеБуквы.Добавить("К");
РусскиеБуквы.Добавить("к");
РусскиеБуквы.Добавить("Л");
РусскиеБуквы.Добавить("л");
РусскиеБуквы.Добавить("М");
РусскиеБуквы.Добавить("м");
РусскиеБуквы.Добавить("Н");
РусскиеБуквы.Добавить("н");
РусскиеБуквы.Добавить("О");
РусскиеБуквы.Добавить("о");
РусскиеБуквы.Добавить("П");
РусскиеБуквы.Добавить("п");
РусскиеБуквы.Добавить("Р");
РусскиеБуквы.Добавить("р");
РусскиеБуквы.Добавить("С");
РусскиеБуквы.Добавить("с");
РусскиеБуквы.Добавить("Т");
РусскиеБуквы.Добавить("т");
РусскиеБуквы.Добавить("У");
РусскиеБуквы.Добавить("у");
РусскиеБуквы.Добавить("Ф");
РусскиеБуквы.Добавить("ф");
РусскиеБуквы.Добавить("Х");
РусскиеБуквы.Добавить("х");
РусскиеБуквы.Добавить("Ц");
РусскиеБуквы.Добавить("ц");
РусскиеБуквы.Добавить("Ч");
РусскиеБуквы.Добавить("ч");
РусскиеБуквы.Добавить("Ш");
РусскиеБуквы.Добавить("ш");
РусскиеБуквы.Добавить("Щ");
РусскиеБуквы.Добавить("щ");
РусскиеБуквы.Добавить("Ъ");
РусскиеБуквы.Добавить("ъ");
РусскиеБуквы.Добавить("Ы");
РусскиеБуквы.Добавить("ы");
РусскиеБуквы.Добавить("Ь");
РусскиеБуквы.Добавить("ь");
РусскиеБуквы.Добавить("Э");
РусскиеБуквы.Добавить("э");
РусскиеБуквы.Добавить("Ю");
РусскиеБуквы.Добавить("ю");
РусскиеБуквы.Добавить("Я");
РусскиеБуквы.Добавить("я");
для индекс = 0 по РусскиеБуквы.Количество()-1 цикл
Строка = СтрЗаменить(Строка, РусскиеБуквы.Получить(индекс), Уникод.Получить(индекс));
конеццикла;
возврат Строка;
конецфункции
Далее необходимо кодировать получившееся в utf8, после в base64. Можно ли это сделать без всяких временных файлов?
функция КодироватьВUTF8ИBase64(Строка)
ВременныйФайл = ПолучитьИмяВременногоФайла();
Запись = Новый ЗаписьТекста(ВременныйФайл, КодировкаТекста.UTF8);
Запись.Записать(Строка);
Запись.Закрыть();
ДвоичныеДанные = Новый ДвоичныеДанные(ВременныйФайл);
Результат = Base64Строка(ДвоичныеДанные);
УдалитьФайлы(ВременныйФайл);
возврат Результат;
конецфункции
Собираем все вместе
функция СформироватьЗаголовокДляChromeLogger()
json = СформироватьChromeLoggerJson();
если НЕ ЗначениеЗаполнено(json) тогда
возврат неопределено;
конецесли;
json = СтрЗаменить(json, "\\u", "\u");
Результат = КодироватьВUTF8ИBase64(json);
Результат = СтрЗаменить(СтрЗаменить(СтрЗаменить(СтрЗаменить(Результат, " ", ""), Символы.ПС, ""), Символ(20), ""), Символ(13), "");
Результат = Прав(Результат, СтрДлина(Результат)-4);
возврат Результат;
конецфункции
На последнем шаге добавляем заголовок к ответу
функция Ответ(ДанныеJSON) экспорт
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=UTF-8");
ЗаголовокДляChromeLogger = СформироватьЗаголовокДляChromeLogger();
если ЗначениеЗаполнено(ЗаголовокДляChromeLogger) тогда
Ответ.Заголовки.Вставить("X-ChromeLogger-Data", ЗаголовокДляChromeLogger);
конецесли;
ОбщегоНазначения.ХранилищеОбщихНастроекСохранить("ChromeLogger", "ChromeLogger", неопределено);
Ответ.УстановитьТелоИзСтроки(ДанныеJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Возврат Ответ;
конецфункции
Таким образом, при вызове в коде функции <ОбщийМодуль>.Отладка("Привет infostart.ru!"); в консоли браузера увидим наше сообщение.
В процессе реализации у меня возникли следующие вопросы:
1. Где лучше хранить отладочную информацию (функция Отладка), в случае использования расширений конфигурации?
2. Как "красиво" и правильно закодировать строку в unicode escape-последовательности (функция КодироватьВUnicodeEscape) штатными средствами?
3. Можно ли кодировать строку в base64 штатными средствами без временных файлов?
Спасибо. Надеюсь, кому-нибудь мой опыт будет полезен.
Обновление №1
Спасибо всем за ответы.
Переписаны функции
- КодироватьВUTF8ИBase64
- КодироватьВUnicodeEscape
- СформироватьChromeLoggerJson
// Наименование модуля
// ХромЛогер
// Получить данные отладки
//
// Возвращаемое значение:
// Массив
//
функция ПолучитьДанныеОтладки()
возврат ОбщегоНазначения.ХранилищеОбщихНастроекЗагрузить("ChromeLogger", "ChromeLogger");
конецфункции
// Установить данные отладки
//
// Параметры:
// Значение - Массив
//
процедура УстановитьДанныеОтладки(Значение)
ОбщегоНазначения.ХранилищеОбщихНастроекСохранить("ChromeLogger", "ChromeLogger", Значение);
конецпроцедуры
// Удалить все данные отладки
//
процедура УдалитьДанныеОтладки()
ОбщегоНазначения.ХранилищеОбщихНастроекСохранить("ChromeLogger", "ChromeLogger", неопределено);
конецпроцедуры
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
// Добавляет отладочное сообщение
//
// Параметры:
// Данные - Строка, или Объект
// Метка - Строка
// Трассировка - Строка
// Тип - Строка - возможные значения: log, warn, error, info, group, groupEnd, groupCollapsed, table
//
процедура Отладка(Данные, Метка="", Трассировка="", Тип="") экспорт
ТекущиеДанные = ПолучитьДанныеОтладки();
если НЕ ЗначениеЗаполнено(ТекущиеДанные) тогда
ТекущиеДанные = новый Массив();
конецесли;
СтрокаДанные = новый Массив();
СтрокаДанные.Вставить(0, Данные);
СтрокаДанные.Вставить(1, Метка);
СтрокаДанные.Вставить(2, Трассировка);
СтрокаДанные.Вставить(3, Тип);
ТекущиеДанные.Добавить(СтрокаДанные);
УстановитьДанныеОтладки(ТекущиеДанные);
конецпроцедуры
// Кодирует строку в UTF8, затем в BASE64
//
// Параметры:
// Строка - Строка, которую необходимо закодировать
//
// Возвращаемое значение:
// Строка - строка в BASE64
//
функция КодироватьВUTF8ИBase64(Строка)
// Совет http://forum.infostart.ru/forum9/topic181969/message1888978/#message1888978
Поток = Новый ПотокВПамяти;
ЗаписьДанных = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8,,,,ложь);
ЗаписьДанных.ЗаписатьСимволы(Строка);
ЗаписьДанных.Закрыть();
ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
Результат = Base64Строка(ДвоичныеДанные);
возврат Результат;
конецфункции
// Преобразовывает число в шестнадцатеричное представление
//
// Параметры:
// Значение - Число
//
// Возвращаемое значение:
// Строка - шестнадцатеричное представление
//
функция ЧислоВHex(Значение)
Результат = "";
Пока Значение > 0 Цикл
Результат = Сред("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", Значение%16 + 1, 1) + Результат;
Значение = Цел(Значение/16) ;
КонецЦикла;
Возврат Результат;
конецфункции
// Кодирует строку с использованием escape-последовательностей
//
// Параметры:
// Строка - Строка, которую необходимо преобразовать
//
// Возвращаемое значение:
// Строка - преобразованная строка
//
функция КодироватьВUnicodeEscape(Строка)
Результат = "";
для индекс = 1 по СтрДлина(Строка) цикл
Символ = Сред(Строка, индекс, 1);
КодСимвола = КодСимвола(Символ);
если КодСимвола > 127 тогда
Символ = "\u" + Прав("0000" + ЧислоВHex(КодСимвола), 4);
конецесли;
Результат = Результат + Символ;
конеццикла;
возврат Результат;
конецфункции
// Вспомогательная фукнция для ЗаписатьJSON() в СформироватьChromeLoggerJson()
//
Функция ПреобразованиеJSON(Знач Свойство, Значение, ДопПараметры, Отказ) Экспорт
// Совет http://forum.infostart.ru/forum9/topic181969/message1888986/#message1888986
ТипЗначения = ТипЗнч(Значение);
Если ТипЗначения = Тип("ТаблицаЗначений") Тогда
мКолонки = Новый Массив;
Для Каждого Колонка ИЗ Значение.Колонки Цикл
мКолонки.Добавить(Колонка.Имя);
КонецЦикла;
СтрКолонки = СтрСоединить(мКолонки, ",");
Массив = Новый Массив;
Для Каждого СтрокаТЗ ИЗ Значение Цикл
Структура = Новый Структура(СтрКолонки);
ЗаполнитьЗначенияСвойств(Структура, СтрокаТЗ, СтрКолонки);
Массив.Добавить(Структура);
КонецЦикла;
Возврат ПерекодироватьВсеСтрокиВМассивеВUnicodeEscape(Массив);
ИначеЕсли ТипЗначения = Тип("Дата") Тогда
Если НачалоДня(Значение) = '00010101' Тогда
Возврат Формат(Значение, "ДФ=HH:mm:ss");
Иначе
Возврат Формат(Значение, "ДФ='dd.MM.yyyy HH:mm:ss'"); //Игнорим настройки системы
КонецЕсли;
Иначе
возврат КодироватьВUnicodeEscape(Строка(Значение)); // рискнем и преобразуем в строку
КонецЕсли;
КонецФункции
// Перекодирует все строки в массиве (рекурсивно)
//
// Параметры:
// МассивЗначений - Массив
//
// Возвращаемое значение:
// Массив - с перекодированными строками
//
функция ПерекодироватьВсеСтрокиВМассивеВUnicodeEscape(МассивЗначений)
если ЗначениеЗаполнено(МассивЗначений) И МассивЗначений.Количество() > 0 тогда
для индекс = 0 по МассивЗначений.Количество()-1 цикл
Элемент = МассивЗначений.Получить(индекс);
если ТипЗнч(Элемент) = Тип("Строка") тогда
МассивЗначений.Установить(индекс, КодироватьВUnicodeEscape(элемент));
иначеесли ТипЗнч(Элемент) = Тип("Массив") тогда
МассивЗначений.Установить(индекс, ПерекодироватьВсеСтрокиВМассивеВUnicodeEscape(элемент));
конецесли;
конеццикла;
конецесли;
возврат МассивЗначений;
конецфункции
// Сформировать json строку для заголовка ChromeLogger'а
//
// Возвращаемое значение:
// Строка - JSON
//
функция СформироватьChromeLoggerJson()
ТекущиеДанные = ПолучитьДанныеОтладки();
если НЕ ЗначениеЗаполнено(ТекущиеДанные) ИЛИ ТекущиеДанные.Количество() = 0 тогда
возврат неопределено;
конецесли;
Запись = новый ЗаписьJSON();
Запись.УстановитьСтроку();
Структура = Новый Структура();
Структура.Вставить("version", "1.0");
Колонки = Новый Массив();
Колонки.Добавить("label");
Колонки.Добавить("log");
Колонки.Добавить("backtrace");
Колонки.Добавить("type");
Структура.Вставить("columns", Колонки);
Структура.Вставить("rows", ПерекодироватьВсеСтрокиВМассивеВUnicodeEscape(ТекущиеДанные));
Структура.Вставить("request_uri", "/");
ЗаписатьJSON(Запись, Структура,,"ПреобразованиеJSON", ХромЛогер);
УдалитьДанныеОтладки();
возврат Запись.Закрыть();
конецфункции
// Сформировать заголовок X-ChromeLogger-Data
//
// Возвращаемое значение:
// Строка - BASE64
//
функция СформироватьЗаголовокДляChromeLogger() Экспорт
json = СформироватьChromeLoggerJson();
если НЕ ЗначениеЗаполнено(json) тогда
возврат неопределено;
конецесли;
json = СтрЗаменить(json, "\\u", "\u");
Результат = КодироватьВUTF8ИBase64(json);
Результат = СтрЗаменить(СтрЗаменить(СтрЗаменить(СтрЗаменить(Результат, " ", ""), Символы.ПС, ""), Символ(20), ""), Символ(13), "");
возврат Результат;
конецфункции
Пример использования
функция Ответ(ДанныеJSON) экспорт
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=UTF-8");
ЗаголовокДляChromeLogger = ХромЛогер.СформироватьЗаголовокДляChromeLogger();
если ЗначениеЗаполнено(ЗаголовокДляChromeLogger) тогда
Ответ.Заголовки.Вставить("X-ChromeLogger-Data", ЗаголовокДляChromeLogger);
конецесли;
Ответ.УстановитьТелоИзСтроки(ДанныеJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Возврат Ответ;
конецфункции