Skip to content

Commit d8e8c72

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 7be2837 commit d8e8c72

File tree

4 files changed

+226
-408
lines changed

4 files changed

+226
-408
lines changed

src/instana/instrumentation/flask/common.py

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,30 @@
22
# (c) Copyright Instana Inc. 2019
33

44

5-
import wrapt
6-
import flask
5+
import re
76
from importlib.metadata import version
8-
from typing import Callable, Tuple, Dict, Any, TYPE_CHECKING, Union
7+
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Type, Union
98

9+
import flask
10+
import wrapt
11+
from opentelemetry import context, trace
1012
from opentelemetry.semconv.trace import SpanAttributes
1113

1214
from instana.log import logger
13-
from instana.singletons import tracer
1415
from instana.propagators.format import Format
15-
16+
from instana.singletons import agent, get_tracer
17+
from instana.util.secrets import strip_secrets_from_query
18+
from instana.util.traceutils import extract_custom_headers
1619

1720
if TYPE_CHECKING:
18-
from werkzeug.exceptions import HTTPException
1921
from flask.typing import ResponseReturnValue
2022
from jinja2.environment import Template
23+
from werkzeug.exceptions import HTTPException
2124

22-
@wrapt.patch_function_wrapper('flask', 'templating._render')
25+
path_tpl_re = re.compile("<.*>")
26+
27+
28+
@wrapt.patch_function_wrapper("flask", "templating._render")
2329
def render_with_instana(
2430
wrapped: Callable[..., str],
2531
instance: object,
@@ -32,6 +38,7 @@ def render_with_instana(
3238

3339
parent_span = flask.g.span
3440
parent_context = parent_span.get_span_context()
41+
tracer = get_tracer()
3542

3643
with tracer.start_as_current_span("render", span_context=parent_context) as span:
3744
try:
@@ -50,7 +57,7 @@ def render_with_instana(
5057
raise
5158

5259

53-
@wrapt.patch_function_wrapper('flask', 'Flask.handle_user_exception')
60+
@wrapt.patch_function_wrapper("flask", "Flask.handle_user_exception")
5461
def handle_user_exception_with_instana(
5562
wrapped: Callable[..., Union["HTTPException", "ResponseReturnValue"]],
5663
instance: flask.app.Flask,
@@ -70,7 +77,7 @@ def handle_user_exception_with_instana(
7077
if isinstance(response, tuple):
7178
status_code = response[1]
7279
else:
73-
if hasattr(response, 'code'):
80+
if hasattr(response, "code"):
7481
status_code = response.code
7582
else:
7683
status_code = response.status_code
@@ -80,12 +87,99 @@ def handle_user_exception_with_instana(
8087

8188
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, int(status_code))
8289

83-
if hasattr(response, 'headers'):
90+
if hasattr(response, "headers"):
91+
tracer = get_tracer()
8492
tracer.inject(span.context, Format.HTTP_HEADERS, response.headers)
8593
if span and span.is_recording():
8694
span.end()
8795
flask.g.span = None
88-
except:
96+
except Exception:
8997
logger.debug("handle_user_exception_with_instana:", exc_info=True)
9098

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

src/instana/instrumentation/flask/vanilla.py

Lines changed: 16 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,24 @@
1-
# (c) Copyright IBM Corp. 2021
1+
# (c) Copyright IBM Corp. 2025
22
# (c) Copyright Instana Inc. 2019
33

44

5-
import re
5+
from typing import Callable, Dict, Tuple
6+
67
import flask
78
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
129

10+
from instana.instrumentation.flask.common import (
11+
create_span,
12+
inject_span,
13+
teardown_request_with_instana,
14+
)
1315
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('<.*>')
2016

2117

2218
def before_request_with_instana() -> None:
2319
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:
20+
create_span()
21+
except Exception:
5622
logger.debug("Flask before_request", exc_info=True)
5723

5824
return None
@@ -61,62 +27,21 @@ def before_request_with_instana() -> None:
6127
def after_request_with_instana(
6228
response: flask.wrappers.Response,
6329
) -> 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
30+
inject_span(response, "Flask after_request", set_flask_g_none=True)
8831
return response
8932

9033

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')
34+
@wrapt.patch_function_wrapper("flask", "Flask.full_dispatch_request")
11235
def full_dispatch_request_with_instana(
11336
wrapped: Callable[..., flask.wrappers.Response],
11437
instance: flask.app.Flask,
11538
argv: Tuple,
11639
kwargs: Dict,
11740
) -> flask.wrappers.Response:
118-
if not hasattr(instance, '_stan_wuz_here'):
119-
logger.debug("Flask(vanilla): Applying flask before/after instrumentation funcs")
41+
if not hasattr(instance, "_stan_wuz_here"):
42+
logger.debug(
43+
"Flask(vanilla): Applying flask before/after instrumentation funcs"
44+
)
12045
setattr(instance, "_stan_wuz_here", True)
12146
instance.before_request(before_request_with_instana)
12247
instance.after_request(after_request_with_instana)

0 commit comments

Comments
 (0)