11import base64
22import functools
3+ import time
34import uuid
45from datetime import datetime
56
5253 validate_and_format_recipient ,
5354 validate_template ,
5455)
56+ from app .request_timings import add_context , get_timings , init_request_timings , record_timing
5557from app .schema_validation import validate
5658from app .utils import try_parse_and_format_phone_number
5759from app .v2 .errors import BadRequestError
7678)
7779
7880
81+ def _log_slow_request (request_start : float , notification_type : str , notification_id : str | None = None ) -> None :
82+ request_time = time .perf_counter () - request_start
83+ if request_time <= 1.0 :
84+ return
85+
86+ timings , context = get_timings ()
87+ service_id = getattr (authenticated_service , "id" , None )
88+ api_key_type = getattr (api_user , "key_type" , None )
89+ add_context (
90+ notification_type = notification_type ,
91+ service_id = str (service_id ) if service_id is not None else None ,
92+ api_key_type = api_key_type ,
93+ endpoint = request .endpoint ,
94+ path = request .path ,
95+ notification_id = notification_id ,
96+ request_time_seconds = round (request_time , 3 ),
97+ )
98+ _ , context = get_timings ()
99+ current_app .logger .info (
100+ "slow request breakdown" ,
101+ extra = {
102+ "timings_ms" : timings ,
103+ ** context ,
104+ },
105+ )
106+
107+
79108@v2_notification_blueprint .route (f"/{ LETTER_TYPE } " , methods = ["POST" ])
80109def post_precompiled_letter_notification ():
110+ init_request_timings ()
111+ request_start = time .perf_counter ()
81112 check_rate_limiting (authenticated_service , api_user , notification_type = LETTER_TYPE )
82113
83114 request_json = get_valid_json ()
@@ -104,13 +135,21 @@ def post_precompiled_letter_notification():
104135 precompiled = True ,
105136 )
106137
138+ notification_id = None
139+ if isinstance (notification , dict ):
140+ notification_id = str (notification .get ("id" ))
141+ _log_slow_request (request_start , LETTER_TYPE , notification_id = notification_id )
142+
107143 return jsonify (notification ), 201
108144
109145
110146@v2_notification_blueprint .route ("/<notification_type>" , methods = ["POST" ])
111147def post_notification (notification_type ):
148+ init_request_timings ()
149+ request_start = time .perf_counter ()
112150 check_rate_limiting (authenticated_service , api_user , notification_type = notification_type )
113151
152+ json_parse_start = time .perf_counter ()
114153 with POST_NOTIFICATION_JSON_PARSE_DURATION_SECONDS .time ():
115154 request_json = get_valid_json ()
116155
@@ -122,9 +161,11 @@ def post_notification(notification_type):
122161 form = validate (request_json , post_letter_request )
123162 else :
124163 abort (404 )
164+ record_timing ("json_parse_validate_ms" , time .perf_counter () - json_parse_start )
125165
126166 check_service_has_permission (authenticated_service , notification_type )
127167
168+ validate_template_start = time .perf_counter ()
128169 template , template_with_content = validate_template (
129170 template_id = form ["template_id" ],
130171 personalisation = form .get ("personalisation" , {}),
@@ -133,6 +174,7 @@ def post_notification(notification_type):
133174 check_char_count = False ,
134175 recipient = form .get ("email_address" ),
135176 )
177+ record_timing ("validate_template_ms" , time .perf_counter () - validate_template_start )
136178
137179 reply_to = get_reply_to_text (notification_type , form , template )
138180
@@ -156,6 +198,11 @@ def post_notification(notification_type):
156198 unsubscribe_link = form .get ("one_click_unsubscribe_url" , None ),
157199 )
158200
201+ notification_id = None
202+ if isinstance (notification , dict ):
203+ notification_id = str (notification .get ("id" ))
204+ _log_slow_request (request_start , notification_type , notification_id = notification_id )
205+
159206 return jsonify (notification ), 201
160207
161208
@@ -172,27 +219,34 @@ def process_sms_or_email_notification(
172219 notification_id = uuid .uuid4 ()
173220 form_send_to = form ["email_address" ] if notification_type == EMAIL_TYPE else form ["phone_number" ]
174221
222+ recipient_validate_start = time .perf_counter ()
175223 recipient_data = validate_and_format_recipient (
176224 send_to = form_send_to , key_type = api_user .key_type , service = service , notification_type = notification_type
177225 )
226+ record_timing ("recipient_validate_ms" , time .perf_counter () - recipient_validate_start )
178227
179228 send_to = recipient_data ["normalised_to" ] if type (recipient_data ) is dict else recipient_data
180229
181230 # Do not persist or send notification to the queue if it is a simulated recipient
182231 simulated = simulated_recipient (send_to , notification_type )
183232
233+ document_upload_start = time .perf_counter ()
184234 personalisation , document_download_count = process_document_uploads (
185235 form .get ("personalisation" ),
186236 service ,
187237 send_to = send_to ,
188238 simulated = simulated ,
189239 )
240+ record_timing ("document_upload_ms" , time .perf_counter () - document_upload_start )
241+ add_context (document_download_count = document_download_count )
190242 if document_download_count :
191243 # We changed personalisation which means we need to update the content
192244 template_with_content .values = personalisation
193245
194246 # validate content length after url is replaced in personalisation.
247+ content_validation_start = time .perf_counter ()
195248 check_is_message_too_long (template_with_content )
249+ record_timing ("content_validation_ms" , time .perf_counter () - content_validation_start )
196250
197251 response = create_response_for_post_notification (
198252 notification_id = notification_id ,
@@ -206,6 +260,7 @@ def process_sms_or_email_notification(
206260 template_with_content = template_with_content ,
207261 )
208262
263+ persist_start = time .perf_counter ()
209264 persist_notification (
210265 notification_id = notification_id ,
211266 template_id = template .id ,
@@ -222,6 +277,7 @@ def process_sms_or_email_notification(
222277 unsubscribe_link = unsubscribe_link ,
223278 document_download_count = document_download_count ,
224279 )
280+ record_timing ("persist_notification_ms" , time .perf_counter () - persist_start )
225281
226282 if not simulated :
227283 send_notification_to_queue_detached (
0 commit comments