Skip to content

Commit

Permalink
feat: added logger (#33)
Browse files Browse the repository at this point in the history
* feat: added basic logger

* build: run ci on any branch

* build: run ci on push
  • Loading branch information
wouteraj authored Apr 17, 2021
1 parent 21305c1 commit f4fae4e
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 16 deletions.
13 changes: 1 addition & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
name: CI

on:
push:
branches:
- release
- release/*
- feature/*
- fix/*
- develop
- master

tags:
- '*'
on: push

env:
NPM_TOKEN: ${{secrets.GA_TOKEN}}
Expand Down
21 changes: 21 additions & 0 deletions packages/nde-erfgoed-core/lib/errors/argument-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BaseError } from './base-error';

/**
* An error thrown when a function is called with invalid arguments.
*/
export class ArgumentError extends BaseError {
public readonly name = ArgumentError.name;

/**
* Instantiates the error.
*
* @param message A message which describes the error.
* @param value The value of the invalid argument.
* @param cause The underlying error.
*/
constructor(message: string, public value: unknown, cause?: Error) {
super(message, cause);

Object.setPrototypeOf(this, ArgumentError.prototype);
}
}
18 changes: 18 additions & 0 deletions packages/nde-erfgoed-core/lib/errors/base-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* A base error which takes a cause.
*/
export class BaseError extends Error {
public readonly name = BaseError.name;

/**
* Instantiates an error.
*
* @param messsage Describes the error.
* @param cause The underlying cause of the error.
*/
constructor(messsage: string, public cause: Error) {
super(messsage);

Object.setPrototypeOf(this, BaseError.prototype);
}
}
5 changes: 5 additions & 0 deletions packages/nde-erfgoed-core/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
* Exports the modules of the package.
*/
export * from './collections/collection';
export * from './errors/argument-error';
export * from './errors/base-error';
export * from './logging/console-logger';
export * from './logging/logger-level';
export * from './logging/logger';
export * from './stores/memory-store';
export * from './stores/resource';
export * from './stores/store';
89 changes: 89 additions & 0 deletions packages/nde-erfgoed-core/lib/logging/console-logger.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { ConsoleLogger } from './console-logger';
import { LoggerLevel } from './logger-level';

describe('ConsoleLogger', () => {
let service: ConsoleLogger;

beforeEach(async () => {
service = new ConsoleLogger(LoggerLevel.silly, LoggerLevel.silly);
});

afterEach(() => {
// clear spies
jest.clearAllMocks();
});

it('should be correctly instantiated', () => {
expect(service).toBeTruthy();
});

describe('log', () => {

const levels = [ 'info', 'debug', 'warn', 'error' ];

it('LoggerLevel.silly should call console.log', () => {
const consoleSpy = jest.spyOn(console, 'log');
service.log(LoggerLevel.silly, 'TestService', 'test message', 'data');
expect(consoleSpy).toHaveBeenCalled();
});

for (const level of levels) {
if (level) {
it(`LoggerLevel.${level} should call console.${level}`, () => {
const consoleSpy = jest.spyOn(console, level as any);
service.log(LoggerLevel[level], 'TestService', 'test message', 'data');
expect(consoleSpy).toHaveBeenCalled();
});
}
}

const params = {
level: LoggerLevel.info,
typeName: ' TestService',
message: 'test message',
};
const args = Object.keys(params);
args.forEach((argument) => {
it(`should throw error when ${argument} is null or undefined`, () => {
const testArgs = args.map((arg) => arg === argument ? null : arg);
expect(() => service.log.apply(service.log, testArgs))
.toThrow(`${argument} should be set`);
});
});
});

describe('level logs', () => {

const levels = [ 'info', 'debug', 'warn', 'error' ];

for (const level of levels) {
if (level) {
it(`should log a ${level} message`, () => {
const loggerSpy = jest.spyOn(service, 'log');
if (level === 'error') {
service[level]('TestService', 'test message', 'test error', 'error');
expect(loggerSpy).toHaveBeenCalledWith(LoggerLevel.error, 'TestService', 'test message', { error: 'test error', caught: 'error' });
} else {
service[level]('TestService', 'test message', 'test data');
expect(loggerSpy).toHaveBeenCalledWith(LoggerLevel[level], 'TestService', 'test message', 'test data');
}
});

// test arguments for null or undefined
const params = {
level: LoggerLevel.info,
typeName: ' TestService',
};
const args = Object.keys(params);
args.forEach((argument) => {
it(`should throw error when ${argument} is null or undefined`, () => {
const testArgs = args.map((arg) => arg === argument ? null : arg);
expect(() => service.log.apply(service[level], testArgs))
.toThrow(`${argument} should be set`);
});
});
}
}
});

});
76 changes: 76 additions & 0 deletions packages/nde-erfgoed-core/lib/logging/console-logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable no-console -- this is a logger service */

import { ArgumentError } from '../errors/argument-error';
import { Logger } from './logger';
import { LoggerLevel } from './logger-level';

/**
* JavaScript console-based logger service
*/
export class ConsoleLogger extends Logger {

/**
* Instantiates the logger.
*
* @param minimumLevel The minimum level of a log to be printed.
* @param minimumLevelPrintData The minimum level of a log for data to be printed.
*/
constructor(
protected readonly minimumLevel: LoggerLevel,
protected readonly minimumLevelPrintData: LoggerLevel,
) {
super(minimumLevel, minimumLevelPrintData);
}

/**
* Logs a message
*
* @param level Severity level of the log
* @param typeName The location of the log
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
log(level: LoggerLevel, typeName: string, message: string, data?: any) {
if (level === null || level === undefined) {
throw new ArgumentError('level should be set', typeName);
}

if (!typeName) {
throw new ArgumentError('typeName should be set', typeName);
}

if (!message) {
throw new ArgumentError('message should be set', message);
}

const timestamp: string = new Date().toISOString();

if (level <= this.minimumLevel) {
const logMessage = `[${timestamp} ${typeName}] ${message}`;
const logData = level >= this.minimumLevelPrintData ? '' : data||'';
const log = [ logMessage, logData ];
switch (level) {

case LoggerLevel.info:
console.info(...log);
break;

case LoggerLevel.debug:
console.debug(...log);
break;

case LoggerLevel.warn:
console.warn(...log);
break;

case LoggerLevel.error:
console.error(...log);
break;

default:
console.log(...log);
break;
}
}
}
}
12 changes: 12 additions & 0 deletions packages/nde-erfgoed-core/lib/logging/logger-level.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Level of log severity based on node.js'
*/
export enum LoggerLevel {
error = 0,
warn = 1,
info = 2,
http = 3,
verbose = 4,
debug = 5,
silly = 6,
}
109 changes: 109 additions & 0 deletions packages/nde-erfgoed-core/lib/logging/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* eslint-disable no-console -- this is a logger service */

import { ArgumentError } from '../errors/argument-error';
import { LoggerLevel } from './logger-level';

/**
* An abstract definition of a logger.
*/
export abstract class Logger {

/**
* Instantiates the logger.
*
* @param minimumLevel The minimum level of a log to be printed.
* @param minimumLevelPrintData The minimum level of a log for data to be printed.
*/
constructor(
protected readonly minimumLevel: LoggerLevel,
protected readonly minimumLevelPrintData: LoggerLevel,
) {}

/**
* Logs an info message
*
* @param typeName The location of the log
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
info(typeName: string, message: string, data?: any) {
if (!typeName) {
throw new ArgumentError('Typename should be set', typeName);
}

if (!message) {
throw new ArgumentError('Message should be set', message);
}

this.log(LoggerLevel.info, typeName, message, data);
}

/**
* Logs a debug message
*
* @param typeName The location of the log
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
debug(typeName: string, message: string, data?: any) {
if (!typeName) {
throw new ArgumentError('Typename should be set', typeName);
}

if (!message) {
throw new ArgumentError('Message should be set', message);
}

this.log(LoggerLevel.debug, typeName, message, data);
}

/**
* Logs a warning message
*
* @param typeName The location of the log
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
warn(typeName: string, message: string, data?: any) {
if (!typeName) {
throw new ArgumentError('Typename should be set', typeName);
}

if (!message) {
throw new ArgumentError('Message should be set', message);
}

this.log(LoggerLevel.warn, typeName, message, data);
}

/**
* Logs an error message
*
* @param typeName The location of the log
* @param message Message that should be logged
* @param error The error that was thrown
* @param caught The error that was caught
*/
error(typeName: string, message: string, error?: Error | any, caught?: any) {
if (!typeName) {
throw new ArgumentError('Typename should be set', typeName);
}

if (!message) {
throw new ArgumentError('Message should be set', message);
}

this.log(LoggerLevel.error, typeName, message, { error, caught });
}

/**
* Logs a message
*
* @param level Severity level of the log
* @param typeName The location of the log
* @param message Message that should be logged
* @param data Any relevant data that should be logged
*/
abstract log(level: LoggerLevel, typeName: string, message: string, data?: any): void;

}
1 change: 1 addition & 0 deletions packages/nde-erfgoed-core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"downlevelIteration": true,
"suppressImplicitAnyIndexErrors": true,
},
"include": ["lib"],
"exclude": [
Expand Down
5 changes: 4 additions & 1 deletion packages/nde-erfgoed-manage/lib/app.root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { interpret, State } from 'xstate';
import { RxLitElement } from 'rx-lit';
import { from } from 'rxjs';
import { tap } from 'rxjs/operators';
import { ConsoleLogger, Logger, LoggerLevel } from '@digita-ai/nde-erfgoed-core';
import { AppActors, appMachine } from './app.machine';
import { CollectionsRootComponent } from './features/collections/root/collections-root.component';
import { AppStates } from './app.states';
Expand All @@ -14,6 +15,8 @@ import { AppContext } from './app.context';
*/
export class AppRootComponent extends RxLitElement {

private logger: Logger = new ConsoleLogger(LoggerLevel.silly, LoggerLevel.silly);

/**
* The constructor of the application root component,
* which starts the root machine actor.
Expand Down Expand Up @@ -45,7 +48,7 @@ export class AppRootComponent extends RxLitElement {
super.firstUpdated(changed);

this.subscribe('state', from(this.actor).pipe(
tap((state) => console.log('AppState change', state)),
tap((state) => this.logger.debug(CollectionsRootComponent.name, 'AppState change:', state)),
));
}

Expand Down
Loading

0 comments on commit f4fae4e

Please sign in to comment.