We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Parsing and usage of boolean environment variables doesn't work as expected
We cannot specify "falsy" values via environment variables, as they will be overridden by `default` in `get_env`.

Also some integrations (e.g. `botocore` for setting `distributed_tracing`) don't parse the result of `get_env` correctly, and thus cannot be changed via environment variables.

I would like to set `config.botocore["distributing_tracing"]` to `False` via the `DD_BOTOCORE_DISTRIBUTED_TRACING` environment variable, but because the default is `True`, there is no way to change the semantics of the setting to disable the behaviour.

There are two issues, and if either of them was fixed, the problem would be fixed.

I would be happy to provide a fix for both of them, or be directed to a way to fix it, without having to add extra code to my project.

## Empty environment variables are assumed to be missing

If you do `DD_BOTOCORE_DISTRIBUTED_TRACING=` (an empty string), [the relevant code](https://github.com/DataDog/dd-trace-py/blob/e7a73336ab7c4c1479d504f86730c4dfaeb2e5f7/ddtrace/utils/formats.py#L47-L58) will replace it with `default`, in which case it would be `True`:

```python
def get_env(*parts, **kwargs):
    ...

    # env = "DD_BOTOCORE_DISTRIBUTED_TRACING"
    value = os.getenv(env) # value = ""
    ...

    # legacy = None
    value = value or legacy # value = None
    # default = True
    return value if value else default # return True
```

The fix would be to check for `None`, instead for "falsy":

```python
if value is None:
    if legacy is not None:
        value = legacy
    else:
        value = default
```

## Usage of `get_env` for booleans should convert text values to boolean

When the `botocore` integration [uses](https://github.com/DataDog/dd-trace-py/blob/e7a73336ab7c4c1479d504f86730c4dfaeb2e5f7/ddtrace/contrib/botocore/patch.py#L39) `get_env` to define a boolean option, it should convert common boolean text values to boolean, and reject others:

```python
config._add(
    "botocore",
    {
        "distributed_tracing": get_bool_env("botocore", "distributed_tracing", default=True),
        ...,
    },
)
```
And
```python
TRUE_STRINGS = [True, "true", "yes", "y", "enable", "enabled", "1"]
FALSE_STRINGS = [False, "false", "no", "n", "disable", "disabled", "0"]


def to_boolean(value: str) -> bool:
    if value.lower() in TRUE_STRINGS:
        return True
    if value.lower() in FALSE_STRINGS:
        return False
    raise ValueError(f"Unknown boolean value '{value}'")



def get_bool_env(*parts, **kwargs):
    return to_boolean(get_env(*parts, **kwargs))
```

### Which version of dd-trace-py are you using?

`ddtrace==0.55.3`

### Which version of pip are you using?

`pip 21.2.4 (python 3.8)`

### Which version of the libraries are you using?

Not relevant

### How can we reproduce your problem?

First issue:

Before running the script:

```shell
DD_BOTOCORE_DISTRIBUTED_TRACING=
```

Run the script:

```python3
from ddtrace import config
from ddtrace.contrib import botocore
print(config.botocore["distributed_tracing"])
print(bool(config.botocore["distributed_tracing"]))
```

Outputs:

```
True
True
```

Second issue:

Before running the script:

```shell
DD_BOTOCORE_DISTRIBUTED_TRACING=False
```

Run the script:

```python3
from ddtrace import config
from ddtrace.contrib import botocore
print(config.botocore["distributed_tracing"])
print(bool(config.botocore["distributed_tracing"]))
```

Outputs:

```
False
True
```


### What is the result that you get?

I cannot meaningfully change the value of `config.botocore["distributed_tracing"]`

### What is the result that you expected?

I want to be able to meaningfully change `config.botocore["distributed_tracing"]` via the environment variable (i.e. `DD_BOTOCORE_DISTRIBUTED_TRACING`)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ddtrace/contrib/botocore/patch.py`
Content:
```
1 """
2 Trace queries to aws api done via botocore client
3 """
4 # 3p
5 import base64
6 import json
7 
8 import botocore.client
9 
10 from ddtrace import config
11 from ddtrace.vendor import wrapt
12 
13 # project
14 from ...constants import ANALYTICS_SAMPLE_RATE_KEY
15 from ...constants import SPAN_MEASURED_KEY
16 from ...ext import SpanTypes
17 from ...ext import aws
18 from ...ext import http
19 from ...internal.logger import get_logger
20 from ...pin import Pin
21 from ...propagation.http import HTTPPropagator
22 from ...utils import get_argument_value
23 from ...utils.formats import deep_getattr
24 from ...utils.formats import get_env
25 from ...utils.wrappers import unwrap
26 
27 
28 # Original botocore client class
29 _Botocore_client = botocore.client.BaseClient
30 
31 ARGS_NAME = ("action", "params", "path", "verb")
32 TRACED_ARGS = {"params", "path", "verb"}
33 
34 log = get_logger(__name__)
35 
36 # Botocore default settings
37 config._add(
38     "botocore",
39     {
40         "distributed_tracing": get_env("botocore", "distributed_tracing", default=True),
41         "invoke_with_legacy_context": get_env("botocore", "invoke_with_legacy_context", default=False),
42     },
43 )
44 
45 
46 def inject_trace_data_to_message_attributes(trace_data, entry):
47     if "MessageAttributes" not in entry:
48         entry["MessageAttributes"] = {}
49     # An Amazon SQS message can contain up to 10 metadata attributes.
50     if len(entry["MessageAttributes"]) < 10:
51         entry["MessageAttributes"]["_datadog"] = {"DataType": "String", "StringValue": json.dumps(trace_data)}
52     else:
53         log.debug("skipping trace injection, max number (10) of MessageAttributes exceeded")
54 
55 
56 def inject_trace_to_sqs_batch_message(args, span):
57     trace_data = {}
58     HTTPPropagator.inject(span.context, trace_data)
59     params = args[1]
60 
61     for entry in params["Entries"]:
62         inject_trace_data_to_message_attributes(trace_data, entry)
63 
64 
65 def inject_trace_to_sqs_message(args, span):
66     trace_data = {}
67     HTTPPropagator.inject(span.context, trace_data)
68     params = args[1]
69 
70     inject_trace_data_to_message_attributes(trace_data, params)
71 
72 
73 def modify_client_context(client_context_object, trace_headers):
74     if config.botocore["invoke_with_legacy_context"]:
75         trace_headers = {"_datadog": trace_headers}
76 
77     if "custom" in client_context_object:
78         client_context_object["custom"].update(trace_headers)
79     else:
80         client_context_object["custom"] = trace_headers
81 
82 
83 def inject_trace_to_client_context(args, span):
84     trace_headers = {}
85     HTTPPropagator.inject(span.context, trace_headers)
86     client_context_object = {}
87     params = args[1]
88     if "ClientContext" in params:
89         try:
90             client_context_json = base64.b64decode(params["ClientContext"]).decode("utf-8")
91             client_context_object = json.loads(client_context_json)
92         except Exception:
93             log.warning("malformed client_context=%s", params["ClientContext"], exc_info=True)
94             return
95     modify_client_context(client_context_object, trace_headers)
96     try:
97         json_context = json.dumps(client_context_object).encode("utf-8")
98     except Exception:
99         log.warning("unable to encode modified client context as json: %s", client_context_object, exc_info=True)
100         return
101     params["ClientContext"] = base64.b64encode(json_context).decode("utf-8")
102 
103 
104 def patch():
105     if getattr(botocore.client, "_datadog_patch", False):
106         return
107     setattr(botocore.client, "_datadog_patch", True)
108 
109     wrapt.wrap_function_wrapper("botocore.client", "BaseClient._make_api_call", patched_api_call)
110     Pin(service="aws", app="aws").onto(botocore.client.BaseClient)
111 
112 
113 def unpatch():
114     if getattr(botocore.client, "_datadog_patch", False):
115         setattr(botocore.client, "_datadog_patch", False)
116         unwrap(botocore.client.BaseClient, "_make_api_call")
117 
118 
119 def patched_api_call(original_func, instance, args, kwargs):
120 
121     pin = Pin.get_from(instance)
122     if not pin or not pin.enabled():
123         return original_func(*args, **kwargs)
124 
125     endpoint_name = deep_getattr(instance, "_endpoint._endpoint_prefix")
126 
127     with pin.tracer.trace(
128         "{}.command".format(endpoint_name), service="{}.{}".format(pin.service, endpoint_name), span_type=SpanTypes.HTTP
129     ) as span:
130         span.set_tag(SPAN_MEASURED_KEY)
131         operation = None
132         if args:
133             operation = get_argument_value(args, kwargs, 0, "operation_name")
134             # DEV: join is the fastest way of concatenating strings that is compatible
135             # across Python versions (see
136             # https://stackoverflow.com/questions/1316887/what-is-the-most-efficient-string-concatenation-method-in-python)
137             span.resource = ".".join((endpoint_name, operation.lower()))
138 
139             if config.botocore["distributed_tracing"]:
140                 if endpoint_name == "lambda" and operation == "Invoke":
141                     inject_trace_to_client_context(args, span)
142                 if endpoint_name == "sqs" and operation == "SendMessage":
143                     inject_trace_to_sqs_message(args, span)
144                 if endpoint_name == "sqs" and operation == "SendMessageBatch":
145                     inject_trace_to_sqs_batch_message(args, span)
146 
147         else:
148             span.resource = endpoint_name
149 
150         aws.add_span_arg_tags(span, endpoint_name, args, ARGS_NAME, TRACED_ARGS)
151 
152         region_name = deep_getattr(instance, "meta.region_name")
153 
154         span._set_str_tag("aws.agent", "botocore")
155         if operation is not None:
156             span._set_str_tag("aws.operation", operation)
157         if region_name is not None:
158             span._set_str_tag("aws.region", region_name)
159 
160         result = original_func(*args, **kwargs)
161 
162         response_meta = result.get("ResponseMetadata")
163         if response_meta:
164             if "HTTPStatusCode" in response_meta:
165                 span.set_tag(http.STATUS_CODE, response_meta["HTTPStatusCode"])
166 
167             if "RetryAttempts" in response_meta:
168                 span.set_tag("retry_attempts", response_meta["RetryAttempts"])
169 
170             if "RequestId" in response_meta:
171                 span.set_tag("aws.requestid", response_meta["RequestId"])
172 
173         # set analytics sample rate
174         span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.botocore.get_analytics_sample_rate())
175 
176         return result
177
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ddtrace/contrib/botocore/patch.py b/ddtrace/contrib/botocore/patch.py
--- a/ddtrace/contrib/botocore/patch.py
+++ b/ddtrace/contrib/botocore/patch.py
@@ -20,6 +20,7 @@
 from ...pin import Pin
 from ...propagation.http import HTTPPropagator
 from ...utils import get_argument_value
+from ...utils.formats import asbool
 from ...utils.formats import deep_getattr
 from ...utils.formats import get_env
 from ...utils.wrappers import unwrap
@@ -37,8 +38,8 @@
 config._add(
     "botocore",
     {
-        "distributed_tracing": get_env("botocore", "distributed_tracing", default=True),
-        "invoke_with_legacy_context": get_env("botocore", "invoke_with_legacy_context", default=False),
+        "distributed_tracing": asbool(get_env("botocore", "distributed_tracing", default=True)),
+        "invoke_with_legacy_context": asbool(get_env("botocore", "invoke_with_legacy_context", default=False)),
     },
 )

{"golden_diff": "diff --git a/ddtrace/contrib/botocore/patch.py b/ddtrace/contrib/botocore/patch.py\n--- a/ddtrace/contrib/botocore/patch.py\n+++ b/ddtrace/contrib/botocore/patch.py\n@@ -20,6 +20,7 @@\n from ...pin import Pin\n from ...propagation.http import HTTPPropagator\n from ...utils import get_argument_value\n+from ...utils.formats import asbool\n from ...utils.formats import deep_getattr\n from ...utils.formats import get_env\n from ...utils.wrappers import unwrap\n@@ -37,8 +38,8 @@\n config._add(\n     \"botocore\",\n     {\n-        \"distributed_tracing\": get_env(\"botocore\", \"distributed_tracing\", default=True),\n-        \"invoke_with_legacy_context\": get_env(\"botocore\", \"invoke_with_legacy_context\", default=False),\n+        \"distributed_tracing\": asbool(get_env(\"botocore\", \"distributed_tracing\", default=True)),\n+        \"invoke_with_legacy_context\": asbool(get_env(\"botocore\", \"invoke_with_legacy_context\", default=False)),\n     },\n )\n", "issue": "Parsing and usage of boolean environment variables doesn't work as expected\nWe cannot specify \"falsy\" values via environment variables, as they will be overridden by `default` in `get_env`.\r\n\r\nAlso some integrations (e.g. `botocore` for setting `distributed_tracing`) don't parse the result of `get_env` correctly, and thus cannot be changed via environment variables.\r\n\r\nI would like to set `config.botocore[\"distributing_tracing\"]` to `False` via the `DD_BOTOCORE_DISTRIBUTED_TRACING` environment variable, but because the default is `True`, there is no way to change the semantics of the setting to disable the behaviour.\r\n\r\nThere are two issues, and if either of them was fixed, the problem would be fixed.\r\n\r\nI would be happy to provide a fix for both of them, or be directed to a way to fix it, without having to add extra code to my project.\r\n\r\n## Empty environment variables are assumed to be missing\r\n\r\nIf you do `DD_BOTOCORE_DISTRIBUTED_TRACING=` (an empty string), [the relevant code](https://github.com/DataDog/dd-trace-py/blob/e7a73336ab7c4c1479d504f86730c4dfaeb2e5f7/ddtrace/utils/formats.py#L47-L58) will replace it with `default`, in which case it would be `True`:\r\n\r\n```python\r\ndef get_env(*parts, **kwargs):\r\n    ...\r\n\r\n    # env = \"DD_BOTOCORE_DISTRIBUTED_TRACING\"\r\n    value = os.getenv(env) # value = \"\"\r\n    ...\r\n\r\n    # legacy = None\r\n    value = value or legacy # value = None\r\n    # default = True\r\n    return value if value else default # return True\r\n```\r\n\r\nThe fix would be to check for `None`, instead for \"falsy\":\r\n\r\n```python\r\nif value is None:\r\n    if legacy is not None:\r\n        value = legacy\r\n    else:\r\n        value = default\r\n```\r\n\r\n## Usage of `get_env` for booleans should convert text values to boolean\r\n\r\nWhen the `botocore` integration [uses](https://github.com/DataDog/dd-trace-py/blob/e7a73336ab7c4c1479d504f86730c4dfaeb2e5f7/ddtrace/contrib/botocore/patch.py#L39) `get_env` to define a boolean option, it should convert common boolean text values to boolean, and reject others:\r\n\r\n```python\r\nconfig._add(\r\n    \"botocore\",\r\n    {\r\n        \"distributed_tracing\": get_bool_env(\"botocore\", \"distributed_tracing\", default=True),\r\n        ...,\r\n    },\r\n)\r\n```\r\nAnd\r\n```python\r\nTRUE_STRINGS = [True, \"true\", \"yes\", \"y\", \"enable\", \"enabled\", \"1\"]\r\nFALSE_STRINGS = [False, \"false\", \"no\", \"n\", \"disable\", \"disabled\", \"0\"]\r\n\r\n\r\ndef to_boolean(value: str) -> bool:\r\n    if value.lower() in TRUE_STRINGS:\r\n        return True\r\n    if value.lower() in FALSE_STRINGS:\r\n        return False\r\n    raise ValueError(f\"Unknown boolean value '{value}'\")\r\n\r\n\r\n\r\ndef get_bool_env(*parts, **kwargs):\r\n    return to_boolean(get_env(*parts, **kwargs))\r\n```\r\n\r\n### Which version of dd-trace-py are you using?\r\n\r\n`ddtrace==0.55.3`\r\n\r\n### Which version of pip are you using?\r\n\r\n`pip 21.2.4 (python 3.8)`\r\n\r\n### Which version of the libraries are you using?\r\n\r\nNot relevant\r\n\r\n### How can we reproduce your problem?\r\n\r\nFirst issue:\r\n\r\nBefore running the script:\r\n\r\n```shell\r\nDD_BOTOCORE_DISTRIBUTED_TRACING=\r\n```\r\n\r\nRun the script:\r\n\r\n```python3\r\nfrom ddtrace import config\r\nfrom ddtrace.contrib import botocore\r\nprint(config.botocore[\"distributed_tracing\"])\r\nprint(bool(config.botocore[\"distributed_tracing\"]))\r\n```\r\n\r\nOutputs:\r\n\r\n```\r\nTrue\r\nTrue\r\n```\r\n\r\nSecond issue:\r\n\r\nBefore running the script:\r\n\r\n```shell\r\nDD_BOTOCORE_DISTRIBUTED_TRACING=False\r\n```\r\n\r\nRun the script:\r\n\r\n```python3\r\nfrom ddtrace import config\r\nfrom ddtrace.contrib import botocore\r\nprint(config.botocore[\"distributed_tracing\"])\r\nprint(bool(config.botocore[\"distributed_tracing\"]))\r\n```\r\n\r\nOutputs:\r\n\r\n```\r\nFalse\r\nTrue\r\n```\r\n\r\n\r\n### What is the result that you get?\r\n\r\nI cannot meaningfully change the value of `config.botocore[\"distributed_tracing\"]`\r\n\r\n### What is the result that you expected?\r\n\r\nI want to be able to meaningfully change `config.botocore[\"distributed_tracing\"]` via the environment variable (i.e. `DD_BOTOCORE_DISTRIBUTED_TRACING`)\r\n\n", "before_files": [{"content": "\"\"\"\nTrace queries to aws api done via botocore client\n\"\"\"\n# 3p\nimport base64\nimport json\n\nimport botocore.client\n\nfrom ddtrace import config\nfrom ddtrace.vendor import wrapt\n\n# project\nfrom ...constants import ANALYTICS_SAMPLE_RATE_KEY\nfrom ...constants import SPAN_MEASURED_KEY\nfrom ...ext import SpanTypes\nfrom ...ext import aws\nfrom ...ext import http\nfrom ...internal.logger import get_logger\nfrom ...pin import Pin\nfrom ...propagation.http import HTTPPropagator\nfrom ...utils import get_argument_value\nfrom ...utils.formats import deep_getattr\nfrom ...utils.formats import get_env\nfrom ...utils.wrappers import unwrap\n\n\n# Original botocore client class\n_Botocore_client = botocore.client.BaseClient\n\nARGS_NAME = (\"action\", \"params\", \"path\", \"verb\")\nTRACED_ARGS = {\"params\", \"path\", \"verb\"}\n\nlog = get_logger(__name__)\n\n# Botocore default settings\nconfig._add(\n    \"botocore\",\n    {\n        \"distributed_tracing\": get_env(\"botocore\", \"distributed_tracing\", default=True),\n        \"invoke_with_legacy_context\": get_env(\"botocore\", \"invoke_with_legacy_context\", default=False),\n    },\n)\n\n\ndef inject_trace_data_to_message_attributes(trace_data, entry):\n    if \"MessageAttributes\" not in entry:\n        entry[\"MessageAttributes\"] = {}\n    # An Amazon SQS message can contain up to 10 metadata attributes.\n    if len(entry[\"MessageAttributes\"]) < 10:\n        entry[\"MessageAttributes\"][\"_datadog\"] = {\"DataType\": \"String\", \"StringValue\": json.dumps(trace_data)}\n    else:\n        log.debug(\"skipping trace injection, max number (10) of MessageAttributes exceeded\")\n\n\ndef inject_trace_to_sqs_batch_message(args, span):\n    trace_data = {}\n    HTTPPropagator.inject(span.context, trace_data)\n    params = args[1]\n\n    for entry in params[\"Entries\"]:\n        inject_trace_data_to_message_attributes(trace_data, entry)\n\n\ndef inject_trace_to_sqs_message(args, span):\n    trace_data = {}\n    HTTPPropagator.inject(span.context, trace_data)\n    params = args[1]\n\n    inject_trace_data_to_message_attributes(trace_data, params)\n\n\ndef modify_client_context(client_context_object, trace_headers):\n    if config.botocore[\"invoke_with_legacy_context\"]:\n        trace_headers = {\"_datadog\": trace_headers}\n\n    if \"custom\" in client_context_object:\n        client_context_object[\"custom\"].update(trace_headers)\n    else:\n        client_context_object[\"custom\"] = trace_headers\n\n\ndef inject_trace_to_client_context(args, span):\n    trace_headers = {}\n    HTTPPropagator.inject(span.context, trace_headers)\n    client_context_object = {}\n    params = args[1]\n    if \"ClientContext\" in params:\n        try:\n            client_context_json = base64.b64decode(params[\"ClientContext\"]).decode(\"utf-8\")\n            client_context_object = json.loads(client_context_json)\n        except Exception:\n            log.warning(\"malformed client_context=%s\", params[\"ClientContext\"], exc_info=True)\n            return\n    modify_client_context(client_context_object, trace_headers)\n    try:\n        json_context = json.dumps(client_context_object).encode(\"utf-8\")\n    except Exception:\n        log.warning(\"unable to encode modified client context as json: %s\", client_context_object, exc_info=True)\n        return\n    params[\"ClientContext\"] = base64.b64encode(json_context).decode(\"utf-8\")\n\n\ndef patch():\n    if getattr(botocore.client, \"_datadog_patch\", False):\n        return\n    setattr(botocore.client, \"_datadog_patch\", True)\n\n    wrapt.wrap_function_wrapper(\"botocore.client\", \"BaseClient._make_api_call\", patched_api_call)\n    Pin(service=\"aws\", app=\"aws\").onto(botocore.client.BaseClient)\n\n\ndef unpatch():\n    if getattr(botocore.client, \"_datadog_patch\", False):\n        setattr(botocore.client, \"_datadog_patch\", False)\n        unwrap(botocore.client.BaseClient, \"_make_api_call\")\n\n\ndef patched_api_call(original_func, instance, args, kwargs):\n\n    pin = Pin.get_from(instance)\n    if not pin or not pin.enabled():\n        return original_func(*args, **kwargs)\n\n    endpoint_name = deep_getattr(instance, \"_endpoint._endpoint_prefix\")\n\n    with pin.tracer.trace(\n        \"{}.command\".format(endpoint_name), service=\"{}.{}\".format(pin.service, endpoint_name), span_type=SpanTypes.HTTP\n    ) as span:\n        span.set_tag(SPAN_MEASURED_KEY)\n        operation = None\n        if args:\n            operation = get_argument_value(args, kwargs, 0, \"operation_name\")\n            # DEV: join is the fastest way of concatenating strings that is compatible\n            # across Python versions (see\n            # https://stackoverflow.com/questions/1316887/what-is-the-most-efficient-string-concatenation-method-in-python)\n            span.resource = \".\".join((endpoint_name, operation.lower()))\n\n            if config.botocore[\"distributed_tracing\"]:\n                if endpoint_name == \"lambda\" and operation == \"Invoke\":\n                    inject_trace_to_client_context(args, span)\n                if endpoint_name == \"sqs\" and operation == \"SendMessage\":\n                    inject_trace_to_sqs_message(args, span)\n                if endpoint_name == \"sqs\" and operation == \"SendMessageBatch\":\n                    inject_trace_to_sqs_batch_message(args, span)\n\n        else:\n            span.resource = endpoint_name\n\n        aws.add_span_arg_tags(span, endpoint_name, args, ARGS_NAME, TRACED_ARGS)\n\n        region_name = deep_getattr(instance, \"meta.region_name\")\n\n        span._set_str_tag(\"aws.agent\", \"botocore\")\n        if operation is not None:\n            span._set_str_tag(\"aws.operation\", operation)\n        if region_name is not None:\n            span._set_str_tag(\"aws.region\", region_name)\n\n        result = original_func(*args, **kwargs)\n\n        response_meta = result.get(\"ResponseMetadata\")\n        if response_meta:\n            if \"HTTPStatusCode\" in response_meta:\n                span.set_tag(http.STATUS_CODE, response_meta[\"HTTPStatusCode\"])\n\n            if \"RetryAttempts\" in response_meta:\n                span.set_tag(\"retry_attempts\", response_meta[\"RetryAttempts\"])\n\n            if \"RequestId\" in response_meta:\n                span.set_tag(\"aws.requestid\", response_meta[\"RequestId\"])\n\n        # set analytics sample rate\n        span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.botocore.get_analytics_sample_rate())\n\n        return result\n", "path": "ddtrace/contrib/botocore/patch.py"}], "after_files": [{"content": "\"\"\"\nTrace queries to aws api done via botocore client\n\"\"\"\n# 3p\nimport base64\nimport json\n\nimport botocore.client\n\nfrom ddtrace import config\nfrom ddtrace.vendor import wrapt\n\n# project\nfrom ...constants import ANALYTICS_SAMPLE_RATE_KEY\nfrom ...constants import SPAN_MEASURED_KEY\nfrom ...ext import SpanTypes\nfrom ...ext import aws\nfrom ...ext import http\nfrom ...internal.logger import get_logger\nfrom ...pin import Pin\nfrom ...propagation.http import HTTPPropagator\nfrom ...utils import get_argument_value\nfrom ...utils.formats import asbool\nfrom ...utils.formats import deep_getattr\nfrom ...utils.formats import get_env\nfrom ...utils.wrappers import unwrap\n\n\n# Original botocore client class\n_Botocore_client = botocore.client.BaseClient\n\nARGS_NAME = (\"action\", \"params\", \"path\", \"verb\")\nTRACED_ARGS = {\"params\", \"path\", \"verb\"}\n\nlog = get_logger(__name__)\n\n# Botocore default settings\nconfig._add(\n    \"botocore\",\n    {\n        \"distributed_tracing\": asbool(get_env(\"botocore\", \"distributed_tracing\", default=True)),\n        \"invoke_with_legacy_context\": asbool(get_env(\"botocore\", \"invoke_with_legacy_context\", default=False)),\n    },\n)\n\n\ndef inject_trace_data_to_message_attributes(trace_data, entry):\n    if \"MessageAttributes\" not in entry:\n        entry[\"MessageAttributes\"] = {}\n    # An Amazon SQS message can contain up to 10 metadata attributes.\n    if len(entry[\"MessageAttributes\"]) < 10:\n        entry[\"MessageAttributes\"][\"_datadog\"] = {\"DataType\": \"String\", \"StringValue\": json.dumps(trace_data)}\n    else:\n        log.debug(\"skipping trace injection, max number (10) of MessageAttributes exceeded\")\n\n\ndef inject_trace_to_sqs_batch_message(args, span):\n    trace_data = {}\n    HTTPPropagator.inject(span.context, trace_data)\n    params = args[1]\n\n    for entry in params[\"Entries\"]:\n        inject_trace_data_to_message_attributes(trace_data, entry)\n\n\ndef inject_trace_to_sqs_message(args, span):\n    trace_data = {}\n    HTTPPropagator.inject(span.context, trace_data)\n    params = args[1]\n\n    inject_trace_data_to_message_attributes(trace_data, params)\n\n\ndef modify_client_context(client_context_object, trace_headers):\n    if config.botocore[\"invoke_with_legacy_context\"]:\n        trace_headers = {\"_datadog\": trace_headers}\n\n    if \"custom\" in client_context_object:\n        client_context_object[\"custom\"].update(trace_headers)\n    else:\n        client_context_object[\"custom\"] = trace_headers\n\n\ndef inject_trace_to_client_context(args, span):\n    trace_headers = {}\n    HTTPPropagator.inject(span.context, trace_headers)\n    client_context_object = {}\n    params = args[1]\n    if \"ClientContext\" in params:\n        try:\n            client_context_json = base64.b64decode(params[\"ClientContext\"]).decode(\"utf-8\")\n            client_context_object = json.loads(client_context_json)\n        except Exception:\n            log.warning(\"malformed client_context=%s\", params[\"ClientContext\"], exc_info=True)\n            return\n    modify_client_context(client_context_object, trace_headers)\n    try:\n        json_context = json.dumps(client_context_object).encode(\"utf-8\")\n    except Exception:\n        log.warning(\"unable to encode modified client context as json: %s\", client_context_object, exc_info=True)\n        return\n    params[\"ClientContext\"] = base64.b64encode(json_context).decode(\"utf-8\")\n\n\ndef patch():\n    if getattr(botocore.client, \"_datadog_patch\", False):\n        return\n    setattr(botocore.client, \"_datadog_patch\", True)\n\n    wrapt.wrap_function_wrapper(\"botocore.client\", \"BaseClient._make_api_call\", patched_api_call)\n    Pin(service=\"aws\", app=\"aws\").onto(botocore.client.BaseClient)\n\n\ndef unpatch():\n    if getattr(botocore.client, \"_datadog_patch\", False):\n        setattr(botocore.client, \"_datadog_patch\", False)\n        unwrap(botocore.client.BaseClient, \"_make_api_call\")\n\n\ndef patched_api_call(original_func, instance, args, kwargs):\n\n    pin = Pin.get_from(instance)\n    if not pin or not pin.enabled():\n        return original_func(*args, **kwargs)\n\n    endpoint_name = deep_getattr(instance, \"_endpoint._endpoint_prefix\")\n\n    with pin.tracer.trace(\n        \"{}.command\".format(endpoint_name), service=\"{}.{}\".format(pin.service, endpoint_name), span_type=SpanTypes.HTTP\n    ) as span:\n        span.set_tag(SPAN_MEASURED_KEY)\n        operation = None\n        if args:\n            operation = get_argument_value(args, kwargs, 0, \"operation_name\")\n            # DEV: join is the fastest way of concatenating strings that is compatible\n            # across Python versions (see\n            # https://stackoverflow.com/questions/1316887/what-is-the-most-efficient-string-concatenation-method-in-python)\n            span.resource = \".\".join((endpoint_name, operation.lower()))\n\n            if config.botocore[\"distributed_tracing\"]:\n                if endpoint_name == \"lambda\" and operation == \"Invoke\":\n                    inject_trace_to_client_context(args, span)\n                if endpoint_name == \"sqs\" and operation == \"SendMessage\":\n                    inject_trace_to_sqs_message(args, span)\n                if endpoint_name == \"sqs\" and operation == \"SendMessageBatch\":\n                    inject_trace_to_sqs_batch_message(args, span)\n\n        else:\n            span.resource = endpoint_name\n\n        aws.add_span_arg_tags(span, endpoint_name, args, ARGS_NAME, TRACED_ARGS)\n\n        region_name = deep_getattr(instance, \"meta.region_name\")\n\n        span._set_str_tag(\"aws.agent\", \"botocore\")\n        if operation is not None:\n            span._set_str_tag(\"aws.operation\", operation)\n        if region_name is not None:\n            span._set_str_tag(\"aws.region\", region_name)\n\n        result = original_func(*args, **kwargs)\n\n        response_meta = result.get(\"ResponseMetadata\")\n        if response_meta:\n            if \"HTTPStatusCode\" in response_meta:\n                span.set_tag(http.STATUS_CODE, response_meta[\"HTTPStatusCode\"])\n\n            if \"RetryAttempts\" in response_meta:\n                span.set_tag(\"retry_attempts\", response_meta[\"RetryAttempts\"])\n\n            if \"RequestId\" in response_meta:\n                span.set_tag(\"aws.requestid\", response_meta[\"RequestId\"])\n\n        # set analytics sample rate\n        span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.botocore.get_analytics_sample_rate())\n\n        return result\n", "path": "ddtrace/contrib/botocore/patch.py"}]}