Skip to content

Commit

Permalink
feat(logger): create logger module
Browse files Browse the repository at this point in the history
  • Loading branch information
KutsenkoA committed Nov 19, 2018
1 parent 4de94a3 commit 9c85f0d
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/api/core/client.ts
@@ -1,6 +1,7 @@
import { InjectionToken, ReflectiveInjector } from "injection-js";
import { Fetch, RequestAdapter } from "../../internal/requestAdapter";
import { deferPromise } from "../../internal/utils";
import { Logger } from "../logger/logger";
import { IModule } from "./module";
import { AuthOptions, IAuthOptions, TokenManager } from "./tokenManager";

Expand Down Expand Up @@ -54,10 +55,14 @@ export class Client {
provide: RequestAdapter,
useFactory: () => new RequestAdapter(this.fetch),
},
Logger,
TokenManager,
]);
this.tokenManager = injector.get(TokenManager);

/* provide logger for the request adapter */
injector.get(RequestAdapter).provideLogger(injector.get(Logger));

/* save only projectID (do not store key and secret) */
this.modules = modules;

Expand Down
212 changes: 212 additions & 0 deletions src/api/logger/logger.spec.ts
@@ -0,0 +1,212 @@
// tslint:disable:no-string-literal
import { Logger, LogLevel } from "./logger";

let logger: Logger;
let consoleMock: any;

describe("Logger", () => {

beforeEach(() => {
logger = new Logger();
consoleMock = {
log: jasmine.createSpy("log"),
};
});

it("should have default config after creating", () => {
expect(logger["level"]).toBe(LogLevel.NONE);
expect(logger["modules"]).toEqual(["all"]);
});

it("config method without arguments should set default values", () => {
logger.config();
expect([logger["level"], logger["modules"]]).toEqual([LogLevel.NONE, ["all"]]);
});

it("config method should set level and modules", () => {
logger.config(LogLevel.WARN, ["TestModule"]);
expect(logger["level"]).toBe(LogLevel.WARN);
expect(logger["modules"]).toEqual(["TestModule"]);
});

describe("debug", () => {

it("should call log method with DEBUG level", () => {
logger["log"] = jasmine.createSpy("log", logger["log"]);
logger.debug("TestModule", "TestMessage");
expect(logger["log"]).toHaveBeenCalledWith(LogLevel.DEBUG, "TestModule", "TestMessage");
});

it("should not print message if level is any but DEBUG", () => {
logger.config(LogLevel.INFO, ["all"], consoleMock);
logger.debug("TestModule", "TestMessage");
logger.config(LogLevel.WARN, ["all"], consoleMock);
logger.debug("TestModule", "TestMessage");
logger.config(LogLevel.ERROR, ["all"], consoleMock);
logger.debug("TestModule", "TestMessage");
logger.config(LogLevel.NONE, ["all"], consoleMock);
logger.debug("TestModule", "TestMessage");

expect(consoleMock.log).toHaveBeenCalledTimes(0);
});

it("should print message if level is DEBUG", () => {
logger.config(LogLevel.DEBUG, ["all"], consoleMock);
logger.debug("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});
});

describe("info", () => {

it("should call log method with INFO level", () => {
logger["log"] = jasmine.createSpy("log", logger["log"]);
logger.info("TestModule", "TestMessage");
expect(logger["log"]).toHaveBeenCalledWith(LogLevel.INFO, "TestModule", "TestMessage");
});

describe("should not print message if level is", () => {

it("WARN", () => {
logger.config(LogLevel.WARN, ["all"], consoleMock);
logger.info("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledTimes(0);
});

it("ERROR", () => {
logger.config(LogLevel.ERROR, ["all"], consoleMock);
logger.info("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledTimes(0);
});

it("NONE", () => {
logger.config(LogLevel.NONE, ["all"], consoleMock);
logger.info("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledTimes(0);
});
});

describe("should print message if level is", () => {

it("DEBUG", () => {
logger.config(LogLevel.DEBUG, ["all"], consoleMock);
logger.info("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});

it("INFO", () => {
logger.config(LogLevel.INFO, ["all"], consoleMock);
logger.info("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});
});
});

describe("warn", () => {

it("should call log method with WARN level", () => {
logger["log"] = jasmine.createSpy("log", logger["log"]);
logger.warn("TestModule", "TestMessage");
expect(logger["log"]).toHaveBeenCalledWith(LogLevel.WARN, "TestModule", "TestMessage");
});

describe("should not print message if level is", () => {

it("ERROR", () => {
logger.config(LogLevel.ERROR, ["all"], consoleMock);
logger.warn("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledTimes(0);
});

it("NONE", () => {
logger.config(LogLevel.NONE, ["all"], consoleMock);
logger.warn("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledTimes(0);
});
});

describe("should print message if level is", () => {

it("DEBUG", () => {
logger.config(LogLevel.DEBUG, ["all"], consoleMock);
logger.warn("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});

it("INFO", () => {
logger.config(LogLevel.INFO, ["all"], consoleMock);
logger.warn("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});

it("WARN", () => {
logger.config(LogLevel.WARN, ["all"], consoleMock);
logger.warn("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});
});
});

describe("error", () => {

it("should call log method with ERROR level", () => {
logger["log"] = jasmine.createSpy("log", logger["log"]);
logger.error("TestModule", "TestMessage");
expect(logger["log"]).toHaveBeenCalledWith(LogLevel.ERROR, "TestModule", "TestMessage");
});

it("should not print message if level is NONE", () => {
logger.config(LogLevel.NONE, ["all"], consoleMock);
logger.warn("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledTimes(0);
});

describe("should print message if level is", () => {

it("DEBUG", () => {
logger.config(LogLevel.DEBUG, ["all"], consoleMock);
logger.error("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});

it("INFO", () => {
logger.config(LogLevel.INFO, ["all"], consoleMock);
logger.error("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});

it("WARN", () => {
logger.config(LogLevel.WARN, ["all"], consoleMock);
logger.error("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});

it("ERROR", () => {
logger.config(LogLevel.ERROR, ["all"], consoleMock);
logger.error("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});
});
});

describe("log", () => {

it("should print message if modules has 'all' value", () => {
logger.config(LogLevel.DEBUG, ["all"], consoleMock);
logger.error("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});

it("should print message if provided module is included in modules", () => {
logger.config(LogLevel.DEBUG, ["TestModule"], consoleMock);
logger.error("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledWith("[TestModule]: TestMessage");
});

it("should not print message if neither provided module nor 'all' is not included in modules", () => {
logger.config(LogLevel.DEBUG, ["AnotherModule"], consoleMock);
logger.error("TestModule", "TestMessage");
expect(consoleMock.log).toHaveBeenCalledTimes(0);
});
});
});
59 changes: 59 additions & 0 deletions src/api/logger/logger.ts
@@ -0,0 +1,59 @@
import { Injectable } from "injection-js";

interface IConsole {
log: (...params: any[]) => void;
}

/* Logging levels
DEBUG - log as detailed as it possible
INFO - log only important steps
WARN - log only incorrect things
ERROR - only errors
NONE - keep silent */
export enum LogLevel {
DEBUG, INFO, WARN, ERROR, NONE
}

/* default configuration for the logger
level NONE means that logging is off */
const DEFAULT_CONFIG = {
level: LogLevel.NONE,
modules: ["all"],
};

@Injectable()
export class Logger {

private level: LogLevel = DEFAULT_CONFIG.level;
private modules: string[] = DEFAULT_CONFIG.modules;
private output: IConsole = console;

/* Logger can be configured with LoggerModule constructor */
public config(level: LogLevel = DEFAULT_CONFIG.level, modules: string[] = DEFAULT_CONFIG.modules,
output?: IConsole) {
this.level = level;
this.modules = modules;
if (output) {
this.output = output;
}
}

public debug(module: string, message: string) {
this.log(LogLevel.DEBUG, module, message);
}
public info(module: string, message: string) {
this.log(LogLevel.INFO, module, message);
}
public warn(module: string, message: string) {
this.log(LogLevel.WARN, module, message);
}
public error(module: string, message: string) {
this.log(LogLevel.ERROR, module, message);
}

private log(l: LogLevel, m: string, message: string) {
if (l >= this.level && (this.modules.includes("all") || this.modules.includes(m))) {
this.output.log(`[${m}]: ${message}`);
}
}
}
33 changes: 33 additions & 0 deletions src/api/logger/loggerModule.spec.ts
@@ -0,0 +1,33 @@
// tslint:disable:no-string-literal
import { ReflectiveInjector } from "injection-js";
import { Logger, LogLevel } from "./logger";
import { LoggerModule } from "./loggerModule";

describe("LoggerModule", () => {
let loggerModule: LoggerModule;

beforeEach(() => {
loggerModule = new LoggerModule(LogLevel.NONE, ["all"]);
});

it("when initializing should configure logger with provided values", () => {
const injector = ReflectiveInjector.resolveAndCreate([
Logger,
]);
loggerModule = new LoggerModule(LogLevel.INFO, ["TestModule"]);
loggerModule.init(injector);

const logger = injector.get(Logger);

expect([logger["level"], logger["modules"]]).toEqual([LogLevel.INFO, ["TestModule"]]);
});

describe("when terminating", () => {

it("should resolve automatically", async () => {
const result = await loggerModule.terminate();
expect(result).toBe(loggerModule);
});
});

});
24 changes: 24 additions & 0 deletions src/api/logger/loggerModule.ts
@@ -0,0 +1,24 @@
import { ReflectiveInjector } from "injection-js";
import { IModule } from "../core/module";
import { Logger, LogLevel } from "./logger";

export class LoggerModule implements IModule {

constructor(
private level: LogLevel,
private module?: string[]
) {}

public init(coreInjector: ReflectiveInjector) {

const logger = coreInjector.get(Logger);
logger.config(this.level, this.module);

return Promise.resolve(this);
}

public terminate() {
return Promise.resolve(this);
}

}
2 changes: 2 additions & 0 deletions src/api/logger/public-api.ts
@@ -0,0 +1,2 @@
export { LoggerModule } from "./loggerModule";
export { LogLevel } from "./logger";
7 changes: 7 additions & 0 deletions src/browser.ts
Expand Up @@ -2,6 +2,7 @@ export * from "./index";
import { Client } from "./api/core/client";
import { TokenStorage, WebStorageComponent } from "./api/core/componentStorage";
import { DataOperationsModule } from "./api/dataops/dataOperationsModule";
import { LoggerModule, LogLevel } from "./api/logger/public-api";
import { IWebSocketBuilder } from "./api/realtime/realTime.interfaces";
import { RealTimeModule } from "./api/realtime/realTimeModule";

Expand Down Expand Up @@ -50,3 +51,9 @@ export function dataOperations(): DataOperationsModule {
export function realTime(webSocketBuilder: IWebSocketBuilder = (appUrl) => new WebSocket(appUrl)): RealTimeModule {
return new RealTimeModule(webSocketBuilder);
}

export function logger(level: LogLevel, modules?: string[]) {
return new LoggerModule(level, modules);
}

export * from "./api/logger/public-api";
9 changes: 9 additions & 0 deletions src/internal/requestAdapter.ts
@@ -1,3 +1,4 @@
import { Logger } from "../api/logger/logger";
import { MESSAGE } from "../config";

/* List of allowed methods */
Expand Down Expand Up @@ -47,9 +48,17 @@ export interface IRequestAdapter {
export type Fetch = (url: string, init?: IRequestOptions) => Promise<IHTTPResponse>;

export class RequestAdapter implements IRequestAdapter {

private logger: Logger = new Logger();

constructor(private fetch: Fetch) {}

public provideLogger(logger: Logger) {
this.logger = logger;
}

public execute(uri: string, opt: IRequestOptions): Promise<any> {
this.logger.debug("RequestAdapter", `${opt.method} ${uri}`);
return this.fetch(uri, {body: JSON.stringify(opt.body), headers: opt.headers, method: opt.method})
/* check response status */
.then(status)
Expand Down

0 comments on commit 9c85f0d

Please sign in to comment.