Add logging prometheus and ruler rules totals (#76812)

pull/77153/head
Konrad Lalik 2 years ago committed by GitHub
parent dff7403b29
commit 4fc0294aab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 75
      public/app/features/alerting/unified/Analytics.ts
  2. 14
      public/app/features/alerting/unified/RuleList.test.tsx
  3. 8
      public/app/features/alerting/unified/components/alert-groups/MatcherFilter.test.tsx
  4. 3
      public/app/features/alerting/unified/components/alert-groups/MatcherFilter.tsx
  5. 13
      public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.test.tsx
  6. 3
      public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx
  7. 4
      public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx
  8. 3
      public/app/features/alerting/unified/components/rules/NoRulesCTA.tsx
  9. 14
      public/app/features/alerting/unified/components/rules/RuleListGroupView.test.tsx
  10. 3
      public/app/features/alerting/unified/components/rules/RuleListGroupView.tsx
  11. 14
      public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx
  12. 3
      public/app/features/alerting/unified/components/rules/RulesFilter.tsx
  13. 15
      public/app/features/alerting/unified/components/rules/RulesGroup.test.tsx
  14. 3
      public/app/features/alerting/unified/components/rules/RulesGroup.tsx
  15. 19
      public/app/features/alerting/unified/state/actions.ts

@ -4,6 +4,9 @@ import { getBackendSrv, logError } from '@grafana/runtime';
import { config, reportInteraction } from '@grafana/runtime/src';
import { contextSrv } from 'app/core/core';
import { RuleNamespace } from '../../../types/unified-alerting';
import { RulerRulesConfigDTO } from '../../../types/unified-alerting-dto';
export const USER_CREATION_MIN_DAYS = 7;
export const LogMessages = {
@ -51,6 +54,78 @@ export function withPerformanceLogging<TFunc extends (...args: any[]) => Promise
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function withPromRulesMetadataLogging<TFunc extends (...args: any[]) => Promise<RuleNamespace[]>>(
func: TFunc,
message: string,
context: Record<string, string>
) {
return async (...args: Parameters<TFunc>) => {
const startLoadingTs = performance.now();
const response = await func(...args);
const { namespacesCount, groupsCount, rulesCount } = getPromRulesMetadata(response);
logInfo(message, {
loadTimeMs: (performance.now() - startLoadingTs).toFixed(0),
namespacesCount,
groupsCount,
rulesCount,
...context,
});
return response;
};
}
function getPromRulesMetadata(promRules: RuleNamespace[]) {
const namespacesCount = promRules.length;
const groupsCount = promRules.flatMap((ns) => ns.groups).length;
const rulesCount = promRules.flatMap((ns) => ns.groups).flatMap((g) => g.rules).length;
const metadata = {
namespacesCount: namespacesCount.toFixed(0),
groupsCount: groupsCount.toFixed(0),
rulesCount: rulesCount.toFixed(0),
};
return metadata;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function withRulerRulesMetadataLogging<TFunc extends (...args: any[]) => Promise<RulerRulesConfigDTO>>(
func: TFunc,
message: string,
context: Record<string, string>
) {
return async (...args: Parameters<TFunc>) => {
const startLoadingTs = performance.now();
const response = await func(...args);
const { namespacesCount, groupsCount, rulesCount } = getRulerRulesMetadata(response);
logInfo(message, {
loadTimeMs: (performance.now() - startLoadingTs).toFixed(0),
namespacesCount,
groupsCount,
rulesCount,
...context,
});
return response;
};
}
function getRulerRulesMetadata(rulerRules: RulerRulesConfigDTO) {
const namespacesCount = Object.keys(rulerRules).length;
const groups = Object.values(rulerRules).flatMap((groups) => groups);
const rules = groups.flatMap((group) => group.rules);
return {
namespacesCount: namespacesCount.toFixed(0),
groupsCount: groups.length.toFixed(0),
rulesCount: rules.length.toFixed(0),
};
}
export async function isNewUser() {
try {
const { createdAt } = await getBackendSrv().get(`/api/user`);

@ -5,14 +5,14 @@ import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';
import { byRole, byTestId, byText } from 'testing-library-selector';
import { DataSourceSrv, locationService, logInfo, setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
import { DataSourceSrv, locationService, setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv';
import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
import * as actions from 'app/features/alerting/unified/state/actions';
import { AccessControlAction } from 'app/types';
import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto';
import { LogMessages } from './Analytics';
import * as analytics from './Analytics';
import RuleList from './RuleList';
import { discoverFeatures } from './api/buildInfo';
import { fetchRules } from './api/prometheus';
@ -46,14 +46,8 @@ jest.mock('app/core/core', () => ({
emit: () => {},
},
}));
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
logInfo: jest.fn(),
};
});
jest.spyOn(analytics, 'logInfo');
jest.spyOn(config, 'getAllDataSources');
jest.spyOn(actions, 'rulesInSameGroupHaveInvalidFor').mockReturnValue([]);
@ -830,7 +824,7 @@ describe('RuleList', () => {
await userEvent.click(button);
expect(logInfo).toHaveBeenCalledWith(LogMessages.alertRuleFromScratch);
expect(analytics.logInfo).toHaveBeenCalledWith(analytics.LogMessages.alertRuleFromScratch);
});
});
});

@ -3,13 +3,11 @@ import userEvent from '@testing-library/user-event';
import lodash from 'lodash'; // eslint-disable-line lodash/import-scope
import React from 'react';
import * as runtime from '@grafana/runtime';
import { LogMessages } from '../../Analytics';
import * as analytics from '../../Analytics';
import { MatcherFilter } from './MatcherFilter';
const logInfoSpy = jest.spyOn(runtime, 'logInfo');
const logInfoSpy = jest.spyOn(analytics, 'logInfo');
describe('Analytics', () => {
beforeEach(() => {
@ -25,7 +23,7 @@ describe('Analytics', () => {
const searchInput = screen.getByTestId('search-query-input');
await userEvent.type(searchInput, 'job=');
expect(logInfoSpy).toHaveBeenCalledWith(LogMessages.filterByLabel);
expect(logInfoSpy).toHaveBeenCalledWith(analytics.LogMessages.filterByLabel);
});
it('should call onChange handler', async () => {

@ -4,10 +4,9 @@ import React, { FormEvent, useEffect, useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { logInfo } from '@grafana/runtime';
import { Label, Tooltip, Input, Icon, useStyles2 } from '@grafana/ui';
import { LogMessages } from '../../Analytics';
import { logInfo, LogMessages } from '../../Analytics';
interface Props {
className?: string;

@ -2,11 +2,10 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { logInfo } from '@grafana/runtime';
import { PanelModel } from 'app/features/dashboard/state';
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import { LogMessages } from '../../Analytics';
import * as analytics from '../../Analytics';
import { NewRuleFromPanelButton } from './NewRuleFromPanelButton';
@ -24,13 +23,7 @@ jest.mock('react-router-dom', () => ({
}),
}));
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
logInfo: jest.fn(),
};
});
jest.spyOn(analytics, 'logInfo');
jest.mock('react-use', () => ({
useAsync: () => ({ loading: false, value: {} }),
@ -52,6 +45,6 @@ describe('Analytics', () => {
await userEvent.click(button);
expect(logInfo).toHaveBeenCalledWith(LogMessages.alertRuleFromPanel);
expect(analytics.logInfo).toHaveBeenCalledWith(analytics.LogMessages.alertRuleFromPanel);
});
});

@ -3,12 +3,11 @@ import { useLocation } from 'react-router-dom';
import { useAsync } from 'react-use';
import { urlUtil } from '@grafana/data';
import { logInfo } from '@grafana/runtime';
import { Alert, Button, LinkButton } from '@grafana/ui';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { useSelector } from 'app/types';
import { LogMessages } from '../../Analytics';
import { logInfo, LogMessages } from '../../Analytics';
import { panelToRuleFormValues } from '../../utils/rule-form';
interface Props {

@ -5,7 +5,7 @@ import { Link, useParams } from 'react-router-dom';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { config, logInfo } from '@grafana/runtime';
import { config } from '@grafana/runtime';
import { Button, ConfirmModal, CustomScrollbar, HorizontalGroup, Spinner, useStyles2 } from '@grafana/ui';
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
import { useAppNotification } from 'app/core/copy/appNotification';
@ -15,7 +15,7 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { useDispatch } from 'app/types';
import { RuleWithLocation } from 'app/types/unified-alerting';
import { LogMessages, trackNewAlerRuleFormError } from '../../../Analytics';
import { logInfo, LogMessages, trackNewAlerRuleFormError } from '../../../Analytics';
import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector';
import { deleteRuleAction, saveRuleFormAction } from '../../../state/actions';
import { RuleFormType, RuleFormValues } from '../../../types/rule-form';

@ -3,11 +3,10 @@ import React from 'react';
import { GrafanaTheme2 } from '@grafana/data/src/themes';
import { Stack } from '@grafana/experimental';
import { logInfo } from '@grafana/runtime';
import { CallToActionCard, useStyles2 } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { LogMessages } from '../../Analytics';
import { logInfo, LogMessages } from '../../Analytics';
import { useRulesAccess } from '../../utils/accessControlHooks';
export const NoRulesSplash = () => {

@ -4,25 +4,19 @@ import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { byRole } from 'testing-library-selector';
import { locationService, logInfo } from '@grafana/runtime';
import { locationService } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv';
import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types';
import { CombinedRuleNamespace } from 'app/types/unified-alerting';
import { LogMessages } from '../../Analytics';
import * as analytics from '../../Analytics';
import { mockCombinedRule, mockDataSource } from '../../mocks';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
import { RuleListGroupView } from './RuleListGroupView';
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
logInfo: jest.fn(),
};
});
jest.spyOn(analytics, 'logInfo');
const ui = {
grafanaRulesHeading: byRole('heading', { name: 'Grafana' }),
@ -97,7 +91,7 @@ describe('RuleListGroupView', () => {
renderRuleList(namespaces);
expect(logInfo).toHaveBeenCalledWith(LogMessages.loadedList);
expect(analytics.logInfo).toHaveBeenCalledWith(analytics.LogMessages.loadedList);
});
});
});

@ -1,9 +1,8 @@
import React, { useEffect, useMemo } from 'react';
import { logInfo } from '@grafana/runtime';
import { CombinedRuleNamespace } from 'app/types/unified-alerting';
import { LogMessages } from '../../Analytics';
import { LogMessages, logInfo } from '../../Analytics';
import { AlertSourceAction } from '../../hooks/useAbilities';
import { isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource';
import { Authorize } from '../Authorize';

@ -4,20 +4,14 @@ import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';
import { byLabelText, byRole } from 'testing-library-selector';
import { locationService, logInfo, setDataSourceSrv } from '@grafana/runtime';
import { locationService, setDataSourceSrv } from '@grafana/runtime';
import { LogMessages } from '../../Analytics';
import * as analytics from '../../Analytics';
import { MockDataSourceSrv } from '../../mocks';
import RulesFilter from './RulesFilter';
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
logInfo: jest.fn(),
};
});
jest.spyOn(analytics, 'logInfo');
jest.mock('./MultipleDataSourcePicker', () => {
const original = jest.requireActual('./MultipleDataSourcePicker');
@ -91,6 +85,6 @@ describe('Analytics', () => {
await userEvent.click(button);
expect(logInfo).toHaveBeenCalledWith(LogMessages.clickingAlertStateFilters);
expect(analytics.logInfo).toHaveBeenCalledWith(analytics.LogMessages.clickingAlertStateFilters);
});
});

@ -4,12 +4,11 @@ import { useForm } from 'react-hook-form';
import { DataSourceInstanceSettings, GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { logInfo } from '@grafana/runtime';
import { Button, Field, Icon, Input, Label, RadioButtonGroup, Tooltip, useStyles2 } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';
import { LogMessages } from '../../Analytics';
import { logInfo, LogMessages } from '../../Analytics';
import { useRulesFilter } from '../../hooks/useFilteredRules';
import { RuleHealth } from '../../search/rulesSearchParser';
import { alertStateToReadable } from '../../utils/rules';

@ -5,13 +5,12 @@ import { Provider } from 'react-redux';
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
import { byRole, byTestId, byText } from 'testing-library-selector';
import { logInfo } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv';
import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types';
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
import { LogMessages } from '../../Analytics';
import * as analytics from '../../Analytics';
import { useHasRuler } from '../../hooks/useHasRuler';
import { mockExportApi, mockFolderApi, setupMswServer } from '../../mockApi';
import { grantUserPermissions, mockCombinedRule, mockDataSource, mockFolder, mockGrafanaRulerRule } from '../../mocks';
@ -19,13 +18,9 @@ import { grantUserPermissions, mockCombinedRule, mockDataSource, mockFolder, moc
import { RulesGroup } from './RulesGroup';
jest.mock('../../hooks/useHasRuler');
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
logInfo: jest.fn(),
};
});
jest.spyOn(analytics, 'logInfo');
jest.mock('react-virtualized-auto-sizer', () => {
return ({ children }: AutoSizerProps) => children({ height: 600, width: 1 });
});
@ -235,7 +230,7 @@ describe('Rules group tests', () => {
await userEvent.click(screen.getByText('Cancel'));
expect(screen.queryByText('Cancel')).not.toBeInTheDocument();
expect(logInfo).toHaveBeenCalledWith(LogMessages.leavingRuleGroupEdit);
expect(analytics.logInfo).toHaveBeenCalledWith(analytics.LogMessages.leavingRuleGroupEdit);
});
});
});

@ -4,12 +4,11 @@ import React, { useEffect, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { logInfo } from '@grafana/runtime';
import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui';
import { useDispatch } from 'app/types';
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
import { LogMessages } from '../../Analytics';
import { logInfo, LogMessages } from '../../Analytics';
import { useFolder } from '../../hooks/useFolder';
import { useHasRuler } from '../../hooks/useHasRuler';
import { deleteRulesGroupAction } from '../../state/actions';

@ -33,7 +33,13 @@ import {
} from 'app/types/unified-alerting-dto';
import { backendSrv } from '../../../../core/services/backend_srv';
import { logInfo, LogMessages, withPerformanceLogging } from '../Analytics';
import {
logInfo,
LogMessages,
withPerformanceLogging,
withPromRulesMetadataLogging,
withRulerRulesMetadataLogging,
} from '../Analytics';
import {
addAlertManagers,
createOrUpdateSilence,
@ -115,10 +121,11 @@ export const fetchPromRulesAction = createAsyncThunk(
): Promise<RuleNamespace[]> => {
await thunkAPI.dispatch(fetchRulesSourceBuildInfoAction({ rulesSourceName }));
const fetchRulesWithLogging = withPerformanceLogging(fetchRules, `[${rulesSourceName}] Prometheus rules loaded`, {
dataSourceName: rulesSourceName,
thunk: 'unifiedalerting/fetchPromRules',
});
const fetchRulesWithLogging = withPromRulesMetadataLogging(
fetchRules,
`[${rulesSourceName}] Prometheus rules loaded`,
{ dataSourceName: rulesSourceName, thunk: 'unifiedalerting/fetchPromRules' }
);
return await withSerializedError(
fetchRulesWithLogging(rulesSourceName, filter, limitAlerts, matcher, state, identifier)
@ -155,7 +162,7 @@ export const fetchRulerRulesAction = createAsyncThunk(
await dispatch(fetchRulesSourceBuildInfoAction({ rulesSourceName }));
const rulerConfig = getDataSourceRulerConfig(getState, rulesSourceName);
const fetchRulerRulesWithLogging = withPerformanceLogging(
const fetchRulerRulesWithLogging = withRulerRulesMetadataLogging(
fetchRulerRules,
`[${rulesSourceName}] Ruler rules loaded`,
{

Loading…
Cancel
Save