We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
pymongo is not collecting the property: db.mongodb.collection
According to the [specs](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md#call-level-attributes-for-specific-technologies) - 
mongodb should capture: "The collection being accessed within the database stated in db.name."
and save it in: `db.mongodb.collection`

**Steps to reproduce**
Instrument a client using PymongoInstrumentor().
Send a request to the db.

**What is the expected behavior?**
Produce a span with `db.mongodb.collection` value containing the collection name.

**What is the actual behavior?**
Produce a span without generating `db.mongodb.collection`.

**Example:**
Here is a simple code example:

```
PymongoInstrumentor().instrument()
client = MongoClient()
RECORD = {"test": "123"}
db = client["MongoDB_Database"]
collection = db["MongoDB_Collection"]
collection.find_one(RECORD)
```

and the result is missing the collection:
```
"attributes": {
        "db.system": "mongodb",
        "db.name": "MongoDB_Database",
        "db.statement": "find",
        "net.peer.name": "localhost",
        "net.peer.port": 27017
    }
```

If you can - assign this to me, thanks :)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """
16 The integration with MongoDB supports the `pymongo`_ library, it can be
17 enabled using the ``PymongoInstrumentor``.
18 
19 .. _pymongo: https://pypi.org/project/pymongo
20 
21 Usage
22 -----
23 
24 .. code:: python
25 
26     from pymongo import MongoClient
27     from opentelemetry.instrumentation.pymongo import PymongoInstrumentor
28 
29     PymongoInstrumentor().instrument()
30     client = MongoClient()
31     db = client["MongoDB_Database"]
32     collection = db["MongoDB_Collection"]
33     collection.find_one()
34 
35 API
36 ---
37 The `instrument` method accepts the following keyword args:
38 
39 tracer_provider (TracerProvider) - an optional tracer provider
40 request_hook (Callable) -
41 a function with extra user-defined logic to be performed before querying mongodb
42 this function signature is:  def request_hook(span: Span, event: CommandStartedEvent) -> None
43 response_hook (Callable) -
44 a function with extra user-defined logic to be performed after the query returns with a successful response
45 this function signature is:  def response_hook(span: Span, event: CommandSucceededEvent) -> None
46 failed_hook (Callable) -
47 a function with extra user-defined logic to be performed after the query returns with a failed response
48 this function signature is:  def failed_hook(span: Span, event: CommandFailedEvent) -> None
49 
50 for example:
51 
52 .. code: python
53 
54     from opentelemetry.instrumentation.pymongo import PymongoInstrumentor
55     from pymongo import MongoClient
56 
57     def request_hook(span, event):
58         # request hook logic
59 
60     def response_hook(span, event):
61         # response hook logic
62 
63     def failed_hook(span, event):
64         # failed hook logic
65 
66     # Instrument pymongo with hooks
67     PymongoInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook, failed_hook=failed_hook)
68 
69     # This will create a span with pymongo specific attributes, including custom attributes added from the hooks
70     client = MongoClient()
71     db = client["MongoDB_Database"]
72     collection = db["MongoDB_Collection"]
73     collection.find_one()
74 
75 """
76 from logging import getLogger
77 from typing import Callable, Collection
78 
79 from pymongo import monitoring
80 
81 from opentelemetry import context
82 from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
83 from opentelemetry.instrumentation.pymongo.package import _instruments
84 from opentelemetry.instrumentation.pymongo.version import __version__
85 from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY
86 from opentelemetry.semconv.trace import DbSystemValues, SpanAttributes
87 from opentelemetry.trace import SpanKind, get_tracer
88 from opentelemetry.trace.span import Span
89 from opentelemetry.trace.status import Status, StatusCode
90 
91 _LOG = getLogger(__name__)
92 
93 RequestHookT = Callable[[Span, monitoring.CommandStartedEvent], None]
94 ResponseHookT = Callable[[Span, monitoring.CommandSucceededEvent], None]
95 FailedHookT = Callable[[Span, monitoring.CommandFailedEvent], None]
96 
97 
98 def dummy_callback(span, event):
99     ...
100 
101 
102 class CommandTracer(monitoring.CommandListener):
103     def __init__(
104         self,
105         tracer,
106         request_hook: RequestHookT = dummy_callback,
107         response_hook: ResponseHookT = dummy_callback,
108         failed_hook: FailedHookT = dummy_callback,
109     ):
110         self._tracer = tracer
111         self._span_dict = {}
112         self.is_enabled = True
113         self.start_hook = request_hook
114         self.success_hook = response_hook
115         self.failed_hook = failed_hook
116 
117     def started(self, event: monitoring.CommandStartedEvent):
118         """Method to handle a pymongo CommandStartedEvent"""
119         if not self.is_enabled or context.get_value(
120             _SUPPRESS_INSTRUMENTATION_KEY
121         ):
122             return
123         command = event.command.get(event.command_name, "")
124         name = event.database_name
125         name += "." + event.command_name
126         statement = event.command_name
127         if command:
128             statement += " " + str(command)
129 
130         try:
131             span = self._tracer.start_span(name, kind=SpanKind.CLIENT)
132             if span.is_recording():
133                 span.set_attribute(
134                     SpanAttributes.DB_SYSTEM, DbSystemValues.MONGODB.value
135                 )
136                 span.set_attribute(SpanAttributes.DB_NAME, event.database_name)
137                 span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
138                 if event.connection_id is not None:
139                     span.set_attribute(
140                         SpanAttributes.NET_PEER_NAME, event.connection_id[0]
141                     )
142                     span.set_attribute(
143                         SpanAttributes.NET_PEER_PORT, event.connection_id[1]
144                     )
145             try:
146                 self.start_hook(span, event)
147             except Exception as hook_exception:  # noqa pylint: disable=broad-except
148                 _LOG.exception(hook_exception)
149 
150             # Add Span to dictionary
151             self._span_dict[_get_span_dict_key(event)] = span
152         except Exception as ex:  # noqa pylint: disable=broad-except
153             if span is not None and span.is_recording():
154                 span.set_status(Status(StatusCode.ERROR, str(ex)))
155                 span.end()
156                 self._pop_span(event)
157 
158     def succeeded(self, event: monitoring.CommandSucceededEvent):
159         """Method to handle a pymongo CommandSucceededEvent"""
160         if not self.is_enabled or context.get_value(
161             _SUPPRESS_INSTRUMENTATION_KEY
162         ):
163             return
164         span = self._pop_span(event)
165         if span is None:
166             return
167         if span.is_recording():
168             try:
169                 self.success_hook(span, event)
170             except Exception as hook_exception:  # noqa pylint: disable=broad-except
171                 _LOG.exception(hook_exception)
172         span.end()
173 
174     def failed(self, event: monitoring.CommandFailedEvent):
175         """Method to handle a pymongo CommandFailedEvent"""
176         if not self.is_enabled or context.get_value(
177             _SUPPRESS_INSTRUMENTATION_KEY
178         ):
179             return
180         span = self._pop_span(event)
181         if span is None:
182             return
183         if span.is_recording():
184             span.set_status(Status(StatusCode.ERROR, event.failure))
185             try:
186                 self.failed_hook(span, event)
187             except Exception as hook_exception:  # noqa pylint: disable=broad-except
188                 _LOG.exception(hook_exception)
189         span.end()
190 
191     def _pop_span(self, event):
192         return self._span_dict.pop(_get_span_dict_key(event), None)
193 
194 
195 def _get_span_dict_key(event):
196     if event.connection_id is not None:
197         return event.request_id, event.connection_id
198     return event.request_id
199 
200 
201 class PymongoInstrumentor(BaseInstrumentor):
202     _commandtracer_instance = None  # type CommandTracer
203     # The instrumentation for PyMongo is based on the event listener interface
204     # https://api.mongodb.com/python/current/api/pymongo/monitoring.html.
205     # This interface only allows to register listeners and does not provide
206     # an unregister API. In order to provide a mechanishm to disable
207     # instrumentation an enabled flag is implemented in CommandTracer,
208     # it's checked in the different listeners.
209 
210     def instrumentation_dependencies(self) -> Collection[str]:
211         return _instruments
212 
213     def _instrument(self, **kwargs):
214         """Integrate with pymongo to trace it using event listener.
215         https://api.mongodb.com/python/current/api/pymongo/monitoring.html
216 
217         Args:
218             tracer_provider: The `TracerProvider` to use. If none is passed the
219                 current configured one is used.
220         """
221 
222         tracer_provider = kwargs.get("tracer_provider")
223         request_hook = kwargs.get("request_hook", dummy_callback)
224         response_hook = kwargs.get("response_hook", dummy_callback)
225         failed_hook = kwargs.get("failed_hook", dummy_callback)
226         # Create and register a CommandTracer only the first time
227         if self._commandtracer_instance is None:
228             tracer = get_tracer(__name__, __version__, tracer_provider)
229 
230             self._commandtracer_instance = CommandTracer(
231                 tracer,
232                 request_hook=request_hook,
233                 response_hook=response_hook,
234                 failed_hook=failed_hook,
235             )
236             monitoring.register(self._commandtracer_instance)
237         # If already created, just enable it
238         self._commandtracer_instance.is_enabled = True
239 
240     def _uninstrument(self, **kwargs):
241         if self._commandtracer_instance is not None:
242             self._commandtracer_instance.is_enabled = False
243
```
--- 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/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py b/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py
@@ -126,6 +126,7 @@
         statement = event.command_name
         if command:
             statement += " " + str(command)
+        collection = event.command.get(event.command_name)
 
         try:
             span = self._tracer.start_span(name, kind=SpanKind.CLIENT)
@@ -135,6 +136,10 @@
                 )
                 span.set_attribute(SpanAttributes.DB_NAME, event.database_name)
                 span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
+                if collection:
+                    span.set_attribute(
+                        SpanAttributes.DB_MONGODB_COLLECTION, collection
+                    )
                 if event.connection_id is not None:
                     span.set_attribute(
                         SpanAttributes.NET_PEER_NAME, event.connection_id[0]

{"golden_diff": "diff --git a/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py b/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py\n--- a/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py\n+++ b/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py\n@@ -126,6 +126,7 @@\n         statement = event.command_name\n         if command:\n             statement += \" \" + str(command)\n+        collection = event.command.get(event.command_name)\n \n         try:\n             span = self._tracer.start_span(name, kind=SpanKind.CLIENT)\n@@ -135,6 +136,10 @@\n                 )\n                 span.set_attribute(SpanAttributes.DB_NAME, event.database_name)\n                 span.set_attribute(SpanAttributes.DB_STATEMENT, statement)\n+                if collection:\n+                    span.set_attribute(\n+                        SpanAttributes.DB_MONGODB_COLLECTION, collection\n+                    )\n                 if event.connection_id is not None:\n                     span.set_attribute(\n                         SpanAttributes.NET_PEER_NAME, event.connection_id[0]\n", "issue": "pymongo is not collecting the property: db.mongodb.collection\nAccording to the [specs](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md#call-level-attributes-for-specific-technologies) - \r\nmongodb should capture: \"The collection being accessed within the database stated in db.name.\"\r\nand save it in: `db.mongodb.collection`\r\n\r\n**Steps to reproduce**\r\nInstrument a client using PymongoInstrumentor().\r\nSend a request to the db.\r\n\r\n**What is the expected behavior?**\r\nProduce a span with `db.mongodb.collection` value containing the collection name.\r\n\r\n**What is the actual behavior?**\r\nProduce a span without generating `db.mongodb.collection`.\r\n\r\n**Example:**\r\nHere is a simple code example:\r\n\r\n```\r\nPymongoInstrumentor().instrument()\r\nclient = MongoClient()\r\nRECORD = {\"test\": \"123\"}\r\ndb = client[\"MongoDB_Database\"]\r\ncollection = db[\"MongoDB_Collection\"]\r\ncollection.find_one(RECORD)\r\n```\r\n\r\nand the result is missing the collection:\r\n```\r\n\"attributes\": {\r\n        \"db.system\": \"mongodb\",\r\n        \"db.name\": \"MongoDB_Database\",\r\n        \"db.statement\": \"find\",\r\n        \"net.peer.name\": \"localhost\",\r\n        \"net.peer.port\": 27017\r\n    }\r\n```\r\n\r\nIf you can - assign this to me, thanks :)\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThe integration with MongoDB supports the `pymongo`_ library, it can be\nenabled using the ``PymongoInstrumentor``.\n\n.. _pymongo: https://pypi.org/project/pymongo\n\nUsage\n-----\n\n.. code:: python\n\n    from pymongo import MongoClient\n    from opentelemetry.instrumentation.pymongo import PymongoInstrumentor\n\n    PymongoInstrumentor().instrument()\n    client = MongoClient()\n    db = client[\"MongoDB_Database\"]\n    collection = db[\"MongoDB_Collection\"]\n    collection.find_one()\n\nAPI\n---\nThe `instrument` method accepts the following keyword args:\n\ntracer_provider (TracerProvider) - an optional tracer provider\nrequest_hook (Callable) -\na function with extra user-defined logic to be performed before querying mongodb\nthis function signature is:  def request_hook(span: Span, event: CommandStartedEvent) -> None\nresponse_hook (Callable) -\na function with extra user-defined logic to be performed after the query returns with a successful response\nthis function signature is:  def response_hook(span: Span, event: CommandSucceededEvent) -> None\nfailed_hook (Callable) -\na function with extra user-defined logic to be performed after the query returns with a failed response\nthis function signature is:  def failed_hook(span: Span, event: CommandFailedEvent) -> None\n\nfor example:\n\n.. code: python\n\n    from opentelemetry.instrumentation.pymongo import PymongoInstrumentor\n    from pymongo import MongoClient\n\n    def request_hook(span, event):\n        # request hook logic\n\n    def response_hook(span, event):\n        # response hook logic\n\n    def failed_hook(span, event):\n        # failed hook logic\n\n    # Instrument pymongo with hooks\n    PymongoInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook, failed_hook=failed_hook)\n\n    # This will create a span with pymongo specific attributes, including custom attributes added from the hooks\n    client = MongoClient()\n    db = client[\"MongoDB_Database\"]\n    collection = db[\"MongoDB_Collection\"]\n    collection.find_one()\n\n\"\"\"\nfrom logging import getLogger\nfrom typing import Callable, Collection\n\nfrom pymongo import monitoring\n\nfrom opentelemetry import context\nfrom opentelemetry.instrumentation.instrumentor import BaseInstrumentor\nfrom opentelemetry.instrumentation.pymongo.package import _instruments\nfrom opentelemetry.instrumentation.pymongo.version import __version__\nfrom opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY\nfrom opentelemetry.semconv.trace import DbSystemValues, SpanAttributes\nfrom opentelemetry.trace import SpanKind, get_tracer\nfrom opentelemetry.trace.span import Span\nfrom opentelemetry.trace.status import Status, StatusCode\n\n_LOG = getLogger(__name__)\n\nRequestHookT = Callable[[Span, monitoring.CommandStartedEvent], None]\nResponseHookT = Callable[[Span, monitoring.CommandSucceededEvent], None]\nFailedHookT = Callable[[Span, monitoring.CommandFailedEvent], None]\n\n\ndef dummy_callback(span, event):\n    ...\n\n\nclass CommandTracer(monitoring.CommandListener):\n    def __init__(\n        self,\n        tracer,\n        request_hook: RequestHookT = dummy_callback,\n        response_hook: ResponseHookT = dummy_callback,\n        failed_hook: FailedHookT = dummy_callback,\n    ):\n        self._tracer = tracer\n        self._span_dict = {}\n        self.is_enabled = True\n        self.start_hook = request_hook\n        self.success_hook = response_hook\n        self.failed_hook = failed_hook\n\n    def started(self, event: monitoring.CommandStartedEvent):\n        \"\"\"Method to handle a pymongo CommandStartedEvent\"\"\"\n        if not self.is_enabled or context.get_value(\n            _SUPPRESS_INSTRUMENTATION_KEY\n        ):\n            return\n        command = event.command.get(event.command_name, \"\")\n        name = event.database_name\n        name += \".\" + event.command_name\n        statement = event.command_name\n        if command:\n            statement += \" \" + str(command)\n\n        try:\n            span = self._tracer.start_span(name, kind=SpanKind.CLIENT)\n            if span.is_recording():\n                span.set_attribute(\n                    SpanAttributes.DB_SYSTEM, DbSystemValues.MONGODB.value\n                )\n                span.set_attribute(SpanAttributes.DB_NAME, event.database_name)\n                span.set_attribute(SpanAttributes.DB_STATEMENT, statement)\n                if event.connection_id is not None:\n                    span.set_attribute(\n                        SpanAttributes.NET_PEER_NAME, event.connection_id[0]\n                    )\n                    span.set_attribute(\n                        SpanAttributes.NET_PEER_PORT, event.connection_id[1]\n                    )\n            try:\n                self.start_hook(span, event)\n            except Exception as hook_exception:  # noqa pylint: disable=broad-except\n                _LOG.exception(hook_exception)\n\n            # Add Span to dictionary\n            self._span_dict[_get_span_dict_key(event)] = span\n        except Exception as ex:  # noqa pylint: disable=broad-except\n            if span is not None and span.is_recording():\n                span.set_status(Status(StatusCode.ERROR, str(ex)))\n                span.end()\n                self._pop_span(event)\n\n    def succeeded(self, event: monitoring.CommandSucceededEvent):\n        \"\"\"Method to handle a pymongo CommandSucceededEvent\"\"\"\n        if not self.is_enabled or context.get_value(\n            _SUPPRESS_INSTRUMENTATION_KEY\n        ):\n            return\n        span = self._pop_span(event)\n        if span is None:\n            return\n        if span.is_recording():\n            try:\n                self.success_hook(span, event)\n            except Exception as hook_exception:  # noqa pylint: disable=broad-except\n                _LOG.exception(hook_exception)\n        span.end()\n\n    def failed(self, event: monitoring.CommandFailedEvent):\n        \"\"\"Method to handle a pymongo CommandFailedEvent\"\"\"\n        if not self.is_enabled or context.get_value(\n            _SUPPRESS_INSTRUMENTATION_KEY\n        ):\n            return\n        span = self._pop_span(event)\n        if span is None:\n            return\n        if span.is_recording():\n            span.set_status(Status(StatusCode.ERROR, event.failure))\n            try:\n                self.failed_hook(span, event)\n            except Exception as hook_exception:  # noqa pylint: disable=broad-except\n                _LOG.exception(hook_exception)\n        span.end()\n\n    def _pop_span(self, event):\n        return self._span_dict.pop(_get_span_dict_key(event), None)\n\n\ndef _get_span_dict_key(event):\n    if event.connection_id is not None:\n        return event.request_id, event.connection_id\n    return event.request_id\n\n\nclass PymongoInstrumentor(BaseInstrumentor):\n    _commandtracer_instance = None  # type CommandTracer\n    # The instrumentation for PyMongo is based on the event listener interface\n    # https://api.mongodb.com/python/current/api/pymongo/monitoring.html.\n    # This interface only allows to register listeners and does not provide\n    # an unregister API. In order to provide a mechanishm to disable\n    # instrumentation an enabled flag is implemented in CommandTracer,\n    # it's checked in the different listeners.\n\n    def instrumentation_dependencies(self) -> Collection[str]:\n        return _instruments\n\n    def _instrument(self, **kwargs):\n        \"\"\"Integrate with pymongo to trace it using event listener.\n        https://api.mongodb.com/python/current/api/pymongo/monitoring.html\n\n        Args:\n            tracer_provider: The `TracerProvider` to use. If none is passed the\n                current configured one is used.\n        \"\"\"\n\n        tracer_provider = kwargs.get(\"tracer_provider\")\n        request_hook = kwargs.get(\"request_hook\", dummy_callback)\n        response_hook = kwargs.get(\"response_hook\", dummy_callback)\n        failed_hook = kwargs.get(\"failed_hook\", dummy_callback)\n        # Create and register a CommandTracer only the first time\n        if self._commandtracer_instance is None:\n            tracer = get_tracer(__name__, __version__, tracer_provider)\n\n            self._commandtracer_instance = CommandTracer(\n                tracer,\n                request_hook=request_hook,\n                response_hook=response_hook,\n                failed_hook=failed_hook,\n            )\n            monitoring.register(self._commandtracer_instance)\n        # If already created, just enable it\n        self._commandtracer_instance.is_enabled = True\n\n    def _uninstrument(self, **kwargs):\n        if self._commandtracer_instance is not None:\n            self._commandtracer_instance.is_enabled = False\n", "path": "instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThe integration with MongoDB supports the `pymongo`_ library, it can be\nenabled using the ``PymongoInstrumentor``.\n\n.. _pymongo: https://pypi.org/project/pymongo\n\nUsage\n-----\n\n.. code:: python\n\n    from pymongo import MongoClient\n    from opentelemetry.instrumentation.pymongo import PymongoInstrumentor\n\n    PymongoInstrumentor().instrument()\n    client = MongoClient()\n    db = client[\"MongoDB_Database\"]\n    collection = db[\"MongoDB_Collection\"]\n    collection.find_one()\n\nAPI\n---\nThe `instrument` method accepts the following keyword args:\n\ntracer_provider (TracerProvider) - an optional tracer provider\nrequest_hook (Callable) -\na function with extra user-defined logic to be performed before querying mongodb\nthis function signature is:  def request_hook(span: Span, event: CommandStartedEvent) -> None\nresponse_hook (Callable) -\na function with extra user-defined logic to be performed after the query returns with a successful response\nthis function signature is:  def response_hook(span: Span, event: CommandSucceededEvent) -> None\nfailed_hook (Callable) -\na function with extra user-defined logic to be performed after the query returns with a failed response\nthis function signature is:  def failed_hook(span: Span, event: CommandFailedEvent) -> None\n\nfor example:\n\n.. code: python\n\n    from opentelemetry.instrumentation.pymongo import PymongoInstrumentor\n    from pymongo import MongoClient\n\n    def request_hook(span, event):\n        # request hook logic\n\n    def response_hook(span, event):\n        # response hook logic\n\n    def failed_hook(span, event):\n        # failed hook logic\n\n    # Instrument pymongo with hooks\n    PymongoInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook, failed_hook=failed_hook)\n\n    # This will create a span with pymongo specific attributes, including custom attributes added from the hooks\n    client = MongoClient()\n    db = client[\"MongoDB_Database\"]\n    collection = db[\"MongoDB_Collection\"]\n    collection.find_one()\n\n\"\"\"\nfrom logging import getLogger\nfrom typing import Callable, Collection\n\nfrom pymongo import monitoring\n\nfrom opentelemetry import context\nfrom opentelemetry.instrumentation.instrumentor import BaseInstrumentor\nfrom opentelemetry.instrumentation.pymongo.package import _instruments\nfrom opentelemetry.instrumentation.pymongo.version import __version__\nfrom opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY\nfrom opentelemetry.semconv.trace import DbSystemValues, SpanAttributes\nfrom opentelemetry.trace import SpanKind, get_tracer\nfrom opentelemetry.trace.span import Span\nfrom opentelemetry.trace.status import Status, StatusCode\n\n_LOG = getLogger(__name__)\n\nRequestHookT = Callable[[Span, monitoring.CommandStartedEvent], None]\nResponseHookT = Callable[[Span, monitoring.CommandSucceededEvent], None]\nFailedHookT = Callable[[Span, monitoring.CommandFailedEvent], None]\n\n\ndef dummy_callback(span, event):\n    ...\n\n\nclass CommandTracer(monitoring.CommandListener):\n    def __init__(\n        self,\n        tracer,\n        request_hook: RequestHookT = dummy_callback,\n        response_hook: ResponseHookT = dummy_callback,\n        failed_hook: FailedHookT = dummy_callback,\n    ):\n        self._tracer = tracer\n        self._span_dict = {}\n        self.is_enabled = True\n        self.start_hook = request_hook\n        self.success_hook = response_hook\n        self.failed_hook = failed_hook\n\n    def started(self, event: monitoring.CommandStartedEvent):\n        \"\"\"Method to handle a pymongo CommandStartedEvent\"\"\"\n        if not self.is_enabled or context.get_value(\n            _SUPPRESS_INSTRUMENTATION_KEY\n        ):\n            return\n        command = event.command.get(event.command_name, \"\")\n        name = event.database_name\n        name += \".\" + event.command_name\n        statement = event.command_name\n        if command:\n            statement += \" \" + str(command)\n        collection = event.command.get(event.command_name)\n\n        try:\n            span = self._tracer.start_span(name, kind=SpanKind.CLIENT)\n            if span.is_recording():\n                span.set_attribute(\n                    SpanAttributes.DB_SYSTEM, DbSystemValues.MONGODB.value\n                )\n                span.set_attribute(SpanAttributes.DB_NAME, event.database_name)\n                span.set_attribute(SpanAttributes.DB_STATEMENT, statement)\n                if collection:\n                    span.set_attribute(\n                        SpanAttributes.DB_MONGODB_COLLECTION, collection\n                    )\n                if event.connection_id is not None:\n                    span.set_attribute(\n                        SpanAttributes.NET_PEER_NAME, event.connection_id[0]\n                    )\n                    span.set_attribute(\n                        SpanAttributes.NET_PEER_PORT, event.connection_id[1]\n                    )\n            try:\n                self.start_hook(span, event)\n            except Exception as hook_exception:  # noqa pylint: disable=broad-except\n                _LOG.exception(hook_exception)\n\n            # Add Span to dictionary\n            self._span_dict[_get_span_dict_key(event)] = span\n        except Exception as ex:  # noqa pylint: disable=broad-except\n            if span is not None and span.is_recording():\n                span.set_status(Status(StatusCode.ERROR, str(ex)))\n                span.end()\n                self._pop_span(event)\n\n    def succeeded(self, event: monitoring.CommandSucceededEvent):\n        \"\"\"Method to handle a pymongo CommandSucceededEvent\"\"\"\n        if not self.is_enabled or context.get_value(\n            _SUPPRESS_INSTRUMENTATION_KEY\n        ):\n            return\n        span = self._pop_span(event)\n        if span is None:\n            return\n        if span.is_recording():\n            try:\n                self.success_hook(span, event)\n            except Exception as hook_exception:  # noqa pylint: disable=broad-except\n                _LOG.exception(hook_exception)\n        span.end()\n\n    def failed(self, event: monitoring.CommandFailedEvent):\n        \"\"\"Method to handle a pymongo CommandFailedEvent\"\"\"\n        if not self.is_enabled or context.get_value(\n            _SUPPRESS_INSTRUMENTATION_KEY\n        ):\n            return\n        span = self._pop_span(event)\n        if span is None:\n            return\n        if span.is_recording():\n            span.set_status(Status(StatusCode.ERROR, event.failure))\n            try:\n                self.failed_hook(span, event)\n            except Exception as hook_exception:  # noqa pylint: disable=broad-except\n                _LOG.exception(hook_exception)\n        span.end()\n\n    def _pop_span(self, event):\n        return self._span_dict.pop(_get_span_dict_key(event), None)\n\n\ndef _get_span_dict_key(event):\n    if event.connection_id is not None:\n        return event.request_id, event.connection_id\n    return event.request_id\n\n\nclass PymongoInstrumentor(BaseInstrumentor):\n    _commandtracer_instance = None  # type CommandTracer\n    # The instrumentation for PyMongo is based on the event listener interface\n    # https://api.mongodb.com/python/current/api/pymongo/monitoring.html.\n    # This interface only allows to register listeners and does not provide\n    # an unregister API. In order to provide a mechanishm to disable\n    # instrumentation an enabled flag is implemented in CommandTracer,\n    # it's checked in the different listeners.\n\n    def instrumentation_dependencies(self) -> Collection[str]:\n        return _instruments\n\n    def _instrument(self, **kwargs):\n        \"\"\"Integrate with pymongo to trace it using event listener.\n        https://api.mongodb.com/python/current/api/pymongo/monitoring.html\n\n        Args:\n            tracer_provider: The `TracerProvider` to use. If none is passed the\n                current configured one is used.\n        \"\"\"\n\n        tracer_provider = kwargs.get(\"tracer_provider\")\n        request_hook = kwargs.get(\"request_hook\", dummy_callback)\n        response_hook = kwargs.get(\"response_hook\", dummy_callback)\n        failed_hook = kwargs.get(\"failed_hook\", dummy_callback)\n        # Create and register a CommandTracer only the first time\n        if self._commandtracer_instance is None:\n            tracer = get_tracer(__name__, __version__, tracer_provider)\n\n            self._commandtracer_instance = CommandTracer(\n                tracer,\n                request_hook=request_hook,\n                response_hook=response_hook,\n                failed_hook=failed_hook,\n            )\n            monitoring.register(self._commandtracer_instance)\n        # If already created, just enable it\n        self._commandtracer_instance.is_enabled = True\n\n    def _uninstrument(self, **kwargs):\n        if self._commandtracer_instance is not None:\n            self._commandtracer_instance.is_enabled = False\n", "path": "instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py"}]}