mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[UII] Allow to reset log level for agents >= 8.15.0 (#183434)
‼️ Should be reverted if https://github.com/elastic/elastic-agent/issues/4747 does not make 8.15.0. ## Summary Resolves #180778 This PR allows agent log level to be reset back to the level set on its policy (or if not set, simply the default agent level, see https://github.com/elastic/elastic-agent/pull/3090). To achieve this, this PR: - Allows `null` to be passed for the log level settings action, i.e.: ``` POST kbn:/api/fleet/agents/<AGENT_ID>/actions {"action":{"type":"SETTINGS","data":{"log_level": null}}} ``` - Enables the agent policy log level setting implemented in https://github.com/elastic/kibana/pull/180607 - Always show `Apply changes` on the agent details > Logs tab - For agents >= 8.15.0, always show `Reset to policy` on the agent details > Logs tab - Ensures both buttons are disabled if user does not have access to write to agents <img width="1254" alt="image" src="bcdf763e
-2053-4071-9aa8-8bcb57b8fee1"> <img width="1267" alt="image" src="182ac54d
-d5ad-435f-9376-70bb24f288f9"> ### Caveats 1. The reported agent log level is not accurate if agent is using the level from its policy and does not have a log level set on its own level (https://github.com/elastic/elastic-agent/issues/4747), so the initial selection on the agent log level could be wrong 2. We have no way to tell where the log level came from (https://github.com/elastic/elastic-agent/issues/4748), so that's why `Apply changes` and `Reset to policy` are always shown ### Testing Use the latest `8.15.0-SNAPSHOT` for agents or fleet server to test this change ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
b2118427f1
commit
4c80f262db
10 changed files with 244 additions and 121 deletions
|
@ -39,17 +39,10 @@ export const DEFAULT_MAX_AGENT_POLICIES_WITH_INACTIVITY_TIMEOUT = 750;
|
|||
export const AGENTLESS_POLICY_ID = 'agentless'; // the policy id defined here: https://github.com/elastic/project-controller/blob/main/internal/project/security/security_kibana_config.go#L86
|
||||
|
||||
export const AGENT_LOG_LEVELS = {
|
||||
ERROR: 'error',
|
||||
WARNING: 'warning',
|
||||
INFO: 'info',
|
||||
DEBUG: 'debug',
|
||||
info: 'info',
|
||||
debug: 'debug',
|
||||
warning: 'warning',
|
||||
error: 'error',
|
||||
};
|
||||
|
||||
export const DEFAULT_LOG_LEVEL = AGENT_LOG_LEVELS.INFO;
|
||||
|
||||
export const agentLoggingLevels = {
|
||||
Info: 'info',
|
||||
Debug: 'debug',
|
||||
Warning: 'warning',
|
||||
Error: 'error',
|
||||
} as const;
|
||||
export const DEFAULT_LOG_LEVEL = AGENT_LOG_LEVELS.info;
|
||||
|
|
|
@ -7294,6 +7294,7 @@
|
|||
"properties": {
|
||||
"log_level": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"enum": [
|
||||
"debug",
|
||||
"info",
|
||||
|
|
|
@ -4674,6 +4674,7 @@ components:
|
|||
properties:
|
||||
log_level:
|
||||
type: string
|
||||
nullable: true
|
||||
enum:
|
||||
- debug
|
||||
- info
|
||||
|
|
|
@ -19,6 +19,7 @@ oneOf:
|
|||
properties:
|
||||
log_level:
|
||||
type: string
|
||||
nullable: true
|
||||
enum:
|
||||
- debug
|
||||
- info
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiCode } from '@elastic/eui';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { agentLoggingLevels } from '../constants';
|
||||
import { AGENT_LOG_LEVELS, DEFAULT_LOG_LEVEL } from '../constants';
|
||||
|
||||
import type { SettingsConfig } from './types';
|
||||
|
||||
|
@ -130,20 +133,19 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [
|
|||
},
|
||||
{
|
||||
name: 'agent.logging.level',
|
||||
hidden: true,
|
||||
title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingLevelTitle', {
|
||||
defaultMessage: 'Agent logging level',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.fleet.settings.agentPolicyAdvanced.agentLoggingLevelDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Sets the log level for all the agents on the policy. The default log level is "info".',
|
||||
}
|
||||
description: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingLevelDescription"
|
||||
defaultMessage="Sets the log level for all the agents on the policy. The default log level is {level}."
|
||||
values={{ level: <EuiCode>{DEFAULT_LOG_LEVEL}</EuiCode> }}
|
||||
/>
|
||||
),
|
||||
api_field: {
|
||||
name: 'agent_logging_level',
|
||||
},
|
||||
schema: z.nativeEnum(agentLoggingLevels).default(agentLoggingLevels.Info),
|
||||
schema: z.nativeEnum(AGENT_LOG_LEVELS).default(DEFAULT_LOG_LEVEL),
|
||||
},
|
||||
];
|
|
@ -65,11 +65,15 @@ describe('AgentLogsUI', () => {
|
|||
},
|
||||
} as any);
|
||||
});
|
||||
const renderComponent = () => {
|
||||
const renderComponent = (
|
||||
opts = {
|
||||
agentVersion: '8.11.0',
|
||||
}
|
||||
) => {
|
||||
const renderer = createFleetTestRendererMock();
|
||||
const agent = {
|
||||
id: 'agent1',
|
||||
local_metadata: { elastic: { agent: { version: '8.11' } } },
|
||||
local_metadata: { elastic: { agent: { version: opts.agentVersion, log_level: 'debug' } } },
|
||||
} as any;
|
||||
const state = {
|
||||
datasets: ['elastic_agent'],
|
||||
|
@ -125,4 +129,35 @@ describe('AgentLogsUI', () => {
|
|||
`http://localhost:5620/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:'2023-20-04T14:00:00.340Z',to:'2023-20-04T14:20:00.340Z'))&_a=(columns:!(event.dataset,message),index:'logs-*',query:(language:kuery,query:'elastic_agent.id:agent1 and (data_stream.dataset:elastic_agent) and (log.level:info or log.level:error)'))`
|
||||
);
|
||||
});
|
||||
|
||||
it('should show log level dropdown with correct value', () => {
|
||||
mockStartServices();
|
||||
const result = renderComponent();
|
||||
const logLevelDropdown = result.getByTestId('selectAgentLogLevel');
|
||||
expect(logLevelDropdown.getElementsByTagName('option').length).toBe(4);
|
||||
expect(logLevelDropdown).toHaveDisplayValue('debug');
|
||||
});
|
||||
|
||||
it('should always show apply log level changes button', () => {
|
||||
mockStartServices();
|
||||
const result = renderComponent();
|
||||
const applyLogLevelBtn = result.getByTestId('applyLogLevelBtn');
|
||||
expect(applyLogLevelBtn).toBeInTheDocument();
|
||||
expect(applyLogLevelBtn).not.toHaveAttribute('disabled');
|
||||
});
|
||||
|
||||
it('should hide reset log level button for agents version < 8.15.0', () => {
|
||||
mockStartServices();
|
||||
const result = renderComponent();
|
||||
const resetLogLevelBtn = result.queryByTestId('resetLogLevelBtn');
|
||||
expect(resetLogLevelBtn).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show reset log level button for agents version >= 8.15.0', () => {
|
||||
mockStartServices();
|
||||
const result = renderComponent({ agentVersion: '8.15.0' });
|
||||
const resetLogLevelBtn = result.getByTestId('resetLogLevelBtn');
|
||||
expect(resetLogLevelBtn).toBeInTheDocument();
|
||||
expect(resetLogLevelBtn).not.toHaveAttribute('disabled');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -342,7 +342,10 @@ export const AgentLogsUI: React.FunctionComponent<AgentLogsProps> = memo(
|
|||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectLogLevel agent={agent} />
|
||||
<SelectLogLevel
|
||||
agent={agent}
|
||||
agentPolicyLogLevel={agentPolicy?.advanced_settings?.agent_logging_level}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</WrapperFlexGroup>
|
||||
);
|
||||
|
|
|
@ -9,106 +9,175 @@ import React, { memo, useState, useCallback } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiSelect, EuiFormLabel, EuiButtonEmpty, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import semverGte from 'semver/functions/gte';
|
||||
|
||||
import type { Agent } from '../../../../../types';
|
||||
import { sendPostAgentAction, useAuthz, useStartServices } from '../../../../../hooks';
|
||||
|
||||
import { AGENT_LOG_LEVELS, DEFAULT_LOG_LEVEL } from '../../../../../../../../common/constants';
|
||||
|
||||
const LEVEL_VALUES = Object.values(AGENT_LOG_LEVELS);
|
||||
export const SelectLogLevel: React.FC<{ agent: Agent; agentPolicyLogLevel?: string }> = memo(
|
||||
({ agent, agentPolicyLogLevel = DEFAULT_LOG_LEVEL }) => {
|
||||
const authz = useAuthz();
|
||||
const { notifications } = useStartServices();
|
||||
const [isSetLevelLoading, setIsSetLevelLoading] = useState(false);
|
||||
const [isResetLevelLoading, setIsResetLevelLoading] = useState(false);
|
||||
const canResetLogLevel = semverGte(
|
||||
agent.local_metadata?.elastic?.agent?.version,
|
||||
'8.15.0',
|
||||
true
|
||||
);
|
||||
|
||||
export const SelectLogLevel: React.FC<{ agent: Agent }> = memo(({ agent }) => {
|
||||
const authz = useAuthz();
|
||||
const { notifications } = useStartServices();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [agentLogLevel, setAgentLogLevel] = useState(
|
||||
agent.local_metadata?.elastic?.agent?.log_level ?? DEFAULT_LOG_LEVEL
|
||||
);
|
||||
const [selectedLogLevel, setSelectedLogLevel] = useState(agentLogLevel);
|
||||
const [agentLogLevel, setAgentLogLevel] = useState(
|
||||
agent.local_metadata?.elastic?.agent?.log_level ?? DEFAULT_LOG_LEVEL
|
||||
);
|
||||
const [selectedLogLevel, setSelectedLogLevel] = useState(agentLogLevel);
|
||||
|
||||
const onClickApply = useCallback(() => {
|
||||
setIsLoading(true);
|
||||
async function send() {
|
||||
try {
|
||||
const res = await sendPostAgentAction(agent.id, {
|
||||
action: {
|
||||
type: 'SETTINGS',
|
||||
data: {
|
||||
log_level: selectedLogLevel,
|
||||
const resetLogLevel = useCallback(() => {
|
||||
setIsResetLevelLoading(true);
|
||||
async function send() {
|
||||
try {
|
||||
const res = await sendPostAgentAction(agent.id, {
|
||||
action: {
|
||||
type: 'SETTINGS',
|
||||
data: {
|
||||
log_level: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
});
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
}
|
||||
|
||||
// TODO: reset to an empty state?
|
||||
setAgentLogLevel(agentPolicyLogLevel);
|
||||
setSelectedLogLevel(agentPolicyLogLevel);
|
||||
|
||||
notifications.toasts.addSuccess(
|
||||
i18n.translate('xpack.fleet.agentLogs.resetLogLevel.successText', {
|
||||
defaultMessage: `Reset agent logging level to policy`,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: i18n.translate('xpack.fleet.agentLogs.resetLogLevel.errorTitleText', {
|
||||
defaultMessage: 'Error resetting agent logging level',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setAgentLogLevel(selectedLogLevel);
|
||||
notifications.toasts.addSuccess(
|
||||
i18n.translate('xpack.fleet.agentLogs.selectLogLevel.successText', {
|
||||
defaultMessage: `Changed agent logging level to '{logLevel}'.`,
|
||||
values: {
|
||||
logLevel: selectedLogLevel,
|
||||
},
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: i18n.translate('xpack.fleet.agentLogs.selectLogLevel.errorTitleText', {
|
||||
defaultMessage: 'Error updating agent logging level',
|
||||
}),
|
||||
});
|
||||
setIsResetLevelLoading(false);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
send();
|
||||
}, [notifications, selectedLogLevel, agent.id]);
|
||||
send();
|
||||
}, [agent.id, agentPolicyLogLevel, notifications]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormLabel htmlFor="selectAgentLogLevel">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentLogs.selectLogLevelLabelText"
|
||||
defaultMessage="Agent logging level"
|
||||
/>
|
||||
</EuiFormLabel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
disabled={isLoading || !authz.fleet.allAgents}
|
||||
compressed={true}
|
||||
id="selectAgentLogLevel"
|
||||
value={selectedLogLevel}
|
||||
onChange={(event) => {
|
||||
setSelectedLogLevel(event.target.value);
|
||||
}}
|
||||
options={LEVEL_VALUES.map((level) => ({ text: level, value: level }))}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{agentLogLevel !== selectedLogLevel && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
size="xs"
|
||||
isLoading={isLoading}
|
||||
disabled={agentLogLevel === selectedLogLevel}
|
||||
iconType="refresh"
|
||||
onClick={onClickApply}
|
||||
>
|
||||
{isLoading ? (
|
||||
const onClickApply = useCallback(() => {
|
||||
setIsSetLevelLoading(true);
|
||||
async function send() {
|
||||
try {
|
||||
const res = await sendPostAgentAction(agent.id, {
|
||||
action: {
|
||||
type: 'SETTINGS',
|
||||
data: {
|
||||
log_level: selectedLogLevel,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
}
|
||||
setAgentLogLevel(selectedLogLevel);
|
||||
notifications.toasts.addSuccess(
|
||||
i18n.translate('xpack.fleet.agentLogs.selectLogLevel.successText', {
|
||||
defaultMessage: `Changed agent logging level to '{logLevel}'`,
|
||||
values: {
|
||||
logLevel: selectedLogLevel,
|
||||
},
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
notifications.toasts.addError(error, {
|
||||
title: i18n.translate('xpack.fleet.agentLogs.selectLogLevel.errorTitleText', {
|
||||
defaultMessage: 'Error updating agent logging level',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsSetLevelLoading(false);
|
||||
}
|
||||
|
||||
send();
|
||||
}, [notifications, selectedLogLevel, agent.id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFormLabel htmlFor="selectAgentLogLevel">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentLogs.updateButtonLoadingText"
|
||||
defaultMessage="Applying changes..."
|
||||
id="xpack.fleet.agentLogs.selectLogLevelLabelText"
|
||||
defaultMessage="Agent logging level"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentLogs.updateButtonText"
|
||||
defaultMessage="Apply changes"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
</EuiFormLabel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
disabled={isSetLevelLoading || !authz.fleet.allAgents}
|
||||
compressed={true}
|
||||
id="selectAgentLogLevel"
|
||||
data-test-subj="selectAgentLogLevel"
|
||||
value={selectedLogLevel}
|
||||
onChange={(event) => {
|
||||
setSelectedLogLevel(event.target.value);
|
||||
}}
|
||||
options={Object.entries(AGENT_LOG_LEVELS).map(([key, value]) => ({
|
||||
value,
|
||||
text: key,
|
||||
}))}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
flush="both"
|
||||
size="xs"
|
||||
disabled={!authz.fleet.allAgents}
|
||||
isLoading={isSetLevelLoading || isResetLevelLoading}
|
||||
iconType="check"
|
||||
onClick={onClickApply}
|
||||
data-test-subj="applyLogLevelBtn"
|
||||
>
|
||||
{isSetLevelLoading ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentLogs.updateButtonLoadingText"
|
||||
defaultMessage="Applying changes..."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentLogs.updateButtonText"
|
||||
defaultMessage="Apply changes"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{canResetLogLevel && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
flush="both"
|
||||
size="xs"
|
||||
disabled={!authz.fleet.allAgents}
|
||||
isLoading={isSetLevelLoading || isResetLevelLoading}
|
||||
iconType="cross"
|
||||
onClick={resetLogLevel}
|
||||
data-test-subj="resetLogLevelBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentLogs.resetLogLevelLabelText"
|
||||
defaultMessage="Reset to policy"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -32,12 +32,14 @@ export const NewAgentActionSchema = schema.oneOf([
|
|||
schema.object({
|
||||
type: schema.oneOf([schema.literal('SETTINGS')]),
|
||||
data: schema.object({
|
||||
log_level: schema.oneOf([
|
||||
schema.literal('debug'),
|
||||
schema.literal('info'),
|
||||
schema.literal('warning'),
|
||||
schema.literal('error'),
|
||||
]),
|
||||
log_level: schema.nullable(
|
||||
schema.oneOf([
|
||||
schema.literal('debug'),
|
||||
schema.literal('info'),
|
||||
schema.literal('warning'),
|
||||
schema.literal('error'),
|
||||
])
|
||||
),
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
|
|
@ -26,7 +26,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
});
|
||||
|
||||
describe('POST /agents/{agentId}/actions', () => {
|
||||
it('should return a 200 if this a valid SETTINGS action request', async () => {
|
||||
it('should return a 200 if this a SETTINGS action request with a valid log level', async () => {
|
||||
const { body: apiResponse } = await supertest
|
||||
.post(`/api/fleet/agents/agent1/actions`)
|
||||
.set('kbn-xsrf', 'xx')
|
||||
|
@ -42,7 +42,23 @@ export default function (providerContext: FtrProviderContext) {
|
|||
expect(apiResponse.item.data).to.eql({ log_level: 'debug' });
|
||||
});
|
||||
|
||||
it('should return a 400 if this a invalid SETTINGS action request', async () => {
|
||||
it('should return a 200 if this a SETTINGS action request with null log level', async () => {
|
||||
const { body: apiResponse } = await supertest
|
||||
.post(`/api/fleet/agents/agent1/actions`)
|
||||
.set('kbn-xsrf', 'xx')
|
||||
.send({
|
||||
action: {
|
||||
type: 'SETTINGS',
|
||||
data: { log_level: null },
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.item.type).to.eql('SETTINGS');
|
||||
expect(apiResponse.item.data).to.eql({ log_level: null });
|
||||
});
|
||||
|
||||
it('should return a 400 if this a SETTINGS action request with an invalid log level', async () => {
|
||||
const { body: apiResponse } = await supertest
|
||||
.post(`/api/fleet/agents/agent1/actions`)
|
||||
.set('kbn-xsrf', 'xx')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue