Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested lib spans #25

Closed
while-loop opened this issue Oct 31, 2018 · 2 comments
Closed

Nested lib spans #25

while-loop opened this issue Oct 31, 2018 · 2 comments
Labels

Comments

@while-loop
Copy link

Does this library support spans across multiple libraries?
Like using flask and sqlalchemy within the same trace?

def test_nested_libs(self):
    engine = create_engine('sqlite://')
    User.metadata.create_all(engine)
    session = sessionmaker(bind=engine)()
    app = Flask('test_app')

    @app.route('/')
    def root():
        user = User(name='Tracer', is_active=True)
        session.add(user)
        session.commit()
        return user

    trace_flask(app)
    trace_sqlalchemy()

    with app.app_context():
        print app.test_client().get('/').data

With 1 flask trace with a child sql span

@mohabusama
Copy link
Contributor

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:
#26
#27

@while-loop
Copy link
Author

Awesome! Thanks for the insight and examples!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants