Repository: DataDog/dd-trace-py

Issue:
`ddtrace.opentracer` incorrectly raises `SpanContextCorruptedException` on `extract` of missing span context
The documentation for `SpanContextCorruptedException` [says](https://opentracing-python.readthedocs.io/en/1.3.0/api.html#opentracing.SpanContextCorruptedException):

> SpanContextCorruptedException should be used when the underlying span context state is seemingly present but not well-formed.

`ddtrace.opentracer`'s `extract` is throwing an error whenever it fails to recover a span, whether or not it was malformed or simply missing.  This completely breaks the normal pattern of "I received an HTTP request, so I'll throw the headers at `extract` and pass the result to `child_of` for my new span, expecting to get `None` and therefore make a new root span if I was called without tracing info".

### Which version of dd-trace-py are you using?

Python 3.7
ddtrace 0.46.0

### How can we reproduce your problem?

```py
In [1]: from opentracing import Format

In [2]: from ddtrace.opentracer import Tracer

In [3]: tracer = Tracer()

In [4]: tracer.extract(Format.HTTP_HEADERS, {})
---------------------------------------------------------------------------
SpanContextCorruptedException             Traceback (most recent call last)
<ipython-input-4-f497fe0c23a2> in <module>
----> 1 tracer.extract(Format.HTTP_HEADERS, {})

~/projects/granular/analysis/analysis-api/.venv/lib/python3.7/site-packages/ddtrace/opentracer/tracer.py in extract(self, format, carrier)
    326         # we have to manually activate the returned context from a distributed
    327         # trace
--> 328         ot_span_ctx = propagator.extract(carrier)
    329         dd_span_ctx = ot_span_ctx._dd_context
    330         self._dd_tracer.context_provider.activate(dd_span_ctx)

~/projects/granular/analysis/analysis-api/.venv/lib/python3.7/site-packages/ddtrace/opentracer/propagation/http.py in extract(self, carrier)
     70         # if this occurs.
     71         if not ddspan_ctx.trace_id:
---> 72             raise SpanContextCorruptedException("failed to extract span context")
     73 
     74         baggage = {}

SpanContextCorruptedException: failed to extract span context
```

### What is the result that you expected?

I expect to get a clean `None` with no error if no DataDog span context material was present.  See Jaeger:

```py
In [1]: from opentracing import Format

In [2]: import jaeger_client

In [3]: tracer = jaeger_client.Config({"service_name": "foo"}).initialize_tracer()

In [4]: tracer.extract(Format.HTTP_HEADERS, {})

In [5]: print(tracer.extract(Format.HTTP_HEADERS, {}))
None
```



Discussion:
Actually, seeing that backtrace above, I'm pretty suspicious about this code in `extract` too:  https://github.com/DataDog/dd-trace-py/blob/master/ddtrace/opentracer/tracer.py#L328-L332

Just because I successfully _extract_ a span context does not necessarily mean I will _use_ it, so I'm not sure why it should be activated.
Thank you @bdowning for reporting the issue. We will look into it and update here when we have a fix.
Running into this (or something similar) as well:

```
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/synapse/http/server.py", line 257, in _async_render_wrapper
    with trace_servlet(request, self._extract_context):
  File "/usr/local/lib/python3.8/contextlib.py", line 113, in __enter__
    return next(self.gen)
  File "/usr/local/lib/python3.8/site-packages/synapse/logging/opentracing.py", line 867, in trace_servlet
    scope = start_active_span_from_request(request, request_name, tags=request_tags)
  File "/usr/local/lib/python3.8/site-packages/synapse/logging/opentracing.py", line 503, in start_active_span_from_request
    context = opentracing.tracer.extract(opentracing.Format.HTTP_HEADERS, header_dict)
  File "/usr/local/lib/python3.8/site-packages/ddtrace/opentracer/tracer.py", line 329, in extract
    ot_span_ctx = propagator.extract(carrier)
  File "/usr/local/lib/python3.8/site-packages/ddtrace/opentracer/propagation/http.py", line 71, in extract
    raise SpanContextCorruptedException("failed to extract span context")
```
@bradtgmurray This is my current set of ddtrace monkey-patches if it helps (for this issue and a few other things that bothered me):
```py
import opentracing
from ddtrace.opentracer import Tracer
from ddtrace.opentracer.span import Span
from opentracing.ext import tags as OTTags

original_extract = Tracer.extract
original_log_kv = Span.log_kv
original_set_tag = Span.set_tag


def extract_wrapper(self, format, carrier):
    try:
        return original_extract(self, format, carrier)
    except opentracing.SpanContextCorruptedException:
        # Workaround for https://github.com/DataDog/dd-trace-py/issues/2030
        return None


def log_kv_wrapper(self, key_values, timestamp=None):
    # Get better error info into Datadog
    original_log_kv(self, key_values, timestamp)
    if key_values.get("event") == "error":
        try:
            exc_type = key_values["error.kind"]
            exc_val = key_values["error.object"]
            exc_tb = key_values["stack"]

            self._dd_span.set_exc_info(exc_type, exc_val, exc_tb)
        except KeyError:
            # welp
            pass
    return self


def set_tag_wrapper(self, key, value):
    original_set_tag(self, key, value)
    # keep actually logging db.statement
    if key == OTTags.DATABASE_STATEMENT:
        self._dd_span.set_tag(key, value)


Tracer.extract = extract_wrapper
Span.log_kv = log_kv_wrapper
Span.set_tag = set_tag_wrapper
```

Resolving patch:
diff --git a/ddtrace/opentracer/propagation/http.py b/ddtrace/opentracer/propagation/http.py
--- a/ddtrace/opentracer/propagation/http.py
+++ b/ddtrace/opentracer/propagation/http.py
@@ -1,7 +1,6 @@
 from typing import Dict
 
 from opentracing import InvalidCarrierException
-from opentracing import SpanContextCorruptedException
 
 from ddtrace.propagation.http import HTTPPropagator as DDHTTPPropagator
 
@@ -67,13 +66,6 @@ def extract(carrier):
             raise InvalidCarrierException("propagator expects carrier to be a dict")
 
         ddspan_ctx = DDHTTPPropagator.extract(carrier)
-
-        # if the dd propagator fails then it will return a new empty span
-        # context (with trace_id=None), we however want to raise an exception
-        # if this occurs.
-        if not ddspan_ctx.trace_id:
-            raise SpanContextCorruptedException("failed to extract span context")
-
         baggage = {}
         for key in carrier:
             if key.startswith(HTTP_BAGGAGE_PREFIX):


Test patch:
diff --git a/tests/opentracer/core/test_tracer.py b/tests/opentracer/core/test_tracer.py
--- a/tests/opentracer/core/test_tracer.py
+++ b/tests/opentracer/core/test_tracer.py
@@ -4,7 +4,6 @@
 import opentracing
 from opentracing import Format
 from opentracing import InvalidCarrierException
-from opentracing import SpanContextCorruptedException
 from opentracing import UnsupportedFormatException
 from opentracing import child_of
 import pytest
@@ -500,12 +499,11 @@ def test_http_headers_baggage(self, ot_tracer):
         assert ext_span_ctx.baggage == span_ctx.baggage
 
     def test_empty_propagated_context(self, ot_tracer):
-        """An empty propagated context should raise a
+        """An empty propagated context should not raise a
         SpanContextCorruptedException when extracted.
         """
         carrier = {}
-        with pytest.raises(SpanContextCorruptedException):
-            ot_tracer.extract(Format.HTTP_HEADERS, carrier)
+        ot_tracer.extract(Format.HTTP_HEADERS, carrier)
 
     def test_text(self, ot_tracer):
         """extract should undo inject for http headers"""
@@ -533,8 +531,7 @@ def test_corrupted_propagated_context(self, ot_tracer):
         corrupted_key = HTTP_HEADER_TRACE_ID[2:]
         carrier[corrupted_key] = 123
 
-        with pytest.raises(SpanContextCorruptedException):
-            ot_tracer.extract(Format.TEXT_MAP, carrier)
+        ot_tracer.extract(Format.TEXT_MAP, carrier)
 
     def test_immutable_span_context(self, ot_tracer):
         """Span contexts should be immutable."""