Skip to content

Commit d03052f

Browse files
committed
refactor: improve code organization based on feedback
- Move imports to top of file instead of inside functions - Extract common header parsing logic into _extract_context_from_headers helper - Reduce code duplication between sync and async decorators - Add comment explaining why crash handler needs to extract context from headers - This addresses the context reset issue where decorators clean up before exception handlers run
1 parent 2ef096d commit d03052f

File tree

2 files changed

+31
-44
lines changed

2 files changed

+31
-44
lines changed

src/functions_framework/aio/__init__.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
import asyncio
1616
import functools
1717
import inspect
18+
import json
19+
import logging
1820
import os
1921
import re
2022
import sys
23+
import traceback
2124

2225
from typing import Any, Awaitable, Callable, Dict, Tuple, Union
2326

@@ -55,41 +58,27 @@
5558

5659

5760
async def _crash_handler(request, exc):
58-
import logging
59-
import traceback
60-
import json
61-
6261
# Log the exception
6362
logger = logging.getLogger()
6463
tb_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
6564
tb_text = ''.join(tb_lines)
6665
error_msg = f"Exception on {request.url.path} [{request.method}]\n{tb_text}".rstrip()
6766

68-
# Check if we need to set execution context for logging
67+
# When execution ID logging is enabled, we need to extract execution_id from headers
68+
# because the decorator resets the context before exception handlers run
6969
if _enable_execution_id_logging():
70-
# Get execution_id from request headers
71-
exec_id = request.headers.get(execution_id.EXECUTION_ID_REQUEST_HEADER)
72-
span_id = None
73-
74-
# Try to get span ID from trace context header
75-
trace_context = request.headers.get(execution_id.TRACE_CONTEXT_REQUEST_HEADER, "")
76-
trace_match = re.match(execution_id._TRACE_CONTEXT_REGEX_PATTERN, trace_context)
77-
if trace_match:
78-
span_id = trace_match.group("span_id") # pragma: no cover
79-
80-
if exec_id:
70+
context = execution_id._extract_context_from_headers(request.headers)
71+
if context.execution_id:
8172
# Temporarily set context for logging
82-
token = execution_id.execution_context_var.set(
83-
execution_id.ExecutionContext(exec_id, span_id)
84-
)
73+
token = execution_id.execution_context_var.set(context)
8574
try:
8675
# Output as JSON so LoggingHandlerAddExecutionId can process it
8776
log_entry = {"message": error_msg, "levelname": "ERROR"}
8877
logger.error(json.dumps(log_entry))
8978
finally:
9079
execution_id.execution_context_var.reset(token)
9180
else: # pragma: no cover
92-
# No execution ID, just log normally
81+
# No execution ID in headers
9382
log_entry = {"message": error_msg, "levelname": "ERROR"}
9483
logger.error(json.dumps(log_entry))
9584
else:

src/functions_framework/execution_id.py

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import contextlib
1616
import contextvars
1717
import functools
18+
import inspect
1819
import io
1920
import json
2021
import logging
@@ -78,6 +79,20 @@ def _generate_execution_id():
7879
)
7980

8081

82+
def _extract_context_from_headers(headers):
83+
"""Extract execution context from request headers."""
84+
execution_id = headers.get(EXECUTION_ID_REQUEST_HEADER)
85+
86+
# Try to get span ID from trace context header
87+
trace_context = re.match(
88+
_TRACE_CONTEXT_REGEX_PATTERN,
89+
headers.get(TRACE_CONTEXT_REQUEST_HEADER, ""),
90+
)
91+
span_id = trace_context.group("span_id") if trace_context else None
92+
93+
return ExecutionContext(execution_id, span_id)
94+
95+
8196
# Middleware to add execution id to request header if one does not already exist
8297
class WsgiMiddleware:
8398
def __init__(self, wsgi_app):
@@ -147,13 +162,8 @@ def set_execution_context(request, enable_id_logging=False):
147162
def decorator(view_function):
148163
@functools.wraps(view_function)
149164
def wrapper(*args, **kwargs):
150-
trace_context = re.match(
151-
_TRACE_CONTEXT_REGEX_PATTERN,
152-
request.headers.get(TRACE_CONTEXT_REQUEST_HEADER, ""),
153-
)
154-
execution_id = request.headers.get(EXECUTION_ID_REQUEST_HEADER)
155-
span_id = trace_context.group("span_id") if trace_context else None
156-
_set_current_context(ExecutionContext(execution_id, span_id))
165+
context = _extract_context_from_headers(request.headers)
166+
_set_current_context(context)
157167

158168
with stderr_redirect, stdout_redirect:
159169
return view_function(*args, **kwargs)
@@ -179,21 +189,15 @@ def set_execution_context_async(enable_id_logging=False):
179189
def decorator(view_function):
180190
@functools.wraps(view_function)
181191
async def async_wrapper(request, *args, **kwargs):
182-
# Extract execution ID and span ID from Starlette request
183-
trace_context = re.match(
184-
_TRACE_CONTEXT_REGEX_PATTERN,
185-
request.headers.get(TRACE_CONTEXT_REQUEST_HEADER, ""),
186-
)
187-
execution_id = request.headers.get(EXECUTION_ID_REQUEST_HEADER)
188-
span_id = trace_context.group("span_id") if trace_context else None
192+
# Extract execution context from headers
193+
context = _extract_context_from_headers(request.headers)
189194

190195
# Set context using contextvars
191-
token = execution_context_var.set(ExecutionContext(execution_id, span_id))
196+
token = execution_context_var.set(context)
192197

193198
try:
194199
with stderr_redirect, stdout_redirect:
195200
# Handle both sync and async functions
196-
import inspect
197201
if inspect.iscoroutinefunction(view_function):
198202
return await view_function(request, *args, **kwargs)
199203
else:
@@ -205,15 +209,10 @@ async def async_wrapper(request, *args, **kwargs):
205209
@functools.wraps(view_function)
206210
def sync_wrapper(request, *args, **kwargs): # pragma: no cover
207211
# For sync functions, we still need to set up the context
208-
trace_context = re.match(
209-
_TRACE_CONTEXT_REGEX_PATTERN,
210-
request.headers.get(TRACE_CONTEXT_REQUEST_HEADER, ""),
211-
)
212-
execution_id = request.headers.get(EXECUTION_ID_REQUEST_HEADER)
213-
span_id = trace_context.group("span_id") if trace_context else None
212+
context = _extract_context_from_headers(request.headers)
214213

215214
# Set context using contextvars
216-
token = execution_context_var.set(ExecutionContext(execution_id, span_id))
215+
token = execution_context_var.set(context)
217216

218217
try:
219218
with stderr_redirect, stdout_redirect:
@@ -223,7 +222,6 @@ def sync_wrapper(request, *args, **kwargs): # pragma: no cover
223222
execution_context_var.reset(token)
224223

225224
# Return appropriate wrapper based on whether the function is async
226-
import inspect
227225
if inspect.iscoroutinefunction(view_function):
228226
return async_wrapper
229227
else:

0 commit comments

Comments
 (0)