[devUtils/toolingLog] give tooling log configurable writers (#22110)

* [devUtils/toolingLog] give tooling log configurable writers

* property shorthand

* remove redundant parameter

* call Error.captureStackTrace when subclassing Error

* describe why we skip stack trace logging for CliError

* always return true/false from log writers

* improve type definitions, writeTo is just an object with write method

* get rid of weird dedent for failures
This commit is contained in:
Spencer 2018-08-21 17:09:27 -07:00 committed by GitHub
parent 3175253056
commit 4d9bc2f121
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 1271 additions and 474 deletions

View file

@ -17,21 +17,4 @@
* under the License.
*/
import { Readable } from 'stream';
type LogLevel = 'silent' | 'error' | 'warning' | 'info' | 'debug' | 'verbose';
export class ToolingLog extends Readable {
public verbose(...args: any[]): void;
public debug(...args: any[]): void;
public info(...args: any[]): void;
public success(...args: any[]): void;
public warning(...args: any[]): void;
public error(errOrMsg: string | Error): void;
public write(...args: any[]): void;
public indent(spaces: number): void;
public getLevel(): LogLevel;
public setLevel(level: LogLevel): void;
}
export function createToolingLog(level?: LogLevel): ToolingLog;
export * from './src/tooling_log';

View file

@ -18,4 +18,4 @@
*/
export { withProcRunner } from './proc_runner';
export { createToolingLog, pickLevelFromFlags } from './tooling_log';
export { ToolingLog, pickLevelFromFlags } from './tooling_log';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { createToolingLog } from '../../tooling_log';
import { ToolingLog } from '../../tooling_log';
import { withProcRunner } from '../with_proc_runner';
describe('proc runner', () => {
@ -34,7 +34,7 @@ describe('proc runner', () => {
}
it('passes procs to a function', async () => {
await withProcRunner(createToolingLog(), async procs => {
await withProcRunner(new ToolingLog(), async procs => {
await runProc({ procs });
await procs.stop('proc');
});

View file

@ -82,7 +82,7 @@ export function createProc(name, { cmd, args, cwd, env, stdin, log }) {
name = name;
lines$ = Rx.merge(observeLines(childProcess.stdout), observeLines(childProcess.stderr)).pipe(
tap(line => log.write(` ${gray('proc')} [${gray(name)}] ${line}`)),
tap(line => log.write(` ${gray('proc')} [${gray(name)}] ${line}`)),
share()
);

View file

@ -0,0 +1,91 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`parses valid log levels correctly: debug 1`] = `
Object {
"flags": Object {
"debug": true,
"error": true,
"info": true,
"silent": true,
"verbose": false,
"warning": true,
},
"name": "debug",
}
`;
exports[`parses valid log levels correctly: error 1`] = `
Object {
"flags": Object {
"debug": false,
"error": true,
"info": false,
"silent": true,
"verbose": false,
"warning": false,
},
"name": "error",
}
`;
exports[`parses valid log levels correctly: info 1`] = `
Object {
"flags": Object {
"debug": false,
"error": true,
"info": true,
"silent": true,
"verbose": false,
"warning": true,
},
"name": "info",
}
`;
exports[`parses valid log levels correctly: silent 1`] = `
Object {
"flags": Object {
"debug": false,
"error": false,
"info": false,
"silent": true,
"verbose": false,
"warning": false,
},
"name": "silent",
}
`;
exports[`parses valid log levels correctly: verbose 1`] = `
Object {
"flags": Object {
"debug": true,
"error": true,
"info": true,
"silent": true,
"verbose": true,
"warning": true,
},
"name": "verbose",
}
`;
exports[`parses valid log levels correctly: warning 1`] = `
Object {
"flags": Object {
"debug": false,
"error": true,
"info": false,
"silent": true,
"verbose": false,
"warning": true,
},
"name": "warning",
}
`;
exports[`throws error for invalid levels: bar 1`] = `"Invalid log level \\"bar\\" (expected one of silent,error,warning,info,debug,verbose)"`;
exports[`throws error for invalid levels: foo 1`] = `"Invalid log level \\"foo\\" (expected one of silent,error,warning,info,debug,verbose)"`;
exports[`throws error for invalid levels: warn 1`] = `"Invalid log level \\"warn\\" (expected one of silent,error,warning,info,debug,verbose)"`;

View file

@ -0,0 +1,226 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`#debug() sends a msg of type "debug" to each writer with indent and arguments 1`] = `
Array [
Array [
Object {
"args": Array [
"foo",
"bar",
"baz",
],
"indent": 0,
"type": "debug",
},
],
]
`;
exports[`#error() sends a msg of type "error" to each writer with indent and arguments 1`] = `
Array [
Array [
Object {
"args": Array [
[Error: error message],
],
"indent": 0,
"type": "error",
},
],
Array [
Object {
"args": Array [
"string message",
],
"indent": 0,
"type": "error",
},
],
]
`;
exports[`#getWritten$() does not emit msg if all writers return false 1`] = `Array []`;
exports[`#getWritten$() does not emit msg when no writers 1`] = `Array []`;
exports[`#getWritten$() emits msg if all writers return true 1`] = `
Array [
Object {
"args": Array [
"foo",
],
"indent": 0,
"type": "debug",
},
Object {
"args": Array [
"bar",
],
"indent": 0,
"type": "info",
},
Object {
"args": Array [
"baz",
],
"indent": 0,
"type": "verbose",
},
]
`;
exports[`#getWritten$() emits msg if some writers return true 1`] = `
Array [
Object {
"args": Array [
"foo",
],
"indent": 0,
"type": "debug",
},
Object {
"args": Array [
"bar",
],
"indent": 0,
"type": "info",
},
Object {
"args": Array [
"baz",
],
"indent": 0,
"type": "verbose",
},
]
`;
exports[`#indent() changes the indent on each written msg 1`] = `
Array [
Array [
Object {
"args": Array [
"foo",
],
"indent": 1,
"type": "debug",
},
],
Array [
Object {
"args": Array [
"bar",
],
"indent": 3,
"type": "debug",
},
],
Array [
Object {
"args": Array [
"baz",
],
"indent": 6,
"type": "debug",
},
],
Array [
Object {
"args": Array [
"box",
],
"indent": 4,
"type": "debug",
},
],
Array [
Object {
"args": Array [
"foo",
],
"indent": 0,
"type": "debug",
},
],
]
`;
exports[`#info() sends a msg of type "info" to each writer with indent and arguments 1`] = `
Array [
Array [
Object {
"args": Array [
"foo",
"bar",
"baz",
],
"indent": 0,
"type": "info",
},
],
]
`;
exports[`#success() sends a msg of type "success" to each writer with indent and arguments 1`] = `
Array [
Array [
Object {
"args": Array [
"foo",
"bar",
"baz",
],
"indent": 0,
"type": "success",
},
],
]
`;
exports[`#verbose() sends a msg of type "verbose" to each writer with indent and arguments 1`] = `
Array [
Array [
Object {
"args": Array [
"foo",
"bar",
"baz",
],
"indent": 0,
"type": "verbose",
},
],
]
`;
exports[`#warning() sends a msg of type "warning" to each writer with indent and arguments 1`] = `
Array [
Array [
Object {
"args": Array [
"foo",
"bar",
"baz",
],
"indent": 0,
"type": "warning",
},
],
]
`;
exports[`#write() sends a msg of type "write" to each writer with indent and arguments 1`] = `
Array [
Array [
Object {
"args": Array [
"foo",
"bar",
"baz",
],
"indent": 0,
"type": "write",
},
],
]
`;

View file

@ -0,0 +1,178 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`formats %s patterns and indents multi-line messages correctly 1`] = `
" │ succ foo bar
│ { foo: { bar: { '1': [Array] } },
│ bar: { bar: { '1': [Array] } } }
│ Infinity
"
`;
exports[`level:debug/type:debug snapshots: is written 1`] = `true`;
exports[`level:debug/type:debug snapshots: output 1`] = `
" debg foo
"
`;
exports[`level:debug/type:error snapshots: is written 1`] = `true`;
exports[`level:debug/type:error snapshots: output 1`] = `
"ERROR foo
"
`;
exports[`level:debug/type:info snapshots: is written 1`] = `true`;
exports[`level:debug/type:info snapshots: output 1`] = `
" info foo
"
`;
exports[`level:debug/type:success snapshots: is written 1`] = `true`;
exports[`level:debug/type:success snapshots: output 1`] = `
" succ foo
"
`;
exports[`level:debug/type:verbose snapshots: is written 1`] = `false`;
exports[`level:debug/type:warning snapshots: is written 1`] = `true`;
exports[`level:debug/type:warning snapshots: output 1`] = `
" warn foo
"
`;
exports[`level:error/type:debug snapshots: is written 1`] = `false`;
exports[`level:error/type:error snapshots: is written 1`] = `true`;
exports[`level:error/type:error snapshots: output 1`] = `
"ERROR foo
"
`;
exports[`level:error/type:info snapshots: is written 1`] = `false`;
exports[`level:error/type:success snapshots: is written 1`] = `false`;
exports[`level:error/type:verbose snapshots: is written 1`] = `false`;
exports[`level:error/type:warning snapshots: is written 1`] = `false`;
exports[`level:info/type:debug snapshots: is written 1`] = `false`;
exports[`level:info/type:error snapshots: is written 1`] = `true`;
exports[`level:info/type:error snapshots: output 1`] = `
"ERROR foo
"
`;
exports[`level:info/type:info snapshots: is written 1`] = `true`;
exports[`level:info/type:info snapshots: output 1`] = `
" info foo
"
`;
exports[`level:info/type:success snapshots: is written 1`] = `true`;
exports[`level:info/type:success snapshots: output 1`] = `
" succ foo
"
`;
exports[`level:info/type:verbose snapshots: is written 1`] = `false`;
exports[`level:info/type:warning snapshots: is written 1`] = `true`;
exports[`level:info/type:warning snapshots: output 1`] = `
" warn foo
"
`;
exports[`level:silent/type:debug snapshots: is written 1`] = `false`;
exports[`level:silent/type:error snapshots: is written 1`] = `false`;
exports[`level:silent/type:info snapshots: is written 1`] = `false`;
exports[`level:silent/type:success snapshots: is written 1`] = `false`;
exports[`level:silent/type:verbose snapshots: is written 1`] = `false`;
exports[`level:silent/type:warning snapshots: is written 1`] = `false`;
exports[`level:verbose/type:debug snapshots: is written 1`] = `true`;
exports[`level:verbose/type:debug snapshots: output 1`] = `
" debg foo
"
`;
exports[`level:verbose/type:error snapshots: is written 1`] = `true`;
exports[`level:verbose/type:error snapshots: output 1`] = `
"ERROR foo
"
`;
exports[`level:verbose/type:info snapshots: is written 1`] = `true`;
exports[`level:verbose/type:info snapshots: output 1`] = `
" info foo
"
`;
exports[`level:verbose/type:success snapshots: is written 1`] = `true`;
exports[`level:verbose/type:success snapshots: output 1`] = `
" succ foo
"
`;
exports[`level:verbose/type:verbose snapshots: is written 1`] = `true`;
exports[`level:verbose/type:verbose snapshots: output 1`] = `
" sill foo
"
`;
exports[`level:verbose/type:warning snapshots: is written 1`] = `true`;
exports[`level:verbose/type:warning snapshots: output 1`] = `
" warn foo
"
`;
exports[`level:warning/type:debug snapshots: is written 1`] = `false`;
exports[`level:warning/type:error snapshots: is written 1`] = `true`;
exports[`level:warning/type:error snapshots: output 1`] = `
"ERROR foo
"
`;
exports[`level:warning/type:info snapshots: is written 1`] = `false`;
exports[`level:warning/type:success snapshots: is written 1`] = `false`;
exports[`level:warning/type:verbose snapshots: is written 1`] = `false`;
exports[`level:warning/type:warning snapshots: is written 1`] = `true`;
exports[`level:warning/type:warning snapshots: output 1`] = `
" warn foo
"
`;
exports[`throws error if created with invalid level 1`] = `"Invalid log level \\"foo\\" (expected one of silent,error,warning,info,debug,verbose)"`;
exports[`throws error if writeTo config is not defined or doesn't have a write method 1`] = `"ToolingLogTextWriter requires the \`writeTo\` option be set to a stream (like process.stdout)"`;
exports[`throws error if writeTo config is not defined or doesn't have a write method 2`] = `"ToolingLogTextWriter requires the \`writeTo\` option be set to a stream (like process.stdout)"`;

View file

@ -1,99 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from 'expect.js';
import Chance from 'chance';
import { createConcatStream, createPromiseFromStreams } from '../../streams';
import { createToolingLog } from '../tooling_log';
const chance = new Chance();
const capture = (level, block) => {
const log = createToolingLog(level);
block(log);
log.end();
return createPromiseFromStreams([log, createConcatStream('')]);
};
const nothingTest = (logLevel, method) => {
describe(`#${method}(...any)`, () => {
it('logs nothing', async () => {
const output = await capture(logLevel, log => log[method]('foo'));
expect(output).to.be('');
});
});
};
const somethingTest = (logLevel, method) => {
describe(`#${method}(...any)`, () => {
it('logs to output stream', async () => {
const output = await capture(logLevel, log => log[method]('foo'));
expect(output).to.contain('foo');
});
});
};
describe('utils: createToolingLog(logLevel, output)', () => {
it('is a readable stream', async () => {
const log = createToolingLog('debug');
log.info('Foo');
log.info('Bar');
log.info('Baz');
log.end();
const output = await createPromiseFromStreams([log, createConcatStream('')]);
expect(output).to.contain('Foo');
expect(output).to.contain('Bar');
expect(output).to.contain('Baz');
});
describe('log level', () => {
describe('logLevel=silent', () => {
nothingTest('silent', 'debug');
nothingTest('silent', 'info');
nothingTest('silent', 'error');
});
describe('logLevel=error', () => {
nothingTest('error', 'debug');
nothingTest('error', 'info');
somethingTest('error', 'error');
});
describe('logLevel=info', () => {
nothingTest('info', 'debug');
somethingTest('info', 'info');
somethingTest('info', 'error');
});
describe('logLevel=debug', () => {
somethingTest('debug', 'debug');
somethingTest('debug', 'info');
somethingTest('debug', 'error');
});
describe('invalid logLevel', () => {
it('throw error', () => {
// avoid the impossibility that a valid level is generated
// by specifying a long length
const level = chance.word({ length: 10 });
expect(() => createToolingLog(level)).to.throwError(level);
});
});
});
});

View file

@ -1,114 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from 'expect.js';
import Chance from 'chance';
import { parseLogLevel } from '../log_levels';
const chance = new Chance();
describe('parseLogLevel(logLevel).flags', () => {
describe('logLevel=silent', () => {
it('produces correct map', () => {
expect(parseLogLevel('silent').flags).to.eql({
silent: true,
error: false,
warning: false,
info: false,
debug: false,
verbose: false,
});
});
});
describe('logLevel=error', () => {
it('produces correct map', () => {
expect(parseLogLevel('error').flags).to.eql({
silent: true,
error: true,
warning: false,
info: false,
debug: false,
verbose: false,
});
});
});
describe('logLevel=warning', () => {
it('produces correct map', () => {
expect(parseLogLevel('warning').flags).to.eql({
silent: true,
error: true,
warning: true,
info: false,
debug: false,
verbose: false,
});
});
});
describe('logLevel=info', () => {
it('produces correct map', () => {
expect(parseLogLevel('info').flags).to.eql({
silent: true,
error: true,
warning: true,
info: true,
debug: false,
verbose: false,
});
});
});
describe('logLevel=debug', () => {
it('produces correct map', () => {
expect(parseLogLevel('debug').flags).to.eql({
silent: true,
error: true,
warning: true,
info: true,
debug: true,
verbose: false,
});
});
});
describe('logLevel=verbose', () => {
it('produces correct map', () => {
expect(parseLogLevel('verbose').flags).to.eql({
silent: true,
error: true,
warning: true,
info: true,
debug: true,
verbose: true,
});
});
});
describe('invalid logLevel', () => {
it('throws error', () => {
// avoid the impossibility that a valid level is generated
// by specifying a long length
const level = chance.word({ length: 10 });
expect(() => parseLogLevel(level)).to.throwError(level);
});
});
});

View file

@ -0,0 +1,22 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { ToolingLog } from './tooling_log';
export { ToolingLogTextWriter, WriterConfig } from './tooling_log_text_writer';
export { pickLevelFromFlags, LogLevel } from './log_levels';

View file

@ -17,5 +17,6 @@
* under the License.
*/
export { createToolingLog } from './tooling_log';
export { ToolingLog } from './tooling_log';
export { ToolingLogTextWriter } from './tooling_log_text_writer';
export { pickLevelFromFlags } from './log_levels';

View file

@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export type LogLevel = 'silent' | 'error' | 'warning' | 'info' | 'debug' | 'verbose';
export interface ParsedLogLevel {
name: LogLevel;
flags: { [key in LogLevel]: boolean };
}
export function pickLevelFromFlags(flags: { [key: string]: any }): LogLevel;
export function parseLogLevel(level: LogLevel): ParsedLogLevel;

View file

@ -0,0 +1,35 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { parseLogLevel } from './log_levels';
it('parses valid log levels correctly', () => {
expect(parseLogLevel('silent')).toMatchSnapshot('silent');
expect(parseLogLevel('error')).toMatchSnapshot('error');
expect(parseLogLevel('warning')).toMatchSnapshot('warning');
expect(parseLogLevel('info')).toMatchSnapshot('info');
expect(parseLogLevel('debug')).toMatchSnapshot('debug');
expect(parseLogLevel('verbose')).toMatchSnapshot('verbose');
});
it('throws error for invalid levels', () => {
expect(() => parseLogLevel('warn')).toThrowErrorMatchingSnapshot('warn');
expect(() => parseLogLevel('foo')).toThrowErrorMatchingSnapshot('foo');
expect(() => parseLogLevel('bar')).toThrowErrorMatchingSnapshot('bar');
});

View file

@ -0,0 +1,45 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// tslint:disable max-classes-per-file
import * as Rx from 'rxjs';
import { ToolingLogWriter, WriterConfig } from './tooling_log_text_writer';
export interface LogMessage {
type: 'verbose' | 'debug' | 'info' | 'success' | 'warning' | 'error' | 'write';
indent: number;
args: any[];
}
export class ToolingLog {
constructor(config?: WriterConfig);
public verbose(...args: any[]): void;
public debug(...args: any[]): void;
public info(...args: any[]): void;
public success(...args: any[]): void;
public warning(...args: any[]): void;
public error(errOrMsg: string | Error): void;
public write(...args: any[]): void;
public indent(spaces: number): void;
public getWriters(): ToolingLogWriter[];
public setWriters(reporters: ToolingLogWriter[]): void;
public getWritten$(): Rx.Observable<LogMessage>;
}

View file

@ -17,87 +17,85 @@
* under the License.
*/
import { format } from 'util';
import { PassThrough } from 'stream';
import * as Rx from 'rxjs';
import { EventEmitter } from 'events';
import { magentaBright, yellow, red, blue, green, dim } from 'chalk';
import { ToolingLogTextWriter } from './tooling_log_text_writer';
import { parseLogLevel } from './log_levels';
export class ToolingLog extends EventEmitter {
/**
* Create a ToolingLog object
* @param {WriterConfig} writerConfig
*/
constructor(writerConfig) {
super();
export function createToolingLog(initialLogLevelName = 'silent') {
// current log level (see logLevel.name and logLevel.flags) changed
// with ToolingLog#setLevel(newLogLevelName);
let logLevel = parseLogLevel(initialLogLevelName);
// current indentation level, changed with ToolingLog#indent(delta)
let indentString = '';
class ToolingLog extends PassThrough {
constructor() {
super({ objectMode: true });
}
verbose(...args) {
if (!logLevel.flags.verbose) return;
this.write(' %s ', magentaBright('sill'), format(...args));
}
debug(...args) {
if (!logLevel.flags.debug) return;
this.write(' %s ', dim('debg'), format(...args));
}
info(...args) {
if (!logLevel.flags.info) return;
this.write(' %s ', blue('info'), format(...args));
}
success(...args) {
if (!logLevel.flags.info) return;
this.write(' %s ', green('succ'), format(...args));
}
warning(...args) {
if (!logLevel.flags.warning) return;
this.write(' %s ', yellow('warn'), format(...args));
}
error(err) {
if (!logLevel.flags.error) return;
if (typeof err !== 'string' && !(err instanceof Error)) {
err = new Error(`"${err}" thrown`);
}
this.write('%s ', red('ERROR'), err.stack || err.message || err);
}
indent(delta = 0) {
const width = Math.max(0, indentString.length + delta);
indentString = ' '.repeat(width);
return indentString.length;
}
getLevel() {
return logLevel.name;
}
setLevel(newLogLevelName) {
logLevel = parseLogLevel(newLogLevelName);
}
write(...args) {
format(...args)
.split('\n')
.forEach((line, i) => {
const subLineIndent = i === 0 ? '' : ' ';
const indent = !indentString
? ''
: indentString.slice(0, -1) + (i === 0 && line[0] === '-' ? '└' : '│');
super.write(`${indent}${subLineIndent}${line}\n`);
});
}
this._indent = 0;
this._writers = writerConfig ? [new ToolingLogTextWriter(writerConfig)] : [];
this._written$ = new Rx.Subject();
}
return new ToolingLog();
indent(delta = 0) {
this._indent = Math.max(this._indent + delta, 0);
return this._indent;
}
verbose(...args) {
this._write('verbose', args);
}
debug(...args) {
this._write('debug', args);
}
info(...args) {
this._write('info', args);
}
success(...args) {
this._write('success', args);
}
warning(...args) {
this._write('warning', args);
}
error(error) {
this._write('error', [error]);
}
write(...args) {
this._write('write', args);
}
getWriters() {
return this._writers.slice(0);
}
setWriters(writers) {
this._writers = [...writers];
}
getWritten$() {
return this._written$.asObservable();
}
_write(type, args) {
const msg = {
type,
indent: this._indent,
args,
};
let written = false;
for (const writer of this._writers) {
if (writer.write(msg)) {
written = true;
}
}
if (written) {
this._written$.next(msg);
}
}
}

View file

@ -0,0 +1,143 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as Rx from 'rxjs';
import { toArray, takeUntil } from 'rxjs/operators';
import { ToolingLog } from './tooling_log';
import { ToolingLogTextWriter } from './tooling_log_text_writer';
it('creates zero writers without a config', () => {
const log = new ToolingLog();
expect(log.getWriters()).toHaveLength(0);
});
it('creates a single writer with a single object', () => {
const log = new ToolingLog({ level: 'warning', writeTo: process.stdout });
expect(log.getWriters()).toHaveLength(1);
const [writer] = log.getWriters();
expect(writer.level).toHaveProperty('name', 'warning');
expect(writer.writeTo).toBe(process.stdout);
});
describe('#get/setWriters()', () => {
it('returns/replaces the current writers', () => {
const log = new ToolingLog();
expect(log.getWriters()).toHaveLength(0);
log.setWriters([
new ToolingLogTextWriter({
level: 'verbose',
writeTo: process.stdout,
}),
new ToolingLogTextWriter({
level: 'verbose',
writeTo: process.stdout,
}),
]);
expect(log.getWriters()).toHaveLength(2);
log.setWriters([]);
expect(log.getWriters()).toHaveLength(0);
});
});
describe('#indent()', () => {
it('changes the indent on each written msg', () => {
const log = new ToolingLog();
const write = jest.fn();
log.setWriters([{ write }]);
log.indent(1);
log.debug('foo');
log.indent(2);
log.debug('bar');
log.indent(3);
log.debug('baz');
log.indent(-2);
log.debug('box');
log.indent(-Infinity);
log.debug('foo');
expect(write.mock.calls).toMatchSnapshot();
});
});
['verbose', 'debug', 'info', 'success', 'warning', 'error', 'write'].forEach(method => {
describe(`#${method}()`, () => {
it(`sends a msg of type "${method}" to each writer with indent and arguments`, () => {
const log = new ToolingLog();
const writeA = jest.fn();
const writeB = jest.fn();
log.setWriters([{ write: writeA }, { write: writeB }]);
if (method === 'error') {
const error = new Error('error message');
error.stack = '... stack trace ...';
log.error(error);
log.error('string message');
} else {
log[method]('foo', 'bar', 'baz');
}
expect(writeA.mock.calls).toMatchSnapshot();
expect(writeA.mock.calls).toEqual(writeB.mock.calls);
});
});
});
describe('#getWritten$()', () => {
async function testWrittenMsgs(writers) {
const log = new ToolingLog();
log.setWriters(writers);
const done$ = new Rx.Subject();
const promise = log
.getWritten$()
.pipe(
takeUntil(done$),
toArray()
)
.toPromise();
log.debug('foo');
log.info('bar');
log.verbose('baz');
done$.next();
expect(await promise).toMatchSnapshot();
}
it('does not emit msg when no writers', async () => {
await testWrittenMsgs([]);
});
it('emits msg if all writers return true', async () => {
await testWrittenMsgs([{ write: jest.fn(() => true) }, { write: jest.fn(() => true) }]);
});
it('emits msg if some writers return true', async () => {
await testWrittenMsgs([{ write: jest.fn(() => true) }, { write: jest.fn(() => false) }]);
});
it('does not emit msg if all writers return false', async () => {
await testWrittenMsgs([{ write: jest.fn(() => false) }, { write: jest.fn(() => false) }]);
});
});

View file

@ -0,0 +1,42 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { LogLevel, ParsedLogLevel } from './log_levels';
import { LogMessage } from './tooling_log';
export interface ToolingLogWriter {
write(msg: LogMessage): boolean;
}
export interface WriteTarget {
write(chunk: string): void;
}
export interface WriterConfig {
level: LogLevel;
writeTo: WriteTarget;
}
export class ToolingLogTextWriter implements ToolingLogTextWriter {
public level: ParsedLogLevel;
public writeTo: WriteTarget;
constructor(config: WriterConfig);
public write(msg: LogMessage): boolean;
}

View file

@ -0,0 +1,92 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { format } from 'util';
import { magentaBright, yellow, red, blue, green, dim } from 'chalk';
import { parseLogLevel } from './log_levels';
const PREFIX_INDENT = ' '.repeat(6);
const MSG_PREFIXES = {
verbose: ` ${magentaBright('sill')} `,
debug: ` ${dim('debg')} `,
info: ` ${blue('info')} `,
success: ` ${green('succ')} `,
warning: ` ${yellow('warn')} `,
error: `${red('ERROR')} `,
};
function shouldWriteType(level, type) {
if (type === 'write') {
return true;
}
return Boolean(level.flags[type === 'success' ? 'info' : type]);
}
function stringifyError(error) {
if (typeof error !== 'string' && !(error instanceof Error)) {
error = new Error(`"${error}" thrown`);
}
return error.stack || error.message || error;
}
export class ToolingLogTextWriter {
constructor(config) {
this.level = parseLogLevel(config.level);
this.writeTo = config.writeTo;
if (!this.writeTo || typeof this.writeTo.write !== 'function') {
throw new Error(
'ToolingLogTextWriter requires the `writeTo` option be set to a stream (like process.stdout)'
);
}
}
write({ type, indent, args }) {
if (!shouldWriteType(this.level, type)) {
return false;
}
const txt = type === 'error' ? stringifyError(args[0]) : format(...args);
const prefix = MSG_PREFIXES[type] || '';
(prefix + txt).split('\n').forEach((line, i) => {
let lineIndent = '';
if (indent > 0) {
// if we are indenting write some spaces followed by a symbol
lineIndent += ' '.repeat(indent - 1);
lineIndent += line.startsWith('-') ? '└' : '│';
}
if (line && prefix && i > 0) {
// apply additional indentation to lines after
// the first if this message gets a prefix
lineIndent += PREFIX_INDENT;
}
this.writeTo.write(`${lineIndent}${line}\n`);
});
return true;
}
}

View file

@ -0,0 +1,98 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ToolingLogTextWriter } from './tooling_log_text_writer';
it('throws error if created with invalid level', () => {
expect(
() =>
new ToolingLogTextWriter({
level: 'foo',
})
).toThrowErrorMatchingSnapshot();
});
it("throws error if writeTo config is not defined or doesn't have a write method", () => {
expect(() => {
new ToolingLogTextWriter({
level: 'verbose',
writeTo: null,
});
}).toThrowErrorMatchingSnapshot();
expect(() => {
new ToolingLogTextWriter({
level: 'verbose',
writeTo: 'foo',
});
}).toThrowErrorMatchingSnapshot();
});
const levels = ['silent', 'verbose', 'debug', 'info', 'warning', 'error'];
const types = ['verbose', 'debug', 'info', 'warning', 'error', 'success'];
for (const level of levels) {
for (const type of types) {
it(`level:${level}/type:${type} snapshots`, () => {
const write = jest.fn();
const writer = new ToolingLogTextWriter({
level,
writeTo: {
write,
},
});
const written = writer.write({
type: type,
indent: 0,
args: ['foo'],
});
expect(written).toMatchSnapshot('is written');
if (written) {
const output = write.mock.calls.reduce((acc, chunk) => `${acc}${chunk}`, '');
expect(output).toMatchSnapshot('output');
}
});
}
}
it('formats %s patterns and indents multi-line messages correctly', () => {
const write = jest.fn();
const writer = new ToolingLogTextWriter({
level: 'debug',
writeTo: {
write,
},
});
writer.write({
type: 'success',
indent: 10,
args: [
'%s\n%O\n\n%d',
'foo bar',
{ foo: { bar: { 1: [1, 2, 3] } }, bar: { bar: { 1: [1, 2, 3] } } },
Infinity,
],
});
const output = write.mock.calls.reduce((acc, chunk) => `${acc}${chunk}`, '');
expect(output).toMatchSnapshot();
});

View file

@ -1,6 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": [
"index.d.ts"
"index.d.ts",
"src/**/*.d.ts"
],
}

View file

@ -17,7 +17,7 @@
* under the License.
*/
const { createToolingLog } = require('@kbn/dev-utils');
const { ToolingLog } = require('@kbn/dev-utils');
const execa = require('execa');
const { Cluster } = require('../cluster');
const { installSource, installSnapshot, installArchive } = require('../install');
@ -30,9 +30,7 @@ jest.mock('../install', () => ({
jest.mock('execa', () => jest.fn());
const log = createToolingLog('verbose');
log.onData = jest.fn();
log.on('data', log.onData);
const log = new ToolingLog();
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));

View file

@ -17,9 +17,11 @@
* under the License.
*/
const { createToolingLog } = require('@kbn/dev-utils');
const { ToolingLog } = require('@kbn/dev-utils');
const log = createToolingLog('verbose');
log.pipe(process.stdout);
const log = new ToolingLog({
level: 'verbose',
writeTo: process.stdout,
});
exports.log = log;

View file

@ -18,7 +18,7 @@
*/
import dedent from 'dedent';
import { createToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { ToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
const options = {
help: { desc: 'Display this menu and exit.' },
@ -99,9 +99,10 @@ export function processOptions(userOptions, defaultConfigPaths) {
}
function createLogger() {
const log = createToolingLog(pickLevelFromFlags(userOptions));
log.pipe(process.stdout);
return log;
return new ToolingLog({
level: pickLevelFromFlags(userOptions),
writeTo: process.stdout,
});
}
return {

View file

@ -18,7 +18,7 @@
*/
import dedent from 'dedent';
import { createToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { ToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
const options = {
help: { desc: 'Display this menu and exit.' },
@ -86,9 +86,10 @@ export function processOptions(userOptions, defaultConfigPath) {
}
function createLogger() {
const log = createToolingLog(pickLevelFromFlags(userOptions));
log.pipe(process.stdout);
return log;
return new ToolingLog({
level: pickLevelFromFlags(userOptions),
writeTo: process.stdout,
});
}
return {

View file

@ -22,6 +22,14 @@ import { inspect } from 'util';
import chalk from 'chalk';
import getopts from 'getopts';
export class CliError extends Error {
constructor(message, exitCode = 1) {
super(message);
this.exitCode = exitCode;
Error.captureStackTrace(this, CliError);
}
}
export async function runCli(getHelpText, run) {
try {
const userOptions = getopts(process.argv.slice(2)) || {};
@ -39,20 +47,25 @@ export async function runCli(getHelpText, run) {
console.log();
console.log(chalk.red(error.message));
// first line in the stack trace is the message, skip it as we log it directly and color it red
if (error.stack) {
console.log(
error.stack
.split('\n')
.slice(1)
.join('\n')
);
} else {
console.log(' (no stack trace)');
// CliError is a special error class that indicates that the error is produced as a part
// of using the CLI, and does not need a stack trace to make sense, so we skip the stack
// trace logging if the error thrown is an instance of this class
if (!(error instanceof CliError)) {
// first line in the stack trace is the message, skip it as we log it directly and color it red
if (error.stack) {
console.log(
error.stack
.split('\n')
.slice(1)
.join('\n')
);
} else {
console.log(' (no stack trace)');
}
}
console.log();
process.exit(1);
process.exit(error.exitCode || 1);
}
}

View file

@ -17,33 +17,26 @@
* under the License.
*/
import { KIBANA_FTR_SCRIPT, PROJECT_ROOT } from './paths';
import { createFunctionalTestRunner } from '../../../../../src/functional_test_runner';
import { CliError } from './run_cli';
export async function runFtr({
procs,
configPath,
cwd = PROJECT_ROOT,
options: { log, bail, grep, updateBaselines },
}) {
const args = [KIBANA_FTR_SCRIPT];
if (getLogFlag(log)) args.push(`--${getLogFlag(log)}`);
if (bail) args.push('--bail');
if (configPath) args.push('--config', configPath);
if (grep) args.push('--grep', grep);
if (updateBaselines) args.push('--updateBaselines');
await procs.run('ftr', {
cmd: 'node',
args,
cwd,
wait: true,
export async function runFtr({ configPath, options: { log, bail, grep, updateBaselines } }) {
const ftr = createFunctionalTestRunner({
log,
configFile: configPath,
configOverrides: {
mochaOpts: {
bail: !!bail,
grep,
},
updateBaselines,
},
});
}
function getLogFlag(log) {
const level = log.getLevel();
if (level === 'info') return null;
return level === 'error' ? 'quiet' : level;
const failureCount = await ftr.run();
if (failureCount > 0) {
throw new CliError(
`${failureCount} functional test ${failureCount === 1 ? 'failure' : 'failures'}`
);
}
}

View file

@ -91,7 +91,8 @@ export async function startServers(options) {
}
async function silence(milliseconds, { log }) {
await Rx.fromEvent(log, 'data')
await log
.getWritten$()
.pipe(
startWith(null),
switchMap(() => Rx.timer(milliseconds)),
@ -115,15 +116,7 @@ async function runSingleConfig(configPath, options) {
const es = await runElasticsearch({ config, options: opts });
await runKibanaServer({ procs, config, options: opts });
// Note: When solving how to incorporate functional_test_runner
// clean this up
await runFtr({
procs,
configPath,
cwd: process.cwd(),
options: opts,
});
await runFtr({ configPath, options: opts });
await procs.stop('kibana');
await es.cleanup();

View file

@ -23,7 +23,7 @@ import getopts from 'getopts';
import dedent from 'dedent';
import chalk from 'chalk';
import { createToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { ToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { buildDistributables } from './build_distributables';
import { isErrorLogged } from './lib';
@ -82,8 +82,10 @@ if (flags.help) {
process.exit(1);
}
const log = createToolingLog(pickLevelFromFlags(flags));
log.pipe(process.stdout);
const log = new ToolingLog({
level: pickLevelFromFlags(flags),
writeTo: process.stdout
});
function isOsPackageDesired(name) {
if (flags['skip-os-packages']) {

View file

@ -20,16 +20,22 @@
import sinon from 'sinon';
import stripAnsi from 'strip-ansi';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import { exec } from '../exec';
describe('dev/build/lib/exec', () => {
const sandbox = sinon.createSandbox();
afterEach(() => sandbox.reset());
const log = createToolingLog('verbose');
const onLogLine = sandbox.stub();
log.on('data', line => onLogLine(stripAnsi(line)));
const log = new ToolingLog({
level: 'verbose',
writeTo: {
write: chunk => {
onLogLine(stripAnsi(chunk));
}
}
});
it('executes a command, logs the command, and logs the output', async () => {
await exec(log, process.execPath, ['-e', 'console.log("hi")']);

View file

@ -20,7 +20,7 @@
import sinon from 'sinon';
import expect from 'expect.js';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import { createRunner } from '../runner';
import { isErrorLogged, markErrorLogged } from '../errors';
@ -29,9 +29,13 @@ describe('dev/build/lib/runner', () => {
const config = {};
const log = createToolingLog('verbose');
const onLogLine = sandbox.stub();
log.on('data', onLogLine);
const log = new ToolingLog({
level: 'verbose',
writeTo: {
write: onLogLine
}
});
const buildMatcher = sinon.match({
isOss: sinon.match.func,

View file

@ -26,7 +26,7 @@ import sinon from 'sinon';
import expect from 'expect.js';
import Wreck from 'wreck';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import { download } from '../download';
const TMP_DESTINATION = resolve(__dirname, '__tmp__');
@ -41,9 +41,13 @@ describe('src/dev/build/tasks/nodejs/download', () => {
const sandbox = sinon.createSandbox();
afterEach(() => sandbox.reset());
const log = createToolingLog('verbose');
const onLogLine = sandbox.stub();
log.on('data', onLogLine);
const log = new ToolingLog({
level: 'verbose',
writeTo: {
write: onLogLine
}
});
const FOO_SHA256 = '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae';
const createSendHandler = (send) => (req, res) => {

View file

@ -22,7 +22,7 @@ import { resolve } from 'path';
import getopts from 'getopts';
import dedent from 'dedent';
import { createToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { ToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { REPO_ROOT } from '../constants';
import { generateNoticeFromSource } from './generate_notice_from_source';
@ -40,8 +40,10 @@ const opts = getopts(process.argv.slice(2), {
}
});
const log = createToolingLog(pickLevelFromFlags(opts));
log.pipe(process.stdout);
const log = new ToolingLog({
level: pickLevelFromFlags(opts),
writeTo: process.stdout
});
if (unknownFlags.length) {
log.error(`Unknown flags ${unknownFlags.map(f => `"${f}"`).join(',')}`);

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { createToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { ToolingLog, pickLevelFromFlags } from '@kbn/dev-utils';
import { isFailError } from './fail';
import { getFlags, getHelp } from './flags';
@ -29,8 +29,10 @@ export async function run(body) {
process.exit(1);
}
const log = createToolingLog(pickLevelFromFlags(flags));
log.pipe(process.stdout);
const log = new ToolingLog({
level: pickLevelFromFlags(flags),
writeTo: process.stdout
});
try {
await body({ log, flags });

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import getopts from 'getopts';
import { execInProjects, filterProjectsByFlag, Project } from '../typescript';
@ -27,8 +27,10 @@ export function runTslintCliOnTsConfigPaths(tsConfigPaths: string[]) {
}
export function runTslintCli(projects?: Project[]) {
const log = createToolingLog('info');
log.pipe(process.stdout);
const log = new ToolingLog({
level: 'info',
writeTo: process.stdout,
});
const opts = getopts(process.argv.slice(2));
projects = projects || filterProjectsByFlag(opts.project);

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import chalk from 'chalk';
import dedent from 'dedent';
import getopts from 'getopts';
@ -38,8 +38,10 @@ export function runTypeCheckCli() {
},
});
const log = createToolingLog('info');
log.pipe(process.stdout);
const log = new ToolingLog({
level: 'info',
writeTo: process.stdout,
});
if (extraFlags.length) {
for (const flag of extraFlags) {

View file

@ -31,7 +31,7 @@ import { Command } from 'commander';
import elasticsearch from 'elasticsearch';
import { EsArchiver } from './es_archiver';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import { readConfigFile } from '../functional_test_runner';
const cmd = new Command('node scripts/es_archiver');
@ -76,8 +76,10 @@ if (missingCommand) {
async function execute(fn) {
try {
const log = createToolingLog(cmd.verbose ? 'debug' : 'info');
log.pipe(process.stdout);
const log = new ToolingLog({
level: cmd.verbose ? 'debug' : 'info',
writeTo: process.stdout
});
if (cmd.config) {
// load default values from the specified config file

View file

@ -22,18 +22,17 @@ import { uniq } from 'lodash';
import sinon from 'sinon';
import { createStats } from '../';
import { createToolingLog } from '@kbn/dev-utils';
import {
createConcatStream,
createPromiseFromStreams
} from '../../../utils';
import { ToolingLog } from '@kbn/dev-utils';
function drain(log) {
log.end();
return createPromiseFromStreams([
log,
createConcatStream('')
]);
function createBufferedLog() {
const log = new ToolingLog({
level: 'debug',
writeTo: {
write: (chunk) => log.buffer += chunk
}
});
log.buffer = '';
return log;
}
function assertDeepClones(a, b) {
@ -66,110 +65,106 @@ function assertDeepClones(a, b) {
describe('esArchiver: Stats', () => {
describe('#skippedIndex(index)', () => {
it('marks the index as skipped', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.skippedIndex('index-name');
const indexStats = stats.toJSON()['index-name'];
expect(indexStats).to.have.property('skipped', true);
});
it('logs that the index was skipped', async () => {
const log = createToolingLog('debug');
const log = createBufferedLog();
const stats = createStats('name', log);
stats.skippedIndex('index-name');
expect(await drain(log)).to.contain('Skipped');
expect(log.buffer).to.contain('Skipped');
});
});
describe('#deletedIndex(index)', () => {
it('marks the index as deleted', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.deletedIndex('index-name');
const indexStats = stats.toJSON()['index-name'];
expect(indexStats).to.have.property('deleted', true);
});
it('logs that the index was deleted', async () => {
const log = createToolingLog('debug');
const log = createBufferedLog();
const stats = createStats('name', log);
stats.deletedIndex('index-name');
expect(await drain(log)).to.contain('Deleted');
expect(log.buffer).to.contain('Deleted');
});
});
describe('#createdIndex(index, [metadata])', () => {
it('marks the index as created', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.createdIndex('index-name');
const indexStats = stats.toJSON()['index-name'];
expect(indexStats).to.have.property('created', true);
});
it('logs that the index was created', async () => {
const log = createToolingLog('debug');
const log = createBufferedLog();
const stats = createStats('name', log);
stats.createdIndex('index-name');
expect(await drain(log)).to.contain('Created');
expect(log.buffer).to.contain('Created');
});
describe('with metadata', () => {
it('debug-logs each key from the metadata', async () => {
const log = createToolingLog('debug');
const log = createBufferedLog();
const stats = createStats('name', log);
stats.createdIndex('index-name', {
foo: 'bar'
});
const output = await drain(log);
expect(output).to.contain('debg');
expect(output).to.contain('foo "bar"');
expect(log.buffer).to.contain('debg');
expect(log.buffer).to.contain('foo "bar"');
});
});
describe('without metadata', () => {
it('no debug logging', async () => {
const log = createToolingLog('debug');
const log = createBufferedLog();
const stats = createStats('name', log);
stats.createdIndex('index-name');
const output = await drain(log);
expect(output).to.not.contain('debg');
expect(log.buffer).to.not.contain('debg');
});
});
});
describe('#archivedIndex(index, [metadata])', () => {
it('marks the index as archived', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.archivedIndex('index-name');
const indexStats = stats.toJSON()['index-name'];
expect(indexStats).to.have.property('archived', true);
});
it('logs that the index was archived', async () => {
const log = createToolingLog('debug');
const log = createBufferedLog();
const stats = createStats('name', log);
stats.archivedIndex('index-name');
expect(await drain(log)).to.contain('Archived');
expect(log.buffer).to.contain('Archived');
});
describe('with metadata', () => {
it('debug-logs each key from the metadata', async () => {
const log = createToolingLog('debug');
const log = createBufferedLog();
const stats = createStats('name', log);
stats.archivedIndex('index-name', {
foo: 'bar'
});
const output = await drain(log);
expect(output).to.contain('debg');
expect(output).to.contain('foo "bar"');
expect(log.buffer).to.contain('debg');
expect(log.buffer).to.contain('foo "bar"');
});
});
describe('without metadata', () => {
it('no debug logging', async () => {
const log = createToolingLog('debug');
const log = createBufferedLog();
const stats = createStats('name', log);
stats.archivedIndex('index-name');
const output = await drain(log);
expect(output).to.not.contain('debg');
expect(log.buffer).to.not.contain('debg');
});
});
});
describe('#indexedDoc(index)', () => {
it('increases the docs.indexed count for the index', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.indexedDoc('index-name');
expect(stats.toJSON()['index-name'].docs.indexed).to.be(1);
stats.indexedDoc('index-name');
@ -180,7 +175,7 @@ describe('esArchiver: Stats', () => {
describe('#archivedDoc(index)', () => {
it('increases the docs.archived count for the index', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.archivedDoc('index-name');
expect(stats.toJSON()['index-name'].docs.archived).to.be(1);
stats.archivedDoc('index-name');
@ -191,13 +186,13 @@ describe('esArchiver: Stats', () => {
describe('#toJSON()', () => {
it('returns the stats for all indexes', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.archivedIndex('index1');
stats.archivedIndex('index2');
expect(Object.keys(stats.toJSON())).to.eql(['index1', 'index2']);
});
it('returns a deep clone of the stats', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.archivedIndex('index1');
stats.archivedIndex('index2');
stats.deletedIndex('index3');
@ -208,7 +203,7 @@ describe('esArchiver: Stats', () => {
describe('#forEachIndex(fn)', () => {
it('iterates a clone of the index stats', () => {
const stats = createStats('name', createToolingLog());
const stats = createStats('name', new ToolingLog());
stats.archivedIndex('index1');
stats.archivedIndex('index2');
stats.deletedIndex('index3');

View file

@ -21,7 +21,7 @@ import { resolve } from 'path';
import { Command } from 'commander';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import { createFunctionalTestRunner } from './functional_test_runner';
const cmd = new Command('node scripts/functional_test_runner');
@ -46,8 +46,10 @@ if (cmd.quiet) logLevel = 'error';
if (cmd.debug) logLevel = 'debug';
if (cmd.verbose) logLevel = 'verbose';
const log = createToolingLog(logLevel);
log.pipe(process.stdout);
const log = new ToolingLog({
level: logLevel,
writeTo: process.stdout
});
const functionalTestRunner = createFunctionalTestRunner({
log,

View file

@ -19,11 +19,11 @@
import expect from 'expect.js';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import { readConfigFile } from '../read_config_file';
import { Config } from '../config';
const log = createToolingLog().resume();
const log = new ToolingLog();
describe('readConfigFile()', () => {
it('reads config from a file, returns an instance of Config class', async () => {

View file

@ -123,25 +123,19 @@ export function MochaReporterProvider({ getService }) {
console.log = realLog;
}
log.indent(-2);
log.write(
`- ${symbols.err} ` +
colors.fail(`fail: "${test.fullTitle()}"`) +
'\n' +
output
.split('\n')
.slice(2) // drop the first two lines, (empty + test title)
.map(line => {
// move leading colors behind leading spaces
return line.replace(/^((?:\[.+m)+)(\s+)/, '$2$1');
})
.map(line => {
// shrink mocha's indentation
return line.replace(/^\s{5,5}/, ' ');
})
// drop the first two lines, (empty + test title)
.slice(2)
// move leading colors behind leading spaces
.map(line => line.replace(/^((?:\[.+m)+)(\s+)/, '$2$1'))
.map(line => ` ${line}`)
.join('\n')
);
log.indent(2);
}
onEnd = () => {

View file

@ -22,7 +22,7 @@ import expect from 'expect.js';
import { createEsTestCluster } from '@kbn/test';
import { createServerWithCorePlugins } from '../../../../test_utils/kbn_server';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config';
describe('createOrUpgradeSavedConfig()', () => {
@ -31,8 +31,10 @@ describe('createOrUpgradeSavedConfig()', () => {
const cleanup = [];
before(async function () {
const log = createToolingLog('debug');
log.pipe(process.stdout);
const log = new ToolingLog({
level: 'debug',
writeTo: process.stdout
});
log.indent(6);
log.info('starting elasticsearch');

View file

@ -18,7 +18,7 @@
*/
import { createEsTestCluster } from '@kbn/test';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
import * as kbnTestServer from '../../../../../test_utils/kbn_server';
let kbnServer;
@ -26,8 +26,10 @@ let services;
let es;
export async function startServers() {
const log = createToolingLog('debug');
log.pipe(process.stdout);
const log = new ToolingLog({
level: 'debug',
writeTo: process.stdout
});
log.indent(6);
log.info('starting elasticsearch');

View file

@ -18,7 +18,7 @@
*/
import { createFunctionalTestRunner } from '../src/functional_test_runner';
import { createToolingLog } from '@kbn/dev-utils';
import { ToolingLog } from '@kbn/dev-utils';
export default function (grunt) {
grunt.registerMultiTask('functional_test_runner', 'run tests with the functional test runner', function () {
@ -28,8 +28,10 @@ export default function (grunt) {
configOverrides
} = this.options();
const log = createToolingLog(logLevel);
log.pipe(process.stdout);
const log = new ToolingLog({
level: logLevel,
writeTo: process.stdout,
});
const functionalTestRunner = createFunctionalTestRunner({
log,

View file

@ -15,7 +15,7 @@ const path = require('path');
const del = require('del');
const runSequence = require('run-sequence');
const pluginHelpers = require('@kbn/plugin-helpers');
const { createToolingLog } = require('@kbn/dev-utils');
const { ToolingLog } = require('@kbn/dev-utils');
const logger = require('./gulp_helpers/logger');
const buildVersion = require('./gulp_helpers/build_version')();
@ -76,8 +76,10 @@ gulp.task('build', ['clean', 'report', 'prepare'], async () => {
});
const buildRoot = path.resolve(buildTarget, 'kibana/x-pack');
const log = createToolingLog('info');
log.pipe(process.stdout);
const log = new ToolingLog({
level: 'info',
writeTo: process.stdout
});
writeFileSync(
path.resolve(buildRoot, 'NOTICE.txt'),