[browser logging] allow to configure root level (#176397)

## Summary

Part of https://github.com/elastic/kibana/issues/144276

- Introduce the concept of browser-side logging configuration, via a
`logging.browser` config prefix
- Allow to configure the log level for the root browser logger via
`logging.browser.root.level`
- Set the default level to `info` for both dev and production mode
(consistent with server-side logging)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Pierre Gayvallet 2024-02-12 13:26:57 +01:00 committed by GitHub
parent 46c10ecbef
commit 1dc0076ca8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 174 additions and 44 deletions

View file

@ -133,6 +133,10 @@
# - name: metrics.ops
# level: debug
# Enables debug logging on the browser (dev console)
#logging.browser.root:
# level: debug
# =================== System: Other ===================
# The path where Kibana stores persistent data not saved in Elasticsearch. Defaults to data
#path.data: data

View file

@ -133,12 +133,9 @@ export class AnalyticsClient implements IAnalyticsClient {
properties: eventData as unknown as Record<string, unknown>,
};
// debug-logging before checking the opt-in status to help during development
if (this.initContext.isDev) {
this.initContext.logger.debug<EventDebugLogMeta>(`Report event "${eventType}"`, {
ebt_event: event,
});
}
this.initContext.logger.debug<EventDebugLogMeta>(`Report event "${eventType}"`, {
ebt_event: event,
});
const optInConfig = this.optInConfig$.value;

View file

@ -10,6 +10,7 @@ import type { PluginName, DiscoveredPlugin } from '@kbn/core-base-common';
import type { ThemeVersion } from '@kbn/ui-shared-deps-npm';
import type { EnvironmentMode, PackageInfo } from '@kbn/config';
import type { CustomBranding } from '@kbn/core-custom-branding-common';
import type { BrowserLoggingConfig } from '@kbn/core-logging-common-internal';
/** @internal */
export interface InjectedMetadataClusterInfo {
@ -45,6 +46,7 @@ export interface InjectedMetadata {
publicBaseUrl?: string;
assetsHrefBase: string;
clusterInfo: InjectedMetadataClusterInfo;
logging: BrowserLoggingConfig;
env: {
mode: EnvironmentMode;
packageInfo: PackageInfo;

View file

@ -15,7 +15,8 @@
"@kbn/config",
"@kbn/ui-shared-deps-npm",
"@kbn/core-base-common",
"@kbn/core-custom-branding-common"
"@kbn/core-custom-branding-common",
"@kbn/core-logging-common-internal"
],
"exclude": [
"target/**/*",

View file

@ -6,19 +6,29 @@
* Side Public License, v 1.
*/
import type { BrowserLoggingConfig } from '@kbn/core-logging-common-internal';
import { unsafeConsole } from '@kbn/security-hardening';
import { BrowserLoggingSystem } from './logging_system';
describe('', () => {
describe('BrowserLoggingSystem', () => {
const timestamp = new Date(Date.UTC(2012, 1, 1, 14, 33, 22, 11));
let mockConsoleLog: jest.SpyInstance;
let loggingSystem: BrowserLoggingSystem;
const createLoggingConfig = (parts: Partial<BrowserLoggingConfig> = {}): BrowserLoggingConfig => {
return {
root: {
level: 'warn',
},
...parts,
};
};
beforeEach(() => {
mockConsoleLog = jest.spyOn(unsafeConsole, 'log').mockReturnValue(undefined);
jest.spyOn<any, any>(global, 'Date').mockImplementation(() => timestamp);
loggingSystem = new BrowserLoggingSystem({ logLevel: 'warn' });
loggingSystem = new BrowserLoggingSystem(createLoggingConfig());
});
afterEach(() => {
@ -74,5 +84,37 @@ describe('', () => {
]
`);
});
it('allows to override the root logger level', () => {
loggingSystem = new BrowserLoggingSystem(createLoggingConfig({ root: { level: 'debug' } }));
const logger = loggingSystem.get('foo.bar');
logger.trace('some trace message');
logger.debug('some debug message');
logger.info('some info message');
logger.warn('some warn message');
logger.error('some error message');
logger.fatal('some fatal message');
expect(mockConsoleLog.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"[2012-01-31T13:33:22.011-05:00][DEBUG][foo.bar] some debug message",
],
Array [
"[2012-01-31T08:33:22.011-05:00][INFO ][foo.bar] some info message",
],
Array [
"[2012-01-31T03:33:22.011-05:00][WARN ][foo.bar] some warn message",
],
Array [
"[2012-01-30T22:33:22.011-05:00][ERROR][foo.bar] some error message",
],
Array [
"[2012-01-30T17:33:22.011-05:00][FATAL][foo.bar] some fatal message",
],
]
`);
});
});
});

View file

@ -6,17 +6,13 @@
* Side Public License, v 1.
*/
import { LogLevel, Logger, LoggerFactory, LogLevelId, DisposableAppender } from '@kbn/logging';
import { getLoggerContext } from '@kbn/core-logging-common-internal';
import { LogLevel, Logger, LoggerFactory, DisposableAppender } from '@kbn/logging';
import { getLoggerContext, BrowserLoggingConfig } from '@kbn/core-logging-common-internal';
import type { LoggerConfigType } from './types';
import { BaseLogger } from './logger';
import { PatternLayout } from './layouts';
import { ConsoleAppender } from './appenders';
export interface BrowserLoggingConfig {
logLevel: LogLevelId;
}
const CONSOLE_APPENDER_ID = 'console';
/**
@ -54,7 +50,7 @@ export class BrowserLoggingSystem implements IBrowserLoggingSystem {
private getLoggerConfigByContext(context: string): LoggerConfigType {
return {
level: this.loggingConfig.logLevel,
level: this.loggingConfig.root.level,
appenders: [CONSOLE_APPENDER_ID],
name: context,
};

View file

@ -22,3 +22,4 @@ export {
ROOT_CONTEXT_NAME,
DEFAULT_APPENDER_NAME,
} from './src';
export type { BrowserLoggingConfig, BrowserRootLoggerConfig } from './src/browser_config';

View file

@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { LogLevelId } from '@kbn/logging';
/**
* @internal
*/
export interface BrowserLoggingConfig {
root: BrowserRootLoggerConfig;
}
/**
* @internal
*/
export interface BrowserRootLoggerConfig {
level: LogLevelId;
}

View file

@ -9,6 +9,7 @@
export { config } from './src/logging_config';
export type {
LoggingConfigType,
LoggingConfigWithBrowserType,
loggerContextConfigSchema,
loggerSchema,
} from './src/logging_config';

View file

@ -12,6 +12,11 @@ test('`schema` creates correct schema with defaults.', () => {
expect(config.schema.validate({})).toMatchInlineSnapshot(`
Object {
"appenders": Map {},
"browser": Object {
"root": Object {
"level": "info",
},
},
"loggers": Array [],
"root": Object {
"appenders": Array [

View file

@ -47,6 +47,12 @@ export const loggerSchema = schema.object({
level: levelSchema,
});
const browserConfig = schema.object({
root: schema.object({
level: levelSchema,
}),
});
export const config = {
path: 'logging',
schema: schema.object({
@ -63,6 +69,7 @@ export const config = {
}),
level: levelSchema,
}),
browser: browserConfig,
}),
};
@ -71,6 +78,10 @@ export type LoggingConfigType = Pick<TypeOf<typeof config.schema>, 'loggers' | '
appenders: Map<string, AppenderConfigType>;
};
/** @internal */
export type LoggingConfigWithBrowserType = LoggingConfigType &
Pick<TypeOf<typeof config.schema>, 'browser'>;
/**
* Config schema for validating the inputs to the {@link LoggingServiceStart.configure} API.
* See {@link LoggerContextConfigType}.

View file

@ -57,6 +57,7 @@ Object {
},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -122,6 +123,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -191,6 +193,7 @@ Object {
},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -256,6 +259,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -321,6 +325,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -390,6 +395,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -455,6 +461,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -520,6 +527,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -594,6 +602,7 @@ Object {
},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -659,6 +668,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -733,6 +743,7 @@ Object {
},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -803,6 +814,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -868,6 +880,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -942,6 +955,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -1012,6 +1026,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {
@ -1082,6 +1097,7 @@ Object {
"user": Object {},
},
},
"logging": Any<Object>,
"publicBaseUrl": "http://myhost.com/mock-server-basepath",
"serverBasePath": "/mock-server-basepath",
"theme": Object {

View file

@ -6,9 +6,16 @@
* Side Public License, v 1.
*/
import { firstValueFrom } from 'rxjs';
import UiSharedDepsNpm from '@kbn/ui-shared-deps-npm';
import * as UiSharedDepsSrc from '@kbn/ui-shared-deps-src';
import type { IConfigService } from '@kbn/config';
import type { BrowserLoggingConfig } from '@kbn/core-logging-common-internal';
import type { UiSettingsParams, UserProvidedValues } from '@kbn/core-ui-settings-common';
import {
config as loggingConfigDef,
type LoggingConfigWithBrowserType,
} from '@kbn/core-logging-server-internal';
export const getSettingValue = <T>(
settingName: string,
@ -54,3 +61,12 @@ export const getStylesheetPaths = ({
]),
];
};
export const getBrowserLoggingConfig = async (
configService: IConfigService
): Promise<BrowserLoggingConfig> => {
const loggingConfig = await firstValueFrom(
configService.atPath<LoggingConfigWithBrowserType>(loggingConfigDef.path)
);
return loggingConfig.browser;
};

View file

@ -17,8 +17,10 @@ jest.doMock('./bootstrap', () => ({
export const getSettingValueMock = jest.fn();
export const getStylesheetPathsMock = jest.fn();
export const getBrowserLoggingConfigMock = jest.fn();
jest.doMock('./render_utils', () => ({
getSettingValue: getSettingValueMock,
getStylesheetPaths: getStylesheetPathsMock,
getBrowserLoggingConfig: getBrowserLoggingConfigMock,
}));

View file

@ -11,6 +11,7 @@ import {
bootstrapRendererMock,
getSettingValueMock,
getStylesheetPathsMock,
getBrowserLoggingConfigMock,
} from './rendering_service.test.mocks';
import { load } from 'cheerio';
@ -32,6 +33,7 @@ const INJECTED_METADATA = {
version: expect.any(String),
branch: expect.any(String),
buildNumber: expect.any(Number),
logging: expect.any(Object),
env: {
mode: {
name: expect.any(String),
@ -199,6 +201,22 @@ function renderTestCases(
const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""');
expect(data).toMatchSnapshot(INJECTED_METADATA);
});
it('renders "core" with logging config injected', async () => {
const loggingConfig = {
root: {
level: 'info',
},
};
getBrowserLoggingConfigMock.mockReturnValue(loggingConfig);
const [render] = await getRender();
const content = await render(createKibanaRequest(), uiSettings, {
isAnonymousPage: false,
});
const dom = load(content);
const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""');
expect(data.logging).toEqual(loggingConfig);
});
});
}
@ -418,8 +436,9 @@ describe('RenderingService', () => {
jest.clearAllMocks();
service = new RenderingService(mockRenderingServiceParams);
getSettingValueMock.mockImplementation((settingName: string) => settingName);
getStylesheetPathsMock.mockReturnValue(['/style-1.css', '/style-2.css']);
getSettingValueMock.mockReset().mockImplementation((settingName: string) => settingName);
getStylesheetPathsMock.mockReset().mockReturnValue(['/style-1.css', '/style-2.css']);
getBrowserLoggingConfigMock.mockReset().mockReturnValue({});
});
describe('preboot()', () => {

View file

@ -29,7 +29,7 @@ import {
RenderingMetadata,
} from './types';
import { registerBootstrapRoute, bootstrapRendererFactory } from './bootstrap';
import { getSettingValue, getStylesheetPaths } from './render_utils';
import { getSettingValue, getStylesheetPaths, getBrowserLoggingConfig } from './render_utils';
import { filterUiPlugins } from './filter_ui_plugins';
import type { InternalRenderingRequestHandlerContext } from './internal_types';
@ -185,6 +185,8 @@ export class RenderingService {
buildNum,
});
const loggingConfig = await getBrowserLoggingConfig(this.coreContext.configService);
const filteredPlugins = filterUiPlugins({ uiPlugins, isAnonymousPage });
const bootstrapScript = isAnonymousPage ? 'bootstrap-anonymous.js' : 'bootstrap.js';
const metadata: RenderingMetadata = {
@ -210,6 +212,7 @@ export class RenderingService {
serverBasePath,
publicBaseUrl,
assetsHrefBase: staticAssetsHrefBase,
logging: loggingConfig,
env,
clusterInfo,
anonymousStatusPage: status?.isStatusPageAnonymous() ?? false,

View file

@ -39,6 +39,8 @@
"@kbn/core-custom-branding-server-mocks",
"@kbn/core-user-settings-server-mocks",
"@kbn/core-user-settings-server-internal",
"@kbn/core-logging-common-internal",
"@kbn/core-logging-server-internal",
],
"exclude": [
"target/**/*",

View file

@ -80,6 +80,11 @@ const defaultCoreSystemParams = {
version: '1.2.3',
},
},
logging: {
root: {
level: 'debug',
},
},
version: 'version',
} as any,
};
@ -192,40 +197,27 @@ describe('constructor', () => {
});
describe('logging system', () => {
it('instantiate the logging system with the correct level when in dev mode', () => {
it('instantiate the logging system with the correct level', () => {
const envMode: EnvironmentMode = {
name: 'development',
dev: true,
prod: false,
};
const injectedMetadata = { env: { mode: envMode } } as any;
const injectedMetadata = {
...defaultCoreSystemParams.injectedMetadata,
env: { mode: envMode },
} as any;
createCoreSystem({
injectedMetadata,
});
expect(LoggingSystemConstructor).toHaveBeenCalledTimes(1);
expect(LoggingSystemConstructor).toHaveBeenCalledWith({
logLevel: 'all',
});
expect(LoggingSystemConstructor).toHaveBeenCalledWith(
defaultCoreSystemParams.injectedMetadata.logging
);
});
it('instantiate the logging system with the correct level when in production mode', () => {
const envMode: EnvironmentMode = {
name: 'production',
dev: false,
prod: true,
};
const injectedMetadata = { env: { mode: envMode } } as any;
createCoreSystem({
injectedMetadata,
});
expect(LoggingSystemConstructor).toHaveBeenCalledTimes(1);
expect(LoggingSystemConstructor).toHaveBeenCalledWith({
logLevel: 'warn',
});
});
it('retrieves the logger factory from the logging system', () => {
createCoreSystem({});
expect(MockLoggingSystem.asLoggerFactory).toHaveBeenCalledTimes(1);

View file

@ -7,7 +7,6 @@
*/
import { filter, firstValueFrom } from 'rxjs';
import type { LogLevelId } from '@kbn/logging';
import type { CoreContext } from '@kbn/core-base-browser-internal';
import {
InjectedMetadataService,
@ -112,8 +111,7 @@ export class CoreSystem {
this.rootDomElement = rootDomElement;
const logLevel: LogLevelId = injectedMetadata.env.mode.dev ? 'all' : 'warn';
this.loggingSystem = new BrowserLoggingSystem({ logLevel });
this.loggingSystem = new BrowserLoggingSystem(injectedMetadata.logging);
this.injectedMetadata = new InjectedMetadataService({
injectedMetadata,

View file

@ -59,7 +59,6 @@
"@kbn/core-integrations-browser-mocks",
"@kbn/core-apps-browser-mocks",
"@kbn/core-logging-browser-mocks",
"@kbn/logging",
"@kbn/config",
"@kbn/core-custom-branding-browser-internal",
"@kbn/core-custom-branding-browser-mocks",