diff --git a/src/core/server/logging/integration_tests/logging.test.ts b/src/core/server/logging/integration_tests/logging.test.ts index b88f5ba2c2b60b..a80939a25ae65e 100644 --- a/src/core/server/logging/integration_tests/logging.test.ts +++ b/src/core/server/logging/integration_tests/logging.test.ts @@ -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; + let setup: InternalCoreSetup; + let mockConsoleLog: jest.SpyInstance; + const loggingConfig$ = new Subject(); + 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'); + }); + }); }); diff --git a/test/plugin_functional/plugins/core_logging/kibana.json b/test/plugin_functional/plugins/core_logging/kibana.json deleted file mode 100644 index 3289c2c627b9a7..00000000000000 --- a/test/plugin_functional/plugins/core_logging/kibana.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "core_logging", - "version": "0.0.1", - "kibanaVersion": "kibana", - "configPath": ["core_logging"], - "server": true -} diff --git a/test/plugin_functional/plugins/core_logging/server/.gitignore b/test/plugin_functional/plugins/core_logging/server/.gitignore deleted file mode 100644 index 9a3d2811791937..00000000000000 --- a/test/plugin_functional/plugins/core_logging/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*debug.log diff --git a/test/plugin_functional/plugins/core_logging/server/index.ts b/test/plugin_functional/plugins/core_logging/server/index.ts deleted file mode 100644 index ca1d9da95b495c..00000000000000 --- a/test/plugin_functional/plugins/core_logging/server/index.ts +++ /dev/null @@ -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); diff --git a/test/plugin_functional/plugins/core_logging/server/plugin.ts b/test/plugin_functional/plugins/core_logging/server/plugin.ts deleted file mode 100644 index a7820a0f675250..00000000000000 --- a/test/plugin_functional/plugins/core_logging/server/plugin.ts +++ /dev/null @@ -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(); - 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() {} -} diff --git a/test/plugin_functional/plugins/core_logging/tsconfig.json b/test/plugin_functional/plugins/core_logging/tsconfig.json deleted file mode 100644 index 7389eb6ce159be..00000000000000 --- a/test/plugin_functional/plugins/core_logging/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../../../tsconfig.json", - "compilerOptions": { - "outDir": "./target", - "skipLibCheck": true - }, - "include": [ - "index.ts", - "server/**/*.ts", - "../../../../typings/**/*", - ], - "exclude": [] -} diff --git a/test/plugin_functional/test_suites/core_plugins/index.ts b/test/plugin_functional/test_suites/core_plugins/index.ts index 8f7c2267d34b44..8f54ec6c0f4cd9 100644 --- a/test/plugin_functional/test_suites/core_plugins/index.ts +++ b/test/plugin_functional/test_suites/core_plugins/index.ts @@ -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')); }); } diff --git a/test/plugin_functional/test_suites/core_plugins/logging.ts b/test/plugin_functional/test_suites/core_plugins/logging.ts deleted file mode 100644 index 06b53ea56e4b41..00000000000000 --- a/test/plugin_functional/test_suites/core_plugins/logging.ts +++ /dev/null @@ -1,147 +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'); - - // Failing: See https://github.com/elastic/kibana/issues/70151 - describe.skip('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', - ]); - }); - }); -}