Implement base browser-side logging system (#144107)

* start refactoring and moving stuff around

* add abstraction for BaseLogger

* plug logging into core system

* add logger factory to plugin init context

* add unit tests for browser package

* add more unit tests

* [CI] Auto-commit changed files from 'node scripts/generate codeowners'

* fix mock

* add mocks, update system tests

* update files due to merge

* [CI] Auto-commit changed files from 'node scripts/generate codeowners'

* update readme and package

* remove chalk usages from client-side

* add plugin context tests

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* fix new packages

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Pierre Gayvallet 2022-11-01 09:16:32 +01:00 committed by GitHub
parent fd6047bb22
commit 2fb12fbc54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 2756 additions and 129 deletions

3
.github/CODEOWNERS vendored
View file

@ -788,6 +788,9 @@ packages/core/lifecycle/core-lifecycle-browser-mocks @elastic/kibana-core
packages/core/lifecycle/core-lifecycle-server @elastic/kibana-core
packages/core/lifecycle/core-lifecycle-server-internal @elastic/kibana-core
packages/core/lifecycle/core-lifecycle-server-mocks @elastic/kibana-core
packages/core/logging/core-logging-browser-internal @elastic/kibana-core
packages/core/logging/core-logging-browser-mocks @elastic/kibana-core
packages/core/logging/core-logging-common-internal @elastic/kibana-core
packages/core/logging/core-logging-server @elastic/kibana-core
packages/core/logging/core-logging-server-internal @elastic/kibana-core
packages/core/logging/core-logging-server-mocks @elastic/kibana-core

View file

@ -243,6 +243,9 @@
"@kbn/core-lifecycle-server": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server",
"@kbn/core-lifecycle-server-internal": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-internal",
"@kbn/core-lifecycle-server-mocks": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-mocks",
"@kbn/core-logging-browser-internal": "link:bazel-bin/packages/core/logging/core-logging-browser-internal",
"@kbn/core-logging-browser-mocks": "link:bazel-bin/packages/core/logging/core-logging-browser-mocks",
"@kbn/core-logging-common-internal": "link:bazel-bin/packages/core/logging/core-logging-common-internal",
"@kbn/core-logging-server": "link:bazel-bin/packages/core/logging/core-logging-server",
"@kbn/core-logging-server-internal": "link:bazel-bin/packages/core/logging/core-logging-server-internal",
"@kbn/core-logging-server-mocks": "link:bazel-bin/packages/core/logging/core-logging-server-mocks",

View file

@ -108,6 +108,9 @@ filegroup(
"//packages/core/lifecycle/core-lifecycle-server:build",
"//packages/core/lifecycle/core-lifecycle-server-internal:build",
"//packages/core/lifecycle/core-lifecycle-server-mocks:build",
"//packages/core/logging/core-logging-browser-internal:build",
"//packages/core/logging/core-logging-browser-mocks:build",
"//packages/core/logging/core-logging-common-internal:build",
"//packages/core/logging/core-logging-server:build",
"//packages/core/logging/core-logging-server-internal:build",
"//packages/core/logging/core-logging-server-mocks:build",
@ -463,6 +466,9 @@ filegroup(
"//packages/core/lifecycle/core-lifecycle-server:build_types",
"//packages/core/lifecycle/core-lifecycle-server-internal:build_types",
"//packages/core/lifecycle/core-lifecycle-server-mocks:build_types",
"//packages/core/logging/core-logging-browser-internal:build_types",
"//packages/core/logging/core-logging-browser-mocks:build_types",
"//packages/core/logging/core-logging-common-internal:build_types",
"//packages/core/logging/core-logging-server:build_types",
"//packages/core/logging/core-logging-server-internal:build_types",
"//packages/core/logging/core-logging-server-mocks:build_types",

View file

@ -7,11 +7,13 @@
*/
import type { EnvironmentMode, PackageInfo } from '@kbn/config';
import type { LoggerFactory } from '@kbn/logging';
import type { CoreId } from '@kbn/core-base-common-internal';
/** @internal */
export interface CoreContext {
coreId: CoreId;
logger: LoggerFactory;
env: {
mode: Readonly<EnvironmentMode>;
packageInfo: Readonly<PackageInfo>;

View file

@ -35,12 +35,14 @@ NPM_MODULE_EXTRA_FILES = [
]
RUNTIME_DEPS = [
"//packages/kbn-logging-mocks",
]
TYPES_DEPS = [
"@npm//@types/node",
"@npm//@types/jest",
"//packages/core/base/core-base-browser-internal:npm_module_types",
"//packages/kbn-logging-mocks:npm_module_types",
"//packages/core/base/core-base-browser-internal:npm_module_types",
]
jsts_transpiler(

View file

@ -6,11 +6,13 @@
* Side Public License, v 1.
*/
import { loggerMock } from '@kbn/logging-mocks';
import type { CoreContext } from '@kbn/core-base-browser-internal';
function createCoreContext({ production = false }: { production?: boolean } = {}): CoreContext {
return {
coreId: Symbol('core context mock'),
logger: loggerMock.create(),
env: {
mode: {
dev: !production,

View file

@ -0,0 +1,114 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "core-logging-browser-internal"
PKG_REQUIRE_NAME = "@kbn/core-logging-browser-internal"
SOURCE_FILES = glob(
[
"**/*.ts",
"**/*.tsx",
],
exclude = [
"**/*.config.js",
"**/*.mock.*",
"**/*.test.*",
"**/*.stories.*",
"**/__snapshots__/**",
"**/integration_tests/**",
"**/mocks/**",
"**/scripts/**",
"**/storybook/**",
"**/test_fixtures/**",
"**/test_helpers/**",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
RUNTIME_DEPS = [
"//packages/core/logging/core-logging-common-internal",
]
TYPES_DEPS = [
"@npm//@types/node",
"@npm//@types/jest",
"//packages/kbn-logging:npm_module_types",
"//packages/core/logging/core-logging-common-internal:npm_module_types",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
jsts_transpiler(
name = "target_web",
srcs = SRCS,
build_pkg_name = package_name(),
web = True,
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//:tsconfig.base.json",
"//:tsconfig.bazel.json",
],
)
ts_project(
name = "tsc_types",
args = ['--pretty'],
srcs = SRCS,
deps = TYPES_DEPS,
declaration = True,
emit_declaration_only = True,
out_dir = "target_types",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
js_library(
name = "npm_module_types",
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm(
name = "build_types",
deps = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,3 @@
# @kbn/core-logging-browser-internal
This package contains the internal types and implementation for Core's browser-side logging service.

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export { BaseLogger, BrowserLoggingSystem, type IBrowserLoggingSystem } from './src';

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/packages/core/logging/core-logging-browser-internal'],
};

View file

@ -0,0 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/core-logging-browser-internal",
"owner": "@elastic/kibana-core",
"runtimeDeps": [],
"typeDeps": [],
}

View file

@ -0,0 +1,10 @@
{
"name": "@kbn/core-logging-browser-internal",
"private": true,
"version": "1.0.0",
"main": "./target_node/index.js",
"browser": "./target_web/index.js",
"author": "Kibana Core",
"license": "SSPL-1.0 OR Elastic License 2.0",
"types": "./target_types/index.d.ts"
}

View file

@ -0,0 +1,56 @@
/*
* 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 { LogRecord, LogLevel } from '@kbn/logging';
import { ConsoleAppender } from './console_appender';
test('`append()` correctly formats records and pushes them to console.', () => {
jest.spyOn(global.console, 'log').mockImplementation(() => {
// noop
});
const records: LogRecord[] = [
{
context: 'context-1',
level: LogLevel.All,
message: 'message-1',
timestamp: new Date(),
pid: 5355,
},
{
context: 'context-2',
level: LogLevel.Trace,
message: 'message-2',
timestamp: new Date(),
pid: 5355,
},
{
context: 'context-3',
error: new Error('Error'),
level: LogLevel.Fatal,
message: 'message-3',
timestamp: new Date(),
pid: 5355,
},
];
const appender = new ConsoleAppender({
format(record) {
return `mock-${JSON.stringify(record)}`;
},
});
for (const record of records) {
appender.append(record);
// eslint-disable-next-line no-console
expect(console.log).toHaveBeenCalledWith(`mock-${JSON.stringify(record)}`);
}
// eslint-disable-next-line no-console
expect(console.log).toHaveBeenCalledTimes(records.length);
});

View file

@ -0,0 +1,38 @@
/*
* 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 { Layout, LogRecord, DisposableAppender } from '@kbn/logging';
/**
*
* Appender that formats all the `LogRecord` instances it receives and logs them via built-in `console`.
* @internal
*/
export class ConsoleAppender implements DisposableAppender {
/**
* Creates ConsoleAppender instance.
* @param layout Instance of `Layout` sub-class responsible for `LogRecord` formatting.
*/
constructor(private readonly layout: Layout) {}
/**
* Formats specified `record` and logs it via built-in `console`.
* @param record `LogRecord` instance to be logged.
*/
public append(record: LogRecord) {
// eslint-disable-next-line no-console
console.log(this.layout.format(record));
}
/**
* Disposes `ConsoleAppender`.
*/
public dispose() {
// noop
}
}

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export { ConsoleAppender } from './console_appender';

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
export { BaseLogger } from './logger';
export { BrowserLoggingSystem, type IBrowserLoggingSystem } from './logging_system';

View file

@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`\`format()\` correctly formats record with custom pattern. 1`] = `"mock-Some error stack-context-1-Some error stack"`;
exports[`\`format()\` correctly formats record with custom pattern. 2`] = `"mock-message-2-context-2-message-2"`;
exports[`\`format()\` correctly formats record with custom pattern. 3`] = `"mock-message-3-context-3-message-3"`;
exports[`\`format()\` correctly formats record with custom pattern. 4`] = `"mock-message-4-context-4-message-4"`;
exports[`\`format()\` correctly formats record with custom pattern. 5`] = `"mock-message-5-context-5-message-5"`;
exports[`\`format()\` correctly formats record with custom pattern. 6`] = `"mock-message-6-context-6-message-6"`;
exports[`\`format()\` correctly formats record with full pattern. 1`] = `"[2012-02-01T09:30:22.011-05:00][FATAL][context-1] Some error stack"`;
exports[`\`format()\` correctly formats record with full pattern. 2`] = `"[2012-02-01T09:30:22.011-05:00][ERROR][context-2] message-2"`;
exports[`\`format()\` correctly formats record with full pattern. 3`] = `"[2012-02-01T09:30:22.011-05:00][WARN ][context-3] message-3"`;
exports[`\`format()\` correctly formats record with full pattern. 4`] = `"[2012-02-01T09:30:22.011-05:00][DEBUG][context-4] message-4"`;
exports[`\`format()\` correctly formats record with full pattern. 5`] = `"[2012-02-01T09:30:22.011-05:00][INFO ][context-5] message-5"`;
exports[`\`format()\` correctly formats record with full pattern. 6`] = `"[2012-02-01T09:30:22.011-05:00][TRACE][context-6] message-6"`;
exports[`allows specifying the PID in custom pattern 1`] = `"%pid-context-1-Some error stack"`;
exports[`allows specifying the PID in custom pattern 2`] = `"%pid-context-2-message-2"`;
exports[`allows specifying the PID in custom pattern 3`] = `"%pid-context-3-message-3"`;
exports[`allows specifying the PID in custom pattern 4`] = `"%pid-context-4-message-4"`;
exports[`allows specifying the PID in custom pattern 5`] = `"%pid-context-5-message-5"`;
exports[`allows specifying the PID in custom pattern 6`] = `"%pid-context-6-message-6"`;

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export { PatternLayout } from './pattern_layout';

View file

@ -0,0 +1,246 @@
/*
* 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 stripAnsi from 'strip-ansi';
import hasAnsi from 'has-ansi';
import { LogLevel, LogRecord } from '@kbn/logging';
import { PatternLayout } from './pattern_layout';
const stripAnsiSnapshotSerializer: jest.SnapshotSerializerPlugin = {
serialize(value: string) {
return stripAnsi(value);
},
test(value: any) {
return typeof value === 'string' && hasAnsi(value);
},
};
const timestamp = new Date(Date.UTC(2012, 1, 1, 14, 30, 22, 11));
const records: LogRecord[] = [
{
context: 'context-1',
error: {
message: 'Some error message',
name: 'Some error name',
stack: 'Some error stack',
},
level: LogLevel.Fatal,
message: 'message-1',
timestamp,
pid: 5355,
},
{
context: 'context-2',
level: LogLevel.Error,
message: 'message-2',
timestamp,
pid: 5355,
},
{
context: 'context-3',
level: LogLevel.Warn,
message: 'message-3',
timestamp,
pid: 5355,
},
{
context: 'context-4',
level: LogLevel.Debug,
message: 'message-4',
timestamp,
pid: 5355,
},
{
context: 'context-5',
level: LogLevel.Info,
message: 'message-5',
timestamp,
pid: 5355,
},
{
context: 'context-6',
level: LogLevel.Trace,
message: 'message-6',
timestamp,
pid: 5355,
},
];
expect.addSnapshotSerializer(stripAnsiSnapshotSerializer);
test('`format()` correctly formats record with full pattern.', () => {
const layout = new PatternLayout();
for (const record of records) {
expect(layout.format(record)).toMatchSnapshot();
}
});
test('`format()` correctly formats record with custom pattern.', () => {
const layout = new PatternLayout('mock-%message-%logger-%message');
for (const record of records) {
expect(layout.format(record)).toMatchSnapshot();
}
});
test('`format()` correctly formats record with meta data.', () => {
const layout = new PatternLayout('[%date][%level][%logger]%meta %message');
expect(
layout.format({
context: 'context-meta',
level: LogLevel.Debug,
message: 'message-meta',
timestamp,
pid: 5355,
meta: {
// @ts-expect-error not valid ECS field
from: 'v7',
to: 'v8',
},
})
).toBe(
'[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta]{"from":"v7","to":"v8"} message-meta'
);
expect(
layout.format({
context: 'context-meta',
level: LogLevel.Debug,
message: 'message-meta',
timestamp,
pid: 5355,
meta: {},
})
).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta]{} message-meta');
expect(
layout.format({
context: 'context-meta',
level: LogLevel.Debug,
message: 'message-meta',
timestamp,
pid: 5355,
})
).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta] message-meta');
});
test('allows specifying the PID in custom pattern', () => {
const layout = new PatternLayout('%pid-%logger-%message');
for (const record of records) {
expect(layout.format(record)).toMatchSnapshot();
}
});
test('`format()` allows specifying pattern with meta.', () => {
const layout = new PatternLayout('%logger-%meta-%message');
const record = {
context: 'context',
level: LogLevel.Debug,
message: 'message',
timestamp,
pid: 5355,
meta: {
from: 'v7',
to: 'v8',
},
};
// @ts-expect-error not valid ECS field
expect(layout.format(record)).toBe('context-{"from":"v7","to":"v8"}-message');
});
describe('format', () => {
describe('timestamp', () => {
const record = {
context: 'context',
level: LogLevel.Debug,
message: 'message',
timestamp,
pid: 5355,
};
it('uses ISO8601_TZ as default', () => {
const layout = new PatternLayout();
expect(layout.format(record)).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context] message');
});
describe('supports specifying a predefined format', () => {
it('ISO8601', () => {
const layout = new PatternLayout('[%date{ISO8601}][%logger]');
expect(layout.format(record)).toBe('[2012-02-01T14:30:22.011Z][context]');
});
it('ISO8601_TZ', () => {
const layout = new PatternLayout('[%date{ISO8601_TZ}][%logger]');
expect(layout.format(record)).toBe('[2012-02-01T09:30:22.011-05:00][context]');
});
it('ABSOLUTE', () => {
const layout = new PatternLayout('[%date{ABSOLUTE}][%logger]');
expect(layout.format(record)).toBe('[09:30:22.011][context]');
});
it('UNIX', () => {
const layout = new PatternLayout('[%date{UNIX}][%logger]');
expect(layout.format(record)).toBe('[1328106622][context]');
});
it('UNIX_MILLIS', () => {
const layout = new PatternLayout('[%date{UNIX_MILLIS}][%logger]');
expect(layout.format(record)).toBe('[1328106622011][context]');
});
});
describe('supports specifying a predefined format and timezone', () => {
it('ISO8601', () => {
const layout = new PatternLayout('[%date{ISO8601}{America/Los_Angeles}][%logger]');
expect(layout.format(record)).toBe('[2012-02-01T14:30:22.011Z][context]');
});
it('ISO8601_TZ', () => {
const layout = new PatternLayout('[%date{ISO8601_TZ}{America/Los_Angeles}][%logger]');
expect(layout.format(record)).toBe('[2012-02-01T06:30:22.011-08:00][context]');
});
it('ABSOLUTE', () => {
const layout = new PatternLayout('[%date{ABSOLUTE}{America/Los_Angeles}][%logger]');
expect(layout.format(record)).toBe('[06:30:22.011][context]');
});
it('UNIX', () => {
const layout = new PatternLayout('[%date{UNIX}{America/Los_Angeles}][%logger]');
expect(layout.format(record)).toBe('[1328106622][context]');
});
it('UNIX_MILLIS', () => {
const layout = new PatternLayout('[%date{UNIX_MILLIS}{America/Los_Angeles}][%logger]');
expect(layout.format(record)).toBe('[1328106622011][context]');
});
});
it('formats several conversions patterns correctly', () => {
const layout = new PatternLayout(
'[%date{ABSOLUTE}{America/Los_Angeles}][%logger][%date{UNIX}]'
);
expect(layout.format(record)).toBe('[06:30:22.011][context][1328106622]');
});
});
});

View file

@ -0,0 +1,42 @@
/*
* 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 {
PatternLayout as BasePatternLayout,
type Conversion,
LoggerConversion,
LevelConversion,
MetaConversion,
MessageConversion,
DateConversion,
} from '@kbn/core-logging-common-internal';
const DEFAULT_PATTERN = `[%date][%level][%logger] %message`;
const conversions: Conversion[] = [
LoggerConversion,
MessageConversion,
LevelConversion,
MetaConversion,
DateConversion,
];
/**
* Layout that formats `LogRecord` using the `pattern` string with optional
* color highlighting (eg. to make log messages easier to read in the terminal).
* @internal
*/
export class PatternLayout extends BasePatternLayout {
constructor(pattern: string = DEFAULT_PATTERN) {
super({
pattern,
highlight: false,
conversions,
});
}
}

View file

@ -0,0 +1,431 @@
/*
* 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 { LogLevel, Appender } from '@kbn/logging';
import { getLoggerContext } from '@kbn/core-logging-common-internal';
import { BaseLogger, BROWSER_PID } from './logger';
const context = getLoggerContext(['context', 'parent', 'child']);
let appenderMocks: Appender[];
let logger: BaseLogger;
const factory = {
get: jest.fn().mockImplementation(() => logger),
};
const timestamp = new Date(2012, 1, 1);
beforeEach(() => {
jest.spyOn<any, any>(global, 'Date').mockImplementation(() => timestamp);
appenderMocks = [{ append: jest.fn() }, { append: jest.fn() }];
logger = new BaseLogger(context, LogLevel.All, appenderMocks, factory);
});
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
test('`trace()` correctly forms `LogRecord` and passes it to all appenders.', () => {
logger.trace('message-1');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Trace,
message: 'message-1',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
// @ts-expect-error ECS custom meta
logger.trace('message-2', { trace: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Trace,
message: 'message-2',
meta: { trace: true },
timestamp,
pid: BROWSER_PID,
});
}
});
test('`debug()` correctly forms `LogRecord` and passes it to all appenders.', () => {
logger.debug('message-1');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Debug,
message: 'message-1',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
// @ts-expect-error ECS custom meta
logger.debug('message-2', { debug: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Debug,
message: 'message-2',
meta: { debug: true },
timestamp,
pid: BROWSER_PID,
});
}
});
test('`info()` correctly forms `LogRecord` and passes it to all appenders.', () => {
logger.info('message-1');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Info,
message: 'message-1',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
// @ts-expect-error ECS custom meta
logger.info('message-2', { info: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Info,
message: 'message-2',
meta: { info: true },
timestamp,
pid: BROWSER_PID,
});
}
});
test('`warn()` correctly forms `LogRecord` and passes it to all appenders.', () => {
logger.warn('message-1');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Warn,
message: 'message-1',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
const error = new Error('message-2');
logger.warn(error);
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error,
level: LogLevel.Warn,
message: 'message-2',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
// @ts-expect-error ECS custom meta
logger.warn('message-3', { warn: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Warn,
message: 'message-3',
meta: { warn: true },
timestamp,
pid: BROWSER_PID,
});
}
});
test('`error()` correctly forms `LogRecord` and passes it to all appenders.', () => {
logger.error('message-1');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Error,
message: 'message-1',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
const error = new Error('message-2');
logger.error(error);
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error,
level: LogLevel.Error,
message: 'message-2',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
// @ts-expect-error ECS custom meta
logger.error('message-3', { error: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Error,
message: 'message-3',
meta: { error: true },
timestamp,
pid: BROWSER_PID,
});
}
});
test('`fatal()` correctly forms `LogRecord` and passes it to all appenders.', () => {
logger.fatal('message-1');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Fatal,
message: 'message-1',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
const error = new Error('message-2');
logger.fatal(error);
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error,
level: LogLevel.Fatal,
message: 'message-2',
meta: undefined,
timestamp,
pid: BROWSER_PID,
});
}
// @ts-expect-error ECS custom meta
logger.fatal('message-3', { fatal: true });
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
error: undefined,
level: LogLevel.Fatal,
message: 'message-3',
meta: { fatal: true },
timestamp,
pid: BROWSER_PID,
});
}
});
test('`log()` just passes the record to all appenders.', () => {
const record = {
context,
level: LogLevel.Info,
message: 'message-1',
timestamp,
pid: 5355,
};
logger.log(record);
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith(record);
}
});
test('`get()` calls the logger factory with proper context and return the result', () => {
logger.get('sub', 'context');
expect(factory.get).toHaveBeenCalledTimes(1);
expect(factory.get).toHaveBeenCalledWith(context, 'sub', 'context');
factory.get.mockClear();
factory.get.mockImplementation(() => 'some-logger');
const childLogger = logger.get('other', 'sub');
expect(factory.get).toHaveBeenCalledTimes(1);
expect(factory.get).toHaveBeenCalledWith(context, 'other', 'sub');
expect(childLogger).toEqual('some-logger');
});
test('logger with `Off` level does not pass any records to appenders.', () => {
const turnedOffLogger = new BaseLogger(context, LogLevel.Off, appenderMocks, factory);
turnedOffLogger.trace('trace-message');
turnedOffLogger.debug('debug-message');
turnedOffLogger.info('info-message');
turnedOffLogger.warn('warn-message');
turnedOffLogger.error('error-message');
turnedOffLogger.fatal('fatal-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).not.toHaveBeenCalled();
}
});
test('logger with `All` level passes all records to appenders.', () => {
const catchAllLogger = new BaseLogger(context, LogLevel.All, appenderMocks, factory);
catchAllLogger.trace('trace-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Trace,
message: 'trace-message',
timestamp,
pid: BROWSER_PID,
});
}
catchAllLogger.debug('debug-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Debug,
message: 'debug-message',
timestamp,
pid: BROWSER_PID,
});
}
catchAllLogger.info('info-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Info,
message: 'info-message',
timestamp,
pid: BROWSER_PID,
});
}
catchAllLogger.warn('warn-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(4);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Warn,
message: 'warn-message',
timestamp,
pid: BROWSER_PID,
});
}
catchAllLogger.error('error-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(5);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Error,
message: 'error-message',
timestamp,
pid: BROWSER_PID,
});
}
catchAllLogger.fatal('fatal-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(6);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Fatal,
message: 'fatal-message',
timestamp,
pid: BROWSER_PID,
});
}
});
test('passes log record to appenders only if log level is supported.', () => {
const warnLogger = new BaseLogger(context, LogLevel.Warn, appenderMocks, factory);
warnLogger.trace('trace-message');
warnLogger.debug('debug-message');
warnLogger.info('info-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).not.toHaveBeenCalled();
}
warnLogger.warn('warn-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Warn,
message: 'warn-message',
timestamp,
pid: BROWSER_PID,
});
}
warnLogger.error('error-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(2);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Error,
message: 'error-message',
timestamp,
pid: BROWSER_PID,
});
}
warnLogger.fatal('fatal-message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);
expect(appenderMock.append).toHaveBeenCalledWith({
context,
level: LogLevel.Fatal,
message: 'fatal-message',
timestamp,
pid: BROWSER_PID,
});
}
});

View file

@ -0,0 +1,46 @@
/*
* 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 { LogLevel, LogRecord, LogMeta } from '@kbn/logging';
import { AbstractLogger } from '@kbn/core-logging-common-internal';
function isError(x: any): x is Error {
return x instanceof Error;
}
export const BROWSER_PID = -1;
/** @internal */
export class BaseLogger extends AbstractLogger {
protected createLogRecord<Meta extends LogMeta>(
level: LogLevel,
errorOrMessage: string | Error,
meta?: Meta
): LogRecord {
if (isError(errorOrMessage)) {
return {
context: this.context,
error: errorOrMessage,
level,
message: errorOrMessage.message,
meta,
timestamp: new Date(),
pid: BROWSER_PID,
};
}
return {
context: this.context,
level,
message: errorOrMessage,
meta,
timestamp: new Date(),
pid: BROWSER_PID,
};
}
}

View file

@ -0,0 +1,77 @@
/*
* 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 { BrowserLoggingSystem } from './logging_system';
describe('', () => {
const timestamp = new Date(Date.UTC(2012, 1, 1, 14, 33, 22, 11));
let mockConsoleLog: jest.SpyInstance;
let loggingSystem: BrowserLoggingSystem;
beforeEach(() => {
mockConsoleLog = jest.spyOn(global.console, 'log').mockReturnValue(undefined);
jest.spyOn<any, any>(global, 'Date').mockImplementation(() => timestamp);
loggingSystem = new BrowserLoggingSystem({ logLevel: 'warn' });
});
afterEach(() => {
mockConsoleLog.mockReset();
});
describe('#get', () => {
it('returns the same logger for same context', () => {
const loggerA = loggingSystem.get('same.logger');
const loggerB = loggingSystem.get('same.logger');
expect(loggerA).toBe(loggerB);
});
it('returns different loggers for different contexts', () => {
const loggerA = loggingSystem.get('some.logger');
const loggerB = loggingSystem.get('another.logger');
expect(loggerA).not.toBe(loggerB);
});
});
describe('logger configuration', () => {
it('properly configure the logger to use the correct context and pattern', () => {
const logger = loggingSystem.get('foo.bar');
logger.warn('some message');
expect(mockConsoleLog.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"[2012-02-01T09:33:22.011-05:00][WARN ][foo.bar] some message",
]
`);
});
it('properly configure the logger to use the correct level', () => {
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-02-01T04:33:22.011-05:00][WARN ][foo.bar] some warn message",
],
Array [
"[2012-01-31T23:33:22.011-05:00][ERROR][foo.bar] some error message",
],
Array [
"[2012-01-31T18:33:22.011-05:00][FATAL][foo.bar] some fatal message",
],
]
`);
});
});
});

View file

@ -0,0 +1,74 @@
/*
* 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 { LogLevel, Logger, LoggerFactory, LogLevelId, DisposableAppender } from '@kbn/logging';
import { getLoggerContext } 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';
/**
* @internal
*/
export interface IBrowserLoggingSystem extends LoggerFactory {
asLoggerFactory(): LoggerFactory;
}
/**
* @internal
*/
export class BrowserLoggingSystem implements IBrowserLoggingSystem {
private readonly loggers: Map<string, Logger> = new Map();
private readonly appenders: Map<string, DisposableAppender> = new Map();
constructor(private readonly loggingConfig: BrowserLoggingConfig) {
this.setupSystem(loggingConfig);
}
public get(...contextParts: string[]): Logger {
const context = getLoggerContext(contextParts);
if (!this.loggers.has(context)) {
this.loggers.set(context, this.createLogger(context));
}
return this.loggers.get(context)!;
}
private createLogger(context: string) {
const { level, appenders } = this.getLoggerConfigByContext(context);
const loggerLevel = LogLevel.fromId(level);
const loggerAppenders = appenders.map((appenderKey) => this.appenders.get(appenderKey)!);
return new BaseLogger(context, loggerLevel, loggerAppenders, this.asLoggerFactory());
}
private getLoggerConfigByContext(context: string): LoggerConfigType {
return {
level: this.loggingConfig.logLevel,
appenders: [CONSOLE_APPENDER_ID],
name: context,
};
}
private setupSystem(loggingConfig: BrowserLoggingConfig) {
const consoleAppender = new ConsoleAppender(new PatternLayout());
this.appenders.set(CONSOLE_APPENDER_ID, consoleAppender);
}
/**
* Safe wrapper that allows passing logging service as immutable LoggerFactory.
*/
public asLoggerFactory(): LoggerFactory {
return { get: (...contextParts: string[]) => this.get(...contextParts) };
}
}

View file

@ -0,0 +1,20 @@
/*
* 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 { LogLevelId } from '@kbn/logging';
/**
* Describes the configuration of a given logger.
*
* @public
*/
export interface LoggerConfigType {
appenders: string[];
name: string;
level: LogLevelId;
}

View file

@ -0,0 +1,16 @@
{
"extends": "../../../../tsconfig.bazel.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "target_types",
"stripInternal": false,
"types": [
"jest",
"node",
]
},
"include": [
"**/*.ts",
]
}

View file

@ -0,0 +1,115 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "core-logging-browser-mocks"
PKG_REQUIRE_NAME = "@kbn/core-logging-browser-mocks"
SOURCE_FILES = glob(
[
"**/*.ts",
"**/*.tsx",
],
exclude = [
"**/*.config.js",
"**/*.test.*",
"**/*.stories.*",
"**/__snapshots__/**",
"**/integration_tests/**",
"**/mocks/**",
"**/scripts/**",
"**/storybook/**",
"**/test_fixtures/**",
"**/test_helpers/**",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
RUNTIME_DEPS = [
"@npm//react",
"//packages/kbn-logging-mocks",
]
TYPES_DEPS = [
"@npm//@types/node",
"@npm//@types/jest",
"@npm//@types/react",
"//packages/kbn-logging-mocks:npm_module_types",
"//packages/core/logging/core-logging-browser-internal:npm_module_types",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
jsts_transpiler(
name = "target_web",
srcs = SRCS,
build_pkg_name = package_name(),
web = True,
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//:tsconfig.base.json",
"//:tsconfig.bazel.json",
],
)
ts_project(
name = "tsc_types",
args = ['--pretty'],
srcs = SRCS,
deps = TYPES_DEPS,
declaration = True,
emit_declaration_only = True,
out_dir = "target_types",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
js_library(
name = "npm_module_types",
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm(
name = "build_types",
deps = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,3 @@
# @kbn/core-logging-browser-mocks
This package contains the mocks for Core's browser-side logging service.

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export { loggingSystemMock } from './src';

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/packages/core/logging/core-logging-browser-mocks'],
};

View file

@ -0,0 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/core-logging-browser-mocks",
"owner": "@elastic/kibana-core",
"runtimeDeps": [],
"typeDeps": [],
}

View file

@ -0,0 +1,10 @@
{
"name": "@kbn/core-logging-browser-mocks",
"private": true,
"version": "1.0.0",
"main": "./target_node/index.js",
"browser": "./target_web/index.js",
"author": "Kibana Core",
"license": "SSPL-1.0 OR Elastic License 2.0",
"types": "./target_types/index.d.ts"
}

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
export { loggingSystemMock } from './logging_system.mock';

View file

@ -0,0 +1,37 @@
/*
* 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 { loggerMock, MockedLogger } from '@kbn/logging-mocks';
import type { IBrowserLoggingSystem } from '@kbn/core-logging-browser-internal';
const createLoggingSystemMock = () => {
const mockLog: MockedLogger = loggerMock.create();
mockLog.get.mockImplementation((...context) => ({
...mockLog,
context,
}));
const mocked: jest.Mocked<IBrowserLoggingSystem> = {
get: jest.fn(),
asLoggerFactory: jest.fn(),
};
mocked.get.mockImplementation((...context) => ({
...mockLog,
context,
}));
mocked.asLoggerFactory.mockImplementation(() => mocked);
return mocked;
};
export const loggingSystemMock = {
create: createLoggingSystemMock,
createLogger: loggerMock.create,
};

View file

@ -0,0 +1,18 @@
{
"extends": "../../../../tsconfig.bazel.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "target_types",
"stripInternal": false,
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
]
}

View file

@ -0,0 +1,116 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "core-logging-common-internal"
PKG_REQUIRE_NAME = "@kbn/core-logging-common-internal"
SOURCE_FILES = glob(
[
"**/*.ts",
"**/*.tsx",
],
exclude = [
"**/*.config.js",
"**/*.mock.*",
"**/*.test.*",
"**/*.stories.*",
"**/__snapshots__/**",
"**/integration_tests/**",
"**/mocks/**",
"**/scripts/**",
"**/storybook/**",
"**/test_fixtures/**",
"**/test_helpers/**",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
RUNTIME_DEPS = [
"@npm//lodash",
"@npm//moment-timezone",
]
TYPES_DEPS = [
"@npm//@types/node",
"@npm//@types/jest",
"@npm//@types/moment-timezone",
"@npm//lodash",
"//packages/kbn-logging:npm_module_types",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
jsts_transpiler(
name = "target_web",
srcs = SRCS,
build_pkg_name = package_name(),
web = True,
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//:tsconfig.base.json",
"//:tsconfig.bazel.json",
],
)
ts_project(
name = "tsc_types",
args = ['--pretty'],
srcs = SRCS,
deps = TYPES_DEPS,
declaration = True,
emit_declaration_only = True,
out_dir = "target_types",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
js_library(
name = "npm_module_types",
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm(
name = "build_types",
deps = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,3 @@
# @kbn/core-logging-common-internal
This package contains common types and the base implementation for the browser and server-side logging systems.

View file

@ -0,0 +1,24 @@
/*
* 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.
*/
export {
PatternLayout,
DateConversion,
LoggerConversion,
MessageConversion,
LevelConversion,
MetaConversion,
type Conversion,
AbstractLogger,
type CreateLogRecordFn,
getLoggerContext,
getParentLoggerContext,
CONTEXT_SEPARATOR,
ROOT_CONTEXT_NAME,
DEFAULT_APPENDER_NAME,
} from './src';

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/packages/core/logging/core-logging-common-internal'],
};

View file

@ -0,0 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/core-logging-common-internal",
"owner": "@elastic/kibana-core",
"runtimeDeps": [],
"typeDeps": [],
}

View file

@ -0,0 +1,10 @@
{
"name": "@kbn/core-logging-common-internal",
"private": true,
"version": "1.0.0",
"main": "./target_node/index.js",
"browser": "./target_web/index.js",
"author": "Kibana Core",
"license": "SSPL-1.0 OR Elastic License 2.0",
"types": "./target_types/index.d.ts"
}

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
export {
PatternLayout,
DateConversion,
LoggerConversion,
MessageConversion,
LevelConversion,
MetaConversion,
type Conversion,
} from './layouts';
export { AbstractLogger, type CreateLogRecordFn } from './logger';
export {
getLoggerContext,
getParentLoggerContext,
CONTEXT_SEPARATOR,
ROOT_CONTEXT_NAME,
DEFAULT_APPENDER_NAME,
} from './logger_context';

View file

@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`\`format()\` correctly formats record with custom pattern. 1`] = `"mock-Some error stack-context-1-Some error stack"`;
exports[`\`format()\` correctly formats record with custom pattern. 2`] = `"mock-message-2-context-2-message-2"`;
exports[`\`format()\` correctly formats record with custom pattern. 3`] = `"mock-message-3-context-3-message-3"`;
exports[`\`format()\` correctly formats record with custom pattern. 4`] = `"mock-message-4-context-4-message-4"`;
exports[`\`format()\` correctly formats record with custom pattern. 5`] = `"mock-message-5-context-5-message-5"`;
exports[`\`format()\` correctly formats record with custom pattern. 6`] = `"mock-message-6-context-6-message-6"`;
exports[`\`format()\` correctly formats record with full pattern. 1`] = `"[2012-02-01T09:30:22.011-05:00][FATAL][context-1] Some error stack"`;
exports[`\`format()\` correctly formats record with full pattern. 2`] = `"[2012-02-01T09:30:22.011-05:00][ERROR][context-2] message-2"`;
exports[`\`format()\` correctly formats record with full pattern. 3`] = `"[2012-02-01T09:30:22.011-05:00][WARN ][context-3] message-3"`;
exports[`\`format()\` correctly formats record with full pattern. 4`] = `"[2012-02-01T09:30:22.011-05:00][DEBUG][context-4] message-4"`;
exports[`\`format()\` correctly formats record with full pattern. 5`] = `"[2012-02-01T09:30:22.011-05:00][INFO ][context-5] message-5"`;
exports[`\`format()\` correctly formats record with full pattern. 6`] = `"[2012-02-01T09:30:22.011-05:00][TRACE][context-6] message-6"`;
exports[`allows specifying the PID in custom pattern 1`] = `"%pid-context-1-Some error stack"`;
exports[`allows specifying the PID in custom pattern 2`] = `"%pid-context-2-message-2"`;
exports[`allows specifying the PID in custom pattern 3`] = `"%pid-context-3-message-3"`;
exports[`allows specifying the PID in custom pattern 4`] = `"%pid-context-4-message-4"`;
exports[`allows specifying the PID in custom pattern 5`] = `"%pid-context-5-message-5"`;
exports[`allows specifying the PID in custom pattern 6`] = `"%pid-context-6-message-6"`;

View file

@ -9,8 +9,7 @@
import moment from 'moment-timezone';
import { last } from 'lodash';
import { LogRecord } from '@kbn/logging';
import { Conversion } from './type';
import { Conversion } from './types';
const dateRegExp = /%date({(?<format>[^}]+)})?({(?<timezone>[^}]+)})?/g;

View file

@ -0,0 +1,14 @@
/*
* 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.
*/
export type { Conversion } from './types';
export { LoggerConversion } from './logger';
export { LevelConversion } from './level';
export { MessageConversion } from './message';
export { MetaConversion } from './meta';
export { DateConversion } from './date';

View file

@ -0,0 +1,18 @@
/*
* 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 { LogRecord } from '@kbn/logging';
import { Conversion } from './types';
export const LevelConversion: Conversion = {
pattern: /%level/g,
convert(record: LogRecord) {
const message = record.level.id.toUpperCase().padEnd(5);
return message;
},
};

View file

@ -0,0 +1,18 @@
/*
* 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 { LogRecord } from '@kbn/logging';
import { Conversion } from './types';
export const LoggerConversion: Conversion = {
pattern: /%logger/g,
convert(record: LogRecord) {
const message = record.context;
return message;
},
};

View file

@ -7,7 +7,7 @@
*/
import { LogRecord } from '@kbn/logging';
import { Conversion } from './type';
import { Conversion } from './types';
export const MessageConversion: Conversion = {
pattern: /%message/g,

View file

@ -7,7 +7,7 @@
*/
import { LogRecord } from '@kbn/logging';
import { Conversion } from './type';
import { Conversion } from './types';
export const MetaConversion: Conversion = {
pattern: /%meta/g,

View file

@ -0,0 +1,17 @@
/*
* 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.
*/
export { PatternLayout } from './pattern_layout';
export {
DateConversion,
LoggerConversion,
MessageConversion,
LevelConversion,
MetaConversion,
type Conversion,
} from './conversions';

View file

@ -0,0 +1,256 @@
/*
* 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 stripAnsi from 'strip-ansi';
import hasAnsi from 'has-ansi';
import { LogLevel, LogRecord } from '@kbn/logging';
import { PatternLayout } from './pattern_layout';
const stripAnsiSnapshotSerializer: jest.SnapshotSerializerPlugin = {
serialize(value: string) {
return stripAnsi(value);
},
test(value: any) {
return typeof value === 'string' && hasAnsi(value);
},
};
const timestamp = new Date(Date.UTC(2012, 1, 1, 14, 30, 22, 11));
const records: LogRecord[] = [
{
context: 'context-1',
error: {
message: 'Some error message',
name: 'Some error name',
stack: 'Some error stack',
},
level: LogLevel.Fatal,
message: 'message-1',
timestamp,
pid: 5355,
},
{
context: 'context-2',
level: LogLevel.Error,
message: 'message-2',
timestamp,
pid: 5355,
},
{
context: 'context-3',
level: LogLevel.Warn,
message: 'message-3',
timestamp,
pid: 5355,
},
{
context: 'context-4',
level: LogLevel.Debug,
message: 'message-4',
timestamp,
pid: 5355,
},
{
context: 'context-5',
level: LogLevel.Info,
message: 'message-5',
timestamp,
pid: 5355,
},
{
context: 'context-6',
level: LogLevel.Trace,
message: 'message-6',
timestamp,
pid: 5355,
},
];
expect.addSnapshotSerializer(stripAnsiSnapshotSerializer);
test('`format()` correctly formats record with full pattern.', () => {
const layout = new PatternLayout();
for (const record of records) {
expect(layout.format(record)).toMatchSnapshot();
}
});
test('`format()` correctly formats record with custom pattern.', () => {
const layout = new PatternLayout({ pattern: 'mock-%message-%logger-%message' });
for (const record of records) {
expect(layout.format(record)).toMatchSnapshot();
}
});
test('`format()` correctly formats record with meta data.', () => {
const layout = new PatternLayout({ pattern: '[%date][%level][%logger]%meta %message' });
expect(
layout.format({
context: 'context-meta',
level: LogLevel.Debug,
message: 'message-meta',
timestamp,
pid: 5355,
meta: {
// @ts-expect-error not valid ECS field
from: 'v7',
to: 'v8',
},
})
).toBe(
'[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta]{"from":"v7","to":"v8"} message-meta'
);
expect(
layout.format({
context: 'context-meta',
level: LogLevel.Debug,
message: 'message-meta',
timestamp,
pid: 5355,
meta: {},
})
).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta]{} message-meta');
expect(
layout.format({
context: 'context-meta',
level: LogLevel.Debug,
message: 'message-meta',
timestamp,
pid: 5355,
})
).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta] message-meta');
});
test('allows specifying the PID in custom pattern', () => {
const layout = new PatternLayout({ pattern: '%pid-%logger-%message' });
for (const record of records) {
expect(layout.format(record)).toMatchSnapshot();
}
});
test('`format()` allows specifying pattern with meta.', () => {
const layout = new PatternLayout({ pattern: '%logger-%meta-%message' });
const record = {
context: 'context',
level: LogLevel.Debug,
message: 'message',
timestamp,
pid: 5355,
meta: {
from: 'v7',
to: 'v8',
},
};
// @ts-expect-error not valid ECS field
expect(layout.format(record)).toBe('context-{"from":"v7","to":"v8"}-message');
});
describe('format', () => {
describe('timestamp', () => {
const record = {
context: 'context',
level: LogLevel.Debug,
message: 'message',
timestamp,
pid: 5355,
};
it('uses ISO8601_TZ as default', () => {
const layout = new PatternLayout();
expect(layout.format(record)).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context] message');
});
describe('supports specifying a predefined format', () => {
it('ISO8601', () => {
const layout = new PatternLayout({ pattern: '[%date{ISO8601}][%logger]' });
expect(layout.format(record)).toBe('[2012-02-01T14:30:22.011Z][context]');
});
it('ISO8601_TZ', () => {
const layout = new PatternLayout({ pattern: '[%date{ISO8601_TZ}][%logger]' });
expect(layout.format(record)).toBe('[2012-02-01T09:30:22.011-05:00][context]');
});
it('ABSOLUTE', () => {
const layout = new PatternLayout({ pattern: '[%date{ABSOLUTE}][%logger]' });
expect(layout.format(record)).toBe('[09:30:22.011][context]');
});
it('UNIX', () => {
const layout = new PatternLayout({ pattern: '[%date{UNIX}][%logger]' });
expect(layout.format(record)).toBe('[1328106622][context]');
});
it('UNIX_MILLIS', () => {
const layout = new PatternLayout({ pattern: '[%date{UNIX_MILLIS}][%logger]' });
expect(layout.format(record)).toBe('[1328106622011][context]');
});
});
describe('supports specifying a predefined format and timezone', () => {
it('ISO8601', () => {
const layout = new PatternLayout({
pattern: '[%date{ISO8601}{America/Los_Angeles}][%logger]',
});
expect(layout.format(record)).toBe('[2012-02-01T14:30:22.011Z][context]');
});
it('ISO8601_TZ', () => {
const layout = new PatternLayout({
pattern: '[%date{ISO8601_TZ}{America/Los_Angeles}][%logger]',
});
expect(layout.format(record)).toBe('[2012-02-01T06:30:22.011-08:00][context]');
});
it('ABSOLUTE', () => {
const layout = new PatternLayout({
pattern: '[%date{ABSOLUTE}{America/Los_Angeles}][%logger]',
});
expect(layout.format(record)).toBe('[06:30:22.011][context]');
});
it('UNIX', () => {
const layout = new PatternLayout({
pattern: '[%date{UNIX}{America/Los_Angeles}][%logger]',
});
expect(layout.format(record)).toBe('[1328106622][context]');
});
it('UNIX_MILLIS', () => {
const layout = new PatternLayout({
pattern: '[%date{UNIX_MILLIS}{America/Los_Angeles}][%logger]',
});
expect(layout.format(record)).toBe('[1328106622011][context]');
});
});
it('formats several conversions patterns correctly', () => {
const layout = new PatternLayout({
pattern: '[%date{ABSOLUTE}{America/Los_Angeles}][%logger][%date{UNIX}]',
});
expect(layout.format(record)).toBe('[06:30:22.011][context][1328106622]');
});
});
});

View file

@ -0,0 +1,73 @@
/*
* 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 { LogRecord, Layout } from '@kbn/logging';
import {
Conversion,
LoggerConversion,
LevelConversion,
MetaConversion,
MessageConversion,
DateConversion,
} from './conversions';
/**
* Default pattern used by PatternLayout if it's not overridden in the configuration.
*/
const DEFAULT_PATTERN = `[%date][%level][%logger] %message`;
const DEFAULT_CONVERSIONS: Conversion[] = [
LoggerConversion,
MessageConversion,
LevelConversion,
MetaConversion,
DateConversion,
];
export interface PatternLayoutOptions {
pattern?: string;
highlight?: boolean;
conversions?: Conversion[];
}
/**
* Layout that formats `LogRecord` using the `pattern` string with optional
* color highlighting (eg. to make log messages easier to read in the terminal).
* @internal
*/
export class PatternLayout implements Layout {
private readonly pattern: string;
private readonly highlight: boolean;
private readonly conversions: Conversion[];
constructor({
pattern = DEFAULT_PATTERN,
highlight = false,
conversions = DEFAULT_CONVERSIONS,
}: PatternLayoutOptions = {}) {
this.pattern = pattern;
this.highlight = highlight;
this.conversions = conversions;
}
/**
* Formats `LogRecord` into a string based on the specified `pattern` and `highlighting` options.
* @param record Instance of `LogRecord` to format into string.
*/
public format(record: LogRecord): string {
let recordString = this.pattern;
for (const conversion of this.conversions) {
recordString = recordString.replace(
conversion.pattern,
conversion.convert.bind(null, record, this.highlight)
);
}
return recordString;
}
}

View file

@ -0,0 +1,241 @@
/*
* 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 { Appender, LogLevel, LogMeta, LogRecord } from '@kbn/logging';
import { getLoggerContext } from '@kbn/core-logging-common-internal';
import { AbstractLogger, CreateLogRecordFn } from './logger';
describe('AbstractLogger', () => {
const context = getLoggerContext(['context', 'parent', 'child']);
const factory = {
get: jest.fn().mockImplementation(() => logger),
};
let appenderMocks: Appender[];
const createLogRecordSpy: jest.MockedFunction<CreateLogRecordFn> = jest.fn();
class TestLogger extends AbstractLogger {
createLogRecord<Meta extends LogMeta>(
level: LogLevel,
errorOrMessage: string | Error,
meta?: Meta
) {
return createLogRecordSpy(level, errorOrMessage, meta);
}
}
let logger: TestLogger;
beforeEach(() => {
appenderMocks = [{ append: jest.fn() }, { append: jest.fn() }];
logger = new TestLogger(context, LogLevel.All, appenderMocks, factory);
createLogRecordSpy.mockImplementation((level, message, meta) => {
return {
level,
message,
meta,
} as LogRecord;
});
});
afterEach(() => {
createLogRecordSpy.mockReset();
});
describe('#trace', () => {
it('calls `createLogRecord` with the correct parameters', () => {
const meta = { tags: ['foo', 'bar'] };
logger.trace('some message', meta);
expect(createLogRecordSpy).toHaveBeenCalledTimes(1);
expect(createLogRecordSpy).toHaveBeenCalledWith(LogLevel.Trace, 'some message', meta);
});
it('pass the log record down to all appenders', () => {
const logRecord = { message: 'dummy', level: LogLevel.Trace } as LogRecord;
createLogRecordSpy.mockReturnValue(logRecord);
logger.trace('some message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith(logRecord);
}
});
});
describe('#debug', () => {
it('calls `createLogRecord` with the correct parameters', () => {
const meta = { tags: ['foo', 'bar'] };
logger.debug('some message', meta);
expect(createLogRecordSpy).toHaveBeenCalledTimes(1);
expect(createLogRecordSpy).toHaveBeenCalledWith(LogLevel.Debug, 'some message', meta);
});
it('pass the log record down to all appenders', () => {
const logRecord = { message: 'dummy', level: LogLevel.Debug } as LogRecord;
createLogRecordSpy.mockReturnValue(logRecord);
logger.debug('some message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith(logRecord);
}
});
});
describe('#info', () => {
it('calls `createLogRecord` with the correct parameters', () => {
const meta = { tags: ['foo', 'bar'] };
logger.info('some message', meta);
expect(createLogRecordSpy).toHaveBeenCalledTimes(1);
expect(createLogRecordSpy).toHaveBeenCalledWith(LogLevel.Info, 'some message', meta);
});
it('pass the log record down to all appenders', () => {
const logRecord = { message: 'dummy', level: LogLevel.Info } as LogRecord;
createLogRecordSpy.mockReturnValue(logRecord);
logger.info('some message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith(logRecord);
}
});
});
describe('#warn', () => {
it('calls `createLogRecord` with the correct parameters', () => {
const meta = { tags: ['foo', 'bar'] };
logger.warn('some message', meta);
expect(createLogRecordSpy).toHaveBeenCalledTimes(1);
expect(createLogRecordSpy).toHaveBeenCalledWith(LogLevel.Warn, 'some message', meta);
});
it('pass the log record down to all appenders', () => {
const logRecord = { message: 'dummy', level: LogLevel.Warn } as LogRecord;
createLogRecordSpy.mockReturnValue(logRecord);
logger.warn('some message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith(logRecord);
}
});
});
describe('#error', () => {
it('calls `createLogRecord` with the correct parameters', () => {
const meta = { tags: ['foo', 'bar'] };
logger.error('some message', meta);
expect(createLogRecordSpy).toHaveBeenCalledTimes(1);
expect(createLogRecordSpy).toHaveBeenCalledWith(LogLevel.Error, 'some message', meta);
});
it('pass the log record down to all appenders', () => {
const logRecord = { message: 'dummy', level: LogLevel.Error } as LogRecord;
createLogRecordSpy.mockReturnValue(logRecord);
logger.error('some message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith(logRecord);
}
});
});
describe('#fatal', () => {
it('calls `createLogRecord` with the correct parameters', () => {
const meta = { tags: ['foo', 'bar'] };
logger.fatal('some message', meta);
expect(createLogRecordSpy).toHaveBeenCalledTimes(1);
expect(createLogRecordSpy).toHaveBeenCalledWith(LogLevel.Fatal, 'some message', meta);
});
it('pass the log record down to all appenders', () => {
const logRecord = { message: 'dummy', level: LogLevel.Fatal } as LogRecord;
createLogRecordSpy.mockReturnValue(logRecord);
logger.fatal('some message');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(1);
expect(appenderMock.append).toHaveBeenCalledWith(logRecord);
}
});
});
describe('#get', () => {
it('calls the logger factory with proper context and return the result', () => {
logger.get('sub', 'context');
expect(factory.get).toHaveBeenCalledTimes(1);
expect(factory.get).toHaveBeenCalledWith(context, 'sub', 'context');
factory.get.mockClear();
factory.get.mockImplementation(() => 'some-logger');
const childLogger = logger.get('other', 'sub');
expect(factory.get).toHaveBeenCalledTimes(1);
expect(factory.get).toHaveBeenCalledWith(context, 'other', 'sub');
expect(childLogger).toEqual('some-logger');
});
});
describe('log level', () => {
it('does not calls appenders for records with unsupported levels', () => {
logger = new TestLogger(context, LogLevel.Warn, appenderMocks, factory);
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');
for (const appenderMock of appenderMocks) {
expect(appenderMock.append).toHaveBeenCalledTimes(3);
expect(appenderMock.append).toHaveBeenCalledWith(
expect.objectContaining({
level: LogLevel.Warn,
})
);
expect(appenderMock.append).toHaveBeenCalledWith(
expect.objectContaining({
level: LogLevel.Error,
})
);
expect(appenderMock.append).toHaveBeenCalledWith(
expect.objectContaining({
level: LogLevel.Fatal,
})
);
}
});
});
describe('isLevelEnabled', () => {
const orderedLogLevels = [
LogLevel.Fatal,
LogLevel.Error,
LogLevel.Warn,
LogLevel.Info,
LogLevel.Debug,
LogLevel.Trace,
LogLevel.All,
];
for (const logLevel of orderedLogLevels) {
it(`returns the correct value for a '${logLevel.id}' level logger`, () => {
const levelLogger = new TestLogger(context, logLevel, appenderMocks, factory);
for (const level of orderedLogLevels) {
const levelEnabled = logLevel.supports(level);
expect(levelLogger.isLevelEnabled(level.id)).toEqual(levelEnabled);
}
});
}
});
});

View file

@ -0,0 +1,86 @@
/*
* 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 {
Appender,
LogLevel,
LogRecord,
LoggerFactory,
LogMeta,
Logger,
LogLevelId,
} from '@kbn/logging';
/**
* @internal
*/
export type CreateLogRecordFn = <Meta extends LogMeta>(
level: LogLevel,
errorOrMessage: string | Error,
meta?: Meta
) => LogRecord;
/**
* A basic, abstract logger implementation that delegates the create of log records to the child's createLogRecord function.
* @internal
*/
export abstract class AbstractLogger implements Logger {
constructor(
protected readonly context: string,
protected readonly level: LogLevel,
protected readonly appenders: Appender[],
protected readonly factory: LoggerFactory
) {}
protected abstract createLogRecord<Meta extends LogMeta>(
level: LogLevel,
errorOrMessage: string | Error,
meta?: Meta
): LogRecord;
public trace<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Trace, message, meta));
}
public debug<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Debug, message, meta));
}
public info<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Info, message, meta));
}
public warn<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Warn, errorOrMessage, meta));
}
public error<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Error, errorOrMessage, meta));
}
public fatal<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Fatal, errorOrMessage, meta));
}
public isLevelEnabled(levelId: LogLevelId): boolean {
return this.level.supports(LogLevel.fromId(levelId));
}
public log(record: LogRecord) {
if (!this.level.supports(record.level)) {
return;
}
for (const appender of this.appenders) {
appender.append(record);
}
}
public get(...childContextPaths: string[]): Logger {
return this.factory.get(...[this.context, ...childContextPaths]);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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 { getLoggerContext, getParentLoggerContext } from './logger_context';
describe('getLoggerContext', () => {
it('returns correct joined context name.', () => {
expect(getLoggerContext(['a', 'b', 'c'])).toEqual('a.b.c');
expect(getLoggerContext(['a', 'b'])).toEqual('a.b');
expect(getLoggerContext(['a'])).toEqual('a');
expect(getLoggerContext([])).toEqual('root');
});
});
describe('getParentLoggerContext', () => {
it('returns correct parent context name.', () => {
expect(getParentLoggerContext('a.b.c')).toEqual('a.b');
expect(getParentLoggerContext('a.b')).toEqual('a');
expect(getParentLoggerContext('a')).toEqual('root');
});
});

View file

@ -0,0 +1,46 @@
/*
* 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.
*/
/**
* Separator string that used within nested context name (eg. plugins.pid).
*/
export const CONTEXT_SEPARATOR = '.';
/**
* Name of the `root` context that always exists and sits at the top of logger hierarchy.
*/
export const ROOT_CONTEXT_NAME = 'root';
/**
* Name of the appender that is always presented and used by `root` logger by default.
*/
export const DEFAULT_APPENDER_NAME = 'default';
/**
* Helper method that joins separate string context parts into single context string.
* In case joined context is an empty string, `root` context name is returned.
* @param contextParts List of the context parts (e.g. ['parent', 'child'].
* @returns {string} Joined context string (e.g. 'parent.child').
*/
export const getLoggerContext = (contextParts: string[]): string => {
return contextParts.join(CONTEXT_SEPARATOR) || ROOT_CONTEXT_NAME;
};
/**
* Helper method that returns parent context for the specified one.
* @param context Context to find parent for.
* @returns Name of the parent context or `root` if the context is the top level one.
*/
export const getParentLoggerContext = (context: string): string => {
const lastIndexOfSeparator = context.lastIndexOf(CONTEXT_SEPARATOR);
if (lastIndexOfSeparator === -1) {
return ROOT_CONTEXT_NAME;
}
return context.slice(0, lastIndexOfSeparator);
};

View file

@ -0,0 +1,16 @@
{
"extends": "../../../../tsconfig.bazel.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "target_types",
"stripInternal": false,
"types": [
"jest",
"node",
]
},
"include": [
"**/*.ts",
]
}

View file

@ -37,10 +37,12 @@ NPM_MODULE_EXTRA_FILES = [
RUNTIME_DEPS = [
"@npm//lodash",
"@npm//moment-timezone",
"@npm//chalk",
"@npm//elastic-apm-node",
"//packages/kbn-safer-lodash-set",
"//packages/kbn-config-schema",
"//packages/kbn-std",
"//packages/core/logging/core-logging-common-internal",
]
TYPES_DEPS = [
@ -50,10 +52,12 @@ TYPES_DEPS = [
"@npm//rxjs",
"@npm//@types/moment-timezone",
"@npm//elastic-apm-node",
"@npm//chalk",
"//packages/kbn-safer-lodash-set:npm_module_types",
"//packages/kbn-logging:npm_module_types",
"//packages/kbn-config-schema:npm_module_types",
"//packages/core/base/core-base-server-internal:npm_module_types",
"//packages/core/logging/core-logging-common-internal:npm_module_types",
"//packages/core/logging/core-logging-server:npm_module_types",
]

View file

@ -7,7 +7,7 @@
*/
import { schema } from '@kbn/config-schema';
import { Layout, LogRecord, DisposableAppender } from '@kbn/logging';
import type { Layout, LogRecord, DisposableAppender } from '@kbn/logging';
import { Layouts } from '../../layouts/layouts';
const { literal, object } = schema;

View file

@ -6,11 +6,11 @@
* Side Public License, v 1.
*/
export type { Conversion } from './type';
export { LoggerConversion } from './logger';
export { LevelConversion } from './level';
export { MessageConversion } from './message';
export { MetaConversion } from './meta';
export { PidConversion } from './pid';
export { DateConversion } from './date';
export { LevelConversion } from './level';
export { LoggerConversion } from './logger';
export {
DateConversion,
MessageConversion,
MetaConversion,
} from '@kbn/core-logging-common-internal';

View file

@ -8,8 +8,7 @@
import chalk from 'chalk';
import { LogRecord, LogLevel } from '@kbn/logging';
import { Conversion } from './type';
import type { Conversion } from '@kbn/core-logging-common-internal';
const LEVEL_COLORS = new Map([
[LogLevel.Fatal, chalk.red],

View file

@ -8,8 +8,7 @@
import chalk from 'chalk';
import { LogRecord } from '@kbn/logging';
import { Conversion } from './type';
import type { Conversion } from '@kbn/core-logging-common-internal';
export const LoggerConversion: Conversion = {
pattern: /%logger/g,

View file

@ -6,8 +6,8 @@
* Side Public License, v 1.
*/
import { LogRecord } from '@kbn/logging';
import { Conversion } from './type';
import type { LogRecord } from '@kbn/logging';
import type { Conversion } from '@kbn/core-logging-common-internal';
export const PidConversion: Conversion = {
pattern: /%pid/g,

View file

@ -7,10 +7,11 @@
*/
import { schema } from '@kbn/config-schema';
import { LogRecord, Layout } from '@kbn/logging';
import {
Conversion,
PatternLayout as BasePatternLayout,
type Conversion,
} from '@kbn/core-logging-common-internal';
import {
LoggerConversion,
LevelConversion,
MetaConversion,
@ -19,9 +20,6 @@ import {
DateConversion,
} from './conversions';
/**
* Default pattern used by PatternLayout if it's not overridden in the configuration.
*/
const DEFAULT_PATTERN = `[%date][%level][%logger] %message`;
export const patternSchema = schema.string({
@ -50,23 +48,14 @@ const conversions: Conversion[] = [
* color highlighting (eg. to make log messages easier to read in the terminal).
* @internal
*/
export class PatternLayout implements Layout {
export class PatternLayout extends BasePatternLayout {
public static configSchema = patternLayoutSchema;
constructor(private readonly pattern = DEFAULT_PATTERN, private readonly highlight = false) {}
/**
* Formats `LogRecord` into a string based on the specified `pattern` and `highlighting` options.
* @param record Instance of `LogRecord` to format into string.
*/
public format(record: LogRecord): string {
let recordString = this.pattern;
for (const conversion of conversions) {
recordString = recordString.replace(
conversion.pattern,
conversion.convert.bind(null, record, this.highlight)
);
}
return recordString;
constructor(pattern: string = DEFAULT_PATTERN, highlight: boolean = false) {
super({
pattern,
highlight,
conversions,
});
}
}

View file

@ -6,72 +6,16 @@
* Side Public License, v 1.
*/
import apmAgent from 'elastic-apm-node';
import {
Appender,
LogLevel,
LogLevelId,
LogRecord,
LoggerFactory,
LogMeta,
Logger,
} from '@kbn/logging';
import { LogLevel, LogRecord, LogMeta } from '@kbn/logging';
import { AbstractLogger } from '@kbn/core-logging-common-internal';
function isError(x: any): x is Error {
return x instanceof Error;
}
/** @internal */
export class BaseLogger implements Logger {
constructor(
private readonly context: string,
private readonly level: LogLevel,
private readonly appenders: Appender[],
private readonly factory: LoggerFactory
) {}
public trace<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Trace, message, meta));
}
public debug<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Debug, message, meta));
}
public info<Meta extends LogMeta = LogMeta>(message: string, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Info, message, meta));
}
public warn<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Warn, errorOrMessage, meta));
}
public error<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Error, errorOrMessage, meta));
}
public fatal<Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta): void {
this.log(this.createLogRecord<Meta>(LogLevel.Fatal, errorOrMessage, meta));
}
public isLevelEnabled(levelId: LogLevelId): boolean {
return this.level.supports(LogLevel.fromId(levelId));
}
public log(record: LogRecord) {
if (!this.level.supports(record.level)) {
return;
}
for (const appender of this.appenders) {
appender.append(record);
}
}
public get(...childContextPaths: string[]): Logger {
return this.factory.get(...[this.context, ...childContextPaths]);
}
private createLogRecord<Meta extends LogMeta>(
export class BaseLogger extends AbstractLogger {
protected createLogRecord<Meta extends LogMeta>(
level: LogLevel,
errorOrMessage: string | Error,
meta?: Meta

View file

@ -7,6 +7,12 @@
*/
import { schema, TypeOf } from '@kbn/config-schema';
import {
ROOT_CONTEXT_NAME,
DEFAULT_APPENDER_NAME,
getLoggerContext,
getParentLoggerContext,
} from '@kbn/core-logging-common-internal';
import type { AppenderConfigType, LoggerConfigType } from '@kbn/core-logging-server';
import { Appenders } from './appenders/appenders';
@ -14,21 +20,6 @@ import { Appenders } from './appenders/appenders';
// (otherwise it assumes an array of A|B instead of a tuple [A,B])
const toTuple = <A, B>(a: A, b: B): [A, B] => [a, b];
/**
* Separator string that used within nested context name (eg. plugins.pid).
*/
const CONTEXT_SEPARATOR = '.';
/**
* Name of the `root` context that always exists and sits at the top of logger hierarchy.
*/
const ROOT_CONTEXT_NAME = 'root';
/**
* Name of the appender that is always presented and used by `root` logger by default.
*/
const DEFAULT_APPENDER_NAME = 'default';
const levelSchema = schema.oneOf(
[
schema.literal('all'),
@ -109,7 +100,7 @@ export class LoggingConfig {
* @returns {string} Joined context string (e.g. 'parent.child').
*/
public static getLoggerContext(contextParts: string[]) {
return contextParts.join(CONTEXT_SEPARATOR) || ROOT_CONTEXT_NAME;
return getLoggerContext(contextParts);
}
/**
@ -118,12 +109,7 @@ export class LoggingConfig {
* @returns Name of the parent context or `root` if the context is the top level one.
*/
public static getParentLoggerContext(context: string) {
const lastIndexOfSeparator = context.lastIndexOf(CONTEXT_SEPARATOR);
if (lastIndexOfSeparator === -1) {
return ROOT_CONTEXT_NAME;
}
return context.slice(0, lastIndexOfSeparator);
return getParentLoggerContext(context);
}
/**

View file

@ -0,0 +1,64 @@
/*
* 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 { DiscoveredPlugin, PluginOpaqueId, PluginType } from '@kbn/core-base-common';
import { type MockedLogger, loggerMock } from '@kbn/logging-mocks';
import type { PluginInitializerContext } from '@kbn/core-plugins-browser';
import { coreContextMock } from '@kbn/core-base-browser-mocks';
import { createPluginInitializerContext } from './plugin_context';
const createPluginManifest = (pluginName: string): DiscoveredPlugin => {
return {
id: pluginName,
configPath: [pluginName],
type: PluginType.standard,
requiredPlugins: [],
optionalPlugins: [],
requiredBundles: [],
};
};
const testPluginId = 'testPluginId';
describe('createPluginInitializerContext', () => {
let pluginId: PluginOpaqueId;
let pluginManifest: DiscoveredPlugin;
let pluginConfig: Record<string, unknown>;
let coreContext: ReturnType<typeof coreContextMock.create>;
let logger: MockedLogger;
let initContext: PluginInitializerContext;
beforeEach(() => {
pluginId = Symbol(testPluginId);
pluginManifest = createPluginManifest(testPluginId);
pluginConfig = {};
coreContext = coreContextMock.create();
logger = coreContext.logger as MockedLogger;
initContext = createPluginInitializerContext(
coreContext,
pluginId,
pluginManifest,
pluginConfig
);
});
describe('logger.get', () => {
it('calls the underlying logger factory with the correct parameters', () => {
initContext.logger.get('service.sub');
expect(logger.get).toHaveBeenCalledTimes(1);
expect(logger.get).toHaveBeenCalledWith('plugins', testPluginId, 'service.sub');
});
it('returns the logger from the underlying factory', () => {
const underlyingLogger = loggerMock.create();
logger.get.mockReturnValue(underlyingLogger);
expect(initContext.logger.get('anything')).toEqual(underlyingLogger);
});
});
});

View file

@ -35,6 +35,11 @@ export function createPluginInitializerContext(
return {
opaqueId,
env: coreContext.env,
logger: {
get(...contextParts) {
return coreContext.logger.get('plugins', pluginManifest.id, ...contextParts);
},
},
config: {
get<T>() {
return pluginConfig as unknown as T;

View file

@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
import { loggerMock } from '@kbn/logging-mocks';
import type { PluginInitializerContext } from '@kbn/core-plugins-browser';
export const createPluginInitializerContextMock = (config: unknown = {}) => {
@ -25,6 +26,7 @@ export const createPluginInitializerContextMock = (config: unknown = {}) => {
dist: false,
},
},
logger: loggerMock.create(),
config: {
get: <T>() => config as T,
},

View file

@ -36,12 +36,14 @@ NPM_MODULE_EXTRA_FILES = [
]
RUNTIME_DEPS = [
"//packages/kbn-logging-mocks",
]
TYPES_DEPS = [
"@npm//@types/node",
"@npm//@types/jest",
"//packages/kbn-utility-types:npm_module_types",
"//packages/kbn-logging-mocks:npm_module_types",
"//packages/core/plugins/core-plugins-browser-internal:npm_module_types",
]

View file

@ -7,6 +7,7 @@
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { loggerMock } from '@kbn/logging-mocks';
import type { PluginInitializerContext } from '@kbn/core-plugins-browser';
import type { PluginsService, PluginsServiceSetup } from '@kbn/core-plugins-browser-internal';
@ -43,6 +44,7 @@ const createPluginInitializerContextMock = (config: unknown = {}) => {
dist: false,
},
},
logger: loggerMock.create(),
config: {
get: <T>() => config as T,
},

View file

@ -7,6 +7,7 @@
*/
import type { PluginOpaqueId } from '@kbn/core-base-common';
import type { LoggerFactory } from '@kbn/logging';
import type { PackageInfo, EnvironmentMode } from '@kbn/config';
import type { Plugin } from './plugin';
@ -37,6 +38,7 @@ export interface PluginInitializerContext<ConfigSchema extends object = object>
mode: Readonly<EnvironmentMode>;
packageInfo: Readonly<PackageInfo>;
};
readonly logger: LoggerFactory;
readonly config: {
get: <T extends object = ConfigSchema>() => T;
};

View file

@ -44,6 +44,7 @@ RUNTIME_DEPS = [
"//packages/kbn-i18n",
"//packages/kbn-ebt-tools",
"//packages/core/application/core-application-browser-internal",
"//packages/core/logging/core-logging-browser-internal",
"//packages/core/injected-metadata/core-injected-metadata-browser-internal",
"//packages/core/doc-links/core-doc-links-browser-internal",
"//packages/core/theme/core-theme-browser-internal",
@ -76,6 +77,7 @@ TYPES_DEPS = [
"//packages/core/execution-context/core-execution-context-browser:npm_module_types",
"//packages/core/application/core-application-browser-internal:npm_module_types",
"//packages/core/base/core-base-browser-internal:npm_module_types",
"//packages/core/logging/core-logging-browser-internal:npm_module_types",
"//packages/core/injected-metadata/core-injected-metadata-browser-internal:npm_module_types",
"//packages/core/doc-links/core-doc-links-browser-internal:npm_module_types",
"//packages/core/theme/core-theme-browser-internal:npm_module_types",

View file

@ -22,6 +22,7 @@ import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
import { renderingServiceMock } from '@kbn/core-rendering-browser-mocks';
import { integrationsServiceMock } from '@kbn/core-integrations-browser-mocks';
import { coreAppsMock } from '@kbn/core-apps-browser-mocks';
import { loggingSystemMock } from '@kbn/core-logging-browser-mocks';
export const analyticsServiceStartMock = analyticsServiceMock.createAnalyticsServiceStart();
export const MockAnalyticsService = analyticsServiceMock.create();
@ -137,3 +138,9 @@ export const ThemeServiceConstructor = jest.fn().mockImplementation(() => MockTh
jest.doMock('@kbn/core-theme-browser-internal', () => ({
ThemeService: ThemeServiceConstructor,
}));
export const MockLoggingSystem = loggingSystemMock.create();
export const LoggingSystemConstructor = jest.fn().mockImplementation(() => MockLoggingSystem);
jest.doMock('@kbn/core-logging-browser-internal', () => ({
BrowserLoggingSystem: LoggingSystemConstructor,
}));

View file

@ -38,8 +38,10 @@ import {
MockAnalyticsService,
analyticsServiceStartMock,
fetchOptionalMemoryInfoMock,
MockLoggingSystem,
LoggingSystemConstructor,
} from './core_system.test.mocks';
import type { EnvironmentMode } from '@kbn/config';
import { CoreSystem } from './core_system';
import {
KIBANA_LOADED_EVENT,
@ -136,6 +138,7 @@ describe('constructor', () => {
expect(CoreAppConstructor).toHaveBeenCalledTimes(1);
expect(ThemeServiceConstructor).toHaveBeenCalledTimes(1);
expect(AnalyticsServiceConstructor).toHaveBeenCalledTimes(1);
expect(LoggingSystemConstructor).toHaveBeenCalledTimes(1);
});
it('passes injectedMetadata param to InjectedMetadataService', () => {
@ -180,6 +183,47 @@ describe('constructor', () => {
stopCoreSystem();
expect(coreSystem.stop).toHaveBeenCalled();
});
describe('logging system', () => {
it('instantiate the logging system with the correct level when in dev mode', () => {
const envMode: EnvironmentMode = {
name: 'development',
dev: true,
prod: false,
};
const injectedMetadata = { env: { mode: envMode } } as any;
createCoreSystem({
injectedMetadata,
});
expect(LoggingSystemConstructor).toHaveBeenCalledTimes(1);
expect(LoggingSystemConstructor).toHaveBeenCalledWith({
logLevel: 'all',
});
});
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);
});
});
});
describe('#setup()', () => {

View file

@ -7,11 +7,13 @@
*/
import { filter, firstValueFrom } from 'rxjs';
import type { LogLevelId } from '@kbn/logging';
import type { CoreContext } from '@kbn/core-base-browser-internal';
import {
InjectedMetadataService,
type InjectedMetadataParams,
} from '@kbn/core-injected-metadata-browser-internal';
import { BrowserLoggingSystem } from '@kbn/core-logging-browser-internal';
import { DocLinksService } from '@kbn/core-doc-links-browser-internal';
import { ThemeService } from '@kbn/core-theme-browser-internal';
import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser';
@ -78,6 +80,7 @@ interface ExtendedNavigator {
* @internal
*/
export class CoreSystem {
private readonly loggingSystem: BrowserLoggingSystem;
private readonly analytics: AnalyticsService;
private readonly fatalErrors: FatalErrorsService;
private readonly injectedMetadata: InjectedMetadataService;
@ -106,20 +109,24 @@ export class CoreSystem {
this.rootDomElement = rootDomElement;
this.i18n = new I18nService();
const logLevel: LogLevelId = injectedMetadata.env.mode.dev ? 'all' : 'warn';
this.loggingSystem = new BrowserLoggingSystem({ logLevel });
this.injectedMetadata = new InjectedMetadataService({
injectedMetadata,
});
this.coreContext = { coreId: Symbol('core'), env: injectedMetadata.env };
this.coreContext = {
coreId: Symbol('core'),
env: injectedMetadata.env,
logger: this.loggingSystem.asLoggerFactory(),
};
this.i18n = new I18nService();
this.analytics = new AnalyticsService(this.coreContext);
this.fatalErrors = new FatalErrorsService(rootDomElement, () => {
// Stop Core before rendering any fatal errors into the DOM
this.stop();
});
this.theme = new ThemeService();
this.notifications = new NotificationsService();
this.http = new HttpService();
@ -136,7 +143,6 @@ export class CoreSystem {
this.integrations = new IntegrationsService();
this.deprecations = new DeprecationsService();
this.executionContext = new ExecutionContextService();
this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins);
this.coreApp = new CoreAppsService(this.coreContext);

View file

@ -32,6 +32,7 @@ export {
} from '@kbn/core-saved-objects-browser-mocks';
export { applicationServiceMock, scopedHistoryMock } from '@kbn/core-application-browser-mocks';
export { deprecationsServiceMock } from '@kbn/core-deprecations-browser-mocks';
export { loggingSystemMock } from '@kbn/core-logging-browser-mocks';
function createStorageMock() {
const storageMock: jest.Mocked<Storage> = {

View file

@ -204,6 +204,12 @@
"@kbn/core-lifecycle-server-internal/*": ["packages/core/lifecycle/core-lifecycle-server-internal/*"],
"@kbn/core-lifecycle-server-mocks": ["packages/core/lifecycle/core-lifecycle-server-mocks"],
"@kbn/core-lifecycle-server-mocks/*": ["packages/core/lifecycle/core-lifecycle-server-mocks/*"],
"@kbn/core-logging-browser-internal": ["packages/core/logging/core-logging-browser-internal"],
"@kbn/core-logging-browser-internal/*": ["packages/core/logging/core-logging-browser-internal/*"],
"@kbn/core-logging-browser-mocks": ["packages/core/logging/core-logging-browser-mocks"],
"@kbn/core-logging-browser-mocks/*": ["packages/core/logging/core-logging-browser-mocks/*"],
"@kbn/core-logging-common-internal": ["packages/core/logging/core-logging-common-internal"],
"@kbn/core-logging-common-internal/*": ["packages/core/logging/core-logging-common-internal/*"],
"@kbn/core-logging-server": ["packages/core/logging/core-logging-server"],
"@kbn/core-logging-server/*": ["packages/core/logging/core-logging-server/*"],
"@kbn/core-logging-server-internal": ["packages/core/logging/core-logging-server-internal"],

View file

@ -3116,6 +3116,18 @@
version "0.0.0"
uid ""
"@kbn/core-logging-browser-internal@link:bazel-bin/packages/core/logging/core-logging-browser-internal":
version "0.0.0"
uid ""
"@kbn/core-logging-browser-mocks@link:bazel-bin/packages/core/logging/core-logging-browser-mocks":
version "0.0.0"
uid ""
"@kbn/core-logging-common-internal@link:bazel-bin/packages/core/logging/core-logging-common-internal":
version "0.0.0"
uid ""
"@kbn/core-logging-server-internal@link:bazel-bin/packages/core/logging/core-logging-server-internal":
version "0.0.0"
uid ""