1.0.0 • Published 2 years ago

@js-utilities/log v1.0.0

Weekly downloads
7
License
MIT
Repository
github
Last release
2 years ago

Automated, styled, declarative javascript debugging (logging)

npm version license Build Status

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

In Node.js

node.js output

Can be used to inject logger, has Light and Dark predefined themes

instance logger

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:

  • JavaScript

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)

  • TypeScript

Decorators are available as an experimental feature of TypeScript. Official instruction here.

@Log decorator can be used in different ways:

// package will automatically detect wether you need browser or node version
import { Log } from "@js-utilities/log";

// default, no options provided
@Log
class MyClass {}

// with options
@Log({ logExecutionTime: true })
class MyClass {}

// shorthand for @Log({ name: "MyLogger" })
@Log("MyLogger")
class MyClass {}

If you want to explicitly import browser / node version:

// browser
import { Log } from "@js-utilities/log/dist/browser";

// node
import { Log } from "@js-utilities/log/dist/node";

Class

When used as a class decorator, will log:

  • all methods, getters and setters calls

  • properties accessing / setting

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";

// with options
export default Log({ provideLogger: true })(class MyClass {});

// without options
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";

// default, no options provided
const logObject = log({ param: "value" });

// with options
const logObject = log({ param: "value" }, { logTimeStamp: true });

// shorthand for log(entity, { name: "MyLogger" })
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:

1) via class @Log decorator

2) 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:

  • $string()

  • $number()

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 = {
    
    /**
     * Technical properties to log (e.g. "updater", "_reactInternalFiber", "__file").
     * 
     * @default undefined
     */
    logTechnical?: boolean | string[];
    
    /**
     * State to log (e.g. "state").
     * 
     * @default undefined
     */
    logState?: boolean | string[];
    
    /**
     * Props to log (e.g. "props").
     * 
     * @default undefined
     */
    logProps?: boolean | string[];
    
    /**
     * Hooks to log (e.g. "render", "shouldComponentUpdate", "onModuleInit").
     * 
     * @default undefined
     */
    logHooks?: boolean | string[];
    
    /**
     * Refs to log (e.g. "refs").
     * 
     * @default undefined
     */
    logRefs?: boolean | string[];
    
    /**
     * Context to log (e.g. "context").
     * 
     * @default undefined
     */
    logContext?: boolean | string[];
    
    /**
     * Other framework properties to log (e.g. "setState", "forceUpdate", "intercept").
     * 
     * @default undefined
     */
    logOther?: boolean | string[];
    
    /**
     * Default log depth for framework properties.
     * 
     * @default 1
     */
    argsLogDepth?: number;
}

Options

Log library is highly-configurable and there are a lot of options available:

type LoggerOptions = {
    
    /**
     * Name of the logger.
     * All messages will be prepended with this field's value.
     *
     * @default function or class name
     */
    name?: string | number;

    /**
     * Flag for enabling/disabling Logger name logging.
     *
     * @default true
     */
    logName?: boolean;

    /**
     * Console method which will be used to output messages.
     *
     * @default "log"
     */
    consoleMethod?: "log" | "group" | "groupCollapsed" | "groupEnd" | "warn" | "info";

    /**
     * Logger theme. If object will be provided, "dark" will be used as main theme
     * and overridden with provided object properties.
     *
     * @default "dark"
     */
    theme?: "dark" | "light" | LoggerTheme;

    /**
     * InstanceMessageLogger description can be found in Instance logger section of readme.
     *
     * @default false
     */
    provideLogger?: boolean;

    /**
     * InstanceMessageLogger description can be found in Instance logger section of readme.
     *
     * @default { logTimeStamp: true, logSuffix: true }
     */
    loggerOptions?: InstanceMessageLoggerOptions;
    
    /**
     * Will be invoked on every log, right before writing to console.
     * Can prevent log by returning "false".
     *
     * @param {LogData} logData   collected data of proxy trap
     *
     * @return {Boolean} log decorated message to console or no
     */
    logInterceptor?(logData: LogData): boolean;

    /**
     * Flag for enabling/disabling logging.
     *
     * @default true
     */
    log?: boolean;

    /**
     * The same as "logProperties", but for all types of the property manipulation.
     * (e.g. "prop" in class, delete class["prop"], getOwnPropertyDescriptor)
     *
     * @default false
     */
    logPropertiesFull?: boolean | PropertyKey[];

    /**
     * Some object just can't fit into well-looking console output,
     * so you can force logger to log full objects separately
     * right after the log message.
     *
     * @default false
     */
    logExtensibleObjects?: boolean;

    /**
     * Option to control "toString()", "valueOf()" and other well-known calls.
     *
     * @default false
     */
    logWellKnownSymbols?: boolean | Symbol[];

    /**
     * Option to control "hasOwnProperty" and other Object.prototype calls.
     *
     * @default false
     */
    logProtoMethods?: boolean | string[];

    /**
     * Will append execution time (of method call etc.) raw to every log message.
     * In browser: Performance API, in Node.js - "perf_hooks" core module.
     *
     * @default false
     */
    logExecutionTime?: boolean;

    /**
     * Add timestamp for each log message.
     *
     * @default true
     */
    logTimeStamp?: boolean;

    /**
     * Alternative for @Log parameter decorator.
     * Array of depth log for each argument.
     *
     * If you want to set log depth 5 only for the second parameter:
     * argsLogDepth: [null, 5]
     *
     * @default []
     */
    argsLogDepth?: (number | null | undefined)[];

    /**
     * Log class constructing.
     *
     * @default false
     */
    logConstructor?: boolean;

    /**
     * Log getPrototypeOf calls to the class.
     *
     * @default false
     */
    logGetPrototypeOf?: boolean;

    /**
     * Log setPrototypeOf calls to the class.
     *
     * @default false
     */
    logSetPrototypeOf?: boolean;

    /**
     * Log isExtensible calls to the class.
     *
     * @default false
     */
    logIsExtensible?: boolean;

    /**
     * Log preventExtensions calls to the class.
     *
     * @default false
     */
    logPreventExtensions?: boolean;

    /**
     * A list of properties (or boolean to affect all of them),
     * whose accessing/setting/calling will be logged.
     *
     * @default true
     */
    logProperties?: boolean | PropertyKey[];

    /**
     * A list of properties (or boolean to affect all of them),
     * whose getOwnPropertyDescriptor calls will be logged.
     *
     * @default false
     */
    logGetOwnPropertyDescriptor?: boolean | PropertyKey[];

    /**
     * A list of properties (or boolean to affect all of them),
     * whose "prop in Obj" checks will be logged.
     *
     * @default false
     */
    logInOperator?: boolean | PropertyKey[];

    /**
     * A list of properties (or boolean to affect all of them),
     * whose "delete prop" executions will be logged.
     *
     * @default false
     */
    logDeleteProperty?: boolean | PropertyKey[];

    /**
     * A list of properties (or boolean to affect all of them),
     * whose defineProperty calls will be logged.
     *
     * @default false
     */
    logDefineProperty?: boolean | PropertyKey[];

    /**
     * A list of properties (or boolean to affect all of them),
     * whose defineProperty calls will be logged.
     *
     * @default false
     */
    logOwnKeys?: boolean;

    /**
     * Log entity invocation (usually a function, but also
     * might be a class extending Function).
     *
     * @default true
     */
    logInvocation?: boolean;
}

Environment requirements

  • Proxy support
  • Reflect support

License

MIT License