Repository: elastic/apm-agent-python

Issue:
Opentracing contrib: `DroppedSpan has no attribute transaction`
**Describe the bug**: ...
When using Elastic APM with Opentracing, the program crashes on `AttributeError: DroppedSpan object has no attribute transaction` exception if `span.finish()` is called. 
 It turns out to be this line:
https://github.com/elastic/apm-agent-python/blob/74cd1fa56f15a3149b367bd65ea4721a6fd95615/elasticapm/contrib/opentracing/span.py#L129
It will access `transaction` attribute which does NOT exist for `DroppedSpan`.

**Expected behavior**: ...
It should be expected to behave normally even if it's `DroppedSpan`.

**Environment (please complete the following information)**
- OS: [e.g. Linux] Linux
- Python version: 2.7
- Framework and version [e.g. Django 2.1]: N/A
- APM Server version: 7.4.0
- Agent version: 5.3.2





Discussion:
Great find @JeckyTian! I'll open a PR with a fix.
@beniwohli  Thanks,   is there an expectation which release has this fix?


Resolving patch:
diff --git a/elasticapm/contrib/opentracing/span.py b/elasticapm/contrib/opentracing/span.py
--- a/elasticapm/contrib/opentracing/span.py
+++ b/elasticapm/contrib/opentracing/span.py
@@ -54,6 +54,7 @@ def __init__(self, tracer, context, elastic_apm_ref):
         super(OTSpan, self).__init__(tracer, context)
         self.elastic_apm_ref = elastic_apm_ref
         self.is_transaction = isinstance(elastic_apm_ref, traces.Transaction)
+        self.is_dropped = isinstance(elastic_apm_ref, traces.DroppedSpan)
         if not context.span:
             context.span = self
 
@@ -101,7 +102,7 @@ def set_tag(self, key, value):
                 traces.set_context({"framework": {"name": value}}, "service")
             else:
                 self.elastic_apm_ref.label(**{key: value})
-        else:
+        elif not self.is_dropped:
             if key.startswith("db."):
                 span_context = self.elastic_apm_ref.context or {}
                 if "db" not in span_context:
@@ -125,7 +126,7 @@ def set_tag(self, key, value):
     def finish(self, finish_time=None):
         if self.is_transaction:
             self.tracer._agent.end_transaction()
-        else:
+        elif not self.is_dropped:
             self.elastic_apm_ref.transaction.end_span()
 
 


Test patch:
diff --git a/tests/contrib/opentracing/tests.py b/tests/contrib/opentracing/tests.py
--- a/tests/contrib/opentracing/tests.py
+++ b/tests/contrib/opentracing/tests.py
@@ -166,6 +166,21 @@ def test_span_tags(tracer):
     assert span2["context"]["tags"] == {"something_else": "bar"}
 
 
+@pytest.mark.parametrize("elasticapm_client", [{"transaction_max_spans": 1}], indirect=True)
+def test_dropped_spans(tracer):
+    assert tracer._agent.config.transaction_max_spans == 1
+    with tracer.start_active_span("transaction") as ot_scope_t:
+        with tracer.start_active_span("span") as ot_scope_s:
+            s = ot_scope_s.span
+            s.set_tag("db.type", "sql")
+        with tracer.start_active_span("span") as ot_scope_s:
+            s = ot_scope_s.span
+            s.set_tag("db.type", "sql")
+    client = tracer._agent
+    spans = client.events[constants.SPAN]
+    assert len(spans) == 1
+
+
 def test_error_log(tracer):
     with tracer.start_active_span("transaction") as tx_scope:
         try: