Skip to content

Commit 9d2f9c8

Browse files
committed
sonarqube: Fix duplicated code blocks for grpcio and flask (vanilla and with_blinker) instrumentations
Signed-off-by: Cagri Yonca <[email protected]>
1 parent 76033bd commit 9d2f9c8

File tree

4 files changed

+228
-414
lines changed

4 files changed

+228
-414
lines changed

src/instana/instrumentation/flask/common.py

Lines changed: 106 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
1-
# (c) Copyright IBM Corp. 2021
2-
# (c) Copyright Instana Inc. 2019
1+
# (c) Copyright IBM Corp. 2021, 2025
32

43

5-
import wrapt
6-
import flask
4+
import re
75
from importlib.metadata import version
8-
from typing import Callable, Tuple, Dict, Any, TYPE_CHECKING, Union
6+
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Type, Union
97

8+
import flask
9+
import wrapt
10+
from opentelemetry import context, trace
1011
from opentelemetry.semconv.trace import SpanAttributes
1112

1213
from instana.log import logger
13-
from instana.singletons import tracer
1414
from instana.propagators.format import Format
15-
15+
from instana.singletons import agent, get_tracer
16+
from instana.util.secrets import strip_secrets_from_query
17+
from instana.util.traceutils import extract_custom_headers
1618

1719
if TYPE_CHECKING:
18-
from werkzeug.exceptions import HTTPException
1920
from flask.typing import ResponseReturnValue
2021
from jinja2.environment import Template
22+
from werkzeug.exceptions import HTTPException
2123

22-
@wrapt.patch_function_wrapper('flask', 'templating._render')
24+
path_tpl_re = re.compile("<.*>")
25+
26+
27+
@wrapt.patch_function_wrapper("flask", "templating._render")
2328
def render_with_instana(
2429
wrapped: Callable[..., str],
2530
instance: object,
@@ -32,6 +37,7 @@ def render_with_instana(
3237

3338
parent_span = flask.g.span
3439
parent_context = parent_span.get_span_context()
40+
tracer = get_tracer()
3541

3642
with tracer.start_as_current_span("render", span_context=parent_context) as span:
3743
try:
@@ -50,7 +56,7 @@ def render_with_instana(
5056
raise
5157

5258

53-
@wrapt.patch_function_wrapper('flask', 'Flask.handle_user_exception')
59+
@wrapt.patch_function_wrapper("flask", "Flask.handle_user_exception")
5460
def handle_user_exception_with_instana(
5561
wrapped: Callable[..., Union["HTTPException", "ResponseReturnValue"]],
5662
instance: flask.app.Flask,
@@ -70,7 +76,7 @@ def handle_user_exception_with_instana(
7076
if isinstance(response, tuple):
7177
status_code = response[1]
7278
else:
73-
if hasattr(response, 'code'):
79+
if hasattr(response, "code"):
7480
status_code = response.code
7581
else:
7682
status_code = response.status_code
@@ -80,12 +86,99 @@ def handle_user_exception_with_instana(
8086

8187
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, int(status_code))
8288

83-
if hasattr(response, 'headers'):
89+
if hasattr(response, "headers"):
90+
tracer = get_tracer()
8491
tracer.inject(span.context, Format.HTTP_HEADERS, response.headers)
8592
if span and span.is_recording():
8693
span.end()
8794
flask.g.span = None
88-
except:
95+
except Exception:
8996
logger.debug("handle_user_exception_with_instana:", exc_info=True)
9097

9198
return response
99+
100+
101+
def create_span():
102+
env = flask.request.environ
103+
tracer = get_tracer()
104+
span_context = tracer.extract(Format.HTTP_HEADERS, env)
105+
106+
span = tracer.start_span("wsgi", span_context=span_context)
107+
flask.g.span = span
108+
109+
ctx = trace.set_span_in_context(span)
110+
token = context.attach(ctx)
111+
flask.g.token = token
112+
113+
extract_custom_headers(span, env, format=True)
114+
115+
span.set_attribute(SpanAttributes.HTTP_METHOD, flask.request.method)
116+
if "PATH_INFO" in env:
117+
span.set_attribute(SpanAttributes.HTTP_URL, env["PATH_INFO"])
118+
if "QUERY_STRING" in env and len(env["QUERY_STRING"]):
119+
scrubbed_params = strip_secrets_from_query(
120+
env["QUERY_STRING"],
121+
agent.options.secrets_matcher,
122+
agent.options.secrets_list,
123+
)
124+
span.set_attribute("http.params", scrubbed_params)
125+
if "HTTP_HOST" in env:
126+
span.set_attribute("http.host", env["HTTP_HOST"])
127+
128+
if hasattr(flask.request.url_rule, "rule") and path_tpl_re.search(
129+
flask.request.url_rule.rule
130+
):
131+
path_tpl = flask.request.url_rule.rule.replace("<", "{")
132+
path_tpl = path_tpl.replace(">", "}")
133+
span.set_attribute("http.path_tpl", path_tpl)
134+
135+
136+
def inject_span(
137+
response: flask.wrappers.Response,
138+
error_message: str,
139+
set_flask_g_none: bool = False,
140+
):
141+
span = None
142+
try:
143+
# If we're not tracing, just return
144+
if not hasattr(flask.g, "span"):
145+
return response
146+
147+
span = flask.g.span
148+
if span:
149+
if 500 <= response.status_code:
150+
span.mark_as_errored()
151+
152+
span.set_attribute(
153+
SpanAttributes.HTTP_STATUS_CODE, int(response.status_code)
154+
)
155+
extract_custom_headers(span, response.headers, format=False)
156+
tracer = get_tracer()
157+
tracer.inject(span.context, Format.HTTP_HEADERS, response.headers)
158+
except Exception:
159+
logger.debug(error_message, exc_info=True)
160+
finally:
161+
if span and span.is_recording():
162+
span.end()
163+
if set_flask_g_none:
164+
flask.g.span = None
165+
166+
167+
def teardown_request_with_instana(*argv: Union[Exception, Type[Exception]]) -> None:
168+
"""
169+
In the case of exceptions, after_request_with_instana isn't called
170+
so we capture those cases here.
171+
"""
172+
if hasattr(flask.g, "span") and flask.g.span:
173+
if len(argv) > 0 and argv[0]:
174+
span = flask.g.span
175+
span.record_exception(argv[0])
176+
if SpanAttributes.HTTP_STATUS_CODE not in span.attributes:
177+
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, 500)
178+
if flask.g.span.is_recording():
179+
flask.g.span.end()
180+
flask.g.span = None
181+
182+
if hasattr(flask.g, "token") and flask.g.token:
183+
context.detach(flask.g.token)
184+
flask.g.token = None

src/instana/instrumentation/flask/vanilla.py

Lines changed: 16 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,23 @@
1-
# (c) Copyright IBM Corp. 2021
2-
# (c) Copyright Instana Inc. 2019
1+
# (c) Copyright IBM Corp. 2021, 2025
32

43

5-
import re
4+
from typing import Callable, Dict, Tuple
5+
66
import flask
77
import wrapt
8-
from typing import Callable, Tuple, Dict, Type, Union
9-
10-
from opentelemetry.semconv.trace import SpanAttributes
11-
from opentelemetry import context, trace
128

9+
from instana.instrumentation.flask.common import (
10+
create_span,
11+
inject_span,
12+
teardown_request_with_instana,
13+
)
1314
from instana.log import logger
14-
from instana.singletons import agent, tracer
15-
from instana.util.secrets import strip_secrets_from_query
16-
from instana.util.traceutils import extract_custom_headers
17-
from instana.propagators.format import Format
18-
19-
path_tpl_re = re.compile('<.*>')
2015

2116

2217
def before_request_with_instana() -> None:
2318
try:
24-
env = flask.request.environ
25-
span_context = tracer.extract(Format.HTTP_HEADERS, env)
26-
27-
span = tracer.start_span("wsgi", span_context=span_context)
28-
flask.g.span = span
29-
30-
ctx = trace.set_span_in_context(span)
31-
token = context.attach(ctx)
32-
flask.g.token = token
33-
34-
extract_custom_headers(span, env, format=True)
35-
36-
span.set_attribute(SpanAttributes.HTTP_METHOD, flask.request.method)
37-
if "PATH_INFO" in env:
38-
span.set_attribute(SpanAttributes.HTTP_URL, env["PATH_INFO"])
39-
if "QUERY_STRING" in env and len(env["QUERY_STRING"]):
40-
scrubbed_params = strip_secrets_from_query(
41-
env["QUERY_STRING"],
42-
agent.options.secrets_matcher,
43-
agent.options.secrets_list,
44-
)
45-
span.set_attribute("http.params", scrubbed_params)
46-
if "HTTP_HOST" in env:
47-
span.set_attribute("http.host", env["HTTP_HOST"])
48-
49-
if hasattr(flask.request.url_rule, "rule") and path_tpl_re.search(
50-
flask.request.url_rule.rule
51-
):
52-
path_tpl = flask.request.url_rule.rule.replace("<", "{")
53-
path_tpl = path_tpl.replace(">", "}")
54-
span.set_attribute("http.path_tpl", path_tpl)
55-
except:
19+
create_span()
20+
except Exception:
5621
logger.debug("Flask before_request", exc_info=True)
5722

5823
return None
@@ -61,62 +26,21 @@ def before_request_with_instana() -> None:
6126
def after_request_with_instana(
6227
response: flask.wrappers.Response,
6328
) -> flask.wrappers.Response:
64-
span = None
65-
try:
66-
# If we're not tracing, just return
67-
if not hasattr(flask.g, "span"):
68-
return response
69-
70-
span = flask.g.span
71-
if span:
72-
73-
if 500 <= response.status_code:
74-
span.mark_as_errored()
75-
76-
span.set_attribute(
77-
SpanAttributes.HTTP_STATUS_CODE, int(response.status_code)
78-
)
79-
extract_custom_headers(span, response.headers, format=False)
80-
81-
tracer.inject(span.context, Format.HTTP_HEADERS, response.headers)
82-
except:
83-
logger.debug("Flask after_request", exc_info=True)
84-
finally:
85-
if span and span.is_recording():
86-
span.end()
87-
flask.g.span = None
29+
inject_span(response, "Flask after_request", set_flask_g_none=True)
8830
return response
8931

9032

91-
def teardown_request_with_instana(*argv: Union[Exception, Type[Exception]]) -> None:
92-
"""
93-
In the case of exceptions, after_request_with_instana isn't called
94-
so we capture those cases here.
95-
"""
96-
if hasattr(flask.g, "span") and flask.g.span:
97-
if len(argv) > 0 and argv[0]:
98-
span = flask.g.span
99-
span.record_exception(argv[0])
100-
if SpanAttributes.HTTP_STATUS_CODE not in span.attributes:
101-
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, 500)
102-
if flask.g.span.is_recording():
103-
flask.g.span.end()
104-
flask.g.span = None
105-
106-
if hasattr(flask.g, "token") and flask.g.token:
107-
context.detach(flask.g.token)
108-
flask.g.token = None
109-
110-
111-
@wrapt.patch_function_wrapper('flask', 'Flask.full_dispatch_request')
33+
@wrapt.patch_function_wrapper("flask", "Flask.full_dispatch_request")
11234
def full_dispatch_request_with_instana(
11335
wrapped: Callable[..., flask.wrappers.Response],
11436
instance: flask.app.Flask,
11537
argv: Tuple,
11638
kwargs: Dict,
11739
) -> flask.wrappers.Response:
118-
if not hasattr(instance, '_stan_wuz_here'):
119-
logger.debug("Flask(vanilla): Applying flask before/after instrumentation funcs")
40+
if not hasattr(instance, "_stan_wuz_here"):
41+
logger.debug(
42+
"Flask(vanilla): Applying flask before/after instrumentation funcs"
43+
)
12044
setattr(instance, "_stan_wuz_here", True)
12145
instance.before_request(before_request_with_instana)
12246
instance.after_request(after_request_with_instana)

0 commit comments

Comments
 (0)