Skip to content

Commit 3113adc

Browse files
authored
Merge pull request #512 from NHSDigital/dtoss-11201-setup-application-insights
DTOSS-11201: Setup Application Insights
2 parents 0fe8279 + 60ca82c commit 3113adc

File tree

10 files changed

+756
-18
lines changed

10 files changed

+756
-18
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ ARG poetry_version
2020

2121
WORKDIR /app
2222

23-
RUN apk add --no-cache libgcc libstdc++ build-base
23+
RUN apk add --no-cache libgcc libstdc++ build-base linux-headers
2424

2525
# Set environment variables
2626
ENV PYTHONDONTWRITEBYTECODE=1

manage_breast_screening/config/.env.tpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ QUEUE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=http;AccountName=devst
4545
REPORTS_CONTAINER_NAME="notifications-reports"
4646
RETRY_QUEUE_NAME="notifications-message-batch-retries"
4747
STATUS_UPDATES_QUEUE_NAME="notifications-message-status-updates"
48+
49+
APPLICATIONINSIGHTS_CONNECTION_STRING=""
50+
APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL=True
51+
APPLICATIONINSIGHTS_LOGGER_NAME="manbrs-notifications"
52+
APPLICATIONINSIGHTS_IS_ENABLED=False
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
from django.apps import AppConfig
22

3+
from manage_breast_screening.notifications.services.application_insights_logging import (
4+
ApplicationInsightsLogging,
5+
)
6+
37

48
class NotificationsConfig(AppConfig):
59
default_auto_field = "django.db.models.BigAutoField"
610
name = "manage_breast_screening.notifications"
11+
12+
def ready(self) -> None:
13+
ApplicationInsightsLogging().configure_azure_monitor()
14+
return super().ready()

manage_breast_screening/notifications/management/commands/retry_failed_message_batch.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212
MessageBatchStatusChoices,
1313
)
1414
from manage_breast_screening.notifications.services.api_client import ApiClient
15+
from manage_breast_screening.notifications.services.application_insights_logging import (
16+
ApplicationInsightsLogging,
17+
)
1518
from manage_breast_screening.notifications.services.queue import Queue
1619

1720
logger = getLogger(__name__)
21+
INSIGHTS_ERROR_NAME = "RetryFailedMessageBatchError"
1822

1923

2024
class Command(BaseCommand):
@@ -24,6 +28,13 @@ class Command(BaseCommand):
2428
"""
2529

2630
def handle(self, *args, **options):
31+
try:
32+
self.retry_failed_message_batch()
33+
except Exception as e:
34+
ApplicationInsightsLogging().exception(f"{INSIGHTS_ERROR_NAME}: {e}")
35+
raise CommandError(e)
36+
37+
def retry_failed_message_batch(self):
2738
logger.info("Retry Failed Message Batch Command started")
2839
queue = Queue.RetryMessageBatches()
2940
queue_message = queue.item()
@@ -57,22 +68,18 @@ def handle(self, *args, **options):
5768
retry_count,
5869
)
5970

60-
try:
61-
response = ApiClient().send_message_batch(message_batch)
62-
63-
if response.status_code == 201:
64-
MessageBatchHelpers.mark_batch_as_sent(
65-
message_batch=message_batch, response_json=response.json()
66-
)
67-
else:
68-
MessageBatchHelpers.mark_batch_as_failed(
69-
message_batch=message_batch,
70-
response=response,
71-
retry_count=(retry_count + 1),
72-
)
71+
response = ApiClient().send_message_batch(message_batch)
7372

74-
except Exception as e:
75-
raise CommandError(e)
73+
if response.status_code == 201:
74+
MessageBatchHelpers.mark_batch_as_sent(
75+
message_batch=message_batch, response_json=response.json()
76+
)
77+
else:
78+
MessageBatchHelpers.mark_batch_as_failed(
79+
message_batch=message_batch,
80+
response=response,
81+
retry_count=(retry_count + 1),
82+
)
7683
else:
7784
logger.error(
7885
"Failed Message Batch with id %s not sent: Retry limit exceeded",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import logging
2+
import os
3+
4+
from azure.monitor.opentelemetry import configure_azure_monitor
5+
6+
7+
class ApplicationInsightsLogging:
8+
def __init__(self) -> None:
9+
self.logger_name = os.getenv(
10+
"APPLICATIONINSIGHTS_LOGGER_NAME", "manbrs-notifications"
11+
)
12+
self.logger = self.getLogger()
13+
14+
def configure_azure_monitor(self):
15+
if os.getenv("APPLICATIONINSIGHTS_IS_ENABLED", "False") == "True":
16+
# Configure OpenTelemetry to use Azure Monitor with the
17+
# APPLICATIONINSIGHTS_CONNECTION_STRING environment variable.
18+
configure_azure_monitor(
19+
# Set the namespace for the logger in which you would like to collect telemetry for if you are collecting logging telemetry. This is imperative so you do not collect logging telemetry from the SDK itself.
20+
logger_name=self.logger_name,
21+
)
22+
else:
23+
default_logger = logging.getLogger(__name__)
24+
default_logger.info("Application Insights logging not enabled")
25+
26+
def getLogger(self):
27+
return logging.getLogger(self.logger_name)
28+
29+
def exception(self, exception_name: str):
30+
self.logger.exception(exception_name, stack_info=True)
31+
32+
def custom_event(self, event_name: str, additional_attrs: str | None = None):
33+
self.logger.warning(
34+
event_name,
35+
extra={
36+
"microsoft.custom_event.name": event_name,
37+
"additional_attrs": additional_attrs,
38+
},
39+
)

manage_breast_screening/notifications/tests/integration/test_retry_message_batches_from_queue.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def setup(self, monkeypatch):
3030
monkeypatch.setenv("API_OAUTH_API_KEY", "a1b2c3d4")
3131
monkeypatch.setenv("API_OAUTH_API_KID", "test-1")
3232
monkeypatch.setenv("API_OAUTH_PRIVATE_KEY", "test-key")
33+
monkeypatch.setenv("APPLICATIONINSIGHTS_IS_ENABLED", "False")
3334

3435
@pytest.fixture
3536
def routing_plan_id(self):

manage_breast_screening/notifications/tests/management/commands/test_retry_failed_message_batch.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
CommandError,
1414
)
1515
from manage_breast_screening.notifications.services.api_client import ApiClient
16+
from manage_breast_screening.notifications.services.application_insights_logging import (
17+
ApplicationInsightsLogging,
18+
)
1619
from manage_breast_screening.notifications.services.queue import Queue
1720
from manage_breast_screening.notifications.tests.factories import MessageBatchFactory
1821

@@ -30,6 +33,14 @@ def setup(monkeypatch):
3033
@patch.object(MessageBatchHelpers, "mark_batch_as_sent")
3134
@patch.object(MessageBatchHelpers, "mark_batch_as_failed")
3235
class TestRetryFailedMessageBatch:
36+
@pytest.fixture(autouse=True)
37+
def mock_insights_logger(self, monkeypatch):
38+
mock_insights_logger = MagicMock()
39+
monkeypatch.setattr(
40+
ApplicationInsightsLogging, "exception", mock_insights_logger
41+
)
42+
return mock_insights_logger
43+
3344
@pytest.mark.django_db
3445
def test_handle_batch_not_found(
3546
self,
@@ -170,3 +181,27 @@ def test_batch_with_retry_count_more_than_5_is_marked_as_failed_unrecoverable(
170181
str(error.value)
171182
== f"Message Batch with id {batch_id} not sent: Retry limit exceeded"
172183
)
184+
185+
@pytest.mark.django_db
186+
def test_calls_insights_logger_if_exception_raised(
187+
self,
188+
mock_mark_batch_as_failed,
189+
mock_mark_batch_as_sent,
190+
mock_retry_message_batches,
191+
mock_send_message_batch,
192+
mock_insights_logger,
193+
):
194+
subject = Command()
195+
batch_id = uuid.uuid4()
196+
_failed_batch = MessageBatchFactory(id=batch_id, status="failed_recoverable")
197+
198+
mock_retry_message_batches.return_value.item.return_value.content = json.dumps(
199+
{"message_batch_id": str(batch_id), "retry_count": 5}
200+
)
201+
202+
with pytest.raises(CommandError):
203+
subject.handle()
204+
205+
mock_insights_logger.assert_called_with(
206+
f"RetryFailedMessageBatchError: Message Batch with id {str(batch_id)} not sent: Retry limit exceeded"
207+
)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from unittest.mock import MagicMock, patch
2+
3+
import pytest
4+
5+
from manage_breast_screening.notifications.services.application_insights_logging import (
6+
ApplicationInsightsLogging,
7+
)
8+
9+
10+
@patch(
11+
"manage_breast_screening.notifications.services.application_insights_logging.configure_azure_monitor",
12+
return_value=MagicMock(),
13+
)
14+
@patch(
15+
"manage_breast_screening.notifications.services.application_insights_logging.logging"
16+
)
17+
class TestApplicationInsightsLogging:
18+
@pytest.fixture(autouse=True)
19+
def setup(self, monkeypatch):
20+
monkeypatch.setenv("APPLICATIONINSIGHTS_LOGGER_NAME", "insights-logger")
21+
22+
def test_configure_azure_monitor(
23+
self, mock_logging, mock_configure_azure, monkeypatch
24+
):
25+
monkeypatch.setenv("APPLICATIONINSIGHTS_IS_ENABLED", "True")
26+
ApplicationInsightsLogging().configure_azure_monitor()
27+
mock_configure_azure.assert_called_with(logger_name="insights-logger")
28+
29+
def test_getLogger(self, mock_logging, mock_configure_azure):
30+
ApplicationInsightsLogging().getLogger()
31+
mock_logging.getLogger.assert_called_with("insights-logger")
32+
33+
def test_raise_exception(self, mock_logging, mock_configure_azure):
34+
ApplicationInsightsLogging().exception("CustomError")
35+
mock_logging.getLogger.assert_called_with("insights-logger")
36+
mock_logging.getLogger.return_value.exception.assert_called_with(
37+
"CustomError", stack_info=True
38+
)
39+
40+
def test_trigger_an_event(self, mock_logging, mock_configure_azure):
41+
ApplicationInsightsLogging().custom_event("CustomEvent", "something went wrong")
42+
mock_logging.getLogger.assert_called_with("insights-logger")
43+
mock_logging.getLogger.return_value.warning.assert_called_with(
44+
"CustomEvent",
45+
extra={
46+
"microsoft.custom_event.name": "CustomEvent",
47+
"additional_attrs": "something went wrong",
48+
},
49+
)

0 commit comments

Comments
 (0)