-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
Changes from 32 commits
b6bb9fd
b575562
7658509
c730814
a6e4ad9
7f7782e
69a0472
874177f
5fbc957
d3d2740
7e481a7
a11e3fb
093a540
e0306fa
1a59360
34fe95a
914beff
c513554
a4df1eb
d3e1bda
885c3e0
e16f886
f4eb382
27e0e0e
a7f737d
8e0c58a
b9a7879
2e489bf
48cb94c
64b704e
f9bcc2d
5e7aad7
1714966
56f267a
ed5f6bf
2120a31
91eb1ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,6 +103,17 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = (settings, fromPath, | |
return settings; | ||
}; | ||
|
||
const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'), | ||
|
@@ -137,4 +148,5 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unu | |
rewriteBasePathDeprecation, | ||
cspRulesDeprecation, | ||
mapManifestServiceUrlDeprecation, | ||
opsLoggingEventDeprecation, | ||
]; |
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; | ||
TinaHeiligers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
/* | ||
* 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 { OpsMetrics } from '..'; | ||
import { getEcsOpsMetricsLog } from './get_ops_metrics_log'; | ||
|
||
function createBaseOpsMetrics(): OpsMetrics { | ||
return { | ||
collected_at: new Date('2020-01-01 01:00:00'), | ||
process: { | ||
memory: { | ||
heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 }, | ||
resident_set_size_in_bytes: 1, | ||
}, | ||
event_loop_delay: 1, | ||
pid: 1, | ||
uptime_in_millis: 1, | ||
}, | ||
os: { | ||
platform: 'darwin' as const, | ||
platformRelease: 'test', | ||
load: { '1m': 1, '5m': 1, '15m': 1 }, | ||
memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 }, | ||
uptime_in_millis: 1, | ||
}, | ||
response_times: { avg_in_millis: 1, max_in_millis: 1 }, | ||
requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } }, | ||
concurrent_connections: 1, | ||
}; | ||
} | ||
|
||
function createMockOpsMetrics(testMetrics: Partial<OpsMetrics>): OpsMetrics { | ||
const base = createBaseOpsMetrics(); | ||
return { | ||
...base, | ||
...testMetrics, | ||
}; | ||
} | ||
const testMetrics = ({ | ||
process: { | ||
memory: { heap: { used_in_bytes: 100 } }, | ||
uptime_in_millis: 1500, | ||
event_loop_delay: 50, | ||
}, | ||
os: { | ||
load: { | ||
'1m': 10, | ||
'5m': 20, | ||
'15m': 30, | ||
}, | ||
}, | ||
} as unknown) as Partial<OpsMetrics>; | ||
|
||
describe('getEcsOpsMetricsLog', () => { | ||
it('provides correctly formatted message', () => { | ||
const result = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); | ||
expect(result.message).toMatchInlineSnapshot( | ||
`"memory: 100.0B uptime: 0:00:01 load: [10.00,20.00,30.00] delay: 50.000"` | ||
); | ||
}); | ||
|
||
it('correctly formats process uptime', () => { | ||
const logMeta = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); | ||
expect(logMeta.process!.uptime).toEqual(1); | ||
}); | ||
|
||
it('excludes values from the message if unavailable', () => { | ||
const baseMetrics = createBaseOpsMetrics(); | ||
const missingMetrics = ({ | ||
...baseMetrics, | ||
process: {}, | ||
os: {}, | ||
} as unknown) as OpsMetrics; | ||
const logMeta = getEcsOpsMetricsLog(missingMetrics); | ||
expect(logMeta.message).toMatchInlineSnapshot(`""`); | ||
}); | ||
|
||
it('specifies correct ECS version', () => { | ||
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); | ||
expect(logMeta.ecs.version).toBe('1.7.0'); | ||
}); | ||
|
||
it('provides an ECS-compatible response', () => { | ||
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); | ||
expect(logMeta).toMatchInlineSnapshot(` | ||
Object { | ||
"ecs": Object { | ||
"version": "1.7.0", | ||
}, | ||
"event": Object { | ||
"category": Array [ | ||
"process", | ||
"host", | ||
], | ||
"kind": "metric", | ||
"type": "info", | ||
}, | ||
"host": Object { | ||
"os": Object { | ||
"load": Object { | ||
"15m": 1, | ||
"1m": 1, | ||
"5m": 1, | ||
}, | ||
}, | ||
}, | ||
"message": "memory: 1.0B load: [1.00,1.00,1.00] delay: 1.000", | ||
"process": Object { | ||
"eventLoopDelay": 1, | ||
"memory": Object { | ||
"heap": Object { | ||
"usedInBytes": 1, | ||
}, | ||
}, | ||
"uptime": 0, | ||
}, | ||
} | ||
`); | ||
}); | ||
|
||
it('logs ECS fields in the log meta', () => { | ||
const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); | ||
expect(logMeta.event!.kind).toBe('metric'); | ||
expect(logMeta.event!.category).toEqual(expect.arrayContaining(['process', 'host'])); | ||
expect(logMeta.event!.type).toBe('info'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* 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 numeral from '@elastic/numeral'; | ||
import { EcsOpsMetricsEvent } from '../../logging'; | ||
import { OpsMetrics } from '..'; | ||
|
||
const ECS_VERSION = '1.7.0'; | ||
/** | ||
* Converts ops metrics into ECS-compliant `LogMeta` for logging | ||
* | ||
* @internal | ||
*/ | ||
export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsOpsMetricsEvent { | ||
const { process, os } = metrics; | ||
const processMemoryUsedInBytes = process?.memory?.heap?.used_in_bytes; | ||
const processMemoryUsedInBytesMsg = processMemoryUsedInBytes | ||
? `memory: ${numeral(processMemoryUsedInBytes).format('0.0b')} ` | ||
: ''; | ||
|
||
// ECS process.uptime is in seconds: | ||
const uptimeVal = process?.uptime_in_millis | ||
? Math.floor(process.uptime_in_millis / 1000) | ||
: undefined; | ||
|
||
// HH:mm:ss message format for backward compatibility | ||
const uptimeValMsg = uptimeVal ? `uptime: ${numeral(uptimeVal).format('00:00:00')} ` : ''; | ||
|
||
// Event loop delay is in ms | ||
const eventLoopDelayVal = process?.event_loop_delay; | ||
const eventLoopDelayValMsg = eventLoopDelayVal | ||
? `delay: ${numeral(process?.event_loop_delay).format('0.000')}` | ||
: ''; | ||
|
||
const loadEntries = { | ||
'1m': os?.load ? os?.load['1m'] : undefined, | ||
'5m': os?.load ? os?.load['5m'] : undefined, | ||
'15m': os?.load ? os?.load['15m'] : undefined, | ||
}; | ||
|
||
const loadVals = [...Object.values(os?.load ?? [])]; | ||
const loadValsMsg = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The legacy implementation shows the load values as an array, without any indication of what the entries correspond with. In https://github.com/elastic/kibana/pull/88070/files#diff-4170a5b93a2348938d696556d9a4b5790e932e47c2273b0c797d4a857ebbac50R39-R43, I chose to be explicit about the entries. |
||
loadVals.length > 0 | ||
? `load: [${loadVals.map((val: number) => { | ||
TinaHeiligers marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return numeral(val).format('0.00'); | ||
})}] ` | ||
: ''; | ||
|
||
return { | ||
ecs: { version: ECS_VERSION }, | ||
message: `${processMemoryUsedInBytesMsg}${uptimeValMsg}${loadValsMsg}${eventLoopDelayValMsg}`, | ||
event: { | ||
kind: 'metric', | ||
category: ['process', 'host'], | ||
type: 'info', | ||
}, | ||
process: { | ||
uptime: uptimeVal, | ||
// @ts-expect-error custom fields not yet part of ECS | ||
memory: { | ||
heap: { | ||
usedInBytes: processMemoryUsedInBytes, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Custom fields are added in camel case to identify them as not being in ECS v1.7.0, as recommended. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional: I'd suggest reducing the amount of runtime logic and memory allocations here. What if we declare all the fields in the meta declaration and add |
||
}, | ||
}, | ||
eventLoopDelay: eventLoopDelayVal, | ||
}, | ||
host: { | ||
os: { | ||
load: loadEntries, | ||
}, | ||
}, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inspired by #87939
There was a problem hiding this comment.
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).