Skip to content

feat: improve logger #4592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions PublicAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ const tns = require("nativescript");
* [getPlaygroundAppQrCode](#getplaygroundappqrcode)
* [cleanupService](#cleanupservice)
* [setCleanupLogFile](#setcleanuplogfile)
* [initializeService](#initializeService)
* [initialize](#initialize)
* [logger](#logger)
* [initialize](#initialize)
* [getLevel](#getlevel)
* [appenders](#appenders)
* [emit-appender](#emit-appender)
* [cli-appender](#cli-appender)
* [custom layouts](#custom-layouts)


## Module projectService

Expand Down Expand Up @@ -1514,6 +1524,176 @@ const tns = require("nativescript");
tns.cleanupService.setCleanupLogFile("/Users/username/cleanup-logs.txt");
```

## initializeService
The `initializeService` is used to initialize CLI's configuration at the beginning and print all warnings related to current environment.

### initialize
This method executes initialization actions based on the passed parameters. In case `loggerOptions` are not passed, the default CLI logger will be used.
After initialization, the method will print all system warnings.

* Definition
```TypeScript
interface IInitializeOptions {
loggerOptions?: ILoggerOptions;
settingsServiceOptions?: IConfigurationSettings;
extensibilityOptions?: { pathToExtensions: string };
}

interface IInitializeService {
initialize(initOpts?: IInitializeOptions): Promise<void>;
}

```

> NOTE: For more information about loggerOptions, you can check `logger`.

* Usage
* Initialization without passing any data - `logger` will be initialized with default CLI settings. Warnings will be printed if there are any.
```JavaScript
const tns = require("nativescript");
tns.initializeService.initialize();
```
* Initialize with custom settings service options:
```JavaScript
const tns = require("nativescript");
tns.initializeService.initialize({ settingsServiceOptions: { profileDir: "/Users/username/customDir", userAgentName: "MyApp" } });
```
* Initialize with custom extensibility path:
```JavaScript
const tns = require("nativescript");
tns.initializeService.initialize({ extensibilityOptions: { pathToExtensions: "/Users/username/customDir/extensions" } });
```

## logger

`logger` module is used to show any kind of information to the user. The `logger` uses `log4js` internally, which allows setting different levels for the messages.
The levels are available in `tns.constants.LoggerLevel` enum. Only messages from the current log level (or higher) are shown to the user, i.e. in case the log level is set to `INFO`, `DEBUG` and `TRACE` messages will not be shown to the user, but `WARN` and `ERROR` messages will be shown. </br>
`logger` module can be configured how to show the messages by using different appenders and layouts. </br>
* `appenders` are responsible for output of log events. They may write events to files, send emails, store them in a database, or anything. Most appenders use layouts to serialise the events to strings for output.
* `layout` is a function for converting a LogEvent into a string representation.

`log4js` has predefined appenders and layouts that can be used. In case you do not pass any options to logger's initialization, CLI will default to [console appender](https://log4js-node.github.io/log4js-node/console.html) with [messagePassThrough layout](https://log4js-node.github.io/log4js-node/layouts.html#message-pass-through) with `INFO` log level.</br>
You can override only the properties you want, i.e. only the log level, the layout or the appender. </br>
`nativescript` itself has additional appenders that you can use. More information about them can be found below. You can get a full list of the available appenders by checking the `tns.constants.LoggerAppenders` object. </br>

> NOTE: When CLI is used as a command-line tool, it uses a custom appender and layout in order to write coloured messages to stdout or stderr.

### initialize
The `initialize` method initializes the log4js settings - level, appender and layout. Once called, the settings cannot be changed anymore for the current process.

* Definition
```TypeScript
interface IAppenderOptions extends IDictionary<any> {
type: string;
layout?: Layout;
}

interface ILoggerOptions {
level?: LoggerLevel;
appenderOptions?: IAppenderOptions;
}

initialize(opts?: ILoggerOptions): void;
```

* Usage
* Initialize with default settings:
```JavaScript
tns.logger.initialize();
```
* Initialize with DEBUG log level:
```JavaScript
tns.logger.initialize({ level: tns.constants.LoggerLevel.DEBUG });
```
* Initialize with different appender, for example [fileSync](https://log4js-node.github.io/log4js-node/fileSync.html) appender:
```JavaScript
tns.logger.initialize({ appenderOptions: { type: "fileSync" } });
```
* Initialize with different layout, for example [Pattern](https://log4js-node.github.io/log4js-node/layouts.html#pattern) layout:
```JavaScript
tns.logger.initialize({ appenderOptions: { layout: { type: "pattern" } } });
```
* Initialize with custom appender, layout and level:
```JavaScript
tns.logger.initialize({ appenderOptions: { type: "fileSync", layout: { type: "pattern" } }, level: tns.constants.LoggerLevel.DEBUG });
```

### getLevel
This method returns information for the current log level.

* Definition
```TypeScript
getLevel(): string;
```

* Usage
```JavaScript
console.log(`Current log level is: ${tns.logger.getLevel()}`);
```

### appenders
The `appenders` are log4js concept. `appenders` are responsible for output of log events. You can use all predefined [log4js appenders](https://log4js-node.github.io/log4js-node/appenders.html) and also several predefined CLI appenders

#### emit-appender
The `emit-appender` is used to emit the log events through a passed emitter instead of writing the messages. Whenever a message should be shown, the `emit-appender` emits `logData` event with an object containing the `loggingEvent` and the message passed through the specified layout stored in `formattedMessage` property.

* Usage:
```JavaScript
const tns = require("nativescript");
const { EventEmitter } = require("events");
const { EMIT_APPENDER_EVENT_NAME, LoggerAppenders } = tns.constants;
const emitter = new EventEmitter();
// IMPORTANT: Add the event handler before calling logger's initialize method.
// This is required as log4js makes a copy of the appenderOptions, where the emitter is passed
// NOTE: In case you want to debug the event handler, place `debugger` in it.
emitter.on(EMIT_APPENDER_EVENT_NAME, (logData) => {
if (logData.loggingEvent.level.levelStr === LoggerLevel.WARN) {
console.log(`WARNING: ${logData.formattedMessage}`);
}
});

const logger = tns.logger;
logger.initialize({
appenderOptions: {
type: LoggerAppenders.emitAppender,
emitter
}
});
```

> NOTE: In several cases CLI passes additional configuration properties in the `context` of the `loggingEvent`. Full list is available in the `tns.constants.LoggerConfigData` object. These properties are used by CLI's layout and appender to change the way the message is printed on the terminal and if it should be on stderr or stdout.

#### cli-appender
`cli-appender` prints messages to stdout or stderr based on the passed options for the message.

* Usage
```JavaScript
const tns = require("nativescript");
const { EventEmitter } = require("events");
const { EMIT_APPENDER_EVENT_NAME, LoggerAppenders } = tns.constants;

const logger = tns.logger;
logger.initialize({
appenderOptions: {
type: LoggerAppenders.cliAppender,
}
});
```

### custom layouts
You can define your own layout function in the following way:
```JavaScript
const log4js = require("nativescript/node_modules/log4js");
const util = require("util");
log4js.addLayout("myCustomLayout", (config) => {
return (loggingEvent) => {
return util.format.apply(null, loggingEvent.data);
}
});

tns.logger.initialize({ appenderOptions: { layout: { type: "myCustomLayout" } } });
```

## How to add a new method to Public API
CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification.
For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`.
Expand Down
6 changes: 4 additions & 2 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require("./common/bootstrap");
$injector.require("logger", "./common/logger");
$injector.requirePublicClass("logger", "./common/logger/logger");
$injector.require("config", "./config");
$injector.require("options", "./options");
// note: order above is important!
Expand Down Expand Up @@ -109,7 +109,7 @@ $injector.require("xcprojService", "./services/xcproj-service");
$injector.require("versionsService", "./services/versions-service");
$injector.requireCommand("install", "./commands/install");

$injector.require("initService", "./services/init-service");
$injector.require("projectInitService", "./services/project-init-service");
$injector.requireCommand("init", "./commands/init");

$injector.require("infoService", "./services/info-service");
Expand Down Expand Up @@ -199,3 +199,5 @@ $injector.requirePublic("cleanupService", "./services/cleanup-service");
$injector.require("applePortalSessionService", "./services/apple-portal/apple-portal-session-service");
$injector.require("applePortalCookieService", "./services/apple-portal/apple-portal-cookie-service");
$injector.require("applePortalApplicationService", "./services/apple-portal/apple-portal-application-service");

$injector.requirePublicClass("initializeService", "./services/initialize-service");
4 changes: 2 additions & 2 deletions lib/commands/appstore-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ export class ListiOSApps implements ICommand {
const applications = await this.$applePortalApplicationService.getApplications({ username, password });

if (!applications || !applications.length) {
this.$logger.out("Seems you don't have any applications yet.");
this.$logger.info("Seems you don't have any applications yet.");
} else {
const table: any = createTable(["Application Name", "Bundle Identifier", "In Flight Version"], applications.map(application => {
const version = (application && application.versionSets && application.versionSets.length && application.versionSets[0].inFlightVersion && application.versionSets[0].inFlightVersion.version) || "";
return [application.name, application.bundleId, version];
}));

this.$logger.out(table.toString());
this.$logger.info(table.toString());
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/extensibility/list-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class ListExtensionsCommand implements ICommand {
});

const table = helpers.createTable(["Name", "Version"], data);
this.$logger.out(table.toString());
this.$logger.info(table.toString());
} else {
this.$logger.info("No extensions installed.");
}
Expand Down
8 changes: 4 additions & 4 deletions lib/commands/init.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export class InitCommand implements ICommand {
export class ProjectInitCommand implements ICommand {
public allowedParameters: ICommandParameter[] = [];
public enableHooks = false;

constructor(private $initService: IInitService) { }
constructor(private $projectInitService: IProjectInitService) { }

public async execute(args: string[]): Promise<void> {
return this.$initService.initialize();
return this.$projectInitService.initialize();
}
}

$injector.registerCommand("init", InitCommand);
$injector.registerCommand("init", ProjectInitCommand);
10 changes: 5 additions & 5 deletions lib/commands/list-platforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ export class ListPlatformsCommand implements ICommand {
if (installedPlatforms.length > 0) {
const preparedPlatforms = this.$platformService.getPreparedPlatforms(this.$projectData);
if (preparedPlatforms.length > 0) {
this.$logger.out("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and"));
this.$logger.info("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and"));
} else {
this.$logger.out("The project is not prepared for any platform");
this.$logger.info("The project is not prepared for any platform");
}

this.$logger.out("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and"));
this.$logger.info("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and"));
} else {
const formattedPlatformsList = helpers.formatListOfNames(this.$platformService.getAvailablePlatforms(this.$projectData), "and");
this.$logger.out("Available platforms for this OS: ", formattedPlatformsList);
this.$logger.out("No installed platforms found. Use $ tns platform add");
this.$logger.info("Available platforms for this OS: ", formattedPlatformsList);
this.$logger.info("No installed platforms found. Use $ tns platform add");
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions lib/commands/plugin/list-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ export class ListPluginsCommand implements ICommand {
const dependenciesData: string[][] = this.createTableCells(installedPlugins.dependencies);

const dependenciesTable: any = createTable(headers, dependenciesData);
this.$logger.out("Dependencies:");
this.$logger.out(dependenciesTable.toString());
this.$logger.info("Dependencies:");
this.$logger.info(dependenciesTable.toString());

if (installedPlugins.devDependencies && installedPlugins.devDependencies.length) {
const devDependenciesData: string[][] = this.createTableCells(installedPlugins.devDependencies);

const devDependenciesTable: any = createTable(headers, devDependenciesData);

this.$logger.out("Dev Dependencies:");
this.$logger.out(devDependenciesTable.toString());
this.$logger.info("Dev Dependencies:");
this.$logger.info(devDependenciesTable.toString());
} else {
this.$logger.out("There are no dev dependencies.");
this.$logger.info("There are no dev dependencies.");
}

const viewDependenciesCommand: string = "npm view <pluginName> grep dependencies".cyan.toString();
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/post-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class PostInstallCliCommand implements ICommand {
}

// Make sure the success message is separated with at least one line from all other messages.
this.$logger.out();
this.$logger.info();
this.$logger.printMarkdown("Installation successful. You are good to go. Connect with us on `http://twitter.com/NativeScript`.");

if (canExecutePostInstallTask) {
Expand Down
1 change: 0 additions & 1 deletion lib/common/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ $injector.require("childProcess", "./child-process");
$injector.require("prompter", "./prompter");
$injector.require("projectHelper", "./project-helper");
$injector.require("pluginVariablesHelper", "./plugin-variables-helper");
$injector.require("progressIndicator", "./progress-indicator");

$injector.requireCommand(["help", "/?"], "./commands/help");
$injector.requireCommand("usage-reporting", "./commands/analytics");
Expand Down
2 changes: 1 addition & 1 deletion lib/common/commands/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class AnalyticsCommand implements ICommand {
break;
case "status":
case "":
this.$logger.out(await this.$analyticsService.getStatusMessage(this.settingName, this.$options.json, this.humanReadableSettingName));
this.$logger.info(await this.$analyticsService.getStatusMessage(this.settingName, this.$options.json, this.humanReadableSettingName));
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/common/commands/autocompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class AutoCompleteCommand implements ICommand {
this.$logger.info("Autocompletion is already enabled");
}
} else {
this.$logger.out("If you are using bash or zsh, you can enable command-line completion.");
this.$logger.info("If you are using bash or zsh, you can enable command-line completion.");
const message = "Do you want to enable it now?";

const autoCompetionStatus = await this.$prompter.confirm(message, () => true);
Expand Down
2 changes: 1 addition & 1 deletion lib/common/commands/device/list-applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class ListApplicationsCommand implements ICommand {
};
await this.$devicesService.execute(action);

this.$logger.out(output.join(EOL));
this.$logger.info(output.join(EOL));
}
}
$injector.registerCommand(["device|list-applications", "devices|list-applications"], ListApplicationsCommand);
10 changes: 5 additions & 5 deletions lib/common/commands/device/list-devices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ export class ListDevicesCommand implements ICommand {
this.printEmulators("\nAvailable emulators", emulators);
}

this.$logger.out("\nConnected devices & emulators");
this.$logger.info("\nConnected devices & emulators");
let index = 1;
await this.$devicesService.initialize({ platform: args[0], deviceId: null, skipInferPlatform: true, skipDeviceDetectionInterval: true, skipEmulatorStart: true });

const table: any = createTable(["#", "Device Name", "Platform", "Device Identifier", "Type", "Status"], []);
let action: (_device: Mobile.IDevice) => Promise<void>;
if (this.$options.json) {
action = async (device) => {
this.$logger.out(JSON.stringify(device.deviceInfo));
this.$logger.info(JSON.stringify(device.deviceInfo));
};
} else {
action = async (device) => {
Expand All @@ -44,18 +44,18 @@ export class ListDevicesCommand implements ICommand {
await this.$devicesService.execute(action, undefined, { allowNoDevices: true });

if (!this.$options.json && table.length) {
this.$logger.out(table.toString());
this.$logger.info(table.toString());
}
}

private printEmulators(title: string, emulators: Mobile.IDeviceInfo[]) {
this.$logger.out(title);
this.$logger.info(title);
const table: any = createTable(["Device Name", "Platform", "Version", "Device Identifier", "Image Identifier", "Error Help"], []);
for (const info of emulators) {
table.push([info.displayName, info.platform, info.version, info.identifier || "", info.imageIdentifier || "", info.errorHelp || ""]);
}

this.$logger.out(table.toString());
this.$logger.info(table.toString());
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/common/commands/proxy/proxy-clear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class ProxyClearCommand extends ProxyCommandBase {

public async execute(args: string[]): Promise<void> {
await this.$proxyService.clearCache();
this.$logger.out("Successfully cleared proxy.");
this.$logger.info("Successfully cleared proxy.");
await this.tryTrackUsage();
}
}
Expand Down
Loading