Closed while-loop closed 6 years ago
Hi @while-loop ,
Yes, it can be done, but with a caveat! here is a test case that records the spans and asserts their hierarchy:
def test_trace_flask_sqlalchemy_nested():
recorder = Recorder()
recorder.spans = []
tracer = BasicTracer(recorder=recorder)
tracer.register_required_propagators()
opentracing.tracer = tracer
engine = create_engine('sqlite://')
User.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
app = Flask('test_app')
@app.route('/')
@trace(span_extractor=extract_span_from_flask_request) # We need this to provide a clear parent for any child spans (in our case sqlalchemy operations)
def root():
user = User(name='Tracer', is_active=True)
session.add(user)
session.commit()
return 'OK'
trace_flask(app)
trace_sqlalchemy()
with app.app_context():
print(app.test_client().get('/').data)
assert len(recorder.spans) == 3
sql_span = recorder.spans[0]
assert sql_span.operation_name == 'insert'
assert sql_span.tags['db.statement'] == 'INSERT INTO users (name, is_active) VALUES (?, ?)'
trace_span = recorder.spans[1]
assert trace_span.operation_name == 'root'
flask_span = recorder.spans[2]
assert flask_span.operation_name == 'root'
assert sql_span.parent_id == trace_span.context.span_id
assert trace_span.parent_id == flask_span.context.span_id
The reason we need the @trace
around the root()
handler is because trace_flask
just inserts the span in the flask.request
object, but root
does not know about it. So we need to tell root
that you should pick up the parent span from the request and from this point on, any traced function calls can be child spans. This of course adds an extra span in the trace (we have 3 spans instead of 2), but I feel this is the most straight forward way to do it, and we are using it in production.
If you want to show only two spans, then you have 2 options:
Option 1 [recommended]
Extract the request span in the root
function
@app.route('/')
def root():
# This edge span variable will help the sqlalchemy to find a parent - via callstack inspection
# Although it is not used
flask_edge_span = extract_span_from_flask_request() # noqa
user = User(name='Tracer', is_active=True)
session.add(user)
session.commit()
return 'OK'
Option 2
Inform trace_sqlalchemy
to pick parent span from Flask request:
trace_sqlalchemy(span_extractor=extract_span_from_flask_request)
I believe with opentracing==2.0
this can be handled even better if the Tracer implements the new scope API. But it is still not supported yet via opentracing-utils
.
I have created couple of issue to enhance the quick start and make these usecases clearer: https://github.com/zalando-zmon/opentracing-utils/issues/26 https://github.com/zalando-zmon/opentracing-utils/issues/27
Awesome! Thanks for the insight and examples!
Does this library support spans across multiple libraries? Like using flask and sqlalchemy within the same trace?
With 1 flask trace with a child sql span