- Adding console.log to every function automatically
- 6 Answers 6
- Proxy Method to log Function calls
- Сверхпростое логгирование в Javascript — два декоратора, и готово
- Зачем оно вообще надо?
- Шашки наголо!
- Нам обещали «конфигурируемый» способ логгирования
- Глобальный конфиг
- Конфиг класса
- Конфиг метода
- Что можно конфигурировать?
- log
- logError
- formatter
- include
- Берем полный контроль над форматированием сообщений
- Заключение
Adding console.log to every function automatically
Is there a way to make any function output a console.log statement when it’s called by registering a global hook somewhere (that is, without modifying the actual function itself) or via some other means?
excellent question, I’d love to know if this is possible but I’m pretty sure it isn’t. Maybe add a feature request for it to be added in the js engine for your favourite browser? 🙂
6 Answers 6
Here’s a way to augment all functions in the global namespace with the function of your choice:
function augment(withFn) < var name, fn; for (name in window) < fn = window[name]; if (typeof fn === 'function') < window[name] = (function(name, fn) < var args = arguments; return function() < withFn.apply(this, args); return fn.apply(this, arguments); >>)(name, fn); > > > augment(function(name, fn) < console.log("calling " + name); >);
One down side is that no functions created after calling augment will have the additional behavior.
@SunnyShah No it doesn’t: jsfiddle.net/Shawn/WnJQ5 But this one does: jsfiddle.net/Shawn/WnJQ5/1 although I’m not sure it will work in ALL cases. The difference is changing fn.apply(this, arguments); to return fn.apply(this, arguments);
works almost fine but i get an error with this jquery:call :if (jQuery.isFunction(lSrc)) and it says :TypeError: jQuery.isFunction is not a function
As to me, this looks like the most elegant solution:
It would be good if we restrict this only over a set of functions. Running this on a webpage basically means, all the methods from libraries will also get logged.
One possible solution could be using this.name to distinguish the functions by name if they have it of course
Proxy Method to log Function calls
There is a new way using Proxy to achieve this functionality in JS. assume that we want to have a console.log whenever a function of a specific class is called:
class TestClass < a() < this.aa = 1; >b() < this.bb = 1; >> const foo = new TestClass() foo.a() // nothing get logged
we can replace our class instantiation with a Proxy that overrides each property of this class. so:
class TestClass < a() < this.aa = 1; >b() < this.bb = 1; >> const logger = className => < return new Proxy(new className(), < get: function(target, name, receiver) < if (!target.hasOwnProperty(name)) < if (typeof target[name] === "function") < console.log( "Calling Method : ", name, "|| on : ", target.constructor.name ); >return new Proxy(target[name], this); > return Reflect.get(target, name, receiver); > >); >; const instance = logger(TestClass) instance.a() // output: "Calling Method : a || on : TestClass"
Remember that using Proxy gives you a lot more functionality than to just logging console names.
Also this method works in Node.js too.
Сверхпростое логгирование в Javascript — два декоратора, и готово
Вам еще не надоело писать logger.info(‘ServiceName.methodName.’) и logger.info(‘ServiceName.methodName -> done.’) на каждый чих? Может вы, так же как и я, неоднократно задумывались о том, чтобы это дело автоматизировать? В данной статье рассказ пойдет о class-logger, как об одном из вариантов решения проблемы с помощью всего лишь двух декораторов.
Зачем оно вообще надо?
Не все перфекционисты — инженеры, но все инженеры — перфекционисты (ну, почти). Нам нравятся красивые лаконичные абстракции. Мы видим красоту в наборе кракозябликов, которые человек неподготовленный и прочитать не сможет. Нам нравится чувствовать себя богами, создавая микро-вселенные, которые живут по нашим правилам. Предположительно, все эти качества берут начало из нашей необъятной лени. Нет, инженер не боится работы как класса, но люто ненавидит рутинные повторяющиеся действия, которые руки так и тянутся автоматизировать.
Написав несколько тысяч строк кода, предназначенного только лишь для логгирования, обычно мы приходим к созданиям каких-то своих паттернов и стандартов написания сообщений. К сожалению, нам все еще приходится применять эти паттерны вручную. Главное «зачем» class-logger — предоставить декларативный конфигурируемый способ простого логгирования шаблонных сообщений при создании класса и вызове его методов.
Шашки наголо!
Не бродя вокруг да около, перейдем сразу к коду.
import < LogClass, Log >from 'class-logger' @LogClass() class ServiceCats < @Log() eat(food: string) < return 'purr' >>
Этот сервис создаст три записи в логе:
- Перед его созданием со списком аргументов, переданных в конструктор.
- Перед вызовом eat со списком аргументов.
- После вызова eat со списком аргументов и результатом выполнения.
// Логгирует перед созданием ServiceCats // `ServiceCats.construct. Args: [].` const serviceCats = new ServiceCats() // Логгирует перед вызовом `eat` // `ServiceCats.eat. Args: [milk].` serviceCats.eat('milk') // Логгирует после вызова `eat` // `ServiceCats.eat -> done. Args: [milk]. Res: purr.`
Полный список событий, которые можно залоггировать:
- Перед созданием класса.
- Перед вызовом синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
- После вызова синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
- Ошибки вызова синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
Функциональное свойство — стрелочная функция присвоенная свойству ( class ServiceCats < private meow = () =>null > ). Наиболее часто используется для сохранения контекста выполнения ( this ).
Нам обещали «конфигурируемый» способ логгирования
class-logger три уровня иерархии конфигурации:
При вызове каждого метода, все три уровня мержатся вместе. Библиотека предоставляет разумный глобальный конфиг по умолчанию, так что ее можно использовать и без какой-либо предварительной настройки.
Глобальный конфиг
Он создается для всего приложения. Его можно переопределить с помощью setConfig .
import < setConfig >from 'class-logger' setConfig(< log: console.info, >)
Конфиг класса
Он свой для каждого класса и применяется для всех методов этого класса. Может переопределять глобальный конфиг.
import < LogClass >from 'class-logger' setConfig(< log: console.info, >) @LogClass(< // Переопределяет глобальный конфиг для этого сервиса log: console.debug, >) class ServiceCats <>
Конфиг метода
Работает только для самого метода. Имеет приоритет над конфигом класса и глобальным конфигом.
import < LogClass >from 'class-logger' setConfig(< log: console.info, >) @LogClass(< // Переопределяет глобальный конфиг для этого сервиса log: console.debug, >) class ServiceCats < private energy = 100 @Log(< // Переопределяет конфиг класса только для этого метода log: console.warn, >) eat(food: string) < return 'purr' >// Этот метод использует `console.debug` предоставленный конфигом класса sleep() < this.energy += 100 >>
Что можно конфигурировать?
Мы рассмотрели как изменять конфиг, но все еще не разобрали, что именно в нем можно изменить.
Объект конфигурации имеет следующие свойства:
log
Это функция, которая занимается, собственно, логгированием. Ей на вход приходит отформатированное сообщения в виде строки. Используется для логгирования следующих событий:
- Перед созданием класса.
- Перед вызовом синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
- После вызова синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
logError
Это функция, которая тоже занимается логгированием, но уже только сообщений об ошибке. Ей также на вход приходит отформатированное сообщения в виде строки. Используется для логгирования следующих событий:
- Ошибки вызова синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
По умолчанию console.error .
formatter
Это объект с двумя методами: start и end . Эти методы создают то самое отформативанное сообщение.
start создает сообщения для следующих событий:
- Перед созданием класса.
- Перед вызовом синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
end создает сообщения для следующих событий:
- После вызова синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
- Ошибки вызова синхронных и асинхронных методов и функциональных свойств, как статических, так и не статических.
По умолчанию new ClassLoggerFormatterService() .
include
Конфиг того, что должно быть включено в конечное сообщение.
args
Это может быть как булево значение, так и объект.
Если это boolean , то он задает включать ли список аргументов (помните Args: [milk] ?) во все сообщения ( start и end ).
Если это объект, то у него должно быть два булевых свойства: start и end . start задает включать ли список аргументов для start сообщений, end — для end сообщений.
construct
Это boolean , который контролирует логгировать ли создание класса или нет.
result
Это boolean , который контролирует логгировать возвращаемое из метода значение. Стоит заметить, что возвращаемое значение — это не только то, что метод возвращает при успешном выполнении, но и ошибка, которую он бросает в случае неудачного выполнения. Помните Res: purr ? Если этот флаг будет false , то не будет никакого Res: purr .
classInstance
Это может быть как булево значение, так и объект. Концепция та же, что и include.args .
Добавляет сериализованное представление инстанса вашего класса в сообщения. Другими словами, если у вашего класса есть какие-то свойства, то они будут сериализованны в JSON и добавлены в логи.
Не все свойства подлежат сериализации. class-logger использует следующую логику:
- Берем все собственные (те, которые не в прототипе) свойства интсанса.
- Почему? Очень редко прототип меняет динамически, так что нет большого смысла логгировать его содержимое.
- Почему? Чаще всего свойства типа function — это просто стрелочные функции, которые не сделали обычными методами ради сохранения контекста ( this ). Они редко имеют динамическую природу, так что и логгировать их нет смысла.
- Что за простые объекты? ClassLoggerFormatterService считает объект простым, если его прототип — это Object.prototype .
- Почему? Часто в качестве свойств выступают инстансы других классов-сервисов. Наши логи распухли бы самым безобразным образом, если бы мы из стали сериализовать.
class ServiceA <> @LogClass(< include: < classInstance: true, >, >) class Test < private serviceA = new ServiceA() private prop1 = 42 private prop2 = < test: 42 >private method1 = () => null @Log() public method2() < return 42 >> // Логгирует перед созданием `Test` // 'Test.construct. Args: []. Class instance: >.' const test = new Test() // Логгирует перед вызовом `method2` // 'Test.method2. Args: []. Class instance: >.' test.method2() // Логгирует после вызова `method2` // 'Test.method2 -> done. Args: []. Class instance: >. Res: 42.'
Берем полный контроль над форматированием сообщений
Что же делать, если вам нравится идея, но ваши представление о прекрасном настаивает на другом формате строки сообщений? Вы можете взять полный контроль над форматированием сообщений, передав свой собственный форматтер.
Вы можете написать свой собственный форматтер с нуля. Безусловно. Но этот вариант не будет освещен в рамках этой (если вам интересна именно эта опцию, загляните в раздел «Formatting» в README).
Самый быстрый и простой способ переопределить форматирование — это унаследовать свой форматтер от форматтера по умолчанию — ClassLoggerFormatterService .
ClassLoggerFormatterService имеет следующие protected методы, которые создают небольшие блоки финальных сообщений:
- base
- Возвращает имя класса и имя метода. Пример: ServiceCats.eat .
- Возвращает -> done или -> error в зависимости от того, успешно ли выполнился метод.
- Возвращает сериализованный список аргументов. Пример: . Args: [milk] . Он использует fast-safe-stringify для сериализации объектов под капотом.
- Возвращает сериализованный инстанс класса. Пример: . Class instance: > . Если вы включили include.classInstance в конфиге, но по какой-то причине сам инстанс в момент логгирования не доступен (например, для статических методов или перед созданием класса), возвращает N/A .
- Возвращает сериализованный результат выполнения или сериализованную ошибку. Использует fast-safe-stringify для объектов под капотом. Сериализованная ошибка включает в себя следующие свойства:
- Имя класса (функции), которая была использована для создания ошибки ( error.constructor.name ).
- Код ( error.code ).
- Сообщение ( error.message ).
- Имя ( error.name ).
- Стек трейс ( error.stack ).
- Возвращает . . Просто . .
Сообщение start состоит из:
Вы можете переопределить только необходимые вам базовые методы.
Давайте рассмотрим, как можно добавить timestamp ко всем сообщениям.
Я не говорю, что это надо делать в реальных проектах. pino, winston и большинство других логгеров способны делать это самостоятельно. Этот пример служит в исключительно образовательных целях.
import < ClassLoggerFormatterService, IClassLoggerFormatterStartData, setConfig, >from 'class-logger' class ClassLoggerTimestampFormatterService extends ClassLoggerFormatterService < protected base(data: IClassLoggerFormatterStartData) < const baseSuper = super.base(data) const timestamp = Date.now() const baseWithTimestamp = `$:$` return baseWithTimestamp > > setConfig(< formatter: new ClassLoggerTimestampFormatterService(), >)
Заключение
Не забывайте следовать инструкциям по установке и ознакомьтесь с требованиями перед тем как использовать эту библиотеку на вашем проекте.
Надеюсь, вы не потратили время зря, и статья была вам хоть чуточку полезна. Просьба пинать и критиковать. Будем учиться кодить лучше вместе.