Skip to content

Commit

Permalink
Add smart logic to log information about plugin status changes (#168207)
Browse files Browse the repository at this point in the history
## Summary

New attempt at fixing #116718
Inspired on #126320

Here's what the newly logged `[status]` information looks like on a
fresh startup:
<img width="1834" alt="image"
src="https://github.com/elastic/kibana/assets/25349407/d78d7f88-139f-4daf-9dc0-c4e6724ea412">

The first 2 entries are logs from Core services 🆕 .
The next 5 entries are emitted due to `taskManager` plugin emitting a
degraded status right at startup.
I have created an issue to tackle that one:
#168237

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
gsoldevila and kibanamachine authored Oct 25, 2023
1 parent 551e4f0 commit 310ae23
Show file tree
Hide file tree
Showing 21 changed files with 1,527 additions and 495 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,37 @@

import type { Observable } from 'rxjs';
import type { PluginName } from '@kbn/core-base-common';
import type { ServiceStatus } from '@kbn/core-status-common';

import { type Deps, PluginsStatusService as BasePluginsStatusService } from './plugins_status';
import type { PluginStatus } from './types';

export class PluginsStatusService extends BasePluginsStatusService {
private all$?: Observable<Record<PluginName, ServiceStatus>>;
private dependenciesStatuses$: Record<PluginName, Observable<Record<PluginName, ServiceStatus>>>;
private derivedStatuses$: Record<PluginName, Observable<ServiceStatus>>;
private all$?: Observable<Record<PluginName, PluginStatus>>;
private dependenciesStatuses$: Record<PluginName, Observable<Record<PluginName, PluginStatus>>>;
private derivedStatuses$: Record<PluginName, Observable<PluginStatus>>;

constructor(deps: Deps) {
super(deps);
this.dependenciesStatuses$ = {};
this.derivedStatuses$ = {};
}

public getAll$(): Observable<Record<PluginName, ServiceStatus>> {
public getAll$(): Observable<Record<PluginName, PluginStatus>> {
if (!this.all$) {
this.all$ = super.getAll$();
}

return this.all$;
}

public getDependenciesStatus$(plugin: PluginName): Observable<Record<PluginName, ServiceStatus>> {
public getDependenciesStatus$(plugin: PluginName): Observable<Record<PluginName, PluginStatus>> {
if (!this.dependenciesStatuses$[plugin]) {
this.dependenciesStatuses$[plugin] = super.getDependenciesStatus$(plugin);
}

return this.dependenciesStatuses$[plugin];
}

public getDerivedStatus$(plugin: PluginName): Observable<ServiceStatus> {
public getDerivedStatus$(plugin: PluginName): Observable<PluginStatus> {
if (!this.derivedStatuses$[plugin]) {
this.derivedStatuses$[plugin] = super.getDerivedStatus$(plugin);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,160 +8,127 @@

import { ServiceStatus, ServiceStatusLevels } from '@kbn/core-status-common';
import { getSummaryStatus } from './get_summary_status';
import { PluginStatus } from './types';

describe('getSummaryStatus', () => {
const available: ServiceStatus = { level: ServiceStatusLevels.available, summary: 'Available' };
const degraded: ServiceStatus = {
const availableService: ServiceStatus = {
level: ServiceStatusLevels.available,
summary: 'Available',
};
const degradedService: ServiceStatus = {
level: ServiceStatusLevels.degraded,
summary: 'This is degraded!',
};
const unavailable: ServiceStatus = {
level: ServiceStatusLevels.unavailable,
summary: 'This is unavailable!',
};
const critical: ServiceStatus = {
const criticalService: ServiceStatus = {
level: ServiceStatusLevels.critical,
summary: 'This is critical!',
};
const availablePluginA: PluginStatus = {
level: ServiceStatusLevels.available,
summary: 'A is available',
reported: true,
};
const unavailablePluginA: PluginStatus = {
level: ServiceStatusLevels.unavailable,
summary: 'A is unavailable!',
reported: true,
};
const availablePluginB: PluginStatus = {
level: ServiceStatusLevels.available,
summary: 'B is available',
reported: true,
};
const unavailablePluginB: PluginStatus = {
level: ServiceStatusLevels.unavailable,
summary: 'B is unavailable!',
reported: true,
};
const unavailablePluginC: PluginStatus = {
level: ServiceStatusLevels.unavailable,
summary: 'C is unavailable!',
// Note that C has an inferred status
};

it('returns available when all status are available', () => {
expect(
getSummaryStatus(
Object.entries({
s1: available,
s2: available,
s3: available,
})
)
).toMatchObject({
level: ServiceStatusLevels.available,
});
getSummaryStatus({
serviceStatuses: { elasticsearch: availableService, savedObjects: availableService },
pluginStatuses: { a: availablePluginA, b: availablePluginB },
})
).toMatchInlineSnapshot(`
Object {
"level": "available",
"summary": "All services and plugins are available",
}
`);
});

it('returns degraded when the worst status is degraded', () => {
expect(
getSummaryStatus(
Object.entries({
s1: available,
s2: degraded,
s3: available,
})
)
).toMatchObject({
level: ServiceStatusLevels.degraded,
});
getSummaryStatus({
serviceStatuses: { elasticsearch: degradedService, savedObjects: availableService },
pluginStatuses: { a: availablePluginA, b: availablePluginB },
})
).toMatchInlineSnapshot(`
Object {
"detail": "See the status page for more information",
"level": "degraded",
"meta": Object {
"affectedPlugins": Array [],
"failingPlugins": Array [],
"failingServices": Array [
"elasticsearch",
],
},
"summary": "1 service(s) and 0 plugin(s) are degraded: elasticsearch",
}
`);
});

it('returns unavailable when the worst status is unavailable', () => {
expect(
getSummaryStatus(
Object.entries({
s1: available,
s2: degraded,
s3: unavailable,
})
)
).toMatchObject({
level: ServiceStatusLevels.unavailable,
});
getSummaryStatus({
serviceStatuses: { elasticsearch: degradedService, savedObjects: availableService },
pluginStatuses: { a: unavailablePluginA, b: unavailablePluginB, c: unavailablePluginC },
})
).toMatchInlineSnapshot(`
Object {
"detail": "See the status page for more information",
"level": "unavailable",
"meta": Object {
"affectedPlugins": Array [
"c",
],
"failingPlugins": Array [
"a",
"b",
],
"failingServices": Array [],
},
"summary": "0 service(s) and 2 plugin(s) are unavailable: a, b",
}
`);
});

it('returns critical when the worst status is critical', () => {
expect(
getSummaryStatus(
Object.entries({
s1: critical,
s2: degraded,
s3: unavailable,
})
)
).toMatchObject({
level: ServiceStatusLevels.critical,
});
});

describe('summary', () => {
it('returns correct summary when a single service is affected', () => {
expect(
getSummaryStatus(
Object.entries({
s1: degraded,
s2: {
level: ServiceStatusLevels.unavailable,
summary: 'Lorem ipsum',
meta: {
custom: { data: 'here' },
},
},
})
)
).toEqual({
level: ServiceStatusLevels.unavailable,
summary: '1 service is unavailable: s2',
detail: 'See the status page for more information',
meta: {
affectedServices: ['s2'],
},
});
});

it('returns correct summary when multiple services are affected', () => {
expect(
getSummaryStatus(
Object.entries({
s1: degraded,
s2: {
level: ServiceStatusLevels.unavailable,
summary: 'Lorem ipsum',
detail: 'Vivamus pulvinar sem ac luctus ultrices.',
documentationUrl: 'http://helpmenow.com/problem1',
meta: {
custom: { data: 'here' },
},
},
s3: {
level: ServiceStatusLevels.unavailable,
summary: 'Proin mattis',
detail: 'Nunc quis nulla at mi lobortis pretium.',
documentationUrl: 'http://helpmenow.com/problem2',
meta: {
other: { data: 'over there' },
},
},
})
)
).toEqual({
level: ServiceStatusLevels.unavailable,
summary: '2 services are unavailable: s2, s3',
detail: 'See the status page for more information',
meta: {
affectedServices: ['s2', 's3'],
},
});
});

it('returns correct summary more than `maxServices` services are affected', () => {
expect(
getSummaryStatus(
Object.entries({
s1: degraded,
s2: available,
s3: degraded,
s4: degraded,
s5: degraded,
s6: available,
s7: degraded,
}),
{ maxServices: 3 }
)
).toEqual({
level: ServiceStatusLevels.degraded,
summary: '5 services are degraded: s1, s3, s4 and 2 other(s)',
detail: 'See the status page for more information',
meta: {
affectedServices: ['s1', 's3', 's4', 's5', 's7'],
getSummaryStatus({
serviceStatuses: { elasticsearch: degradedService, savedObjects: criticalService },
pluginStatuses: { a: availablePluginA, b: unavailablePluginB },
})
).toMatchInlineSnapshot(`
Object {
"detail": "See the status page for more information",
"level": "critical",
"meta": Object {
"affectedPlugins": Array [],
"failingPlugins": Array [],
"failingServices": Array [
"savedObjects",
],
},
});
});
"summary": "1 service(s) and 0 plugin(s) are critical: savedObjects",
}
`);
});
});
Loading

0 comments on commit 310ae23

Please sign in to comment.