Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core.logging] Add ops logs to the KP logging system #88070

Merged
merged 37 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b6bb9fd
POC for KP ops metrics logging
TinaHeiligers Jan 12, 2021
b575562
Adds jest integration test for metrics service
TinaHeiligers Jan 14, 2021
7658509
Merge branch 'master' of github.com:elastic/kibana into logging/ops-m…
TinaHeiligers Jan 14, 2021
c730814
Adds documentation on KP implementation of ops metrics logs
TinaHeiligers Jan 14, 2021
a6e4ad9
Typo
TinaHeiligers Jan 14, 2021
7f7782e
Merge branch 'master' into logging/ops-metrics
kibanamachine Jan 17, 2021
69a0472
Merge branch 'master' into logging/ops-metrics
kibanamachine Jan 18, 2021
874177f
Merge branch 'master' of github.com:elastic/kibana into logging/ops-m…
TinaHeiligers Jan 20, 2021
5fbc957
Updates license headers
TinaHeiligers Jan 20, 2021
d3d2740
Uses new logger context and changes to ECS-compatible meta structure
TinaHeiligers Jan 20, 2021
7e481a7
Uses inline snapshots
TinaHeiligers Jan 20, 2021
a11e3fb
Add config deprecation for logging.events.ops
TinaHeiligers Jan 20, 2021
093a540
Uses real option for ops metrics deprecation test
TinaHeiligers Jan 20, 2021
e0306fa
Disable legacy ops logs when logging.verbose=true
TinaHeiligers Jan 20, 2021
1a59360
Removes unused method
TinaHeiligers Jan 20, 2021
34fe95a
removes unused import
TinaHeiligers Jan 20, 2021
914beff
Updates docs
TinaHeiligers Jan 20, 2021
c513554
Merge branch 'master' of github.com:elastic/kibana into logging/ops-m…
TinaHeiligers Jan 21, 2021
a4df1eb
reformats message string construction to handle empty message
TinaHeiligers Jan 21, 2021
d3e1bda
Moves ecs kind and ecs category fields to event namespace, adds event…
TinaHeiligers Jan 21, 2021
885c3e0
Moves root.shutdown to afterEach, removes unused code
TinaHeiligers Jan 21, 2021
e16f886
Moves test for log meta containing ECS fields to a unit test
TinaHeiligers Jan 21, 2021
f4eb382
Updates ops format table in the logging README
TinaHeiligers Jan 21, 2021
27e0e0e
Updates metrics_service unit test snapshots
TinaHeiligers Jan 21, 2021
a7f737d
Merge branch 'master' into logging/ops-metrics
kibanamachine Jan 25, 2021
8e0c58a
Moves custom fields into meta declaration
TinaHeiligers Jan 25, 2021
b9a7879
Deletes metrics_service jest integration test that is already covered…
TinaHeiligers Jan 25, 2021
2e489bf
Type fixes
TinaHeiligers Jan 25, 2021
48cb94c
Moves logger meta ECS type declarations to logging
TinaHeiligers Jan 25, 2021
64b704e
Merge branch 'master' into logging/ops-metrics
kibanamachine Jan 26, 2021
f9bcc2d
removes unnecessary log, simplifies test
TinaHeiligers Jan 26, 2021
5e7aad7
simplifies test after removing text log
TinaHeiligers Jan 26, 2021
1714966
Updates logging README, explicitly types event fields
TinaHeiligers Jan 26, 2021
56f267a
minor fix
TinaHeiligers Jan 26, 2021
ed5f6bf
Another logging README update
TinaHeiligers Jan 26, 2021
2120a31
Merge branch 'master' into logging/ops-metrics
kibanamachine Jan 26, 2021
91eb1ff
Merge branch 'master' into logging/ops-metrics
kibanamachine Jan 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/kbn-legacy-logging/src/get_logging_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ export function getLoggingConfiguration(config: LegacyLoggingConfig, opsInterval
} else if (config.verbose) {
_.defaults(events, {
log: '*',
ops: '*',
// To avoid duplicate logs, we explicitly disable this in verbose
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by #87939

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, assuming we for sure want to move forward with changing this behavior (I flagged this in my PR to be safe).

// mode as it is already provided by the new logging config under
// the `metrics.ops` context.
ops: '!',
request: '*',
response: '*',
error: '*',
Expand Down
6 changes: 4 additions & 2 deletions src/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ Even though the new validation system provided by the `core` is also based on Jo
rules tailored to our needs (e.g. `byteSize`, `duration` etc.). That means that config values that were previously accepted
by the "legacy" Kibana may be rejected by the `core` now.

Even though `core` has its own logging system it doesn't output log records directly (e.g. to file or terminal), but instead
forward them to the "legacy" Kibana so that they look the same as the rest of the log records throughout Kibana.
### Logging
`core` has its own [logging system](./server/logging/README.md) and will output log records directly (e.g. to file or terminal) when configured. When no
specific configuration is provided, logs are forwarded to the "legacy" Kibana so that they look the same as the rest of the
log records throughout Kibana.

## Core API Review
To provide a stable API for plugin developers, it is important that the Core Public and Server API's are stable and
Expand Down
20 changes: 20 additions & 0 deletions src/core/server/config/deprecation/core_deprecations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,24 @@ describe('core deprecations', () => {
).toEqual([`worker-src blob:`]);
});
});

describe('logging.events.ops', () => {
it('warns when ops events are used', () => {
const { messages } = applyCoreDeprecations({
logging: { events: { ops: '*' } },
});
expect(messages).toMatchInlineSnapshot(`
Array [
"\\"logging.events.ops\\" has been deprecated and will be removed in 8.0. To access ops data moving forward, please enable debug logs for the \\"metrics.ops\\" context in your logging configuration.",
]
`);
});

it('does not warn when other events are configured', () => {
const { messages } = applyCoreDeprecations({
logging: { events: { log: '*' } },
});
expect(messages).toEqual([]);
});
});
});
12 changes: 12 additions & 0 deletions src/core/server/config/deprecation/core_deprecations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = (settings, fromPath,
return settings;
};

const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
Copy link
Contributor Author

@TinaHeiligers TinaHeiligers Jan 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a similar approach as to #87939 (comment) but am open to other ways of implementing it.

if (has(settings, 'logging.events.ops')) {
log(
'"logging.events.ops" has been deprecated and will be removed ' +
'in 8.0. To access ops data moving forward, please enable debug logs for the ' +
'"metrics.ops" context in your logging configuration.'
);
}
return settings;
};

export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [
unusedFromRoot('savedObjects.indexCheckTimeout'),
unusedFromRoot('server.xsrf.token'),
Expand Down Expand Up @@ -137,4 +148,5 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unu
rewriteBasePathDeprecation,
cspRulesDeprecation,
mapManifestServiceUrlDeprecation,
opsLoggingEventDeprecation,
];
26 changes: 25 additions & 1 deletion src/core/server/logging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ logging:
- context: telemetry
level: all
appenders: [json-file-appender]
- context: metrics.ops
level: debug
appenders: [console]
```

Here is what we get with the config above:
Expand All @@ -324,12 +327,33 @@ Here is what we get with the config above:
| server | console, file | fatal |
| optimize | console | error |
| telemetry | json-file-appender | all |
| metrics.ops | console | debug |


### Dedicated loggers

The `root` logger has a dedicated configuration node since this context is special and should always exist. By
default `root` is configured with `info` level and `default` appender that is also always available. This is the
configuration that all custom loggers will use unless they're re-configured explicitly.

The `metrics.ops` logger is configured with `debug` level and will automatically output sample system and process information at a regular interval.
TinaHeiligers marked this conversation as resolved.
Show resolved Hide resolved
The metrics that are logged are a subset of the data collected and are formatted in the log message as follows:

| Ops formatted log property | Location in metrics service | Log units
| :------------------------- | :-------------------------- | :-------------------------- |
| memory | process.memory.heap.used_in_bytes | [depends on the value](http://numeraljs.com/#format), typically MB or GB |
| uptime | process.uptime_in_millis | HH:mm:ss |
| load | os.load | [ "load for the last 1 min" "load for the last 5 min" "load for the last 15 min"] |
| delay | process.event_loop_delay | ms |

The log interval is the same as the interval at which system and process information is refreshed and is configurable under `ops.interval`:

```yaml
ops.interval: 5000
```

The minimum interval is 100ms and defaults to 5000ms.
TinaHeiligers marked this conversation as resolved.
Show resolved Hide resolved

For example to see _all_ log messages that fall back on the `root` logger configuration, just add one line to the configuration:

```yaml
Expand Down Expand Up @@ -478,4 +502,4 @@ TBD
| meta | separate property `"meta": {"to": "v8"}` | merged in log record `{... "to": "v8"}` |
| pid | `pid: 12345` | `pid: 12345` |
| type | N/A | `type: log` |
| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` |
| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` |
161 changes: 161 additions & 0 deletions src/core/server/metrics/integration_tests/metrics_service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/

import * as kbnTestServer from '../../../test_helpers/kbn_server';
import { InternalCoreSetup } from '../../internal_types';
import { Root } from '../../root';
import { OpsMetrics } from '../types';

const otherTestSettings = {
ops: {
interval: 500,
},
logging: {
silent: true, // set "true" in kbnTestServer
appenders: {
'custom-console': {
kind: 'console',
layout: {
highlight: false,
kind: 'pattern',
pattern: '%message|%meta',
},
},
},
root: {
appenders: ['custom-console', 'default'],
level: 'warn',
},
loggers: [
{
context: 'metrics.ops',
appenders: ['custom-console'],
level: 'debug',
},
],
},
plugins: {
initialize: false,
},
};

describe('metrics service', () => {
let root: Root;
let coreSetup: InternalCoreSetup;
let mockConsoleLog: jest.SpyInstance;
let testData: OpsMetrics;

beforeAll(async () => {
mockConsoleLog = jest.spyOn(global.console, 'log');
});

afterEach(async () => {
mockConsoleLog.mockClear();
await root.shutdown();
});

afterAll(async () => {
mockConsoleLog.mockRestore();
if (root) {
await root.shutdown();
}
TinaHeiligers marked this conversation as resolved.
Show resolved Hide resolved
TinaHeiligers marked this conversation as resolved.
Show resolved Hide resolved
});

describe('setup', () => {
it('returns ops interval and getOpsMetrics$ observable', async () => {
root = kbnTestServer.createRoot({ ...otherTestSettings });
coreSetup = await root.setup();
expect(coreSetup.metrics).toHaveProperty(
'collectionInterval',
otherTestSettings.ops.interval
);
expect(coreSetup.metrics).toHaveProperty('getOpsMetrics$');
TinaHeiligers marked this conversation as resolved.
Show resolved Hide resolved
});
});

describe('ops metrics logging configuration', () => {
it('does not log with logging set to quiet', async () => {
TinaHeiligers marked this conversation as resolved.
Show resolved Hide resolved
root = kbnTestServer.createRoot({ logging: { quiet: true } });
coreSetup = await root.setup();

expect(mockConsoleLog).not.toHaveBeenCalled();
});

it('logs at the correct level and with the correct context', async () => {
const testSettings = {
ops: {
interval: 500,
},
logging: {
silent: true, // set "true" in kbnTestServer
appenders: {
'custom-console': {
kind: 'console',
layout: {
highlight: false,
kind: 'pattern',
pattern: '%level|%logger',
},
},
},
root: {
appenders: ['custom-console', 'default'],
level: 'warn',
},
loggers: [
{
context: 'metrics.ops',
appenders: ['custom-console'],
level: 'debug',
},
],
},
plugins: {
initialize: false,
},
};
root = kbnTestServer.createRoot({ ...testSettings });
coreSetup = await root.setup();

expect(mockConsoleLog).toHaveBeenCalledTimes(1);
const [level, logger] = mockConsoleLog.mock.calls[0][0].split('|');
expect(level).toBe('DEBUG');
expect(logger).toBe('metrics.ops');
});
});
describe('ops metrics logging content', () => {
it('logs memory, uptime, load and delay ops metrics in the message', async () => {
root = kbnTestServer.createRoot({ ...otherTestSettings });
coreSetup = await root.setup();

coreSetup.metrics.getOpsMetrics$().subscribe((opsMetrics) => {
testData = opsMetrics;
});
const expectedArray = ['memory:', 'uptime:', 'load:', 'delay:'];

expect(testData).toBeTruthy();
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
const [message] = mockConsoleLog.mock.calls[0][0].split('|');

const messageParts = message.split(' ');
const testParts = [messageParts[0], messageParts[2], messageParts[4], messageParts[6]];
// the contents of the message are variable based on the process environment,
// so we are only performing assertions against parts of the string
expect(testParts).toEqual(expect.arrayContaining(expectedArray));
});

it('logs structured data in the log meta', async () => {
root = kbnTestServer.createRoot({ ...otherTestSettings });
coreSetup = await root.setup();

const [, meta] = mockConsoleLog.mock.calls[0][0].split('|');
expect(Object.keys(JSON.parse(meta).host.os.load)).toEqual(['1m', '5m', '15m']);
TinaHeiligers marked this conversation as resolved.
Show resolved Hide resolved
expect(Object.keys(JSON.parse(meta).process)).toEqual(expect.arrayContaining(['uptime']));
});
});
});
46 changes: 46 additions & 0 deletions src/core/server/metrics/logging/ecs.ts
Original file line number Diff line number Diff line change
@@ -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
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/

/**
* Typings for some ECS fields which core uses internally.
* These are not a complete set of ECS typings and should not
* be used externally; the only types included here are ones
* currently used in core.
*
* @internal
*/

export interface EcsOpsMetricsEvent {
/**
* These typings were written as of ECS 1.7.0.
* Don't change this value without checking the rest
* of the types to conform to that ECS version.
*
* https://www.elastic.co/guide/en/ecs/1.7/index.html
*/
ecs: { version: '1.7.0' };

// base fields
['@timestamp']?: string;
labels?: Record<string, unknown>;
message?: string;
tags?: string[];
// other fields
process?: EcsProcessField;
event?: EcsEventField;
}

interface EcsProcessField {
uptime?: number;
}

interface EcsEventField {
kind?: string;
category?: string[];
type?: string;
}
Loading