Skip to content

Commit e12ccd4

Browse files
[7.x] [core.logging] Add ops logs to the KP logging system (#88070) (#89419)
Co-authored-by: Kibana Machine <[email protected]>
1 parent 68ae33c commit e12ccd4

File tree

12 files changed

+482
-6
lines changed

12 files changed

+482
-6
lines changed

packages/kbn-legacy-logging/src/get_logging_config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export function getLoggingConfiguration(config: LegacyLoggingConfig, opsInterval
2828
} else if (config.verbose) {
2929
_.defaults(events, {
3030
log: '*',
31-
ops: '*',
31+
// To avoid duplicate logs, we explicitly disable this in verbose
32+
// mode as it is already provided by the new logging config under
33+
// the `metrics.ops` context.
34+
ops: '!',
3235
request: '*',
3336
response: '*',
3437
error: '*',

src/core/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ Even though the new validation system provided by the `core` is also based on Jo
2828
rules tailored to our needs (e.g. `byteSize`, `duration` etc.). That means that config values that were previously accepted
2929
by the "legacy" Kibana may be rejected by the `core` now.
3030

31-
Even though `core` has its own logging system it doesn't output log records directly (e.g. to file or terminal), but instead
32-
forward them to the "legacy" Kibana so that they look the same as the rest of the log records throughout Kibana.
31+
### Logging
32+
`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
33+
specific configuration is provided, logs are forwarded to the "legacy" Kibana so that they look the same as the rest of the
34+
log records throughout Kibana.
3335

3436
## Core API Review
3537
To provide a stable API for plugin developers, it is important that the Core Public and Server API's are stable and

src/core/server/config/deprecation/core_deprecations.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,24 @@ describe('core deprecations', () => {
256256
).toEqual([`worker-src blob:`]);
257257
});
258258
});
259+
260+
describe('logging.events.ops', () => {
261+
it('warns when ops events are used', () => {
262+
const { messages } = applyCoreDeprecations({
263+
logging: { events: { ops: '*' } },
264+
});
265+
expect(messages).toMatchInlineSnapshot(`
266+
Array [
267+
"\\"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.",
268+
]
269+
`);
270+
});
271+
272+
it('does not warn when other events are configured', () => {
273+
const { messages } = applyCoreDeprecations({
274+
logging: { events: { log: '*' } },
275+
});
276+
expect(messages).toEqual([]);
277+
});
278+
});
259279
});

src/core/server/config/deprecation/core_deprecations.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ const serverHostZeroDeprecation: ConfigDeprecation = (settings, fromPath, log) =
113113
return settings;
114114
};
115115

116+
const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
117+
if (has(settings, 'logging.events.ops')) {
118+
log(
119+
'"logging.events.ops" has been deprecated and will be removed ' +
120+
'in 8.0. To access ops data moving forward, please enable debug logs for the ' +
121+
'"metrics.ops" context in your logging configuration.'
122+
);
123+
}
124+
return settings;
125+
};
126+
116127
export const coreDeprecationProvider: ConfigDeprecationProvider = ({
117128
unusedFromRoot,
118129
renameFromRoot,
@@ -159,4 +170,5 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({
159170
cspRulesDeprecation,
160171
mapManifestServiceUrlDeprecation,
161172
serverHostZeroDeprecation,
173+
opsLoggingEventDeprecation,
162174
];

src/core/server/logging/README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ logging:
312312
- context: telemetry
313313
level: all
314314
appenders: [json-file-appender]
315+
- context: metrics.ops
316+
level: debug
317+
appenders: [console]
315318
```
316319

317320
Here is what we get with the config above:
@@ -324,6 +327,7 @@ Here is what we get with the config above:
324327
| server | console, file | fatal |
325328
| optimize | console | error |
326329
| telemetry | json-file-appender | all |
330+
| metrics.ops | console | debug |
327331

328332

329333
The `root` logger has a dedicated configuration node since this context is special and should always exist. By
@@ -341,7 +345,25 @@ Or disable logging entirely with `off`:
341345
```yaml
342346
logging.root.level: off
343347
```
348+
### Dedicated loggers
349+
350+
The `metrics.ops` logger is configured with `debug` level and will automatically output sample system and process information at a regular interval.
351+
The metrics that are logged are a subset of the data collected and are formatted in the log message as follows:
352+
353+
| Ops formatted log property | Location in metrics service | Log units
354+
| :------------------------- | :-------------------------- | :-------------------------- |
355+
| memory | process.memory.heap.used_in_bytes | [depends on the value](http://numeraljs.com/#format), typically MB or GB |
356+
| uptime | process.uptime_in_millis | HH:mm:ss |
357+
| load | os.load | [ "load for the last 1 min" "load for the last 5 min" "load for the last 15 min"] |
358+
| delay | process.event_loop_delay | ms |
359+
360+
The log interval is the same as the interval at which system and process information is refreshed and is configurable under `ops.interval`:
361+
362+
```yaml
363+
ops.interval: 5000
364+
```
344365

366+
The minimum interval is 100ms and defaults to 5000ms.
345367
## Usage
346368

347369
Usage is very straightforward, one should just get a logger for a specific context and use it to log messages with
@@ -478,4 +500,4 @@ TBD
478500
| meta | separate property `"meta": {"to": "v8"}` | merged in log record `{... "to": "v8"}` |
479501
| pid | `pid: 12345` | `pid: 12345` |
480502
| type | N/A | `type: log` |
481-
| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` |
503+
| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` |

src/core/server/logging/ecs.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* and the Server Side Public License, v 1; you may not use this file except in
5+
* compliance with, at your election, the Elastic License or the Server Side
6+
* Public License, v 1.
7+
*/
8+
9+
/**
10+
* Typings for some ECS fields which core uses internally.
11+
* These are not a complete set of ECS typings and should not
12+
* be used externally; the only types included here are ones
13+
* currently used in core.
14+
*
15+
* @internal
16+
*/
17+
18+
export interface EcsOpsMetricsEvent {
19+
/**
20+
* These typings were written as of ECS 1.7.0.
21+
* Don't change this value without checking the rest
22+
* of the types to conform to that ECS version.
23+
*
24+
* https://www.elastic.co/guide/en/ecs/1.7/index.html
25+
*/
26+
ecs: { version: '1.7.0' };
27+
28+
// base fields
29+
['@timestamp']?: string;
30+
labels?: Record<string, unknown>;
31+
message?: string;
32+
tags?: string[];
33+
// other fields
34+
process?: EcsProcessField;
35+
event?: EcsEventField;
36+
}
37+
38+
interface EcsProcessField {
39+
uptime?: number;
40+
}
41+
42+
export interface EcsEventField {
43+
kind?: EcsEventKind;
44+
category?: EcsEventCategory[];
45+
type?: EcsEventType;
46+
}
47+
48+
export enum EcsEventKind {
49+
ALERT = 'alert',
50+
EVENT = 'event',
51+
METRIC = 'metric',
52+
STATE = 'state',
53+
PIPELINE_ERROR = 'pipeline_error',
54+
SIGNAL = 'signal',
55+
}
56+
57+
export enum EcsEventCategory {
58+
AUTHENTICATION = 'authentication',
59+
CONFIGURATION = 'configuration',
60+
DATABASE = 'database',
61+
DRIVER = 'driver',
62+
FILE = 'file',
63+
HOST = 'host',
64+
IAM = 'iam',
65+
INTRUSION_DETECTION = 'intrusion_detection',
66+
MALWARE = 'malware',
67+
NETWORK = 'network',
68+
PACKAGE = 'package',
69+
PROCESS = 'process',
70+
WEB = 'web',
71+
}
72+
73+
export enum EcsEventType {
74+
ACCESS = 'access',
75+
ADMIN = 'admin',
76+
ALLOWED = 'allowed',
77+
CHANGE = 'change',
78+
CONNECTION = 'connection',
79+
CREATION = 'creation',
80+
DELETION = 'deletion',
81+
DENIED = 'denied',
82+
END = 'end',
83+
ERROR = 'error',
84+
GROUP = 'group',
85+
INFO = 'info',
86+
INSTALLATION = 'installation',
87+
PROTOCOL = 'protocol',
88+
START = 'start',
89+
USER = 'user',
90+
}

src/core/server/logging/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ export {
1717
LogLevelId,
1818
LogLevel,
1919
} from '@kbn/logging';
20+
export {
21+
EcsOpsMetricsEvent,
22+
EcsEventField,
23+
EcsEventKind,
24+
EcsEventCategory,
25+
EcsEventType,
26+
} from './ecs';
2027
export {
2128
config,
2229
LoggingConfigType,
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* and the Server Side Public License, v 1; you may not use this file except in
5+
* compliance with, at your election, the Elastic License or the Server Side
6+
* Public License, v 1.
7+
*/
8+
9+
import { OpsMetrics } from '..';
10+
import { getEcsOpsMetricsLog } from './get_ops_metrics_log';
11+
12+
function createBaseOpsMetrics(): OpsMetrics {
13+
return {
14+
collected_at: new Date('2020-01-01 01:00:00'),
15+
process: {
16+
memory: {
17+
heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 },
18+
resident_set_size_in_bytes: 1,
19+
},
20+
event_loop_delay: 1,
21+
pid: 1,
22+
uptime_in_millis: 1,
23+
},
24+
os: {
25+
platform: 'darwin' as const,
26+
platformRelease: 'test',
27+
load: { '1m': 1, '5m': 1, '15m': 1 },
28+
memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 },
29+
uptime_in_millis: 1,
30+
},
31+
response_times: { avg_in_millis: 1, max_in_millis: 1 },
32+
requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } },
33+
concurrent_connections: 1,
34+
};
35+
}
36+
37+
function createMockOpsMetrics(testMetrics: Partial<OpsMetrics>): OpsMetrics {
38+
const base = createBaseOpsMetrics();
39+
return {
40+
...base,
41+
...testMetrics,
42+
};
43+
}
44+
const testMetrics = ({
45+
process: {
46+
memory: { heap: { used_in_bytes: 100 } },
47+
uptime_in_millis: 1500,
48+
event_loop_delay: 50,
49+
},
50+
os: {
51+
load: {
52+
'1m': 10,
53+
'5m': 20,
54+
'15m': 30,
55+
},
56+
},
57+
} as unknown) as Partial<OpsMetrics>;
58+
59+
describe('getEcsOpsMetricsLog', () => {
60+
it('provides correctly formatted message', () => {
61+
const result = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics));
62+
expect(result.message).toMatchInlineSnapshot(
63+
`"memory: 100.0B uptime: 0:00:01 load: [10.00,20.00,30.00] delay: 50.000"`
64+
);
65+
});
66+
67+
it('correctly formats process uptime', () => {
68+
const logMeta = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics));
69+
expect(logMeta.process!.uptime).toEqual(1);
70+
});
71+
72+
it('excludes values from the message if unavailable', () => {
73+
const baseMetrics = createBaseOpsMetrics();
74+
const missingMetrics = ({
75+
...baseMetrics,
76+
process: {},
77+
os: {},
78+
} as unknown) as OpsMetrics;
79+
const logMeta = getEcsOpsMetricsLog(missingMetrics);
80+
expect(logMeta.message).toMatchInlineSnapshot(`""`);
81+
});
82+
83+
it('specifies correct ECS version', () => {
84+
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics());
85+
expect(logMeta.ecs.version).toBe('1.7.0');
86+
});
87+
88+
it('provides an ECS-compatible response', () => {
89+
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics());
90+
expect(logMeta).toMatchInlineSnapshot(`
91+
Object {
92+
"ecs": Object {
93+
"version": "1.7.0",
94+
},
95+
"event": Object {
96+
"category": Array [
97+
"process",
98+
"host",
99+
],
100+
"kind": "metric",
101+
"type": "info",
102+
},
103+
"host": Object {
104+
"os": Object {
105+
"load": Object {
106+
"15m": 1,
107+
"1m": 1,
108+
"5m": 1,
109+
},
110+
},
111+
},
112+
"message": "memory: 1.0B load: [1.00,1.00,1.00] delay: 1.000",
113+
"process": Object {
114+
"eventLoopDelay": 1,
115+
"memory": Object {
116+
"heap": Object {
117+
"usedInBytes": 1,
118+
},
119+
},
120+
"uptime": 0,
121+
},
122+
}
123+
`);
124+
});
125+
126+
it('logs ECS fields in the log meta', () => {
127+
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics());
128+
expect(logMeta.event!.kind).toBe('metric');
129+
expect(logMeta.event!.category).toEqual(expect.arrayContaining(['process', 'host']));
130+
expect(logMeta.event!.type).toBe('info');
131+
});
132+
});

0 commit comments

Comments
 (0)