Api 4XX error msg customization #1236 (#11527)

* Adding API_400_ERROR_LOG_FORMAT setting
* Adding functional tests for API_400_ERROR_LOG_FORMAT
Co-authored-by: nixocio <nixocio@gmail.com>
This commit is contained in:
John Westcott IV
2022-01-19 11:16:21 -05:00
committed by GitHub
parent 60831cae88
commit e63ce9ed08
12 changed files with 99 additions and 11 deletions

View File

@@ -44,6 +44,7 @@ from awx.main.views import ApiErrorView
from awx.api.serializers import ResourceAccessListElementSerializer, CopySerializer, UserSerializer from awx.api.serializers import ResourceAccessListElementSerializer, CopySerializer, UserSerializer
from awx.api.versioning import URLPathVersioning from awx.api.versioning import URLPathVersioning
from awx.api.metadata import SublistAttachDetatchMetadata, Metadata from awx.api.metadata import SublistAttachDetatchMetadata, Metadata
from awx.conf import settings_registry
__all__ = [ __all__ = [
'APIView', 'APIView',
@@ -208,12 +209,20 @@ class APIView(views.APIView):
return response return response
if response.status_code >= 400: if response.status_code >= 400:
status_msg = "status %s received by user %s attempting to access %s from %s" % ( msg_data = {
response.status_code, 'status_code': response.status_code,
request.user, 'user_name': request.user,
request.path, 'url_path': request.path,
request.META.get('REMOTE_ADDR', None), 'remote_addr': request.META.get('REMOTE_ADDR', None),
) 'error': response.data.get('error', response.status_text),
}
try:
status_msg = getattr(settings, 'API_400_ERROR_LOG_FORMAT').format(**msg_data)
except Exception as e:
if getattr(settings, 'API_400_ERROR_LOG_FORMAT', None):
logger.error("Unable to format API_400_ERROR_LOG_FORMAT setting, defaulting log message: {}".format(e))
status_msg = settings_registry.get_setting_field('API_400_ERROR_LOG_FORMAT').get_default().format(**msg_data)
if hasattr(self, '__init_request_error__'): if hasattr(self, '__init_request_error__'):
response = self.handle_exception(self.__init_request_error__) response = self.handle_exception(self.__init_request_error__)
if response.status_code == 401: if response.status_code == 401:
@@ -221,6 +230,7 @@ class APIView(views.APIView):
logger.info(status_msg) logger.info(status_msg)
else: else:
logger.warning(status_msg) logger.warning(status_msg)
response = super(APIView, self).finalize_response(request, response, *args, **kwargs) response = super(APIView, self).finalize_response(request, response, *args, **kwargs)
time_started = getattr(self, 'time_started', None) time_started = getattr(self, 'time_started', None)
response['X-API-Product-Version'] = get_awx_version() response['X-API-Product-Version'] = get_awx_version()

View File

@@ -674,6 +674,24 @@ register(
category=_('Logging'), category=_('Logging'),
category_slug='logging', category_slug='logging',
) )
register(
'API_400_ERROR_LOG_FORMAT',
field_class=fields.CharField,
default='status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}',
label=_('Log Format For API 4XX Errors'),
help_text=_(
'The format of logged messages when an API 4XX error occurs, '
'the following variables will be substituted: \n'
'status_code - The HTTP status code of the error\n'
'user_name - The user name attempting to use the API\n'
'url_path - The URL path to the API endpoint called\n'
'remote_addr - The remote address seen for the user\n'
'error - The error set by the api endpoint\n'
'Variables need to be in the format {<variable name>}.'
),
category=_('Logging'),
category_slug='logging',
)
register( register(

View File

@@ -0,0 +1,26 @@
import pytest
from django.test.utils import override_settings
from awx.api.versioning import reverse
@pytest.mark.django_db
def test_change_400_error_log(caplog, post, admin_user):
with override_settings(API_400_ERROR_LOG_FORMAT='Test'):
post(url=reverse('api:setting_logging_test'), data={}, user=admin_user, expect=409)
assert 'Test' in caplog.text
@pytest.mark.django_db
def test_bad_400_error_log(caplog, post, admin_user):
with override_settings(API_400_ERROR_LOG_FORMAT="Not good {junk}"):
post(url=reverse('api:setting_logging_test'), data={}, user=admin_user, expect=409)
assert "Unable to format API_400_ERROR_LOG_FORMAT setting, defaulting log message: 'junk'" in caplog.text
assert 'status 409 received by user admin attempting to access /api/v2/settings/logging/test/ from 127.0.0.1' in caplog.text
@pytest.mark.django_db
def test_custom_400_error_log(caplog, post, admin_user):
with override_settings(API_400_ERROR_LOG_FORMAT="{status_code} {error}"):
post(url=reverse('api:setting_logging_test'), data={}, user=admin_user, expect=409)
assert '409 Logging not enabled' in caplog.text

View File

@@ -761,6 +761,7 @@ LOG_AGGREGATOR_MAX_DISK_USAGE_GB = 1
LOG_AGGREGATOR_MAX_DISK_USAGE_PATH = '/var/lib/awx' LOG_AGGREGATOR_MAX_DISK_USAGE_PATH = '/var/lib/awx'
LOG_AGGREGATOR_RSYSLOGD_DEBUG = False LOG_AGGREGATOR_RSYSLOGD_DEBUG = False
LOG_AGGREGATOR_RSYSLOGD_ERROR_LOG_FILE = '/var/log/tower/rsyslog.err' LOG_AGGREGATOR_RSYSLOGD_ERROR_LOG_FILE = '/var/log/tower/rsyslog.err'
API_400_ERROR_LOG_FORMAT = 'status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}'
# The number of retry attempts for websocket session establishment # The number of retry attempts for websocket session establishment
# If you're encountering issues establishing websockets in a cluster, # If you're encountering issues establishing websockets in a cluster,

View File

@@ -32,6 +32,8 @@ SettingsAPI.readCategory.mockResolvedValue({
LOG_AGGREGATOR_MAX_DISK_USAGE_GB: 1, LOG_AGGREGATOR_MAX_DISK_USAGE_GB: 1,
LOG_AGGREGATOR_MAX_DISK_USAGE_PATH: '/var/lib/awx', LOG_AGGREGATOR_MAX_DISK_USAGE_PATH: '/var/lib/awx',
LOG_AGGREGATOR_RSYSLOGD_DEBUG: false, LOG_AGGREGATOR_RSYSLOGD_DEBUG: false,
API_400_ERROR_LOG_FORMAT:
'status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}',
}, },
}); });

View File

@@ -42,7 +42,8 @@ function LoggingDetail() {
'LOG_AGGREGATOR_TCP_TIMEOUT', 'LOG_AGGREGATOR_TCP_TIMEOUT',
'LOG_AGGREGATOR_TYPE', 'LOG_AGGREGATOR_TYPE',
'LOG_AGGREGATOR_USERNAME', 'LOG_AGGREGATOR_USERNAME',
'LOG_AGGREGATOR_VERIFY_CERT' 'LOG_AGGREGATOR_VERIFY_CERT',
'API_400_ERROR_LOG_FORMAT'
); );
const mergedData = {}; const mergedData = {};

View File

@@ -59,6 +59,7 @@ describe('<LoggingDetail />', () => {
assertDetail(wrapper, 'Logging Aggregator Protocol', 'https'); assertDetail(wrapper, 'Logging Aggregator Protocol', 'https');
assertDetail(wrapper, 'TCP Connection Timeout', '5 seconds'); assertDetail(wrapper, 'TCP Connection Timeout', '5 seconds');
assertDetail(wrapper, 'Logging Aggregator Level Threshold', 'INFO'); assertDetail(wrapper, 'Logging Aggregator Level Threshold', 'INFO');
assertDetail(wrapper, 'Log Format For API 4XX Errors', 'Test Log Line');
assertDetail( assertDetail(
wrapper, wrapper,
'Enable/disable HTTPS certificate verification', 'Enable/disable HTTPS certificate verification',

View File

@@ -8,7 +8,7 @@ import { CardBody } from 'components/Card';
import ContentError from 'components/ContentError'; import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading'; import ContentLoading from 'components/ContentLoading';
import { FormSubmitError } from 'components/FormField'; import { FormSubmitError } from 'components/FormField';
import { FormColumnLayout } from 'components/FormLayout'; import { FormColumnLayout, FormFullWidthLayout } from 'components/FormLayout';
import { useSettings } from 'contexts/Settings'; import { useSettings } from 'contexts/Settings';
import useModal from 'hooks/useModal'; import useModal from 'hooks/useModal';
import useRequest from 'hooks/useRequest'; import useRequest from 'hooks/useRequest';
@@ -71,6 +71,7 @@ function LoggingEdit() {
LOG_AGGREGATOR_LOGGERS: formatJson(form.LOG_AGGREGATOR_LOGGERS), LOG_AGGREGATOR_LOGGERS: formatJson(form.LOG_AGGREGATOR_LOGGERS),
LOG_AGGREGATOR_HOST: form.LOG_AGGREGATOR_HOST || null, LOG_AGGREGATOR_HOST: form.LOG_AGGREGATOR_HOST || null,
LOG_AGGREGATOR_TYPE: form.LOG_AGGREGATOR_TYPE || null, LOG_AGGREGATOR_TYPE: form.LOG_AGGREGATOR_TYPE || null,
API_400_ERROR_LOG_FORMAT: form.API_400_ERROR_LOG_FORMAT || null,
}); });
}; };
@@ -196,6 +197,12 @@ function LoggingEdit() {
name="LOG_AGGREGATOR_LOGGERS" name="LOG_AGGREGATOR_LOGGERS"
config={logging.LOG_AGGREGATOR_LOGGERS} config={logging.LOG_AGGREGATOR_LOGGERS}
/> />
<FormFullWidthLayout>
<InputField
name="API_400_ERROR_LOG_FORMAT"
config={logging.API_400_ERROR_LOG_FORMAT}
/>
</FormFullWidthLayout>
{submitError && <FormSubmitError error={submitError} />} {submitError && <FormSubmitError error={submitError} />}
{revertError && <FormSubmitError error={revertError} />} {revertError && <FormSubmitError error={revertError} />}
<RevertFormActionGroup <RevertFormActionGroup

View File

@@ -34,6 +34,8 @@ const mockSettings = {
LOG_AGGREGATOR_MAX_DISK_USAGE_GB: 1, LOG_AGGREGATOR_MAX_DISK_USAGE_GB: 1,
LOG_AGGREGATOR_MAX_DISK_USAGE_PATH: '/var/lib/awx', LOG_AGGREGATOR_MAX_DISK_USAGE_PATH: '/var/lib/awx',
LOG_AGGREGATOR_RSYSLOGD_DEBUG: false, LOG_AGGREGATOR_RSYSLOGD_DEBUG: false,
API_400_ERROR_LOG_FORMAT:
'status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}',
}; };
describe('<LoggingEdit />', () => { describe('<LoggingEdit />', () => {

View File

@@ -564,6 +564,15 @@
"category_slug": "logging", "category_slug": "logging",
"default": false "default": false
}, },
"API_400_ERROR_LOG_FORMAT": {
"type": "string",
"required": false,
"label": "Log Format For API 4XX Errors",
"help_text": "The format of logged messages when an API 4XX error occurs, the following variables will be substituted: \nstatus_code - The HTTP status code of the error\nuser_name - The user name attempting to use the API\nurl_path - The URL path to the API endpoint called\nremote_addr - The remote address seen for the user\nerror - The error set by the api endpoint\nVariables need to be in the format {<variable name>}.",
"category": "Logging",
"category_slug": "logging",
"default": "status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}"
},
"AUTOMATION_ANALYTICS_LAST_GATHER": { "AUTOMATION_ANALYTICS_LAST_GATHER": {
"type": "datetime", "type": "datetime",
"required": true, "required": true,
@@ -4221,6 +4230,15 @@
"category_slug": "logging", "category_slug": "logging",
"defined_in_file": false "defined_in_file": false
}, },
"API_400_ERROR_LOG_FORMAT": {
"type": "string",
"required": false,
"label": "Log Format For API 4XX Errors",
"help_text": "The format of logged messages when an API 4XX error occurs, the following variables will be substituted: \nstatus_code - The HTTP status code of the error\nuser_name - The user name attempting to use the API\nurl_path - The URL path to the API endpoint called\nremote_addr - The remote address seen for the user\nerror - The error set by the api endpoint\nVariables need to be in the format {<variable name>}.",
"category": "Logging",
"category_slug": "logging",
"default": "status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}"
},
"AUTOMATION_ANALYTICS_LAST_GATHER": { "AUTOMATION_ANALYTICS_LAST_GATHER": {
"type": "datetime", "type": "datetime",
"label": "Last gather date for Insights for Ansible Automation Platform.", "label": "Last gather date for Insights for Ansible Automation Platform.",
@@ -6329,7 +6347,7 @@
}, },
"SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR": { "SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR": {
"type": "nested object", "type": "nested object",
"label": "SAML User Flags Attribute Mapping", "label": "SAML User Flags Attribute Mapping",
"help_text": "Used to map super users and system auditors from SAML.", "help_text": "Used to map super users and system auditors from SAML.",
"category": "SAML", "category": "SAML",
"category_slug": "saml", "category_slug": "saml",

View File

@@ -70,6 +70,7 @@
"LOG_AGGREGATOR_MAX_DISK_USAGE_GB":1, "LOG_AGGREGATOR_MAX_DISK_USAGE_GB":1,
"LOG_AGGREGATOR_MAX_DISK_USAGE_PATH":"/var/lib/awx", "LOG_AGGREGATOR_MAX_DISK_USAGE_PATH":"/var/lib/awx",
"LOG_AGGREGATOR_RSYSLOGD_DEBUG":false, "LOG_AGGREGATOR_RSYSLOGD_DEBUG":false,
"API_400_ERROR_LOG_FORMAT":"status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}",
"AUTOMATION_ANALYTICS_LAST_GATHER":null, "AUTOMATION_ANALYTICS_LAST_GATHER":null,
"AUTOMATION_ANALYTICS_GATHER_INTERVAL":14400, "AUTOMATION_ANALYTICS_GATHER_INTERVAL":14400,
"SESSION_COOKIE_AGE":1800, "SESSION_COOKIE_AGE":1800,

View File

@@ -17,5 +17,6 @@
"LOG_AGGREGATOR_LEVEL": "INFO", "LOG_AGGREGATOR_LEVEL": "INFO",
"LOG_AGGREGATOR_MAX_DISK_USAGE_GB": 1, "LOG_AGGREGATOR_MAX_DISK_USAGE_GB": 1,
"LOG_AGGREGATOR_MAX_DISK_USAGE_PATH": "/var/lib/awx", "LOG_AGGREGATOR_MAX_DISK_USAGE_PATH": "/var/lib/awx",
"LOG_AGGREGATOR_RSYSLOGD_DEBUG": false "LOG_AGGREGATOR_RSYSLOGD_DEBUG": false,
"API_400_ERROR_LOG_FORMAT": "Test Log Line"
} }