Skip to content

Commit

Permalink
Merge pull request #10 from aigoncharov/fix/6
Browse files Browse the repository at this point in the history
Fix/6
  • Loading branch information
aigoncharov authored Jan 25, 2020
2 parents 2df85da + 7932b63 commit 1ae5d53
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 125 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# class-logger [![Build Status](https://travis-ci.org/keenondrums/class-logger.svg?branch=master)](https://travis-ci.org/keenondrums/class-logger) [![Coverage Status](https://coveralls.io/repos/github/keenondrums/class-logger/badge.svg?branch=master)](https://coveralls.io/github/keenondrums/class-logger?branch=master) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Boilerplate-free%20decorator-based%20class%20logging.&url=https://github.com/keenondrums/class-logger&hashtags=typescript,javascript,decorators,logging)
# class-logger [![Build Status](https://travis-ci.org/aigoncharov/class-logger.svg?branch=master)](https://travis-ci.org/aigoncharov/class-logger) [![Coverage Status](https://coveralls.io/repos/github/aigoncharov/class-logger/badge.svg?branch=master)](https://coveralls.io/github/aigoncharov/class-logger?branch=master) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Boilerplate-free%20decorator-based%20class%20logging.&url=https://github.com/aigoncharov/class-logger&hashtags=typescript,javascript,decorators,logging)

Boilerplate-free decorator-based class logging. Log method calls and creation of your class easily with the help of two decorators. No prototype mutation. Highly configurable. Built with TypeScript. Works with Node.js and in browser.

Expand Down Expand Up @@ -72,7 +72,7 @@ Logs `Test.method1 -> done. Args: []. Res: 123.` after it.

## Requirements

Your evnvironment must support [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). For Node.js it's [6.4.0+](https://node.green/), for browsers it's [Edge 12+, Firefox 18+, Chrome 49+, Safari 10+](https://caniuse.com/#search=proxy).
Your environment must support [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). For Node.js it's [6.4.0+](https://node.green/), for browsers it's [Edge 12+, Firefox 18+, Chrome 49+, Safari 10+](https://caniuse.com/#search=proxy).

## Quick start [(Live demo)](https://stackblitz.com/edit/class-logger-demo-basic)

Expand Down Expand Up @@ -140,7 +140,7 @@ test.methodAsync1()
// 'Test.methodError. Args: [].'
test.methodError()
// Logs to the console after the method call:
// 'Test.methodError -> error. Args: []. Res: {"className":"Error","name":"Error","message":"","stack":"some stack trace"}.'
// 'Test.methodError -> error. Args: []. Res: Error {"name":"Error","message":"","stack":"some stack trace"}.'

// Logs to the console before the method call:
// 'Test.property1. Args: [].'
Expand Down Expand Up @@ -278,9 +278,9 @@ It enables/disabled including the formatted class instance to your log messages.
- Why? It's a rare case when your prototype changes dynamically, therefore it hardly makes any sense to log it.
- Drop any of them that have `function` type.
- Why? Most of the time `function` properties are just immutable arrow functions used instead of regular class methods to preserve `this` context. It doesn't make much sense to bloat your logs with stringified bodies of those functions.
- Drop any of them that are not plain objects.
- Transform any of them that are not plain objects recursively.
- What objects are plain ones? `ClassLoggerFormatterService` considers an object a plain object if its prototype is strictly equal to `Object.prototype`.
- Why? Often we include instances of other classes as properties (inject them as dependencies). Our logs would become extremely fat if we included stringified versions of these dependencies.
- Why? Often we include instances of other classes as properties (inject them as dependencies). By stringifying them using the same algorithm we can see what we injected.
- Stringify what's left.

Example:
Expand All @@ -306,14 +306,14 @@ class Test {
}

// Logs to the console before the class' construction:
// 'Test.construct. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.'
// 'Test.construct. Args: []. Class instance: {"serviceA": ServiceA {},"prop1":42,"prop2":{"test":42}}.'
const test = new Test()

// Logs to the console before the method call:
// 'Test.method2. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.'
// 'Test.method2. Args: []. Class instance: {"serviceA": ServiceA {},"prop1":42,"prop2":{"test":42}}.'
test.method2()
// Logs to the console after the method call:
// 'Test.method2 -> done. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}. Res: 42.'
// 'Test.method2 -> done. Args: []. Class instance: {"serviceA": ServiceA {},"prop1":42,"prop2":{"test":42}}. Res: 42.'
```

> If a class instance is not available at the moment (e.g. for class construction or calls of static methods), it logs `N/A`.
Expand Down
66 changes: 53 additions & 13 deletions index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('index', () => {
expect(spyConsoleInfo).toBeCalledTimes(1)
expect(spyConsoleInfo).toBeCalledWith('Test.staticError. Args: [test1, test2]. Class instance: N/A.')
expect(spyConsoleError).toBeCalledWith(
'Test.staticError -> error. Args: [test1, test2]. Class instance: N/A. Res: {"className":"TestError","code":"codeTest","message":"messageTest","name":"Error","stack":"stackTest"}.',
'Test.staticError -> error. Args: [test1, test2]. Class instance: N/A. Res: TestError {"code":"codeTest","message":"messageTest","name":"Error","stack":"stackTest"}.',
)
})
test('propSyncSuccess', () => {
Expand All @@ -99,10 +99,13 @@ describe('index', () => {
expect(spyConsoleError).toBeCalledTimes(0)
expect(spyConsoleInfo).toBeCalledTimes(3)
expect(spyConsoleInfo).toHaveBeenNthCalledWith(1, 'Test.construct. Args: []. Class instance: N/A.')
expect(spyConsoleInfo).toHaveBeenNthCalledWith(2, 'Test.propSyncSuccess. Args: []. Class instance: {"prop1":123}.')
expect(spyConsoleInfo).toHaveBeenNthCalledWith(
2,
'Test.propSyncSuccess. Args: []. Class instance: Test {"prop1":123}.',
)
expect(spyConsoleInfo).toHaveBeenNthCalledWith(
3,
'Test.propSyncSuccess -> done. Args: []. Class instance: {"prop1":123}. Res: syncSuccessResTest.',
'Test.propSyncSuccess -> done. Args: []. Class instance: Test {"prop1":123}. Res: syncSuccessResTest.',
)
})
test('propSyncError', () => {
Expand All @@ -112,9 +115,12 @@ describe('index', () => {
expect(spyConsoleError).toBeCalledTimes(1)
expect(spyConsoleInfo).toBeCalledTimes(2)
expect(spyConsoleInfo).toHaveBeenNthCalledWith(1, 'Test.construct. Args: []. Class instance: N/A.')
expect(spyConsoleInfo).toHaveBeenNthCalledWith(2, 'Test.propSyncError. Args: []. Class instance: {"prop1":123}.')
expect(spyConsoleInfo).toHaveBeenNthCalledWith(
2,
'Test.propSyncError. Args: []. Class instance: Test {"prop1":123}.',
)
expect(spyConsoleError).toBeCalledWith(
'Test.propSyncError -> error. Args: []. Class instance: {"prop1":123}. Res: {"className":"TestError","code":"codeTest","message":"messageTest","name":"Error","stack":"stackTest"}.',
'Test.propSyncError -> error. Args: []. Class instance: Test {"prop1":123}. Res: TestError {"code":"codeTest","message":"messageTest","name":"Error","stack":"stackTest"}.',
)
})
test('syncSuccess', () => {
Expand All @@ -127,10 +133,13 @@ describe('index', () => {
expect(spyConsoleInfo).toBeCalledTimes(1)
expect(spyConsoleDebug).toBeCalledTimes(2)
expect(spyConsoleInfo).toBeCalledWith('Test.construct. Args: []. Class instance: N/A.')
expect(spyConsoleDebug).toHaveBeenNthCalledWith(1, 'Test.syncSuccess. Args: []. Class instance: {"prop1":123}.')
expect(spyConsoleDebug).toHaveBeenNthCalledWith(
1,
'Test.syncSuccess. Args: []. Class instance: Test {"prop1":123}.',
)
expect(spyConsoleDebug).toHaveBeenNthCalledWith(
2,
'Test.syncSuccess -> done. Args: []. Class instance: {"prop1":123}. Res: syncSuccessResTest.',
'Test.syncSuccess -> done. Args: []. Class instance: Test {"prop1":123}. Res: syncSuccessResTest.',
)
})
test('syncError', () => {
Expand All @@ -142,9 +151,9 @@ describe('index', () => {
expect(spyConsoleInfo).toBeCalledTimes(1)
expect(spyConsoleDebug).toBeCalledTimes(1)
expect(spyConsoleInfo).toBeCalledWith('Test.construct. Args: []. Class instance: N/A.')
expect(spyConsoleDebug).toBeCalledWith('Test.syncError. Args: []. Class instance: {"prop1":123}.')
expect(spyConsoleDebug).toBeCalledWith('Test.syncError. Args: []. Class instance: Test {"prop1":123}.')
expect(spyConsoleError).toBeCalledWith(
'Test.syncError -> error. Args: []. Class instance: {"prop1":123}. Res: {"className":"TestError","code":"codeTest","message":"messageTest","name":"Error","stack":"stackTest"}.',
'Test.syncError -> error. Args: []. Class instance: Test {"prop1":123}. Res: TestError {"code":"codeTest","message":"messageTest","name":"Error","stack":"stackTest"}.',
)
})
test('asyncSuccess', async () => {
Expand All @@ -159,11 +168,11 @@ describe('index', () => {
expect(spyConsoleInfo).toHaveBeenNthCalledWith(1, 'Test.construct. Args: []. Class instance: N/A.')
expect(spyConsoleInfo).toHaveBeenNthCalledWith(
2,
'Test.asyncSuccess. Args: [Symbol()]. Class instance: {"prop1":123}.',
'Test.asyncSuccess. Args: [Symbol()]. Class instance: Test {"prop1":123}.',
)
expect(spyConsoleInfo).toHaveBeenNthCalledWith(
3,
'Test.asyncSuccess -> done. Args: [Symbol()]. Class instance: {"prop1":123}. Res: syncSuccessResTest.',
'Test.asyncSuccess -> done. Args: [Symbol()]. Class instance: Test {"prop1":123}. Res: syncSuccessResTest.',
)
})
test('asyncError', async () => {
Expand All @@ -177,10 +186,41 @@ describe('index', () => {
expect(spyConsoleInfo).toHaveBeenNthCalledWith(1, 'Test.construct. Args: []. Class instance: N/A.')
expect(spyConsoleInfo).toHaveBeenNthCalledWith(
2,
'Test.asyncError. Args: [Symbol()]. Class instance: {"prop1":123}.',
'Test.asyncError. Args: [Symbol()]. Class instance: Test {"prop1":123}.',
)
expect(spyConsoleDebug).toBeCalledWith(
'Test.asyncError -> error. Args: [Symbol()]. Class instance: {"prop1":123}. Res: {"className":"TestError","code":"codeTest","message":"messageTest","name":"Error","stack":"stackTest"}.',
'Test.asyncError -> error. Args: [Symbol()]. Class instance: Test {"prop1":123}. Res: TestError {"code":"codeTest","message":"messageTest","name":"Error","stack":"stackTest"}.',
)
})

test('keeps third-party metadata', () => {
class TestMeta {
@Log()
public static static1() {} // tslint:disable-line no-empty

@Log()
public method1() {} // tslint:disable-line no-empty
}

const keyClass = Symbol()
Reflect.defineMetadata(keyClass, 42, TestMeta)

const keyPrototype = Symbol()
Reflect.defineMetadata(keyPrototype, 43, TestMeta.prototype)

const keyProp = Symbol()
Reflect.defineMetadata(keyProp, 44, TestMeta.prototype, 'method1')

const keyStatic = Symbol()
Reflect.defineMetadata(keyStatic, 45, TestMeta, 'static1')

const TestMetaWrapped = LogClass()(TestMeta)

expect(Reflect.getMetadata(keyClass, TestMeta)).toBe(42)
expect(Reflect.getMetadata(keyStatic, TestMeta, 'static1')).toBe(45)

const instance = new TestMetaWrapped()
expect(Reflect.getMetadata(keyPrototype, instance)).toBe(43)
expect(Reflect.getMetadata(keyProp, instance, 'method1')).toBe(44)
})
})
74 changes: 74 additions & 0 deletions integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'reflect-metadata'

import { Container, inject, injectable } from 'inversify'

import { Log, LogClass, setConfig } from './index'

describe('integration', () => {
// https://github.com/aigoncharov/class-logger/issues/6
describe('inversify', () => {
it('', () => {
const logFn = jest.fn()

setConfig({
include: {
construct: true,
},
log: logFn,
})

const TYPES = {
AI: Symbol.for('AI'),
CPU: Symbol.for('CPU'),
}

@LogClass()
@injectable()
class CPU {
private readonly res = 42

@Log()
public calc() {
return this.res
}
}

@LogClass()
@injectable()
class AI {
constructor(@inject(TYPES.CPU) private readonly _cpu: CPU) {}

@Log()
public takeOverTheWorld() {
return this._cpu.calc() * 2
}
}

const myContainer = new Container()
myContainer.bind(TYPES.CPU).to(CPU)
myContainer.bind(TYPES.AI).to(AI)

const cpu = myContainer.get<CPU>(TYPES.CPU)
expect(cpu).toBeInstanceOf(CPU)

const ai = myContainer.get<AI>(TYPES.AI)
expect(ai).toBeInstanceOf(AI)

const res = ai.takeOverTheWorld()
expect(res).toBe(84)

expect(logFn).toBeCalledTimes(7)
expect(logFn.mock.calls).toEqual([
// Getting CPU from the container explicitly
['CPU.construct. Args: [].'],
// Injecting CPU into AI
['CPU.construct. Args: [].'],
['AI.construct. Args: [CPU {"res":42}].'],
['AI.takeOverTheWorld. Args: [].'],
['CPU.calc. Args: [].'],
['CPU.calc -> done. Args: []. Res: 42.'],
['AI.takeOverTheWorld -> done. Args: []. Res: 84.'],
])
})
})
})
49 changes: 16 additions & 33 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "class-logger",
"version": "1.2.0",
"version": "1.3.0",
"description": "Boilerplate-free decorator-based class logging",
"keywords": [
"decorator",
Expand Down Expand Up @@ -34,6 +34,7 @@
"coveralls": "^3.0.3",
"doctoc": "^1.4.0",
"husky": "^1.3.1",
"inversify": "^5.0.1",
"jest": "^24.5.0",
"lint-staged": "^8.1.5",
"prettier": "^1.16.4",
Expand All @@ -43,7 +44,7 @@
"tslint": "^5.14.0",
"tslint-config-prettier": "^1.18.0",
"tslint-config-standard": "^8.0.1",
"typescript": "^3.3.4000"
"typescript": "^3.7.5"
},
"peerDependencies": {
"reflect-metadata": "^0.1.13"
Expand Down
Loading

0 comments on commit 1ae5d53

Please sign in to comment.