Use docLinks plugin to generate server side URL (#147940)

Resolves: https://github.com/elastic/kibana/issues/109937

In this PR, I'm fixing the `Task Manager detected a degradation in
performance` log message to provide the proper versioned link to the
task manager health monitoring docs. Prior to this PR, it would always
point to main / master docs.

## To verify

1. Turn on debug logging in your kibana.yml file
```
logging:
  loggers:
    - name: plugins.taskManager
      level: debug
```
2. Move this code line outside of the `if (logLevel..` statement =>
4c7ce9d249/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts (L99)
3. Startup Kibana
4. Notice the `Task Manager detected a degradation in performance...`
logged
5. Test the URL provided by the log message

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Mike Côté 2023-01-03 15:38:27 -05:00 committed by GitHub
parent 2ca590e006
commit 750e5e0e95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 71 additions and 32 deletions

View file

@ -490,6 +490,9 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
teamsAction: `${KIBANA_DOCS}teams-action-type.html#configuring-teams`,
connectors: `${KIBANA_DOCS}action-types.html`,
},
taskManager: {
healthMonitoring: `${KIBANA_DOCS}task-manager-health-monitoring.html`,
},
maps: {
guide: `${KIBANA_DOCS}maps.html`,
importGeospatialPrivileges: `${KIBANA_DOCS}import-geospatial-data.html#import-geospatial-privileges`,

View file

@ -351,7 +351,31 @@ export interface DocLinks {
syntheticsCommandReference: string;
syntheticsProjectMonitors: string;
}>;
readonly alerting: Record<string, string>;
readonly alerting: Readonly<{
guide: string;
actionTypes: string;
apmRules: string;
emailAction: string;
emailActionConfig: string;
emailExchangeClientSecretConfig: string;
emailExchangeClientIdConfig: string;
generalSettings: string;
indexAction: string;
esQuery: string;
indexThreshold: string;
pagerDutyAction: string;
preconfiguredConnectors: string;
preconfiguredAlertHistoryConnector: string;
serviceNowAction: string;
serviceNowSIRAction: string;
setupPrerequisites: string;
slackAction: string;
teamsAction: string;
connectors: string;
}>;
readonly taskManager: Readonly<{
healthMonitoring: string;
}>;
readonly maps: Readonly<{
guide: string;
importGeospatialPrivileges: string;

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { merge } from 'lodash';
import { loggingSystemMock } from '@kbn/core/server/mocks';
import { loggingSystemMock, docLinksServiceMock } from '@kbn/core/server/mocks';
import { configSchema, TaskManagerConfig } from '../config';
import { HealthStatus } from '../monitoring';
import { MonitoredHealth } from '../routes/health';
@ -18,6 +18,8 @@ jest.mock('./calculate_health_status', () => ({
}));
describe('logHealthMetrics', () => {
const docLinks = docLinksServiceMock.create().setup();
afterEach(() => {
const { calculateHealthStatus } = jest.requireMock('./calculate_health_status');
// Reset the last state by running through this as OK
@ -40,16 +42,16 @@ describe('logHealthMetrics', () => {
// We must change from OK to Warning
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.OK);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(
() => HealthStatus.Warning
);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
// We must change from OK to Error
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.OK);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.Error);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
const debugCalls = (logger as jest.Mocked<Logger>).debug.mock.calls;
const performanceMessage = /^Task Manager detected a degradation in performance/;
@ -78,9 +80,9 @@ describe('logHealthMetrics', () => {
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(
() => HealthStatus.Warning
);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.OK);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
expect((logger as jest.Mocked<Logger>).warn).not.toHaveBeenCalled();
});
@ -99,9 +101,9 @@ describe('logHealthMetrics', () => {
// We must change from Error to OK
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.Error);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.OK);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
expect((logger as jest.Mocked<Logger>).warn).not.toHaveBeenCalled();
});
@ -116,7 +118,7 @@ describe('logHealthMetrics', () => {
});
const health = getMockMonitoredHealth();
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
const firstDebug = JSON.parse(
(logger as jest.Mocked<Logger>).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
@ -135,7 +137,7 @@ describe('logHealthMetrics', () => {
});
const health = getMockMonitoredHealth();
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
const firstInfo = JSON.parse(
(logger as jest.Mocked<Logger>).info.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
@ -154,7 +156,7 @@ describe('logHealthMetrics', () => {
});
const health = getMockMonitoredHealth();
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
const firstDebug = JSON.parse(
(logger as jest.Mocked<Logger>).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
@ -177,7 +179,7 @@ describe('logHealthMetrics', () => {
() => HealthStatus.Warning
);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
const logMessage = JSON.parse(
((logger as jest.Mocked<Logger>).warn.mock.calls[0][0] as string).replace(
@ -201,7 +203,7 @@ describe('logHealthMetrics', () => {
const { calculateHealthStatus } = jest.requireMock('./calculate_health_status');
(calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.Error);
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
const logMessage = JSON.parse(
((logger as jest.Mocked<Logger>).error.mock.calls[0][0] as string).replace(
@ -241,7 +243,7 @@ describe('logHealthMetrics', () => {
},
});
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
expect((logger as jest.Mocked<Logger>).warn.mock.calls[0][0] as string).toBe(
`Detected delay task start of 60s for task(s) \"taskType:test\" (which exceeds configured value of 60s)`
@ -285,7 +287,7 @@ describe('logHealthMetrics', () => {
},
});
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
expect((logger as jest.Mocked<Logger>).warn.mock.calls[0][0] as string).toBe(
`Detected delay task start of 60s for task(s) \"taskType:test, taskType:test2\" (which exceeds configured value of 60s)`
@ -317,7 +319,7 @@ describe('logHealthMetrics', () => {
stats: {},
};
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
const firstDebug = JSON.parse(
(logger as jest.Mocked<Logger>).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
@ -354,7 +356,7 @@ describe('logHealthMetrics', () => {
},
});
logHealthMetrics(health, logger, config, false);
logHealthMetrics(health, logger, config, false, docLinks);
const firstDebug = JSON.parse(
(logger as jest.Mocked<Logger>).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
@ -379,7 +381,7 @@ describe('logHealthMetrics', () => {
},
});
logHealthMetrics(health, logger, config, true);
logHealthMetrics(health, logger, config, true, docLinks);
const { calculateHealthStatus } = jest.requireMock('./calculate_health_status');
expect(calculateHealthStatus).toBeCalledTimes(1);

View file

@ -5,10 +5,8 @@
* 2.0.
*/
import { kibanaPackageJson } from '@kbn/repo-info';
import { isEmpty } from 'lodash';
import { Logger } from '@kbn/core/server';
import { Logger, DocLinksServiceSetup } from '@kbn/core/server';
import { HealthStatus } from '../monitoring';
import { TaskManagerConfig } from '../config';
import { MonitoredHealth } from '../routes/health';
@ -29,7 +27,8 @@ export function logHealthMetrics(
monitoredHealth: MonitoredHealth,
logger: Logger,
config: TaskManagerConfig,
shouldRunTasks: boolean
shouldRunTasks: boolean,
docLinks: DocLinksServiceSetup
) {
let logLevel: LogLevel =
config.monitored_stats_health_verbose_log.level === 'info' ? LogLevel.Info : LogLevel.Debug;
@ -54,10 +53,7 @@ export function logHealthMetrics(
}
const message = `Latest Monitored Stats: ${JSON.stringify(monitoredHealth)}`;
// TODO: remove when docs support "main"
const docsBranch = kibanaPackageJson.branch === 'main' ? 'master' : 'main';
const docLink = `https://www.elastic.co/guide/en/kibana/${docsBranch}/task-manager-health-monitoring.html`;
const docLink = docLinks.links.taskManager.healthMonitoring;
const detectedProblemMessage = `Task Manager detected a degradation in performance. This is usually temporary, and Kibana can recover automatically. If the problem persists, check the docs for troubleshooting information: ${docLink} .`;
// Drift looks at runtime stats which are not available when task manager is not running tasks

View file

@ -135,6 +135,7 @@ export class TaskManagerPlugin
getClusterClient: () =>
startServicesPromise.then(({ elasticsearch }) => elasticsearch.client),
shouldRunTasks: this.shouldRunBackgroundTasks,
docLinks: core.docLinks,
});
const monitoredUtilization$ = backgroundTaskUtilizationRoute({
router,
@ -200,6 +201,7 @@ export class TaskManagerPlugin
savedObjects,
elasticsearch,
executionContext,
docLinks,
}: CoreStart): TaskManagerStartContract {
const savedObjectsRepository = savedObjects.createInternalRepository(['task']);

View file

@ -9,7 +9,7 @@ import { Observable, of, Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import { merge } from 'lodash';
import uuid from 'uuid';
import { httpServiceMock } from '@kbn/core/server/mocks';
import { httpServiceMock, docLinksServiceMock } from '@kbn/core/server/mocks';
import { healthRoute } from './health';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { sleep } from '../test_utils';
@ -47,6 +47,7 @@ const createMockClusterClient = (response: any) => {
describe('healthRoute', () => {
const logger = loggingSystemMock.create().get();
const docLinks = docLinksServiceMock.create().setup();
beforeEach(() => {
jest.resetAllMocks();
@ -65,6 +66,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
const [config] = router.get.mock.calls[0];
@ -88,6 +90,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(mockClusterClient),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
const [, handler] = router.get.mock.calls[0];
@ -129,6 +132,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(mockClusterClient),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
const [, handler] = router.get.mock.calls[0];
@ -175,6 +179,7 @@ describe('healthRoute', () => {
kibanaIndexName: 'foo',
getClusterClient: () => Promise.resolve(mockClusterClient),
shouldRunTasks: true,
docLinks,
});
const [, handler] = router.get.mock.calls[0];
@ -218,6 +223,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
stats$.next(mockStat);
@ -278,6 +284,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
stats$.next(warnRuntimeStat);
@ -356,6 +363,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
stats$.next(errorRuntimeStat);
@ -420,6 +428,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
const serviceStatus = getLatest(serviceStatus$);
@ -502,6 +511,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
await sleep(0);
@ -576,6 +586,7 @@ describe('healthRoute', () => {
getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()),
usageCounter: mockUsageCounter,
shouldRunTasks: true,
docLinks,
});
await sleep(0);

View file

@ -12,7 +12,7 @@ import {
IKibanaResponse,
KibanaResponseFactory,
} from '@kbn/core/server';
import { IClusterClient } from '@kbn/core/server';
import { IClusterClient, DocLinksServiceSetup } from '@kbn/core/server';
import { Observable, Subject } from 'rxjs';
import { tap, map } from 'rxjs/operators';
import { throttleTime } from 'rxjs/operators';
@ -60,6 +60,7 @@ export interface HealthRouteParams {
shouldRunTasks: boolean;
getClusterClient: () => Promise<IClusterClient>;
usageCounter?: UsageCounter;
docLinks: DocLinksServiceSetup;
}
export function healthRoute(params: HealthRouteParams): {
@ -77,6 +78,7 @@ export function healthRoute(params: HealthRouteParams): {
getClusterClient,
usageCounter,
shouldRunTasks,
docLinks,
} = params;
// if "hot" health stats are any more stale than monitored_stats_required_freshness (pollInterval +1s buffer by default)
@ -111,7 +113,7 @@ export function healthRoute(params: HealthRouteParams): {
.subscribe(([monitoredHealth, serviceStatus]) => {
serviceStatus$.next(serviceStatus);
monitoredHealth$.next(monitoredHealth);
logHealthMetrics(monitoredHealth, logger, config, shouldRunTasks);
logHealthMetrics(monitoredHealth, logger, config, shouldRunTasks, docLinks);
});
router.get(

View file

@ -16,7 +16,6 @@
"@kbn/utility-types",
"@kbn/safer-lodash-set",
"@kbn/es-types",
"@kbn/repo-info",
"@kbn/apm-utils",
],
"exclude": [