We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
pymongo trace exception
We're noticing this exception - using latest `pymongo==3.4.0` driver:

```exceptions.AttributeError: 'long' object has no attribute 'items'
Traceback (most recent call last):
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 105, in send_message_with_response
    span.resource = _resource_from_cmd(cmd)
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 220, in _resource_from_cmd
    nq = normalize_filter(cmd.query)
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 207, in normalize_filter
    out[k] = normalize_filter(v)
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 207, in normalize_filter
    out[k] = normalize_filter(v)
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 199, in normalize_filter
    return [normalize_filter(s) for s in f]
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 204, in normalize_filter
    for k, v in iteritems(f):
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/compat.py", line 32, in iteritems
    func = obj.items
AttributeError: 'long' object has no attribute 'items'
```
pymongo trace exception
We're noticing this exception - using latest `pymongo==3.4.0` driver:

```exceptions.AttributeError: 'long' object has no attribute 'items'
Traceback (most recent call last):
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 105, in send_message_with_response
    span.resource = _resource_from_cmd(cmd)
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 220, in _resource_from_cmd
    nq = normalize_filter(cmd.query)
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 207, in normalize_filter
    out[k] = normalize_filter(v)
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 207, in normalize_filter
    out[k] = normalize_filter(v)
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 199, in normalize_filter
    return [normalize_filter(s) for s in f]
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py", line 204, in normalize_filter
    for k, v in iteritems(f):
  File "/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/compat.py", line 32, in iteritems
    func = obj.items
AttributeError: 'long' object has no attribute 'items'
```

--- 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/pymongo/client.py`
Content:
```
1 # stdlib
2 import contextlib
3 import logging
4 import json
5 
6 # 3p
7 import pymongo
8 from wrapt import ObjectProxy
9 
10 # project
11 import ddtrace
12 from ...compat import iteritems
13 from ...ext import AppTypes
14 from ...ext import mongo as mongox
15 from ...ext import net as netx
16 from ...util import deprecated
17 from .parse import parse_spec, parse_query, parse_msg
18 
19 # Original Client class
20 _MongoClient = pymongo.MongoClient
21 
22 log = logging.getLogger(__name__)
23 
24 
25 @deprecated(message='Use patching instead (see the docs).', version='0.6.0')
26 def trace_mongo_client(client, tracer, service=mongox.TYPE):
27     tracer.set_service_info(
28         service=service,
29         app=mongox.TYPE,
30         app_type=AppTypes.db,
31     )
32     traced_client = TracedMongoClient(client)
33     ddtrace.Pin(service=service, tracer=tracer).onto(traced_client)
34     return traced_client
35 
36 
37 class TracedMongoClient(ObjectProxy):
38 
39     def __init__(self, client=None, *args, **kwargs):
40         # To support the former trace_mongo_client interface, we have to keep this old interface
41         # TODO(Benjamin): drop it in a later version
42         if not isinstance(client, _MongoClient):
43             # Patched interface, instanciate the client
44             # Note that, in that case, the client argument isn't a client, it's just the first arg
45             client = _MongoClient(client, *args, **kwargs)
46 
47         super(TracedMongoClient, self).__init__(client)
48         # Default Pin
49         ddtrace.Pin(service=mongox.TYPE).onto(self)
50         # NOTE[matt] the TracedMongoClient attempts to trace all of the network
51         # calls in the trace library. This is good because it measures the
52         # actual network time. It's bad because it uses a private API which
53         # could change. We'll see how this goes.
54         client._topology = TracedTopology(client._topology)
55 
56     def __setddpin__(self, pin):
57         pin.onto(self._topology)
58 
59     def __getddpin__(self):
60         return ddtrace.Pin.get_from(self._topology)
61 
62 
63 class TracedTopology(ObjectProxy):
64 
65     def __init__(self, topology):
66         super(TracedTopology, self).__init__(topology)
67 
68     def select_server(self, *args, **kwargs):
69         s = self.__wrapped__.select_server(*args, **kwargs)
70         if not isinstance(s, TracedServer):
71             s = TracedServer(s)
72         # Reattach the pin every time in case it changed since the initial patching
73         ddtrace.Pin.get_from(self).onto(s)
74         return s
75 
76 
77 class TracedServer(ObjectProxy):
78 
79     def __init__(self, server):
80         super(TracedServer, self).__init__(server)
81 
82     def send_message_with_response(self, operation, *args, **kwargs):
83         cmd = None
84         # Only try to parse something we think is a query.
85         if self._is_query(operation):
86             try:
87                 cmd = parse_query(operation)
88             except Exception:
89                 log.exception("error parsing query")
90 
91         pin = ddtrace.Pin.get_from(self)
92 
93         # if we couldn't parse or shouldn't trace the message, just go.
94         if not cmd or not pin or not pin.enabled():
95             return self.__wrapped__.send_message_with_response(
96                 operation,
97                 *args,
98                 **kwargs)
99 
100         with pin.tracer.trace(
101                 "pymongo.cmd",
102                 span_type=mongox.TYPE,
103                 service=pin.service) as span:
104 
105             span.resource = _resource_from_cmd(cmd)
106             span.set_tag(mongox.DB, cmd.db)
107             span.set_tag(mongox.COLLECTION, cmd.coll)
108             span.set_tags(cmd.tags)
109 
110             result = self.__wrapped__.send_message_with_response(
111                 operation,
112                 *args,
113                 **kwargs)
114 
115             if result and result.address:
116                 _set_address_tags(span, result.address)
117             return result
118 
119     @contextlib.contextmanager
120     def get_socket(self, *args, **kwargs):
121         with self.__wrapped__.get_socket(*args, **kwargs) as s:
122             if not isinstance(s, TracedSocket):
123                 s = TracedSocket(s)
124             ddtrace.Pin.get_from(self).onto(s)
125             yield s
126 
127     @staticmethod
128     def _is_query(op):
129         # NOTE: _Query should alwyas have a spec field
130         return hasattr(op, 'spec')
131 
132 
133 class TracedSocket(ObjectProxy):
134 
135     def __init__(self, socket):
136         super(TracedSocket, self).__init__(socket)
137 
138     def command(self, dbname, spec, *args, **kwargs):
139         cmd = None
140         try:
141             cmd = parse_spec(spec, dbname)
142         except Exception:
143             log.exception("error parsing spec. skipping trace")
144 
145         pin = ddtrace.Pin.get_from(self)
146         # skip tracing if we don't have a piece of data we need
147         if not dbname or not cmd or not pin or not pin.enabled():
148             return self.__wrapped__.command(dbname, spec, *args, **kwargs)
149 
150         cmd.db = dbname
151         with self.__trace(cmd):
152             return self.__wrapped__.command(dbname, spec, *args, **kwargs)
153 
154     def write_command(self, request_id, msg):
155         cmd = None
156         try:
157             cmd = parse_msg(msg)
158         except Exception:
159             log.exception("error parsing msg")
160 
161         pin = ddtrace.Pin.get_from(self)
162         # if we couldn't parse it, don't try to trace it.
163         if not cmd or not pin or not pin.enabled():
164             return self.__wrapped__.write_command(request_id, msg)
165 
166         with self.__trace(cmd) as s:
167             s.resource = _resource_from_cmd(cmd)
168             result = self.__wrapped__.write_command(request_id, msg)
169             if result:
170                 s.set_metric(mongox.ROWS, result.get("n", -1))
171             return result
172 
173     def __trace(self, cmd):
174         pin = ddtrace.Pin.get_from(self)
175         s = pin.tracer.trace(
176             "pymongo.cmd",
177             span_type=mongox.TYPE,
178             service=pin.service)
179 
180         if cmd.db:
181             s.set_tag(mongox.DB, cmd.db)
182         if cmd:
183             s.set_tag(mongox.COLLECTION, cmd.coll)
184             s.set_tags(cmd.tags)
185             s.set_metrics(cmd.metrics)
186 
187         s.resource = _resource_from_cmd(cmd)
188         if self.address:
189             _set_address_tags(s, self.address)
190         return s
191 
192 
193 def normalize_filter(f=None):
194     if f is None:
195         return {}
196     elif isinstance(f, list):
197         # normalize lists of filters
198         # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]}
199         return [normalize_filter(s) for s in f]
200     else:
201         # normalize dicts of filters
202         # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]})
203         out = {}
204         for k, v in iteritems(f):
205             if isinstance(v, list) or isinstance(v, dict):
206                 # RECURSION ALERT: needs to move to the agent
207                 out[k] = normalize_filter(v)
208             else:
209                 out[k] = '?'
210         return out
211 
212 def _set_address_tags(span, address):
213     # the address is only set after the cursor is done.
214     if address:
215         span.set_tag(netx.TARGET_HOST, address[0])
216         span.set_tag(netx.TARGET_PORT, address[1])
217 
218 def _resource_from_cmd(cmd):
219     if cmd.query is not None:
220         nq = normalize_filter(cmd.query)
221         # needed to dump json so we don't get unicode
222         # dict keys like {u'foo':'bar'}
223         q = json.dumps(nq)
224         return "%s %s %s" % (cmd.name, cmd.coll, q)
225     else:
226         return "%s %s" % (cmd.name, cmd.coll)
227
```
--- 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/pymongo/client.py b/ddtrace/contrib/pymongo/client.py
--- a/ddtrace/contrib/pymongo/client.py
+++ b/ddtrace/contrib/pymongo/client.py
@@ -197,17 +197,25 @@
         # normalize lists of filters
         # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]}
         return [normalize_filter(s) for s in f]
-    else:
+    elif isinstance(f, dict):
         # normalize dicts of filters
-        # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]})
+        #   {$or: [ { age: { $lt: 30 } }, { type: 1 } ]})
         out = {}
         for k, v in iteritems(f):
-            if isinstance(v, list) or isinstance(v, dict):
+            if k == "$in" or k == "$nin":
+                # special case $in queries so we don't loop over lists.
+                out[k] = "?"
+            elif isinstance(v, list) or isinstance(v, dict):
                 # RECURSION ALERT: needs to move to the agent
                 out[k] = normalize_filter(v)
             else:
+                # NOTE: this shouldn't happen, but let's have a safeguard.
                 out[k] = '?'
         return out
+    else:
+        # FIXME[matt] unexpected type. not sure this should ever happen, but at
+        # least it won't crash.
+        return {}
 
 def _set_address_tags(span, address):
     # the address is only set after the cursor is done.

{"golden_diff": "diff --git a/ddtrace/contrib/pymongo/client.py b/ddtrace/contrib/pymongo/client.py\n--- a/ddtrace/contrib/pymongo/client.py\n+++ b/ddtrace/contrib/pymongo/client.py\n@@ -197,17 +197,25 @@\n         # normalize lists of filters\n         # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]}\n         return [normalize_filter(s) for s in f]\n-    else:\n+    elif isinstance(f, dict):\n         # normalize dicts of filters\n-        # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]})\n+        #   {$or: [ { age: { $lt: 30 } }, { type: 1 } ]})\n         out = {}\n         for k, v in iteritems(f):\n-            if isinstance(v, list) or isinstance(v, dict):\n+            if k == \"$in\" or k == \"$nin\":\n+                # special case $in queries so we don't loop over lists.\n+                out[k] = \"?\"\n+            elif isinstance(v, list) or isinstance(v, dict):\n                 # RECURSION ALERT: needs to move to the agent\n                 out[k] = normalize_filter(v)\n             else:\n+                # NOTE: this shouldn't happen, but let's have a safeguard.\n                 out[k] = '?'\n         return out\n+    else:\n+        # FIXME[matt] unexpected type. not sure this should ever happen, but at\n+        # least it won't crash.\n+        return {}\n \n def _set_address_tags(span, address):\n     # the address is only set after the cursor is done.\n", "issue": "pymongo trace exception\nWe're noticing this exception - using latest `pymongo==3.4.0` driver:\r\n\r\n```exceptions.AttributeError: 'long' object has no attribute 'items'\r\nTraceback (most recent call last):\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 105, in send_message_with_response\r\n    span.resource = _resource_from_cmd(cmd)\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 220, in _resource_from_cmd\r\n    nq = normalize_filter(cmd.query)\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 207, in normalize_filter\r\n    out[k] = normalize_filter(v)\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 207, in normalize_filter\r\n    out[k] = normalize_filter(v)\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 199, in normalize_filter\r\n    return [normalize_filter(s) for s in f]\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 204, in normalize_filter\r\n    for k, v in iteritems(f):\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/compat.py\", line 32, in iteritems\r\n    func = obj.items\r\nAttributeError: 'long' object has no attribute 'items'\r\n```\npymongo trace exception\nWe're noticing this exception - using latest `pymongo==3.4.0` driver:\r\n\r\n```exceptions.AttributeError: 'long' object has no attribute 'items'\r\nTraceback (most recent call last):\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 105, in send_message_with_response\r\n    span.resource = _resource_from_cmd(cmd)\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 220, in _resource_from_cmd\r\n    nq = normalize_filter(cmd.query)\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 207, in normalize_filter\r\n    out[k] = normalize_filter(v)\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 207, in normalize_filter\r\n    out[k] = normalize_filter(v)\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 199, in normalize_filter\r\n    return [normalize_filter(s) for s in f]\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/contrib/pymongo/client.py\", line 204, in normalize_filter\r\n    for k, v in iteritems(f):\r\n  File \"/home/deploy/virtualenvs/discord/local/lib/python2.7/site-packages/ddtrace/compat.py\", line 32, in iteritems\r\n    func = obj.items\r\nAttributeError: 'long' object has no attribute 'items'\r\n```\n", "before_files": [{"content": "# stdlib\nimport contextlib\nimport logging\nimport json\n\n# 3p\nimport pymongo\nfrom wrapt import ObjectProxy\n\n# project\nimport ddtrace\nfrom ...compat import iteritems\nfrom ...ext import AppTypes\nfrom ...ext import mongo as mongox\nfrom ...ext import net as netx\nfrom ...util import deprecated\nfrom .parse import parse_spec, parse_query, parse_msg\n\n# Original Client class\n_MongoClient = pymongo.MongoClient\n\nlog = logging.getLogger(__name__)\n\n\n@deprecated(message='Use patching instead (see the docs).', version='0.6.0')\ndef trace_mongo_client(client, tracer, service=mongox.TYPE):\n    tracer.set_service_info(\n        service=service,\n        app=mongox.TYPE,\n        app_type=AppTypes.db,\n    )\n    traced_client = TracedMongoClient(client)\n    ddtrace.Pin(service=service, tracer=tracer).onto(traced_client)\n    return traced_client\n\n\nclass TracedMongoClient(ObjectProxy):\n\n    def __init__(self, client=None, *args, **kwargs):\n        # To support the former trace_mongo_client interface, we have to keep this old interface\n        # TODO(Benjamin): drop it in a later version\n        if not isinstance(client, _MongoClient):\n            # Patched interface, instanciate the client\n            # Note that, in that case, the client argument isn't a client, it's just the first arg\n            client = _MongoClient(client, *args, **kwargs)\n\n        super(TracedMongoClient, self).__init__(client)\n        # Default Pin\n        ddtrace.Pin(service=mongox.TYPE).onto(self)\n        # NOTE[matt] the TracedMongoClient attempts to trace all of the network\n        # calls in the trace library. This is good because it measures the\n        # actual network time. It's bad because it uses a private API which\n        # could change. We'll see how this goes.\n        client._topology = TracedTopology(client._topology)\n\n    def __setddpin__(self, pin):\n        pin.onto(self._topology)\n\n    def __getddpin__(self):\n        return ddtrace.Pin.get_from(self._topology)\n\n\nclass TracedTopology(ObjectProxy):\n\n    def __init__(self, topology):\n        super(TracedTopology, self).__init__(topology)\n\n    def select_server(self, *args, **kwargs):\n        s = self.__wrapped__.select_server(*args, **kwargs)\n        if not isinstance(s, TracedServer):\n            s = TracedServer(s)\n        # Reattach the pin every time in case it changed since the initial patching\n        ddtrace.Pin.get_from(self).onto(s)\n        return s\n\n\nclass TracedServer(ObjectProxy):\n\n    def __init__(self, server):\n        super(TracedServer, self).__init__(server)\n\n    def send_message_with_response(self, operation, *args, **kwargs):\n        cmd = None\n        # Only try to parse something we think is a query.\n        if self._is_query(operation):\n            try:\n                cmd = parse_query(operation)\n            except Exception:\n                log.exception(\"error parsing query\")\n\n        pin = ddtrace.Pin.get_from(self)\n\n        # if we couldn't parse or shouldn't trace the message, just go.\n        if not cmd or not pin or not pin.enabled():\n            return self.__wrapped__.send_message_with_response(\n                operation,\n                *args,\n                **kwargs)\n\n        with pin.tracer.trace(\n                \"pymongo.cmd\",\n                span_type=mongox.TYPE,\n                service=pin.service) as span:\n\n            span.resource = _resource_from_cmd(cmd)\n            span.set_tag(mongox.DB, cmd.db)\n            span.set_tag(mongox.COLLECTION, cmd.coll)\n            span.set_tags(cmd.tags)\n\n            result = self.__wrapped__.send_message_with_response(\n                operation,\n                *args,\n                **kwargs)\n\n            if result and result.address:\n                _set_address_tags(span, result.address)\n            return result\n\n    @contextlib.contextmanager\n    def get_socket(self, *args, **kwargs):\n        with self.__wrapped__.get_socket(*args, **kwargs) as s:\n            if not isinstance(s, TracedSocket):\n                s = TracedSocket(s)\n            ddtrace.Pin.get_from(self).onto(s)\n            yield s\n\n    @staticmethod\n    def _is_query(op):\n        # NOTE: _Query should alwyas have a spec field\n        return hasattr(op, 'spec')\n\n\nclass TracedSocket(ObjectProxy):\n\n    def __init__(self, socket):\n        super(TracedSocket, self).__init__(socket)\n\n    def command(self, dbname, spec, *args, **kwargs):\n        cmd = None\n        try:\n            cmd = parse_spec(spec, dbname)\n        except Exception:\n            log.exception(\"error parsing spec. skipping trace\")\n\n        pin = ddtrace.Pin.get_from(self)\n        # skip tracing if we don't have a piece of data we need\n        if not dbname or not cmd or not pin or not pin.enabled():\n            return self.__wrapped__.command(dbname, spec, *args, **kwargs)\n\n        cmd.db = dbname\n        with self.__trace(cmd):\n            return self.__wrapped__.command(dbname, spec, *args, **kwargs)\n\n    def write_command(self, request_id, msg):\n        cmd = None\n        try:\n            cmd = parse_msg(msg)\n        except Exception:\n            log.exception(\"error parsing msg\")\n\n        pin = ddtrace.Pin.get_from(self)\n        # if we couldn't parse it, don't try to trace it.\n        if not cmd or not pin or not pin.enabled():\n            return self.__wrapped__.write_command(request_id, msg)\n\n        with self.__trace(cmd) as s:\n            s.resource = _resource_from_cmd(cmd)\n            result = self.__wrapped__.write_command(request_id, msg)\n            if result:\n                s.set_metric(mongox.ROWS, result.get(\"n\", -1))\n            return result\n\n    def __trace(self, cmd):\n        pin = ddtrace.Pin.get_from(self)\n        s = pin.tracer.trace(\n            \"pymongo.cmd\",\n            span_type=mongox.TYPE,\n            service=pin.service)\n\n        if cmd.db:\n            s.set_tag(mongox.DB, cmd.db)\n        if cmd:\n            s.set_tag(mongox.COLLECTION, cmd.coll)\n            s.set_tags(cmd.tags)\n            s.set_metrics(cmd.metrics)\n\n        s.resource = _resource_from_cmd(cmd)\n        if self.address:\n            _set_address_tags(s, self.address)\n        return s\n\n\ndef normalize_filter(f=None):\n    if f is None:\n        return {}\n    elif isinstance(f, list):\n        # normalize lists of filters\n        # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]}\n        return [normalize_filter(s) for s in f]\n    else:\n        # normalize dicts of filters\n        # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]})\n        out = {}\n        for k, v in iteritems(f):\n            if isinstance(v, list) or isinstance(v, dict):\n                # RECURSION ALERT: needs to move to the agent\n                out[k] = normalize_filter(v)\n            else:\n                out[k] = '?'\n        return out\n\ndef _set_address_tags(span, address):\n    # the address is only set after the cursor is done.\n    if address:\n        span.set_tag(netx.TARGET_HOST, address[0])\n        span.set_tag(netx.TARGET_PORT, address[1])\n\ndef _resource_from_cmd(cmd):\n    if cmd.query is not None:\n        nq = normalize_filter(cmd.query)\n        # needed to dump json so we don't get unicode\n        # dict keys like {u'foo':'bar'}\n        q = json.dumps(nq)\n        return \"%s %s %s\" % (cmd.name, cmd.coll, q)\n    else:\n        return \"%s %s\" % (cmd.name, cmd.coll)\n", "path": "ddtrace/contrib/pymongo/client.py"}], "after_files": [{"content": "# stdlib\nimport contextlib\nimport logging\nimport json\n\n# 3p\nimport pymongo\nfrom wrapt import ObjectProxy\n\n# project\nimport ddtrace\nfrom ...compat import iteritems\nfrom ...ext import AppTypes\nfrom ...ext import mongo as mongox\nfrom ...ext import net as netx\nfrom ...util import deprecated\nfrom .parse import parse_spec, parse_query, parse_msg\n\n# Original Client class\n_MongoClient = pymongo.MongoClient\n\nlog = logging.getLogger(__name__)\n\n\n@deprecated(message='Use patching instead (see the docs).', version='0.6.0')\ndef trace_mongo_client(client, tracer, service=mongox.TYPE):\n    tracer.set_service_info(\n        service=service,\n        app=mongox.TYPE,\n        app_type=AppTypes.db,\n    )\n    traced_client = TracedMongoClient(client)\n    ddtrace.Pin(service=service, tracer=tracer).onto(traced_client)\n    return traced_client\n\n\nclass TracedMongoClient(ObjectProxy):\n\n    def __init__(self, client=None, *args, **kwargs):\n        # To support the former trace_mongo_client interface, we have to keep this old interface\n        # TODO(Benjamin): drop it in a later version\n        if not isinstance(client, _MongoClient):\n            # Patched interface, instanciate the client\n            # Note that, in that case, the client argument isn't a client, it's just the first arg\n            client = _MongoClient(client, *args, **kwargs)\n\n        super(TracedMongoClient, self).__init__(client)\n        # Default Pin\n        ddtrace.Pin(service=mongox.TYPE).onto(self)\n        # NOTE[matt] the TracedMongoClient attempts to trace all of the network\n        # calls in the trace library. This is good because it measures the\n        # actual network time. It's bad because it uses a private API which\n        # could change. We'll see how this goes.\n        client._topology = TracedTopology(client._topology)\n\n    def __setddpin__(self, pin):\n        pin.onto(self._topology)\n\n    def __getddpin__(self):\n        return ddtrace.Pin.get_from(self._topology)\n\n\nclass TracedTopology(ObjectProxy):\n\n    def __init__(self, topology):\n        super(TracedTopology, self).__init__(topology)\n\n    def select_server(self, *args, **kwargs):\n        s = self.__wrapped__.select_server(*args, **kwargs)\n        if not isinstance(s, TracedServer):\n            s = TracedServer(s)\n        # Reattach the pin every time in case it changed since the initial patching\n        ddtrace.Pin.get_from(self).onto(s)\n        return s\n\n\nclass TracedServer(ObjectProxy):\n\n    def __init__(self, server):\n        super(TracedServer, self).__init__(server)\n\n    def send_message_with_response(self, operation, *args, **kwargs):\n        cmd = None\n        # Only try to parse something we think is a query.\n        if self._is_query(operation):\n            try:\n                cmd = parse_query(operation)\n            except Exception:\n                log.exception(\"error parsing query\")\n\n        pin = ddtrace.Pin.get_from(self)\n\n        # if we couldn't parse or shouldn't trace the message, just go.\n        if not cmd or not pin or not pin.enabled():\n            return self.__wrapped__.send_message_with_response(\n                operation,\n                *args,\n                **kwargs)\n\n        with pin.tracer.trace(\n                \"pymongo.cmd\",\n                span_type=mongox.TYPE,\n                service=pin.service) as span:\n\n            span.resource = _resource_from_cmd(cmd)\n            span.set_tag(mongox.DB, cmd.db)\n            span.set_tag(mongox.COLLECTION, cmd.coll)\n            span.set_tags(cmd.tags)\n\n            result = self.__wrapped__.send_message_with_response(\n                operation,\n                *args,\n                **kwargs)\n\n            if result and result.address:\n                _set_address_tags(span, result.address)\n            return result\n\n    @contextlib.contextmanager\n    def get_socket(self, *args, **kwargs):\n        with self.__wrapped__.get_socket(*args, **kwargs) as s:\n            if not isinstance(s, TracedSocket):\n                s = TracedSocket(s)\n            ddtrace.Pin.get_from(self).onto(s)\n            yield s\n\n    @staticmethod\n    def _is_query(op):\n        # NOTE: _Query should alwyas have a spec field\n        return hasattr(op, 'spec')\n\n\nclass TracedSocket(ObjectProxy):\n\n    def __init__(self, socket):\n        super(TracedSocket, self).__init__(socket)\n\n    def command(self, dbname, spec, *args, **kwargs):\n        cmd = None\n        try:\n            cmd = parse_spec(spec, dbname)\n        except Exception:\n            log.exception(\"error parsing spec. skipping trace\")\n\n        pin = ddtrace.Pin.get_from(self)\n        # skip tracing if we don't have a piece of data we need\n        if not dbname or not cmd or not pin or not pin.enabled():\n            return self.__wrapped__.command(dbname, spec, *args, **kwargs)\n\n        cmd.db = dbname\n        with self.__trace(cmd):\n            return self.__wrapped__.command(dbname, spec, *args, **kwargs)\n\n    def write_command(self, request_id, msg):\n        cmd = None\n        try:\n            cmd = parse_msg(msg)\n        except Exception:\n            log.exception(\"error parsing msg\")\n\n        pin = ddtrace.Pin.get_from(self)\n        # if we couldn't parse it, don't try to trace it.\n        if not cmd or not pin or not pin.enabled():\n            return self.__wrapped__.write_command(request_id, msg)\n\n        with self.__trace(cmd) as s:\n            s.resource = _resource_from_cmd(cmd)\n            result = self.__wrapped__.write_command(request_id, msg)\n            if result:\n                s.set_metric(mongox.ROWS, result.get(\"n\", -1))\n            return result\n\n    def __trace(self, cmd):\n        pin = ddtrace.Pin.get_from(self)\n        s = pin.tracer.trace(\n            \"pymongo.cmd\",\n            span_type=mongox.TYPE,\n            service=pin.service)\n\n        if cmd.db:\n            s.set_tag(mongox.DB, cmd.db)\n        if cmd:\n            s.set_tag(mongox.COLLECTION, cmd.coll)\n            s.set_tags(cmd.tags)\n            s.set_metrics(cmd.metrics)\n\n        s.resource = _resource_from_cmd(cmd)\n        if self.address:\n            _set_address_tags(s, self.address)\n        return s\n\n\ndef normalize_filter(f=None):\n    if f is None:\n        return {}\n    elif isinstance(f, list):\n        # normalize lists of filters\n        # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]}\n        return [normalize_filter(s) for s in f]\n    elif isinstance(f, dict):\n        # normalize dicts of filters\n        #   {$or: [ { age: { $lt: 30 } }, { type: 1 } ]})\n        out = {}\n        for k, v in iteritems(f):\n            if k == \"$in\" or k == \"$nin\":\n                # special case $in queries so we don't loop over lists.\n                out[k] = \"?\"\n            elif isinstance(v, list) or isinstance(v, dict):\n                # RECURSION ALERT: needs to move to the agent\n                out[k] = normalize_filter(v)\n            else:\n                # NOTE: this shouldn't happen, but let's have a safeguard.\n                out[k] = '?'\n        return out\n    else:\n        # FIXME[matt] unexpected type. not sure this should ever happen, but at\n        # least it won't crash.\n        return {}\n\ndef _set_address_tags(span, address):\n    # the address is only set after the cursor is done.\n    if address:\n        span.set_tag(netx.TARGET_HOST, address[0])\n        span.set_tag(netx.TARGET_PORT, address[1])\n\ndef _resource_from_cmd(cmd):\n    if cmd.query is not None:\n        nq = normalize_filter(cmd.query)\n        # needed to dump json so we don't get unicode\n        # dict keys like {u'foo':'bar'}\n        q = json.dumps(nq)\n        return \"%s %s %s\" % (cmd.name, cmd.coll, q)\n    else:\n        return \"%s %s\" % (cmd.name, cmd.coll)\n", "path": "ddtrace/contrib/pymongo/client.py"}]}