22# (c) Copyright Instana Inc. 2019
33
44
5- import wrapt
6- import flask
5+ import re
76from 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
1012from opentelemetry .semconv .trace import SpanAttributes
1113
1214from instana .log import logger
13- from instana .singletons import tracer
1415from 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
1720if 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" )
2329def 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" )
5461def 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
0 commit comments