mirror of
https://github.com/grafana/grafana.git
synced 2026-07-29 15:59:50 -05:00
Add logging prometheus and ruler rules totals (#76812)
This commit is contained in:
@@ -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;
|
||||
|
||||
+3
-10
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
+1
-2
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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`,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user