Move logger configuration integration test to jest (#70378)

This commit is contained in:
Josh Dover 2020-07-01 08:05:59 -06:00 committed by GitHub
parent 1d1051b1e9
commit 7ed1fe05d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 161 additions and 309 deletions

View file

@ -18,6 +18,9 @@
*/
import * as kbnTestServer from '../../../../test_utils/kbn_server';
import { InternalCoreSetup } from '../../internal_types';
import { LoggerContextConfigInput } from '../logging_config';
import { Subject } from 'rxjs';
function createRoot() {
return kbnTestServer.createRoot({
@ -111,4 +114,162 @@ describe('logging service', () => {
expect(mockConsoleLog).toHaveBeenCalledTimes(0);
});
});
describe('custom context configuration', () => {
const CUSTOM_LOGGING_CONFIG: LoggerContextConfigInput = {
appenders: {
customJsonConsole: {
kind: 'console',
layout: {
kind: 'json',
},
},
customPatternConsole: {
kind: 'console',
layout: {
kind: 'pattern',
pattern: 'CUSTOM - PATTERN [%logger][%level] %message',
},
},
},
loggers: [
{ context: 'debug_json', appenders: ['customJsonConsole'], level: 'debug' },
{ context: 'debug_pattern', appenders: ['customPatternConsole'], level: 'debug' },
{ context: 'info_json', appenders: ['customJsonConsole'], level: 'info' },
{ context: 'info_pattern', appenders: ['customPatternConsole'], level: 'info' },
{
context: 'all',
appenders: ['customJsonConsole', 'customPatternConsole'],
level: 'debug',
},
],
};
let root: ReturnType<typeof createRoot>;
let setup: InternalCoreSetup;
let mockConsoleLog: jest.SpyInstance;
const loggingConfig$ = new Subject<LoggerContextConfigInput>();
const setContextConfig = (enable: boolean) =>
enable ? loggingConfig$.next(CUSTOM_LOGGING_CONFIG) : loggingConfig$.next({});
beforeAll(async () => {
mockConsoleLog = jest.spyOn(global.console, 'log');
root = kbnTestServer.createRoot();
setup = await root.setup();
setup.logging.configure(['plugins', 'myplugin'], loggingConfig$);
}, 30000);
beforeEach(() => {
mockConsoleLog.mockClear();
});
afterAll(async () => {
mockConsoleLog.mockRestore();
await root.shutdown();
});
it('does not write to custom appenders when not configured', async () => {
const logger = root.logger.get('plugins.myplugin.debug_pattern');
setContextConfig(false);
logger.info('log1');
setContextConfig(true);
logger.debug('log2');
logger.info('log3');
setContextConfig(false);
logger.info('log4');
expect(mockConsoleLog).toHaveBeenCalledTimes(2);
expect(mockConsoleLog).toHaveBeenCalledWith(
'CUSTOM - PATTERN [plugins.myplugin.debug_pattern][DEBUG] log2'
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'CUSTOM - PATTERN [plugins.myplugin.debug_pattern][INFO ] log3'
);
});
it('writes debug_json context to custom JSON appender', async () => {
setContextConfig(true);
const logger = root.logger.get('plugins.myplugin.debug_json');
logger.debug('log1');
logger.info('log2');
expect(mockConsoleLog).toHaveBeenCalledTimes(2);
const [firstCall, secondCall] = mockConsoleLog.mock.calls.map(([jsonString]) =>
JSON.parse(jsonString)
);
expect(firstCall).toMatchObject({
level: 'DEBUG',
context: 'plugins.myplugin.debug_json',
message: 'log1',
});
expect(secondCall).toMatchObject({
level: 'INFO',
context: 'plugins.myplugin.debug_json',
message: 'log2',
});
});
it('writes info_json context to custom JSON appender', async () => {
setContextConfig(true);
const logger = root.logger.get('plugins.myplugin.info_json');
logger.debug('i should not be logged!');
logger.info('log2');
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchObject({
level: 'INFO',
context: 'plugins.myplugin.info_json',
message: 'log2',
});
});
it('writes debug_pattern context to custom pattern appender', async () => {
setContextConfig(true);
const logger = root.logger.get('plugins.myplugin.debug_pattern');
logger.debug('log1');
logger.info('log2');
expect(mockConsoleLog).toHaveBeenCalledTimes(2);
expect(mockConsoleLog).toHaveBeenCalledWith(
'CUSTOM - PATTERN [plugins.myplugin.debug_pattern][DEBUG] log1'
);
expect(mockConsoleLog).toHaveBeenCalledWith(
'CUSTOM - PATTERN [plugins.myplugin.debug_pattern][INFO ] log2'
);
});
it('writes info_pattern context to custom pattern appender', async () => {
setContextConfig(true);
const logger = root.logger.get('plugins.myplugin.info_pattern');
logger.debug('i should not be logged!');
logger.info('log2');
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
expect(mockConsoleLog).toHaveBeenCalledWith(
'CUSTOM - PATTERN [plugins.myplugin.info_pattern][INFO ] log2'
);
});
it('writes all context to both appenders', async () => {
setContextConfig(true);
const logger = root.logger.get('plugins.myplugin.all');
logger.debug('log1');
logger.info('log2');
expect(mockConsoleLog).toHaveBeenCalledTimes(4);
const logs = mockConsoleLog.mock.calls.map(([jsonString]) => jsonString);
expect(JSON.parse(logs[0])).toMatchObject({
level: 'DEBUG',
context: 'plugins.myplugin.all',
message: 'log1',
});
expect(logs[1]).toEqual('CUSTOM - PATTERN [plugins.myplugin.all][DEBUG] log1');
expect(JSON.parse(logs[2])).toMatchObject({
level: 'INFO',
context: 'plugins.myplugin.all',
message: 'log2',
});
expect(logs[3]).toEqual('CUSTOM - PATTERN [plugins.myplugin.all][INFO ] log2');
});
});
});

View file

@ -1,7 +0,0 @@
{
"id": "core_logging",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["core_logging"],
"server": true
}

View file

@ -1 +0,0 @@
/*debug.log

View file

@ -1,23 +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 type { PluginInitializerContext } from '../../../../../src/core/server';
import { CoreLoggingPlugin } from './plugin';
export const plugin = (init: PluginInitializerContext) => new CoreLoggingPlugin(init);

View file

@ -1,118 +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 { resolve } from 'path';
import { Subject } from 'rxjs';
import { schema } from '@kbn/config-schema';
import type {
PluginInitializerContext,
Plugin,
CoreSetup,
LoggerContextConfigInput,
Logger,
} from '../../../../../src/core/server';
const CUSTOM_LOGGING_CONFIG: LoggerContextConfigInput = {
appenders: {
customJsonFile: {
kind: 'file',
path: resolve(__dirname, 'json_debug.log'), // use 'debug.log' suffix so file watcher does not restart server
layout: {
kind: 'json',
},
},
customPatternFile: {
kind: 'file',
path: resolve(__dirname, 'pattern_debug.log'),
layout: {
kind: 'pattern',
pattern: 'CUSTOM - PATTERN [%logger][%level] %message',
},
},
},
loggers: [
{ context: 'debug_json', appenders: ['customJsonFile'], level: 'debug' },
{ context: 'debug_pattern', appenders: ['customPatternFile'], level: 'debug' },
{ context: 'info_json', appenders: ['customJsonFile'], level: 'info' },
{ context: 'info_pattern', appenders: ['customPatternFile'], level: 'info' },
{ context: 'all', appenders: ['customJsonFile', 'customPatternFile'], level: 'debug' },
],
};
export class CoreLoggingPlugin implements Plugin {
private readonly logger: Logger;
constructor(init: PluginInitializerContext) {
this.logger = init.logger.get();
}
public setup(core: CoreSetup) {
const loggingConfig$ = new Subject<LoggerContextConfigInput>();
core.logging.configure(loggingConfig$);
const router = core.http.createRouter();
// Expose a route that allows our test suite to write logs as this plugin
router.post(
{
path: '/internal/core-logging/write-log',
validate: {
body: schema.object({
level: schema.oneOf([schema.literal('debug'), schema.literal('info')]),
message: schema.string(),
context: schema.arrayOf(schema.string()),
}),
},
},
(ctx, req, res) => {
const { level, message, context } = req.body;
const logger = this.logger.get(...context);
if (level === 'debug') {
logger.debug(message);
} else if (level === 'info') {
logger.info(message);
}
return res.ok();
}
);
// Expose a route to toggle on and off the custom config
router.post(
{
path: '/internal/core-logging/update-config',
validate: { body: schema.object({ enableCustomConfig: schema.boolean() }) },
},
(ctx, req, res) => {
if (req.body.enableCustomConfig) {
loggingConfig$.next(CUSTOM_LOGGING_CONFIG);
} else {
loggingConfig$.next({});
}
return res.ok({ body: `Updated config: ${req.body.enableCustomConfig}` });
}
);
}
public start() {}
public stop() {}
}

View file

@ -1,13 +0,0 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"index.ts",
"server/**/*.ts",
"../../../../typings/**/*",
],
"exclude": []
}

View file

@ -30,6 +30,5 @@ export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
loadTestFile(require.resolve('./application_leave_confirm'));
loadTestFile(require.resolve('./application_status'));
loadTestFile(require.resolve('./rendering'));
loadTestFile(require.resolve('./logging'));
});
}

View file

@ -1,146 +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 { resolve } from 'path';
import fs from 'fs';
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
// eslint-disable-next-line import/no-default-export
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
describe('plugin logging', function describeIndexTests() {
const LOG_FILE_DIRECTORY = resolve(__dirname, '..', '..', 'plugins', 'core_logging', 'server');
const JSON_FILE_PATH = resolve(LOG_FILE_DIRECTORY, 'json_debug.log');
const PATTERN_FILE_PATH = resolve(LOG_FILE_DIRECTORY, 'pattern_debug.log');
beforeEach(async () => {
// "touch" each file to ensure it exists and is empty before each test
await fs.promises.writeFile(JSON_FILE_PATH, '');
await fs.promises.writeFile(PATTERN_FILE_PATH, '');
});
async function readLines(path: string) {
const contents = await fs.promises.readFile(path, { encoding: 'utf8' });
return contents.trim().split('\n');
}
async function readJsonLines() {
return (await readLines(JSON_FILE_PATH))
.filter((line) => line.length > 0)
.map((line) => JSON.parse(line))
.map(({ level, message, context }) => ({ level, message, context }));
}
function writeLog(context: string[], level: string, message: string) {
return supertest
.post('/internal/core-logging/write-log')
.set('kbn-xsrf', 'anything')
.send({ context, level, message })
.expect(200);
}
function setContextConfig(enable: boolean) {
return supertest
.post('/internal/core-logging/update-config')
.set('kbn-xsrf', 'anything')
.send({ enableCustomConfig: enable })
.expect(200);
}
it('does not write to custom appenders when not configured', async () => {
await setContextConfig(false);
await writeLog(['debug_json'], 'info', 'i go to the default appender!');
expect(await readJsonLines()).to.eql([]);
});
it('writes debug_json context to custom JSON appender', async () => {
await setContextConfig(true);
await writeLog(['debug_json'], 'debug', 'log1');
await writeLog(['debug_json'], 'info', 'log2');
expect(await readJsonLines()).to.eql([
{
level: 'DEBUG',
context: 'plugins.core_logging.debug_json',
message: 'log1',
},
{
level: 'INFO',
context: 'plugins.core_logging.debug_json',
message: 'log2',
},
]);
});
it('writes info_json context to custom JSON appender', async () => {
await setContextConfig(true);
await writeLog(['info_json'], 'debug', 'i should not be logged!');
await writeLog(['info_json'], 'info', 'log2');
expect(await readJsonLines()).to.eql([
{
level: 'INFO',
context: 'plugins.core_logging.info_json',
message: 'log2',
},
]);
});
it('writes debug_pattern context to custom pattern appender', async () => {
await setContextConfig(true);
await writeLog(['debug_pattern'], 'debug', 'log1');
await writeLog(['debug_pattern'], 'info', 'log2');
expect(await readLines(PATTERN_FILE_PATH)).to.eql([
'CUSTOM - PATTERN [plugins.core_logging.debug_pattern][DEBUG] log1',
'CUSTOM - PATTERN [plugins.core_logging.debug_pattern][INFO ] log2',
]);
});
it('writes info_pattern context to custom pattern appender', async () => {
await setContextConfig(true);
await writeLog(['info_pattern'], 'debug', 'i should not be logged!');
await writeLog(['info_pattern'], 'info', 'log2');
expect(await readLines(PATTERN_FILE_PATH)).to.eql([
'CUSTOM - PATTERN [plugins.core_logging.info_pattern][INFO ] log2',
]);
});
it('writes all context to both appenders', async () => {
await setContextConfig(true);
await writeLog(['all'], 'debug', 'log1');
await writeLog(['all'], 'info', 'log2');
expect(await readJsonLines()).to.eql([
{
level: 'DEBUG',
context: 'plugins.core_logging.all',
message: 'log1',
},
{
level: 'INFO',
context: 'plugins.core_logging.all',
message: 'log2',
},
]);
expect(await readLines(PATTERN_FILE_PATH)).to.eql([
'CUSTOM - PATTERN [plugins.core_logging.all][DEBUG] log1',
'CUSTOM - PATTERN [plugins.core_logging.all][INFO ] log2',
]);
});
});
}