Automated, styled, declarative javascript debugging (logging)
![Build Status](https://travis-ci.org/vitalishapovalov/log.svg?branch=master)
An utility to automatically log object properties access/set/delete etc., descriptor modification, function calls and a lot more in a human-readable way.
In Browser
![browser output](https://raw.githubusercontent.com/vitalishapovalov/js-utilities/master/docs/1.png)
In Node.js
![node.js output](https://raw.githubusercontent.com/vitalishapovalov/js-utilities/master/docs/2.png)
Can be used to inject logger, has Light
and Dark
predefined themes
![instance logger](https://raw.githubusercontent.com/vitalishapovalov/js-utilities/master/docs/3-4.png)
Table of Contents
Installation
npm i -S @js-utilities/log
Usage
Decorator
Before using @Log
as decorator, make sure that decorators
are supported in your build:
Decorators are not part of ECMAScript 2016 (aka 7). Decorators are currently in Stage 2 Draft out of the total 4 stages a feature goes through before being finalized and becoming part of the language.
So, to use it, you should transform your code with transpilers (e.g. Babel and proposal-decorators plugin)
Decorators are available as an experimental feature of TypeScript. Official instruction here.
@Log
decorator can be used in different ways:
import { Log } from "@js-utilities/log";
@Log
class MyClass {}
@Log({ logExecutionTime: true })
class MyClass {}
@Log("MyLogger")
class MyClass {}
If you want to explicitly import browser / node version:
import { Log } from "@js-utilities/log/dist/browser";
import { Log } from "@js-utilities/log/dist/node";
Class
When used as a class decorator, will log:
Here you can define which properties to log, setup log of prototype getting/setting, constructing, defining and deleting properties and a lot more.
Example with property set:
import { Log } from "@js-utilities/log";
@Log
class MyClass {}
new MyClass().myProp = "value";
Console output:
Also, it can be used as a decorator, but without decorators syntax:
import { Log } from "@js-utilities/log";
export default Log({ provideLogger: true })(class MyClass {});
export default Log(class MyClass {});
Method/Getter/Setter
When used as a method/getter/setter or a static
method/getter/setter decorator, will log all of the calls.
import { Log } from "@js-utilities/log";
class MyClass {
@Log
myMethod() {
return ["string1", "string2"];
}
}
new MyClass().myMethod();
Console output:
Also, can be used to override already declared options of owner class.
For example:
Disable specific method logging:
@Log
class MyClass {
@Log({ log: false })
get prop() {
return ["string1", "string2"];
}
}
Overriding name
option for specific method:
@Log
class MyClass {
@Log("Special name")
myMethod() {
return ["string1", "string2"];
}
}
Also, if class owner has @Log
declaration, method logger will use class's options and merge them with method's @Log
declaration options. Not for static method/getter/setter.
@Log({ logTimeStamp: true })
class MyClass {
@Log({ logExecutionTime: true })
myMethod() {
return ["string1", "string2"];
}
}
Console output:
If method return value type is Promise
, logger will resolve it's value and log it, instead of promise object. Execution time will also be re-calculated.
Property
Can be used on a property, but due to some limitations, only along with class decorator.
Same as in method/get/set, property decorator can used to override log options for the specific property.
import { Log } from "@js-utilities/log";
@Log({ logTimeStamp: true })
class MyClass {
@Log({
name: "'prop' with initial value 2000",
logTimeStamp: false,
})
prop = 1000;
}
new MyClass().prop = 2000;
Console output:
Parameter
For the moment, the only reason to use @Log
as parameter decorator is to extend specific param log output.
Normally, to avoid logs polluting, Arrays and Objects will be printed as Array
and Object
correspondingly.
To log array/object entries, decorate it with @Log
and provide max amount of entries to print @Log(2)
.
import { Log } from "@js-utilities/log";
class MyClass {
@Log
myMethod(@Log(2) arr1, arr2) {
return null;
}
}
new MyClass().myMethod(["val1", 100, 200], ["val2"]);
Console output:
Function
If you need to log an already instantiated class or a plain object or a function - you need log
function.
log
function accepts 2 arguments: entity to log and options (optional)
import { log } from "@js-utilities/log";
const logObject = log({ param: "value" });
const logObject = log({ param: "value" }, { logTimeStamp: true });
const logObject = log({ param: "value" }, "MyLogger");
Objects
Works the same way as for class, except for some options. Some of them are pointless (logConstructor
, logSubclass
):
import { log } from "@js-utilities/log";
const object = {
myMethod() {
return "value";
}
};
const logObject = log(object, {
name: "MyObject",
logExecutionTime: true
});
logObject.myMethod();
Console output:
Functions
log
with functions used the same as for objects:
import { log } from "@js-utilities/log";
const logObject = log(function (arg) { return null; }, "MyFunction");
logObject({ param: "value" });
Console output:
Instance logger
Providing & Interface
If there is a need to use log-like styling imperatively, you can access special logger
object:
-
via class @Log
decorator
-
via log
function
You can enable it with provideLogger
option set to true. You can configure provided logger via loggerOptions
option.
logger
interface:
interface InstanceMessageLogger {
log(msg: string, ...args: any[]): void;
info(msg: string, ...args: any[]): void;
warn(msg: string, ...args: any[]): void;
error(msg: string, ...args: any[]): void;
}
loggerOptions
option:
type InstanceMessageLoggerOptions = {
logName?: boolean;
logTimeStamp?: boolean;
logMs?: boolean;
logSuffix?: boolean | string;
}
Styling
For styling in browser template-colors-web is used, and chalk is used for node.js.
Logger will style your message based on provided options theme.
Also, it will parse and paint some values in corresponding theme color:
Logger usage
Example:
import { Log } from "@js-utilities/log";
@Log({ provideLogger: true })
class MyClass {
myMethod() {
this.logger.warn("styling $string(string), $number(1000)");
}
}
new MyClass().myMethod();
Console output:
Usage in TypeScript
In TypeScript, you should declare logger
before using:
import { Log, InstanceMessageLogger } from "@js-utilities/log";
@Log({ provideLogger: true })
class MyClass {
readonly logger!: InstanceMessageLogger;
}
Frameworks
Usually, framework entities (e.g. React/Angular components) do a lot of work under the hood of an object that we don't need to know about.
Logging them will pollute console with unwanted information. But we don't know how to distinct tech. properties/hooks.
So we need to explicitly define which properties should be logged and which should be logged in a special way.
Frameworks are detected on-the-fly, but can be forced by providing framework name in logger options. Even if provided with empty object.
List of framework, which are detected and filtered by Log
:
React
import * as React from "react";
import { Log } from "@js-utilities/log";
@Log({
react: { logHooks: true }
})
class MyComponent extends React.Component {
render() {
return null;
}
}
Console output:
Nest
On-class usage:
import { Module } from '@nestjs/common';
import { Log } from "@js-utilities/log";
@Log({ nest: { logHooks: true } })
@Module({
imports: [],
})
export class AppModule {
configure() {
return {};
}
}
Console output:
HTTP methods
import { Controller, Get } from '@nestjs/common';
import { Log } from "@js-utilities/log";
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get("hello")
@Log({
logTimeStamp: true,
logExecutionTime: true,
})
async getHello(): Promise<string> {
await this.appService.asyncAction();
return this.appService.getHello();
}
}
Console output:
Vue
On-object usage
It is important to define vue
in provided logger options when using with log
function, at least with empty object as a value.
It's needed to distinct common objects from vue components.
<script>
import { log } from "@js-utilities/log";
export default log({
name: 'HelloWorld',
props: { msg: String }
}, {
vue: { logHooks: true }
});
</script>
Console output:
On-class usage
<script lang="ts">
import { Log } from '@js-utilities/log';
import { Component, Vue } from 'vue-property-decorator';
@Log
@Component
export default class HelloWorld extends Vue {
@Log
protected mounted() {
this.method();
}
private method() {
return 'Mounted!';
}
}
</script>
Console output:
Angular
Ensure that you have reflect-metadata
polyfill and "emitDecoratorMetadata": true
set in tsconfig.json
.
import { Component } from '@angular/core';
import { Log } from "@js-utilities/log";
@Log({
angular: { logHooks: true }
})
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
prop: string;
ngOnInit() {
this.prop = "value";
}
}
Console output:
Other
To avoid console polluting with other frameworks / libraries, disable all props logging and enable only wanted ones explicitly:
import { Log } from "@js-utilities/log";
@Log({ logProperties: ["myProp"] })
class MyClass extends FrameworkEntity {
myProp = "value";
}
Framework config
Framework configuration description:
type FrameworkConfig = {
logTechnical?: boolean | string[];
logState?: boolean | string[];
logProps?: boolean | string[];
logHooks?: boolean | string[];
logRefs?: boolean | string[];
logContext?: boolean | string[];
logOther?: boolean | string[];
argsLogDepth?: number;
}
Options
Log library is highly-configurable and there are a lot of options available:
type LoggerOptions = {
name?: string | number;
logName?: boolean;
consoleMethod?: "log" | "group" | "groupCollapsed" | "groupEnd" | "warn" | "info";
theme?: "dark" | "light" | LoggerTheme;
provideLogger?: boolean;
loggerOptions?: InstanceMessageLoggerOptions;
logInterceptor?(logData: LogData): boolean;
log?: boolean;
logPropertiesFull?: boolean | PropertyKey[];
logExtensibleObjects?: boolean;
logWellKnownSymbols?: boolean | Symbol[];
logProtoMethods?: boolean | string[];
logExecutionTime?: boolean;
logTimeStamp?: boolean;
argsLogDepth?: (number | null | undefined)[];
logConstructor?: boolean;
logGetPrototypeOf?: boolean;
logSetPrototypeOf?: boolean;
logIsExtensible?: boolean;
logPreventExtensions?: boolean;
logProperties?: boolean | PropertyKey[];
logGetOwnPropertyDescriptor?: boolean | PropertyKey[];
logInOperator?: boolean | PropertyKey[];
logDeleteProperty?: boolean | PropertyKey[];
logDefineProperty?: boolean | PropertyKey[];
logOwnKeys?: boolean;
logInvocation?: boolean;
}
Environment requirements
Proxy
supportReflect
support
License
MIT License