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

feat: improve logger #4592

merged 13 commits into from
May 8, 2019

Conversation

rosen-vladimirov
Copy link
Contributor

@rosen-vladimirov rosen-vladimirov commented May 8, 2019

Currently CLI's logger uses different ways to print messages on stdout and stderr:

  • using log4js in some cases
  • using console.log
  • using process.stdout.write
  • using process.stderr.write

Also, whenever we want to colorize the message, we include the colorization of the message in the message itself, before passing it to one of the methods above. This is a problem when CLI is used as a library, when it might not be used in a terminal environment - the messages contain terminal colorization. Also, in order to get the messages, you need to parse stdout and stderr of the process in which you've required CLI as a library.

To resolve the above, streamline the logger ways for handling messages - use log4js everywhere. This allows using different logging mechanism when CLI is used as a command line and when it is used as a library.
log4js has the concept of using appenders and layout when printing messages.

  • 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.

Currently we have the several use cases for logging messages:

  • print on stdout or stderr - for some of the methods we need to print errors and we want them to be shown on stderr. This can be handled in the appender, as it is the one which actually prints the message. To handle the case, pass useStderr property in the context of the LogEvent.
  • add new line at the end of the message or skip it - in some cases we do not want to append new line at the end of the message. This logic is for the presentation of the message, so it can be handled in the layout. Check the value of skipNewLine property of the LogEvent context.
  • wrap message with borders - in some cases, especially for command line usage, we want to wrap the message in some borders, to make it more visible to the user. This is a layout specific logic that can be handled based on the wrapMessageWithBorders property of the LogEvent context.

Each of the properties can be passed directly to each of the methods exposed from logger, i.e.:

const a = 1;
this.$logger.info("This is info message");
this.$logger.info("This is info message with placeholder %s", a);
this.$logger.info("This is info message without new line at the end", { skipNewLine: true });
this.$logger.info("This is info message with placeholder %s and without new line at the end,", a, { skipNewLine: true });
this.$logger.info("This is info message without new line at the end and printed on stderr", { skipNewLine: true, useStderr: true });
this.$logger.info("This is another info message without new line at the end and printed on stderr", { skipNewLine: true}, { useStderr: true });
this.$logger.info("This is info message with placeholder %s and without new line at the end printed on stderr", a, { skipNewLine: true, useStderr: true });
this.$logger.info("This is another info message with placeholder %s and without new line at the end printed on stderr", a, { skipNewLine: true }, { useStderr: true });

Full list of the properties that can be set is available in constants.LoggerConfigData.

When using CLI as a library, it makes sense to skip the colorization and instead of printing to stdout/stderr, to allow the consumer to take care of the messages on its own. In order to handle this, introduce an easy way for initializing the logger with different settings, i.e. appender, layout, level. This can be achieved through the logger.initialize method or initializeService.initialize method.
To make it easy when CLI is used as a library, introduce a custom emit-appender that emits the logged data instead of printing it. Its usage is fairly simple:

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
	}
});

Additional code changes

During the implementation of the logger, several changes in other services were required:

  • rename initService to projectInitService - it was required to make it easier to determine if the service is for initializing CLI or the project.
  • remove progress-indicator - it was not used anywhere
  • remove printMsgWithTimeout method from logger - it was used only in progress-indicator
  • introduce initializeService - it is used to initialize CLI specific modules (like logger) and show all system warnings (for example for not supported macOS version). This way we no longer need to expose different methods when we want to show some warnings in the command line and when CLI is used as a library. We can just place them in the initialize method.
  • remove warnWithLabel method from logger - it has been used on only two places and did not make sense to keep it.
  • remove out method from logger - its usage is replaced with logger.info
  • remove write method from logger - its intention was to print message on stdout, without modifying it (i.e. without adding new line, etc.), so it is replaced with info method and passing skipNewLine to true:
// OLD:
this.logger.write(line, platform, deviceIdentifier);
// NEW:
this.logger.info(line, platform, deviceIdentifier, { [LoggerConfigData.skipNewLine]: true });
  • remove printInfoMessageOnSameLine method from logger - it was used to print messages on stdout without adding new line and only when log level is info. However, the implementation was incorrect (i.e. it was not checking the log level correctly). Replace the method with logger.info calls:
// OLD:
this.$logger.printInfoMessageOnSameLine("Waiting for emulator device initialization...");
// NEW:
this.$logger.info("Waiting for emulator device initialization...", { [LoggerConfigData.skipNewLine]: true });
  • change logger.error to print to stderr instead of stdout.
  • remove printOnStderr method from logger. This can be achieve with any log level by passing useStderr: true. Usage is replaced with logger.error.
  • introduce initializeCliLogger method in logger, so it will be easy to setup CLI's log4js options
  • introduce special layout function for CLI - based on the LoggingEvent context it changes the way message is printed. This layout should not be used when CLI is as a library.
  • move all logger related files in a logger directory.

PR Checklist

What is the current behavior?

What is the new behavior?

Implements #4602

@rosen-vladimirov rosen-vladimirov added this to the 5.4.0 milestone May 8, 2019
@rosen-vladimirov rosen-vladimirov self-assigned this May 8, 2019
@cla-bot cla-bot bot added the cla: yes label May 8, 2019
@ghost ghost added the new PR label May 8, 2019
@rosen-vladimirov
Copy link
Contributor Author

test cli-smoke cli-create cli-plugin cli-preview cli-misc

@rosen-vladimirov
Copy link
Contributor Author

test cli-smoke cli-misc

1 similar comment
@rosen-vladimirov
Copy link
Contributor Author

test cli-smoke cli-misc

Rename `initService` to `projectInitService` as the new name has more sense.
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.
Introduce initializeService which can be used to setup some specifics of CLI (currently the logger) and to print all required warnings. At the moment, whenever we want to introduce new warnings, we need to introduce new methods that we call from CLI and when it is used as a library.
With the new solution all such warnings can be placed in the initialize method and it will automatically show those warnings.
Refactor the logic in CLI's logger to use log4js methods only - currently we have some methods that use `console.log`, others use `process.stdout`, `process.stderr`, etc.
Also, there are some methods that apply console colorization for the message, but in fact CLI may be used as a library or in CI, where these colorization does not make sense.
To resolve this, introduce a new appender and new layout that CLI will pass to `log4js`.
The appender (called `cli-appender`) will control if the message will be shown on `stdout` or `stderr`.
The layout (called `cli-layout`) will control the presentation of the message on the terminal, i.e. if we should add new line at the end of the message, if we should wrap it in borders (with `*` symbols, etc.) and the colorization of the message.
The layout will be applied on all methods, so you can use it with any log4js method, i.e. you can do:
```TypeScript
this.$logger.warn("This is my message", { useStderr: true });
this.$logger.info("This is my message", { useStderr: true });
```

Before passing the data to `log4js`, CLI will set required properties in the context of the logging event, so the appender and the layout can read them.

This way, in case CLI is used as a library, the custom properties and CLI specific layout, will not be shown.

Also delete some methods from logger API, so it is streamlined. Also change logger.error to print on stderr instead of stdout.
Move logger.ts to logger directory, so all logger logic will be on a single place.
There are several changes in logger API that need to be changed in the code - they are mostly renaming of methods.
Improve initialization service, so it can be used to initialize all settings required at the beginning of CLI process.
@rosen-vladimirov rosen-vladimirov force-pushed the vladimirov/expose-logger branch from cb08737 to 194106c Compare May 8, 2019 16:19
@rosen-vladimirov
Copy link
Contributor Author

test cli-smoke cli-create cli-plugin

@rosen-vladimirov
Copy link
Contributor Author

test cli-smoke cli-misc

@rosen-vladimirov rosen-vladimirov merged commit 09b6908 into master May 8, 2019
@rosen-vladimirov rosen-vladimirov deleted the vladimirov/expose-logger branch May 8, 2019 17:11
@ghost ghost removed the new PR label May 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants