Skip to content

Commit 5169829

Browse files
feat: expose logger and enable configuring it
Expose `$logger` for using when CLI is required as a library. Also introduce `initialize` method in it, that allows full configuration of the log4js instance. Implement a new `emit-appender` logger, that emits `logData` event with information for the logging instead of printing it to the stdout. This emitter can be used with the following code: ```JavaScript const tns = require("nativescript"); const { LoggerAppenders } = tns.constants; const { EventEmitter } = require("events"); const emitter = new EventEmitter(); // IMPORTANT: Due to current log4js behavior, you must set the event handler BEFORE calling initialize of the logger. emitter.on("logData", logData => { // logData contains two properties: loggingEvent and formattedMessage // loggingEvent contains information about the level of the message and the raw message itself // formattedMessage is the message with specified layout (i.e. it is string). Default layout is `messagePassThrough`. }); tns.logger.initialize({ appenderOptions: { type: LoggerAppenders.emitAppender, emitter }); ``` This is the easiest way to use the new appender. You can also use LoggerLevel from constants and specify different logging levels. You can pass whatever layout you need for the message.
1 parent 822173a commit 5169829

File tree

9 files changed

+188
-30
lines changed

9 files changed

+188
-30
lines changed

lib/bootstrap.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require("./common/bootstrap");
2-
$injector.require("logger", "./common/logger");
2+
$injector.requirePublicClass("logger", "./common/logger");
33
$injector.require("config", "./config");
44
$injector.require("options", "./options");
55
// note: order above is important!

lib/common/definitions/logger.d.ts

+37-15
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,40 @@
1-
interface ILogger {
2-
getLevel(): string;
3-
fatal(formatStr?: any, ...args: any[]): void;
4-
error(formatStr?: any, ...args: any[]): void;
5-
warn(formatStr?: any, ...args: any[]): void;
6-
warnWithLabel(formatStr?: any, ...args: any[]): void;
7-
info(formatStr?: any, ...args: any[]): void;
8-
debug(formatStr?: any, ...args: any[]): void;
9-
trace(formatStr?: any, ...args: any[]): void;
10-
printMarkdown(...args: any[]): void;
1+
import { Layout, LoggingEvent, Configuration, Level } from "log4js";
2+
import { EventEmitter } from "events";
3+
import { LoggerLevel } from "../../constants";
114

12-
out(formatStr?: any, ...args: any[]): void;
13-
write(...args: any[]): void;
5+
declare global {
6+
interface IAppenderOptions extends IDictionary<any> {
7+
type: string;
8+
}
149

15-
prepare(item: any): string;
16-
printInfoMessageOnSameLine(message: string): void;
17-
printOnStderr(formatStr?: any, ...args: any[]): void;
10+
interface ILoggerOptions {
11+
level?: LoggerLevel;
12+
appenderOptions?: IAppenderOptions;
13+
}
14+
15+
interface ILogger {
16+
initialize(opts?: ILoggerOptions): void;
17+
getLevel(): string;
18+
fatal(formatStr?: any, ...args: any[]): void;
19+
error(formatStr?: any, ...args: any[]): void;
20+
warn(formatStr?: any, ...args: any[]): void;
21+
warnWithLabel(formatStr?: any, ...args: any[]): void;
22+
info(formatStr?: any, ...args: any[]): void;
23+
debug(formatStr?: any, ...args: any[]): void;
24+
trace(formatStr?: any, ...args: any[]): void;
25+
printMarkdown(...args: any[]): void;
26+
27+
out(formatStr?: any, ...args: any[]): void;
28+
write(...args: any[]): void;
29+
30+
prepare(item: any): string;
31+
printInfoMessageOnSameLine(message: string): void;
32+
printOnStderr(formatStr?: any, ...args: any[]): void;
33+
}
34+
35+
36+
interface Log4JSEmitAppenderConfiguration extends Configuration {
37+
layout: Layout;
38+
emitter: EventEmitter;
39+
}
1840
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { LoggingEvent } from "log4js";
2+
import { EventEmitter } from "events";
3+
4+
const logDataEventName = "logData";
5+
function emitAppender(layout: Function, emitter: EventEmitter) {
6+
const appender = (loggingEvent: LoggingEvent) => {
7+
emitter.emit(logDataEventName, { loggingEvent, formattedMessage: layout(loggingEvent) });
8+
};
9+
10+
appender.shutdown = () => {
11+
emitter.removeAllListeners(logDataEventName);
12+
};
13+
14+
return appender;
15+
}
16+
17+
function configure(config: Log4JSEmitAppenderConfiguration, layouts: any) {
18+
// the default layout for the appender
19+
let layout = layouts.messagePassThroughLayout;
20+
21+
// check if there is another layout specified
22+
if (config.layout) {
23+
layout = layouts.layout(config.layout.type, config.layout);
24+
}
25+
26+
if (!config.emitter) {
27+
throw new Error("Emitter must be passed to emit-appender");
28+
}
29+
30+
if (!config.emitter.emit || typeof config.emitter.emit !== "function") {
31+
throw new Error("The passed emitter must be instance of EventEmitter");
32+
}
33+
34+
// create a new appender instance
35+
return emitAppender(layout, config.emitter);
36+
}
37+
38+
exports.configure = configure;

lib/common/logger.ts

+39-13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as log4js from "log4js";
22
import * as util from "util";
33
import * as stream from "stream";
44
import * as marked from "marked";
5+
import { cache } from "./decorators";
56
const TerminalRenderer = require("marked-terminal");
67
const chalk = require("chalk");
78

@@ -11,26 +12,35 @@ export class Logger implements ILogger {
1112
private passwordReplacement = "$1$3*******$2$4";
1213
private static LABEL = "[WARNING]:";
1314

14-
constructor($config: Config.IConfig,
15+
constructor(private $config: Config.IConfig,
1516
private $options: IOptions) {
16-
const appenders: IDictionary<log4js.Appender> = {};
17-
const categories: IDictionary<{ appenders: string[]; level: string; }> = {};
18-
let level: string = null;
19-
if (this.$options.log) {
20-
level = this.$options.log;
21-
} else {
22-
level = $config.DEBUG ? "TRACE" : "INFO";
23-
}
17+
}
18+
19+
@cache()
20+
public initialize(opts?: ILoggerOptions): void {
21+
opts = opts || {};
22+
const { appenderOptions: appenderOpts, level } = opts;
2423

25-
appenders["out"] = {
24+
const appender: any = {
2625
type: "console",
2726
layout: {
2827
type: "messagePassThrough"
2928
}
3029
};
31-
categories["default"] = {
32-
appenders: ['out'],
33-
level
30+
31+
if (appenderOpts) {
32+
_.merge(appender, appenderOpts);
33+
}
34+
35+
const appenders: IDictionary<log4js.Appender> = {
36+
out: appender
37+
};
38+
39+
const categories: IDictionary<{ appenders: string[]; level: string; }> = {
40+
default: {
41+
appenders: ['out'],
42+
level: level || (this.$config.DEBUG ? "TRACE" : "INFO")
43+
}
3444
};
3545

3646
log4js.configure({ appenders, categories });
@@ -39,42 +49,58 @@ export class Logger implements ILogger {
3949
}
4050

4151
getLevel(): string {
52+
this.initialize();
53+
4254
return this.log4jsLogger.level.toString();
4355
}
4456

4557
fatal(...args: string[]): void {
58+
this.initialize();
59+
4660
this.log4jsLogger.fatal.apply(this.log4jsLogger, args);
4761
}
4862

4963
error(...args: string[]): void {
64+
this.initialize();
65+
5066
const message = util.format.apply(null, args);
5167
const colorizedMessage = message.red;
5268

5369
this.log4jsLogger.error.apply(this.log4jsLogger, [colorizedMessage]);
5470
}
5571

5672
warn(...args: string[]): void {
73+
this.initialize();
74+
5775
const message = util.format.apply(null, args);
5876
const colorizedMessage = message.yellow;
5977

6078
this.log4jsLogger.warn.apply(this.log4jsLogger, [colorizedMessage]);
6179
}
6280

6381
warnWithLabel(...args: string[]): void {
82+
this.initialize();
83+
6484
const message = util.format.apply(null, args);
6585
this.warn(`${Logger.LABEL} ${message}`);
6686
}
6787

6888
info(...args: string[]): void {
89+
this.initialize();
90+
6991
this.log4jsLogger.info.apply(this.log4jsLogger, args);
7092
}
7193

7294
debug(...args: string[]): void {
95+
this.initialize();
96+
7397
const encodedArgs: string[] = this.getPasswordEncodedArguments(args);
7498
this.log4jsLogger.debug.apply(this.log4jsLogger, encodedArgs);
7599
}
76100

77101
trace(...args: string[]): void {
102+
this.initialize();
103+
78104
const encodedArgs: string[] = this.getPasswordEncodedArguments(args);
79105
this.log4jsLogger.trace.apply(this.log4jsLogger, encodedArgs);
80106
}

lib/common/test/unit-tests/logger.ts

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ describe("logger", () => {
4545
}
4646
};
4747

48+
// Initialize the logger manually, so we can overwrite the log4jsLogger property
49+
logger.initialize();
4850
logger.log4jsLogger = log4jsLogger;
4951
});
5052

lib/common/test/unit-tests/stubs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class LockServiceStub implements ILockService {
1818
}
1919

2020
export class CommonLoggerStub implements ILogger {
21+
initialize(opts?: ILoggerOptions): void { }
2122
getLevel(): string { return undefined; }
2223
fatal(...args: string[]): void { }
2324
error(...args: string[]): void { }

lib/constants.ts

+63
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require("colors");
22
import { PreviewAppLiveSyncEvents } from "./services/livesync/playground/preview-app-constants";
3+
import { join } from "path";
34

45
export const APP_FOLDER_NAME = "app";
56
export const APP_RESOURCES_FOLDER_NAME = "App_Resources";
@@ -299,3 +300,65 @@ export enum IOSNativeTargetTypes {
299300
watchExtension = "watch_extension",
300301
appExtension = "app_extension"
301302
}
303+
304+
export const LoggerAppenders = {
305+
emitAppender: join(__dirname, "common", "logger-appenders", "emit-appender"),
306+
};
307+
308+
export enum LoggerLevel {
309+
/**
310+
* Show all log messages.
311+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
312+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
313+
*/
314+
ALL = "ALL",
315+
316+
/**
317+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
318+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
319+
*/
320+
TRACE = "TRACE",
321+
322+
/**
323+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
324+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
325+
*/
326+
DEBUG = "DEBUG",
327+
328+
/**
329+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
330+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
331+
*/
332+
INFO = "INFO",
333+
334+
/**
335+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
336+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
337+
*/
338+
WARN = "WARN",
339+
340+
/**
341+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
342+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
343+
*/
344+
ERROR = "ERROR",
345+
346+
/**
347+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
348+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
349+
*/
350+
FATAL = "FATAL",
351+
352+
/**
353+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
354+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
355+
*/
356+
MARK = "MARK",
357+
358+
/**
359+
* Disable all logging.
360+
* Log levels are used to assign importance to log messages, with the integer value being used to sort them.
361+
* If you do not specify anything in your configuration, the default values are used (ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF)
362+
*/
363+
OFF = "OFF"
364+
}

test/nativescript-cli-lib.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe("nativescript-cli-lib", () => {
2323
"getIOSAssetsStructure",
2424
"getAndroidAssetsStructure"
2525
],
26-
constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME"],
26+
constants: ["CONFIG_NS_APP_RESOURCES_ENTRY", "CONFIG_NS_APP_ENTRY", "CONFIG_NS_FILE_NAME", "LoggerLevel", "LoggerAppenders"],
2727
localBuildService: ["build"],
2828
deviceLogProvider: null,
2929
packageManager: ["install", "uninstall", "view", "search"],
@@ -62,6 +62,11 @@ describe("nativescript-cli-lib", () => {
6262
],
6363
cleanupService: [
6464
"setCleanupLogFile"
65+
],
66+
logger: [
67+
"initialize",
68+
"getLevel",
69+
"info"
6570
]
6671
};
6772

test/stubs.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { HostInfo } from "./../lib/common/host-info";
1111
import { DevicePlatformsConstants } from "./../lib/common/mobile/device-platforms-constants";
1212

1313
export class LoggerStub implements ILogger {
14+
initialize(opts?: ILoggerOptions): void { }
1415
getLevel(): string { return undefined; }
1516
fatal(...args: string[]): void { }
1617
error(...args: string[]): void { }

0 commit comments

Comments
 (0)