Skip to content

Commit 09b6908

Browse files
Merge pull request #4592 from NativeScript/vladimirov/expose-logger
feat: improve logger
2 parents 296dbe9 + 194106c commit 09b6908

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+817
-431
lines changed

PublicAPI.md

+180
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ const tns = require("nativescript");
7272
* [getPlaygroundAppQrCode](#getplaygroundappqrcode)
7373
* [cleanupService](#cleanupservice)
7474
* [setCleanupLogFile](#setcleanuplogfile)
75+
* [initializeService](#initializeService)
76+
* [initialize](#initialize)
77+
* [logger](#logger)
78+
* [initialize](#initialize)
79+
* [getLevel](#getlevel)
80+
* [appenders](#appenders)
81+
* [emit-appender](#emit-appender)
82+
* [cli-appender](#cli-appender)
83+
* [custom layouts](#custom-layouts)
84+
7585

7686
## Module projectService
7787

@@ -1514,6 +1524,176 @@ const tns = require("nativescript");
15141524
tns.cleanupService.setCleanupLogFile("/Users/username/cleanup-logs.txt");
15151525
```
15161526
1527+
## initializeService
1528+
The `initializeService` is used to initialize CLI's configuration at the beginning and print all warnings related to current environment.
1529+
1530+
### initialize
1531+
This method executes initialization actions based on the passed parameters. In case `loggerOptions` are not passed, the default CLI logger will be used.
1532+
After initialization, the method will print all system warnings.
1533+
1534+
* Definition
1535+
```TypeScript
1536+
interface IInitializeOptions {
1537+
loggerOptions?: ILoggerOptions;
1538+
settingsServiceOptions?: IConfigurationSettings;
1539+
extensibilityOptions?: { pathToExtensions: string };
1540+
}
1541+
1542+
interface IInitializeService {
1543+
initialize(initOpts?: IInitializeOptions): Promise<void>;
1544+
}
1545+
1546+
```
1547+
1548+
> NOTE: For more information about loggerOptions, you can check `logger`.
1549+
1550+
* Usage
1551+
* Initialization without passing any data - `logger` will be initialized with default CLI settings. Warnings will be printed if there are any.
1552+
```JavaScript
1553+
const tns = require("nativescript");
1554+
tns.initializeService.initialize();
1555+
```
1556+
* Initialize with custom settings service options:
1557+
```JavaScript
1558+
const tns = require("nativescript");
1559+
tns.initializeService.initialize({ settingsServiceOptions: { profileDir: "/Users/username/customDir", userAgentName: "MyApp" } });
1560+
```
1561+
* Initialize with custom extensibility path:
1562+
```JavaScript
1563+
const tns = require("nativescript");
1564+
tns.initializeService.initialize({ extensibilityOptions: { pathToExtensions: "/Users/username/customDir/extensions" } });
1565+
```
1566+
1567+
## logger
1568+
1569+
`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.
1570+
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>
1571+
`logger` module can be configured how to show the messages by using different appenders and layouts. </br>
1572+
* `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.
1573+
* `layout` is a function for converting a LogEvent into a string representation.
1574+
1575+
`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>
1576+
You can override only the properties you want, i.e. only the log level, the layout or the appender. </br>
1577+
`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>
1578+
1579+
> 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.
1580+
1581+
### initialize
1582+
The `initialize` method initializes the log4js settings - level, appender and layout. Once called, the settings cannot be changed anymore for the current process.
1583+
1584+
* Definition
1585+
```TypeScript
1586+
interface IAppenderOptions extends IDictionary<any> {
1587+
type: string;
1588+
layout?: Layout;
1589+
}
1590+
1591+
interface ILoggerOptions {
1592+
level?: LoggerLevel;
1593+
appenderOptions?: IAppenderOptions;
1594+
}
1595+
1596+
initialize(opts?: ILoggerOptions): void;
1597+
```
1598+
1599+
* Usage
1600+
* Initialize with default settings:
1601+
```JavaScript
1602+
tns.logger.initialize();
1603+
```
1604+
* Initialize with DEBUG log level:
1605+
```JavaScript
1606+
tns.logger.initialize({ level: tns.constants.LoggerLevel.DEBUG });
1607+
```
1608+
* Initialize with different appender, for example [fileSync](https://log4js-node.github.io/log4js-node/fileSync.html) appender:
1609+
```JavaScript
1610+
tns.logger.initialize({ appenderOptions: { type: "fileSync" } });
1611+
```
1612+
* Initialize with different layout, for example [Pattern](https://log4js-node.github.io/log4js-node/layouts.html#pattern) layout:
1613+
```JavaScript
1614+
tns.logger.initialize({ appenderOptions: { layout: { type: "pattern" } } });
1615+
```
1616+
* Initialize with custom appender, layout and level:
1617+
```JavaScript
1618+
tns.logger.initialize({ appenderOptions: { type: "fileSync", layout: { type: "pattern" } }, level: tns.constants.LoggerLevel.DEBUG });
1619+
```
1620+
1621+
### getLevel
1622+
This method returns information for the current log level.
1623+
1624+
* Definition
1625+
```TypeScript
1626+
getLevel(): string;
1627+
```
1628+
1629+
* Usage
1630+
```JavaScript
1631+
console.log(`Current log level is: ${tns.logger.getLevel()}`);
1632+
```
1633+
1634+
### appenders
1635+
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
1636+
1637+
#### emit-appender
1638+
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.
1639+
1640+
* Usage:
1641+
```JavaScript
1642+
const tns = require("nativescript");
1643+
const { EventEmitter } = require("events");
1644+
const { EMIT_APPENDER_EVENT_NAME, LoggerAppenders } = tns.constants;
1645+
const emitter = new EventEmitter();
1646+
// IMPORTANT: Add the event handler before calling logger's initialize method.
1647+
// This is required as log4js makes a copy of the appenderOptions, where the emitter is passed
1648+
// NOTE: In case you want to debug the event handler, place `debugger` in it.
1649+
emitter.on(EMIT_APPENDER_EVENT_NAME, (logData) => {
1650+
if (logData.loggingEvent.level.levelStr === LoggerLevel.WARN) {
1651+
console.log(`WARNING: ${logData.formattedMessage}`);
1652+
}
1653+
});
1654+
1655+
const logger = tns.logger;
1656+
logger.initialize({
1657+
appenderOptions: {
1658+
type: LoggerAppenders.emitAppender,
1659+
emitter
1660+
}
1661+
});
1662+
```
1663+
1664+
> 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.
1665+
1666+
#### cli-appender
1667+
`cli-appender` prints messages to stdout or stderr based on the passed options for the message.
1668+
1669+
* Usage
1670+
```JavaScript
1671+
const tns = require("nativescript");
1672+
const { EventEmitter } = require("events");
1673+
const { EMIT_APPENDER_EVENT_NAME, LoggerAppenders } = tns.constants;
1674+
1675+
const logger = tns.logger;
1676+
logger.initialize({
1677+
appenderOptions: {
1678+
type: LoggerAppenders.cliAppender,
1679+
}
1680+
});
1681+
```
1682+
1683+
### custom layouts
1684+
You can define your own layout function in the following way:
1685+
```JavaScript
1686+
const log4js = require("nativescript/node_modules/log4js");
1687+
const util = require("util");
1688+
log4js.addLayout("myCustomLayout", (config) => {
1689+
return (loggingEvent) => {
1690+
return util.format.apply(null, loggingEvent.data);
1691+
}
1692+
});
1693+
1694+
tns.logger.initialize({ appenderOptions: { layout: { type: "myCustomLayout" } } });
1695+
```
1696+
15171697
## How to add a new method to Public API
15181698
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.
15191699
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`.

lib/bootstrap.ts

+4-2
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/logger");
33
$injector.require("config", "./config");
44
$injector.require("options", "./options");
55
// note: order above is important!
@@ -109,7 +109,7 @@ $injector.require("xcprojService", "./services/xcproj-service");
109109
$injector.require("versionsService", "./services/versions-service");
110110
$injector.requireCommand("install", "./commands/install");
111111

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

115115
$injector.require("infoService", "./services/info-service");
@@ -199,3 +199,5 @@ $injector.requirePublic("cleanupService", "./services/cleanup-service");
199199
$injector.require("applePortalSessionService", "./services/apple-portal/apple-portal-session-service");
200200
$injector.require("applePortalCookieService", "./services/apple-portal/apple-portal-cookie-service");
201201
$injector.require("applePortalApplicationService", "./services/apple-portal/apple-portal-application-service");
202+
203+
$injector.requirePublicClass("initializeService", "./services/initialize-service");

lib/commands/appstore-list.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ export class ListiOSApps implements ICommand {
3434
const applications = await this.$applePortalApplicationService.getApplications({ username, password });
3535

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

44-
this.$logger.out(table.toString());
44+
this.$logger.info(table.toString());
4545
}
4646
}
4747
}

lib/commands/extensibility/list-extensions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class ListExtensionsCommand implements ICommand {
1313
});
1414

1515
const table = helpers.createTable(["Name", "Version"], data);
16-
this.$logger.out(table.toString());
16+
this.$logger.info(table.toString());
1717
} else {
1818
this.$logger.info("No extensions installed.");
1919
}

lib/commands/init.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
export class InitCommand implements ICommand {
1+
export class ProjectInitCommand implements ICommand {
22
public allowedParameters: ICommandParameter[] = [];
33
public enableHooks = false;
44

5-
constructor(private $initService: IInitService) { }
5+
constructor(private $projectInitService: IProjectInitService) { }
66

77
public async execute(args: string[]): Promise<void> {
8-
return this.$initService.initialize();
8+
return this.$projectInitService.initialize();
99
}
1010
}
1111

12-
$injector.registerCommand("init", InitCommand);
12+
$injector.registerCommand("init", ProjectInitCommand);

lib/commands/list-platforms.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@ export class ListPlatformsCommand implements ICommand {
1515
if (installedPlatforms.length > 0) {
1616
const preparedPlatforms = this.$platformService.getPreparedPlatforms(this.$projectData);
1717
if (preparedPlatforms.length > 0) {
18-
this.$logger.out("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and"));
18+
this.$logger.info("The project is prepared for: ", helpers.formatListOfNames(preparedPlatforms, "and"));
1919
} else {
20-
this.$logger.out("The project is not prepared for any platform");
20+
this.$logger.info("The project is not prepared for any platform");
2121
}
2222

23-
this.$logger.out("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and"));
23+
this.$logger.info("Installed platforms: ", helpers.formatListOfNames(installedPlatforms, "and"));
2424
} else {
2525
const formattedPlatformsList = helpers.formatListOfNames(this.$platformService.getAvailablePlatforms(this.$projectData), "and");
26-
this.$logger.out("Available platforms for this OS: ", formattedPlatformsList);
27-
this.$logger.out("No installed platforms found. Use $ tns platform add");
26+
this.$logger.info("Available platforms for this OS: ", formattedPlatformsList);
27+
this.$logger.info("No installed platforms found. Use $ tns platform add");
2828
}
2929
}
3030
}

lib/commands/plugin/list-plugins.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ export class ListPluginsCommand implements ICommand {
1616
const dependenciesData: string[][] = this.createTableCells(installedPlugins.dependencies);
1717

1818
const dependenciesTable: any = createTable(headers, dependenciesData);
19-
this.$logger.out("Dependencies:");
20-
this.$logger.out(dependenciesTable.toString());
19+
this.$logger.info("Dependencies:");
20+
this.$logger.info(dependenciesTable.toString());
2121

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

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

27-
this.$logger.out("Dev Dependencies:");
28-
this.$logger.out(devDependenciesTable.toString());
27+
this.$logger.info("Dev Dependencies:");
28+
this.$logger.info(devDependenciesTable.toString());
2929
} else {
30-
this.$logger.out("There are no dev dependencies.");
30+
this.$logger.info("There are no dev dependencies.");
3131
}
3232

3333
const viewDependenciesCommand: string = "npm view <pluginName> grep dependencies".cyan.toString();

lib/commands/post-install.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class PostInstallCliCommand implements ICommand {
3838
}
3939

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

4444
if (canExecutePostInstallTask) {

lib/common/bootstrap.ts

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ $injector.require("childProcess", "./child-process");
2727
$injector.require("prompter", "./prompter");
2828
$injector.require("projectHelper", "./project-helper");
2929
$injector.require("pluginVariablesHelper", "./plugin-variables-helper");
30-
$injector.require("progressIndicator", "./progress-indicator");
3130

3231
$injector.requireCommand(["help", "/?"], "./commands/help");
3332
$injector.requireCommand("usage-reporting", "./commands/analytics");

lib/common/commands/analytics.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class AnalyticsCommand implements ICommand {
4141
break;
4242
case "status":
4343
case "":
44-
this.$logger.out(await this.$analyticsService.getStatusMessage(this.settingName, this.$options.json, this.humanReadableSettingName));
44+
this.$logger.info(await this.$analyticsService.getStatusMessage(this.settingName, this.$options.json, this.humanReadableSettingName));
4545
break;
4646
}
4747
}

lib/common/commands/autocompletion.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class AutoCompleteCommand implements ICommand {
1919
this.$logger.info("Autocompletion is already enabled");
2020
}
2121
} else {
22-
this.$logger.out("If you are using bash or zsh, you can enable command-line completion.");
22+
this.$logger.info("If you are using bash or zsh, you can enable command-line completion.");
2323
const message = "Do you want to enable it now?";
2424

2525
const autoCompetionStatus = await this.$prompter.confirm(message, () => true);

lib/common/commands/device/list-applications.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class ListApplicationsCommand implements ICommand {
1919
};
2020
await this.$devicesService.execute(action);
2121

22-
this.$logger.out(output.join(EOL));
22+
this.$logger.info(output.join(EOL));
2323
}
2424
}
2525
$injector.registerCommand(["device|list-applications", "devices|list-applications"], ListApplicationsCommand);

lib/common/commands/device/list-devices.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ export class ListDevicesCommand implements ICommand {
2323
this.printEmulators("\nAvailable emulators", emulators);
2424
}
2525

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

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

4646
if (!this.$options.json && table.length) {
47-
this.$logger.out(table.toString());
47+
this.$logger.info(table.toString());
4848
}
4949
}
5050

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

58-
this.$logger.out(table.toString());
58+
this.$logger.info(table.toString());
5959
}
6060
}
6161

lib/common/commands/proxy/proxy-clear.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class ProxyClearCommand extends ProxyCommandBase {
1010

1111
public async execute(args: string[]): Promise<void> {
1212
await this.$proxyService.clearCache();
13-
this.$logger.out("Successfully cleared proxy.");
13+
this.$logger.info("Successfully cleared proxy.");
1414
await this.tryTrackUsage();
1515
}
1616
}

0 commit comments

Comments
 (0)