feat(logger): add packages/logger package from npmlog (#20161)

* feat: move logger into appium/logger

* add logger in tsconfig.json

* add smoke with loading the index.js

* modify files to fit other modules

* refer to @appium/logger

* update with npm run sync-pkgs and a few

* cleanup changelog and package-lock.json

* update smoke

* pin deps

* adjust tsconfig.json with others

* Update README.md

* update license

* update license

* Update LICENSE

* fix review
This commit is contained in:
Kazuaki Matsuo
2024-06-04 09:12:27 -07:00
committed by GitHub
parent 65c47acf59
commit 70449cd077
13 changed files with 1006 additions and 1 deletions

View File

@@ -183,3 +183,5 @@ documentation on [How Does Appium Work?](https://appium.io/docs/en/latest/intro/
[Apache-2.0](./LICENSE)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fappium%2Fappium?ref=badge_large)
`@appium/logger` package is under [ISC](./packages/logger/LICENSE) License.

24
package-lock.json generated
View File

@@ -195,6 +195,10 @@
"resolved": "packages/universal-xml-plugin",
"link": true
},
"node_modules/@apppium/logger": {
"resolved": "packages/logger",
"link": true
},
"node_modules/@babel/code-frame": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
@@ -22525,6 +22529,19 @@
"@img/sharp-win32-x64": "0.33.0"
}
},
"packages/logger": {
"name": "@apppium/logger",
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
"console-control-strings": "1.1.0",
"set-blocking": "2.0.0"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
"npm": ">=8"
}
},
"packages/opencv": {
"name": "@appium/opencv",
"version": "3.0.4",
@@ -23967,6 +23984,13 @@
"xpath": "0.0.34"
}
},
"@apppium/logger": {
"version": "file:packages/logger",
"requires": {
"console-control-strings": "1.1.0",
"set-blocking": "2.0.0"
}
},
"@babel/code-frame": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",

View File

18
packages/logger/LICENSE Normal file
View File

@@ -0,0 +1,18 @@
ISC License
Copyright npm, Inc.
Permission to use, copy, modify, and/or distribute this
software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this
permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND NPM DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
EVENT SHALL NPM BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
USE OR PERFORMANCE OF THIS SOFTWARE.

31
packages/logger/README.md Normal file
View File

@@ -0,0 +1,31 @@
# Appium Logger
The logger util that Appium uses.
# Installation
```console
npm install @appium/logger --save
```
# Basic Usage
```js
import log from '@appium/logger';
// additional stuff ---------------------------+
// message ----------+ |
// prefix ----+ | |
// level -+ | | |
// v v v v
log.info('fyi', 'I have a kitty cat: %j', myKittyCat);
```
# History
This module is forked from [npmlog](https://github.com/npm/npmlog) under ISC License because the original project has been archived.
Please check [the npmlog changelog](https://github.com/npm/npmlog/blob/main/CHANGELOG.md) to see the list of former module updates before it was forked.
# License
ISC License

5
packages/logger/index.ts Normal file
View File

@@ -0,0 +1,5 @@
import log from './lib/log';
export type * from './lib/types';
export {log};
export default log;

310
packages/logger/lib/log.ts Normal file
View File

@@ -0,0 +1,310 @@
import {EventEmitter} from 'node:events';
import setBlocking from 'set-blocking';
import consoleControl from 'console-control-strings';
import * as util from 'node:util';
import type {MessageObject, StyleObject, Logger, LogLevel} from './types';
import type {Writable} from 'node:stream';
const DEFAULT_LOG_LEVELS: any[][] = [
['silly', -Infinity, {inverse: true}, 'sill'],
['verbose', 1000, {fg: 'cyan', bg: 'black'}, 'verb'],
['info', 2000, {fg: 'green'}],
['timing', 2500, {fg: 'green', bg: 'black'}],
['http', 3000, {fg: 'green', bg: 'black'}],
['notice', 3500, {fg: 'cyan', bg: 'black'}],
['warn', 4000, {fg: 'black', bg: 'yellow'}, 'WARN'],
['error', 5000, {fg: 'red', bg: 'black'}, 'ERR!'],
['silent', Infinity],
] as const;
setBlocking(true);
export class Log extends EventEmitter implements Logger {
level: LogLevel | string;
record: MessageObject[];
maxRecordSize: number;
prefixStyle: StyleObject;
headingStyle: StyleObject;
heading: string;
stream: Writable; // Defaults to process.stderr
_colorEnabled?: boolean;
_buffer: MessageObject[];
_style: Record<LogLevel | string, StyleObject | undefined>;
_levels: Record<LogLevel | string, number>;
_disp: Record<LogLevel | string, number | string>;
_id: number;
_paused: boolean;
constructor() {
super();
this.level = 'info';
this._buffer = [];
this.record = [];
this.maxRecordSize = 10000;
this.stream = process.stderr;
this.prefixStyle = {fg: 'magenta'};
this.headingStyle = {fg: 'white', bg: 'black'};
this._id = 0;
this._paused = false;
this._style = {};
this._levels = {};
this._disp = {};
this.initDefaultLevels();
// allow 'error' prefix
this.on('error', () => {});
}
private useColor(): boolean {
// by default, decide based on tty-ness.
return (
this._colorEnabled ?? Boolean(this.stream && 'isTTY' in this.stream && this.stream.isTTY)
);
}
enableColor(): void {
this._colorEnabled = true;
}
disableColor(): void {
this._colorEnabled = false;
}
// this functionality has been deliberately disabled
enableUnicode(): void {}
disableUnicode(): void {}
enableProgress(): void {}
disableProgress(): void {}
progressEnabled(): boolean {
return false;
}
/**
* Temporarily stop emitting, but don't drop
*/
pause(): void {
this._paused = true;
}
resume(): void {
if (!this._paused) {
return;
}
this._paused = false;
const b = this._buffer;
this._buffer = [];
for (const m of b) {
this.emitLog(m);
}
}
silly(prefix: string, message: any, ...args: any[]): void {
this.log('silly', prefix, message, ...args);
}
verbose(prefix: string, message: any, ...args: any[]): void {
this.log('verbose', prefix, message, ...args);
}
info(prefix: string, message: any, ...args: any[]): void {
this.log('info', prefix, message, ...args);
}
timing(prefix: string, message: any, ...args: any[]): void {
this.log('timing', prefix, message, ...args);
}
http(prefix: string, message: any, ...args: any[]): void {
this.log('http', prefix, message, ...args);
}
notice(prefix: string, message: any, ...args: any[]): void {
this.log('notice', prefix, message, ...args);
}
warn(prefix: string, message: any, ...args: any[]): void {
this.log('warn', prefix, message, ...args);
}
error(prefix: string, message: any, ...args: any[]): void {
this.log('error', prefix, message, ...args);
}
silent(prefix: string, message: any, ...args: any[]): void {
this.log('silent', prefix, message, ...args);
}
addLevel(level: string, n: number, style?: StyleObject, disp?: string): void {
this._levels[level] = n;
this._style[level] = style;
if (!this[level]) {
this[level] = (prefix: string, message: any, ...args: any[]) => {
this.log(level, prefix, message, ...args);
};
}
// If 'disp' is null or undefined, use the level as a default
this._disp[level] = disp ?? level;
}
/**
* Creates a log message
* @param level
* @param prefix
* @param message message of the log which will be formatted using utils.format()
* @param args additional arguments appended to the log message also formatted using utils.format()
*/
log(level: LogLevel | string, prefix: string, message: any, ...args: any[]): void {
const l = this._levels[level];
if (l === undefined) {
this.emit('error', new Error(util.format('Undefined log level: %j', level)));
return;
}
const messageArguments: any[] = [];
let stack: string | null = null;
for (const formatArg of [message, ...args]) {
messageArguments.push(formatArg);
// resolve stack traces to a plain string.
if (typeof formatArg === 'object' && formatArg instanceof Error && formatArg.stack) {
Object.defineProperty(formatArg, 'stack', {
value: (stack = formatArg.stack + ''),
enumerable: true,
writable: true,
});
}
}
if (stack) {
messageArguments.unshift(`${stack}\n`);
}
const formattedMessage: string = util.format(...messageArguments);
const m: MessageObject = {
id: this._id++,
timestamp: Date.now(),
level,
prefix: String(prefix || ''),
message: formattedMessage,
};
this.emit('log', m);
this.emit('log.' + level, m);
if (m.prefix) {
this.emit(m.prefix, m);
}
this.record.push(m);
const mrs = this.maxRecordSize;
if (this.record.length > mrs) {
this.record.shift();
}
this.emitLog(m);
}
private emitLog(m: MessageObject): void {
if (this._paused) {
this._buffer.push(m);
return;
}
const l = this._levels[m.level];
if (l === undefined) {
return;
}
if (l < this._levels[this.level]) {
return;
}
if (l > 0 && !isFinite(l)) {
return;
}
// If 'disp' is null or undefined, use the lvl as a default
// Allows: '', 0 as valid disp
const disp = this._disp[m.level];
this.clearProgress();
for (const line of m.message.split(/\r?\n/)) {
const heading = this.heading;
if (heading) {
this.write(heading, this.headingStyle);
this.write(' ');
}
this.write(String(disp), this._style[m.level]);
const p = m.prefix || '';
if (p) {
this.write(' ');
}
this.write(p, this.prefixStyle);
this.write(` ${line}\n`);
}
this.showProgress();
}
private _format(msg: string, style: StyleObject = {}): string | undefined {
if (!this.stream) {
return;
}
let output = '';
if (this.useColor()) {
const settings: string[] = [];
if (style.fg) {
settings.push(style.fg);
}
if (style.bg) {
settings.push('bg' + style.bg[0].toUpperCase() + style.bg.slice(1));
}
if (style.bold) {
settings.push('bold');
}
if (style.underline) {
settings.push('underline');
}
if (style.inverse) {
settings.push('inverse');
}
if (settings.length) {
output += consoleControl.color(settings);
}
if (style.bell) {
output += consoleControl.beep();
}
}
output += msg;
if (this.useColor()) {
output += consoleControl.color('reset');
}
return output;
}
private write(msg: string, style: StyleObject = {}): void {
if (!this.stream) {
return;
}
const formatted = this._format(msg, style);
if (formatted !== undefined) {
this.stream.write(formatted);
}
}
private initDefaultLevels(): void {
for (const [level, index, style, disp] of DEFAULT_LOG_LEVELS) {
this._levels[level] = index;
this._style[level] = style;
this._disp[level] = disp ?? level;
}
}
// this functionality has been deliberately disabled
private clearProgress(): void {}
private showProgress(): void {}
}
export const GLOBAL_LOG = new Log();
export default GLOBAL_LOG;

View File

@@ -0,0 +1,83 @@
import type {EventEmitter} from 'node:events';
export interface Logger extends EventEmitter {
level: string;
record: MessageObject[];
maxRecordSize: number;
prefixStyle: StyleObject;
headingStyle: StyleObject;
heading: string;
stream: any; // Defaults to process.stderr
/**
* Creates a log message
* @param level
* @param prefix
* @param message message of the log which will be formatted using utils.format()
* @param args additional arguments appended to the log message also formatted using utils.format()
*/
log(level: LogLevel | string, prefix: string, message: any, ...args: any[]): void;
/**
* @param prefix
* @param message message of the log which will be formatted using utils.format()
* @param args additional arguments appended to the log message also formatted using utils.format()
*/
silly(prefix: string, message: any, ...args: any[]): void;
verbose(prefix: string, message: any, ...args: any[]): void;
info(prefix: string, message: any, ...args: any[]): void;
timing(prefix: string, message: any, ...args: any[]): void;
http(prefix: string, message: any, ...args: any[]): void;
notice(prefix: string, message: any, ...args: any[]): void;
warn(prefix: string, message: any, ...args: any[]): void;
error(prefix: string, message: any, ...args: any[]): void;
silent(prefix: string, message: any, ...args: any[]): void;
enableColor(): void;
disableColor(): void;
enableProgress(): void;
disableProgress(): void;
progressEnabled(): boolean;
enableUnicode(): void;
disableUnicode(): void;
pause(): void;
resume(): void;
addLevel(level: string, n: number, style?: StyleObject, disp?: string): void;
// Allows for custom log levels
// log.addLevel("custom", level)
// log.custom(prefix, message)
[key: string]: any;
}
export type LogLevel =
| 'silly'
| 'verbose'
| 'info'
| 'timing'
| 'http'
| 'notice'
| 'warn'
| 'error'
| 'silent';
export interface StyleObject {
fg?: string | undefined;
bg?: string | undefined;
bold?: boolean | undefined;
inverse?: boolean | undefined;
underline?: boolean | undefined;
bell?: boolean | undefined;
}
export interface MessageObject {
id: number;
timestamp: number;
level: string;
prefix: string;
message: string;
}

View File

@@ -0,0 +1,56 @@
{
"name": "@apppium/logger",
"version": "1.0.0",
"author": "https://github.com/appium",
"description": "A Universal Logger For The Appium Ecosystem",
"repository": {
"type": "git",
"url": "https://github.com/appium/appium.git",
"directory": "packages/logger"
},
"main": "build/index.js",
"types": "index.ts",
"files": [
"index.ts",
"lib",
"build",
"tsconfig.json",
"!build/tsconfig.tsbuildinfo",
"CHANGELOG.md"
],
"directories": {
"lib": "./lib"
},
"scripts": {
"test": "npm run test:unit",
"test:smoke": "node ./build/index.js",
"test:unit": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.js\""
},
"dependencies": {
"console-control-strings": "1.1.0",
"set-blocking": "2.0.0"
},
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
"npm": ">=8"
},
"bugs": {
"url": "https://github.com/appium/appium/issues"
},
"homepage": "https://appium.io",
"publishConfig": {
"access": "public"
},
"gitHead": "8480a85ce2fa466360e0fb1a7f66628331907f02",
"keywords": [
"automation",
"javascript",
"selenium",
"webdriver",
"ios",
"android",
"firefoxos",
"testing"
]
}

View File

@@ -0,0 +1,425 @@
/* eslint-disable no-console */
import { Log } from '../../lib/log';
import {Stream} from 'node:stream';
describe('basic', async function () {
const chai = await import('chai');
chai.should();
let log;
describe('logging', function () {
let s;
let result = [];
let logEvents = [];
let logInfoEvents = [];
let logPrefixEvents = [];
const resultExpect = [
// eslint-disable-next-line max-len
'\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[7msill\u001b[0m \u001b[0m\u001b[35msilly prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[36;40mverb\u001b[0m \u001b[0m\u001b[35mverbose prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32minfo\u001b[0m \u001b[0m\u001b[35minfo prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32;40mtiming\u001b[0m \u001b[0m\u001b[35mtiming prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32;40mhttp\u001b[0m \u001b[0m\u001b[35mhttp prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[36;40mnotice\u001b[0m \u001b[0m\u001b[35mnotice prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[30;43mWARN\u001b[0m \u001b[0m\u001b[35mwarn prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35merror prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32minfo\u001b[0m \u001b[0m\u001b[35minfo prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32;40mtiming\u001b[0m \u001b[0m\u001b[35mtiming prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[32;40mhttp\u001b[0m \u001b[0m\u001b[35mhttp prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[36;40mnotice\u001b[0m \u001b[0m\u001b[35mnotice prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[30;43mWARN\u001b[0m \u001b[0m\u001b[35mwarn prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35merror prefix\u001b[0m x = {"foo":{"bar":"baz"}}\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35m404\u001b[0m This is a longer\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35m404\u001b[0m message, with some details\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35m404\u001b[0m and maybe a stack.\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u001b[31;40mERR!\u001b[0m \u001b[0m\u001b[35m404\u001b[0m \n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u0007noise\u001b[0m\u001b[35m\u001b[0m LOUD NOISES\n',
// eslint-disable-next-line max-len
'\u001b[0m\u001b[37;40mnpm\u001b[0m \u001b[0m\u0007noise\u001b[0m \u001b[0m\u001b[35merror\u001b[0m erroring\n',
'\u001b[0m',
];
const logPrefixEventsExpect = [
{ id: 2,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 11,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 20,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
];
// should be the same.
const logInfoEventsExpect = logPrefixEventsExpect;
const logEventsExpect = [
{ id: 0,
level: 'silly',
prefix: 'silly prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 1,
level: 'verbose',
prefix: 'verbose prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 2,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 3,
level: 'timing',
prefix: 'timing prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 4,
level: 'http',
prefix: 'http prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 5,
level: 'notice',
prefix: 'notice prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 6,
level: 'warn',
prefix: 'warn prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 7,
level: 'error',
prefix: 'error prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 8,
level: 'silent',
prefix: 'silent prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 9,
level: 'silly',
prefix: 'silly prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 10,
level: 'verbose',
prefix: 'verbose prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 11,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 12,
level: 'timing',
prefix: 'timing prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 13,
level: 'http',
prefix: 'http prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 14,
level: 'notice',
prefix: 'notice prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 15,
level: 'warn',
prefix: 'warn prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 16,
level: 'error',
prefix: 'error prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 17,
level: 'silent',
prefix: 'silent prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 18,
level: 'silly',
prefix: 'silly prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 19,
level: 'verbose',
prefix: 'verbose prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 20,
level: 'info',
prefix: 'info prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 21,
level: 'timing',
prefix: 'timing prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 22,
level: 'http',
prefix: 'http prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 23,
level: 'notice',
prefix: 'notice prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 24,
level: 'warn',
prefix: 'warn prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 25,
level: 'error',
prefix: 'error prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 26,
level: 'silent',
prefix: 'silent prefix',
message: 'x = {"foo":{"bar":"baz"}}',
},
{ id: 27,
level: 'error',
prefix: '404',
message: 'This is a longer\nmessage, with some details\nand maybe a stack.\n',
},
{ id: 28,
level: 'noise',
prefix: '',
message: 'LOUD NOISES',
},
{ id: 29,
level: 'noise',
prefix: 'error',
message: 'erroring',
},
];
this.beforeEach(function () {
result = [];
logEvents = [];
logInfoEvents = [];
logPrefixEvents = [];
log = new Log();
s = new Stream();
s.write = (m) => result.push(m);
s.writable = true;
s.isTTY = true;
s.end = () => {};
log.stream = s;
log.heading = 'npm';
});
it('should work', function () {
log.stream.should.equal(s);
log.on('log', logEvents.push.bind(logEvents));
log.on('log.info', logInfoEvents.push.bind(logInfoEvents));
log.on('info prefix', logPrefixEvents.push.bind(logPrefixEvents));
console.error('log.level=silly');
log.level = 'silly';
log.silly('silly prefix', 'x = %j', { foo: { bar: 'baz' } });
log.verbose('verbose prefix', 'x = %j', { foo: { bar: 'baz' } });
log.info('info prefix', 'x = %j', { foo: { bar: 'baz' } });
log.timing('timing prefix', 'x = %j', { foo: { bar: 'baz' } });
log.http('http prefix', 'x = %j', { foo: { bar: 'baz' } });
log.notice('notice prefix', 'x = %j', { foo: { bar: 'baz' } });
log.warn('warn prefix', 'x = %j', { foo: { bar: 'baz' } });
log.error('error prefix', 'x = %j', { foo: { bar: 'baz' } });
log.silent('silent prefix', 'x = %j', { foo: { bar: 'baz' } });
console.error('log.level=silent');
log.level = 'silent';
log.silly('silly prefix', 'x = %j', { foo: { bar: 'baz' } });
log.verbose('verbose prefix', 'x = %j', { foo: { bar: 'baz' } });
log.info('info prefix', 'x = %j', { foo: { bar: 'baz' } });
log.timing('timing prefix', 'x = %j', { foo: { bar: 'baz' } });
log.http('http prefix', 'x = %j', { foo: { bar: 'baz' } });
log.notice('notice prefix', 'x = %j', { foo: { bar: 'baz' } });
log.warn('warn prefix', 'x = %j', { foo: { bar: 'baz' } });
log.error('error prefix', 'x = %j', { foo: { bar: 'baz' } });
log.silent('silent prefix', 'x = %j', { foo: { bar: 'baz' } });
console.error('log.level=info');
log.level = 'info';
log.silly('silly prefix', 'x = %j', { foo: { bar: 'baz' } });
log.verbose('verbose prefix', 'x = %j', { foo: { bar: 'baz' } });
log.info('info prefix', 'x = %j', { foo: { bar: 'baz' } });
log.timing('timing prefix', 'x = %j', { foo: { bar: 'baz' } });
log.http('http prefix', 'x = %j', { foo: { bar: 'baz' } });
log.notice('notice prefix', 'x = %j', { foo: { bar: 'baz' } });
log.warn('warn prefix', 'x = %j', { foo: { bar: 'baz' } });
log.error('error prefix', 'x = %j', { foo: { bar: 'baz' } });
log.silent('silent prefix', 'x = %j', { foo: { bar: 'baz' } });
log.error('404', 'This is a longer\n' +
'message, with some details\n' +
'and maybe a stack.\n');
log.addLevel('noise', 10000, { bell: true });
log.noise(false, 'LOUD NOISES');
log.noise('error', 'erroring');
result.join('').trim().should.equal(resultExpect.join('').trim());
const withoutTimestamps = (x) => x.map((m) => {
Boolean(m.timestamp).should.be.true;
const copy = JSON.parse(JSON.stringify(m));
delete copy.timestamp;
return copy;
});
withoutTimestamps(log.record).should.eql(logEventsExpect);
withoutTimestamps(logEvents).should.eql(logEventsExpect);
withoutTimestamps(logInfoEvents).should.eql(logInfoEventsExpect);
withoutTimestamps(logPrefixEvents).should.eql(logPrefixEventsExpect);
});
});
describe('utils', function () {
it('enableColor', function () {
log.enableColor();
log.useColor().should.be.true;
});
it('disableColor', function () {
log.disableColor();
log.useColor().should.be.false;
});
it('_buffer while paused', function () {
log.pause();
log.log('verbose', 'test', 'test log');
log._buffer.length.should.equal(1);
log.resume();
log._buffer.length.should.equal(0);
});
});
describe('log.log', function () {
beforeEach(function () {
log = new Log();
});
it('emits error on bad loglevel', async function() {
await new Promise((resolve, reject) => {
log.once('error', (err) => {
/Undefined log level: "asdf"/.test(err).should.be.true;
resolve();
});
log.log('asdf', 'bad loglevel');
setTimeout(reject, 1000);
});
});
it('resolves stack traces to a plain string', async function() {
await new Promise((resolve, reject) => {
log.once('log', (m) => {
/Error: with a stack trace/.test(m.message).should.be.true;
/at Test/.test(m.message).should.be.true;
resolve();
});
const err = new Error('with a stack trace');
log.log('verbose', 'oops', err);
setTimeout(reject, 1000);
});
});
it('max record size', function() {
log.maxRecordSize = 3;
log.log('verbose', 'test', 'log 1');
log.log('verbose', 'test', 'log 2');
log.log('verbose', 'test', 'log 3');
log.log('verbose', 'test', 'log 4');
log.record.length.should.equal(3);
});
});
describe('stream', function () {
beforeEach(function () {
log = new Log();
});
it('write with no stream', function() {
log.stream = null;
log.write('message');
});
});
describe('emitLog', function () {
beforeEach(function () {
log = new Log();
});
it('to nonexistant level', function() {
log.emitLog({ prefix: 'test', level: 'asdf' });
});
});
describe('format', function () {
beforeEach(function () {
log = new Log();
});
it('with nonexistant stream', function() {
log.stream = null;
(log._format('message') === undefined).should.be.true;
});
it('fg', function () {
log.enableColor();
const o = log._format('test message', { bg: 'blue' });
o.includes('\u001b[44mtest message\u001b[0m').should.be.true;
});
it('bg', function () {
log.enableColor();
const o = log._format('test message', { bg: 'white' });
o.includes('\u001b[47mtest message\u001b[0m').should.be.true;
});
it('bold', function () {
log.enableColor();
const o = log._format('test message', { bold: true });
o.includes('\u001b[1mtest message\u001b[0m').should.be.true;
});
it('underline', function () {
log.enableColor();
const o = log._format('test message', { underline: true });
o.includes('\u001b[4mtest message\u001b[0m').should.be.true;
});
it('inverse', function () {
log.enableColor();
const o = log._format('test message', { inverse: true });
o.includes('\u001b[7mtest message\u001b[0m').should.be.true;
});
});
});

View File

@@ -0,0 +1,34 @@
import { Log } from '../../lib/log';
import { waitForCondition } from 'asyncbox';
describe('display', function () {
let log;
describe('explicitly set new log level display to empty string', function () {
let actual;
beforeEach(function () {
actual = '';
log = new Log();
log.write = (msg) => {
actual += msg;
};
});
it('explicitly set new log level display to empty string', async function () {
log.addLevel('explicitNoLevelDisplayed', 20000, {}, '');
log.explicitNoLevelDisplayed('1', '2');
await waitForCondition(() => actual.trim() === '1 2', {waitMs: 1000, intervalMs: 50});
actual = '';
log.explicitNoLevelDisplayed('', '1');
await waitForCondition(() => actual.trim() === '1', {waitMs: 1000, intervalMs: 50});
});
it('explicitly set new log level display to 0', async function () {
log.addLevel('explicitNoLevelDisplayed', 20000, {}, 0);
log.explicitNoLevelDisplayed('', '1');
await waitForCondition(() => actual.trim() === '0 1', {waitMs: 1000, intervalMs: 50});
});
});
});

View File

@@ -0,0 +1,14 @@
{
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"esModuleInterop": true,
"strict": false,
"outDir": "build",
"types": ["node"],
"checkJs": true
},
"include": [
"index.ts",
"lib"
]
}

View File

@@ -67,6 +67,9 @@
},
{
"path": "packages/strongbox"
}
},
{
"path": "packages/logger"
},
]
}