mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
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:
parent
fd6047bb22
commit
2fb12fbc54
80 changed files with 2756 additions and 129 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
114
packages/core/logging/core-logging-browser-internal/BUILD.bazel
Normal file
114
packages/core/logging/core-logging-browser-internal/BUILD.bazel
Normal 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"],
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/core-logging-browser-internal
|
||||
|
||||
This package contains the internal types and implementation for Core's browser-side logging service.
|
|
@ -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';
|
|
@ -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'],
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-logging-browser-internal",
|
||||
"owner": "@elastic/kibana-core",
|
||||
"runtimeDeps": [],
|
||||
"typeDeps": [],
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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);
|
||||
});
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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';
|
|
@ -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';
|
|
@ -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"`;
|
|
@ -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';
|
|
@ -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]');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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) };
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.bazel.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "target_types",
|
||||
"stripInternal": false,
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
]
|
||||
}
|
115
packages/core/logging/core-logging-browser-mocks/BUILD.bazel
Normal file
115
packages/core/logging/core-logging-browser-mocks/BUILD.bazel
Normal 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"],
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/core-logging-browser-mocks
|
||||
|
||||
This package contains the mocks for Core's browser-side logging service.
|
|
@ -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';
|
|
@ -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'],
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-logging-browser-mocks",
|
||||
"owner": "@elastic/kibana-core",
|
||||
"runtimeDeps": [],
|
||||
"typeDeps": [],
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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';
|
|
@ -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,
|
||||
};
|
|
@ -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",
|
||||
]
|
||||
}
|
116
packages/core/logging/core-logging-common-internal/BUILD.bazel
Normal file
116
packages/core/logging/core-logging-common-internal/BUILD.bazel
Normal 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"],
|
||||
)
|
|
@ -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.
|
24
packages/core/logging/core-logging-common-internal/index.ts
Normal file
24
packages/core/logging/core-logging-common-internal/index.ts
Normal 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';
|
|
@ -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'],
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-logging-common-internal",
|
||||
"owner": "@elastic/kibana-core",
|
||||
"runtimeDeps": [],
|
||||
"typeDeps": [],
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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';
|
|
@ -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"`;
|
|
@ -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;
|
||||
|
|
@ -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';
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
},
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { LogRecord } from '@kbn/logging';
|
||||
import { Conversion } from './type';
|
||||
import { Conversion } from './types';
|
||||
|
||||
export const MessageConversion: Conversion = {
|
||||
pattern: /%message/g,
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { LogRecord } from '@kbn/logging';
|
||||
import { Conversion } from './type';
|
||||
import { Conversion } from './types';
|
||||
|
||||
export const MetaConversion: Conversion = {
|
||||
pattern: /%meta/g,
|
|
@ -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';
|
|
@ -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]');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.bazel.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "target_types",
|
||||
"stripInternal": false,
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
]
|
||||
}
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
}));
|
||||
|
|
|
@ -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()', () => {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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> = {
|
||||
|
|
|
@ -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"],
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -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 ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue