Skip to content

Commit 6a8f94c

Browse files
authored
Backend dependency granularity for nosql databases (elastic#1639)
* use keyspace as db instance for cassandra * service target fields for pymongo * update changelog
1 parent b726995 commit 6a8f94c

File tree

6 files changed

+74
-12
lines changed

6 files changed

+74
-12
lines changed

CHANGELOG.asciidoc

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ endif::[]
3333
3434
// Unreleased changes go here
3535
// When the next release happens, nest these changes under the "Python Agent version 6.x" heading
36-
//[float]
37-
//===== Features
36+
[float]
37+
===== Features
38+
* Add backend granularity data to SQL backends as well as Cassandra and pymongo {pull}1585[#1585], {pull}1639[#1639]
3839
3940
[float]
4041
===== Bug fixes

elasticapm/instrumentation/packages/cassandra.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2828
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2929
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
from typing import Optional
3031

3132
from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule
3233
from elasticapm.instrumentation.packages.dbapi2 import extract_signature
@@ -49,6 +50,9 @@ def call(self, module, method, wrapped, instance, args, kwargs):
4950
else:
5051
host = instance.endpoints_resolved[0].address
5152
port = instance.endpoints_resolved[0].port
53+
keyspace: Optional[str] = args[0] if args else kwargs.get("keyspace")
54+
if keyspace:
55+
context["db"] = {"instance": keyspace}
5256
else:
5357
hosts = list(instance.hosts)
5458
if hasattr(hosts[0], "endpoint"):
@@ -58,6 +62,9 @@ def call(self, module, method, wrapped, instance, args, kwargs):
5862
# < cassandra-driver 3.18
5963
host = hosts[0].address
6064
port = instance.cluster.port
65+
db_context = {}
66+
if instance.keyspace:
67+
db_context["instance"] = instance.keyspace
6168
span_action = "query"
6269
query = args[0] if args else kwargs.get("query")
6370
if hasattr(query, "query_string"):
@@ -70,7 +77,9 @@ def call(self, module, method, wrapped, instance, args, kwargs):
7077
query_str = None
7178
if query_str:
7279
name = extract_signature(query_str)
73-
context["db"] = {"type": "sql", "statement": query_str}
80+
db_context.update({"type": "sql", "statement": query_str})
81+
if db_context:
82+
context["db"] = db_context
7483
context["destination"] = {
7584
"address": host,
7685
"port": port,

elasticapm/instrumentation/packages/pymongo.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,16 @@ def call(self, module, method, wrapped, instance, args, kwargs):
8383
"address": host,
8484
"port": port,
8585
}
86+
context = {"destination": destination_info}
87+
if instance.database.name:
88+
context["db"] = {"instance": instance.database.name}
8689
with capture_span(
8790
signature,
8891
span_type="db",
8992
span_subtype="mongodb",
9093
span_action="query",
9194
leaf=True,
92-
extra={"destination": destination_info},
95+
extra=context,
9396
):
9497
return wrapped(*args, **kwargs)
9598

@@ -121,6 +124,9 @@ class PyMongoCursorInstrumentation(AbstractInstrumentedModule):
121124
def call(self, module, method, wrapped, instance, args, kwargs):
122125
collection = instance.collection
123126
signature = ".".join([collection.full_name, "cursor.refresh"])
127+
context = {"destination": {}}
128+
if instance.collection.database.name:
129+
context["db"] = {"instance": instance.collection.database.name}
124130
with capture_span(
125131
signature,
126132
span_type="db",

tests/docker-compose.yml

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ services:
4141

4242
mongodb36:
4343
image: mongo:3.6
44+
ports:
45+
- "27017:27017"
4446
volumes:
4547
- pymongodata36:/data/db
4648

tests/instrumentation/cassandra_tests.py

+44-4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import socket
3737

3838
from cassandra.cluster import Cluster
39+
from cassandra.protocol import ConfigurationException
3940
from cassandra.query import SimpleStatement
4041

4142
from elasticapm.conf.constants import TRANSACTION
@@ -57,6 +58,11 @@ def cassandra_cluster():
5758
@pytest.fixture()
5859
def cassandra_session(cassandra_cluster):
5960
session = cassandra_cluster.connect()
61+
try:
62+
# make sure the keyspace doesn't exist
63+
session.execute("DROP KEYSPACE testkeyspace;")
64+
except ConfigurationException:
65+
pass
6066
session.execute(
6167
"""
6268
CREATE KEYSPACE testkeyspace
@@ -89,6 +95,38 @@ def test_cassandra_connect(instrument, elasticapm_client, cassandra_cluster):
8995
}
9096

9197

98+
def test_cassandra_connect_keyspace(instrument, elasticapm_client, cassandra_cluster):
99+
session = cassandra_cluster.connect()
100+
try:
101+
session.execute(
102+
"""
103+
CREATE KEYSPACE testkeyspace
104+
WITH REPLICATION = { 'class' : 'SimpleStrategy' ,'replication_factor' : 1 }
105+
"""
106+
)
107+
elasticapm_client.begin_transaction("transaction.test")
108+
sess = cassandra_cluster.connect("testkeyspace")
109+
elasticapm_client.end_transaction("test")
110+
finally:
111+
session.execute("DROP KEYSPACE testkeyspace;")
112+
113+
transactions = elasticapm_client.events[TRANSACTION]
114+
span = elasticapm_client.spans_for_transaction(transactions[0])[0]
115+
116+
assert span["type"] == "db"
117+
assert span["subtype"] == "cassandra"
118+
assert span["action"] == "connect"
119+
assert span["duration"] > 0
120+
assert span["name"] == "Cluster.connect"
121+
assert span["context"]["destination"] == {
122+
"address": socket.gethostbyname(os.environ.get("CASSANDRA_HOST", "localhost")),
123+
"port": 9042,
124+
"service": {"name": "", "resource": "cassandra/testkeyspace", "type": ""},
125+
}
126+
assert span["context"]["service"]["target"]["type"] == "cassandra"
127+
assert span["context"]["service"]["target"]["name"] == "testkeyspace"
128+
129+
92130
def test_select_query_string(instrument, cassandra_session, elasticapm_client):
93131
elasticapm_client.begin_transaction("transaction.test")
94132
cassandra_session.execute("SELECT name from users")
@@ -99,12 +137,14 @@ def test_select_query_string(instrument, cassandra_session, elasticapm_client):
99137
assert span["subtype"] == "cassandra"
100138
assert span["action"] == "query"
101139
assert span["name"] == "SELECT FROM users"
102-
assert span["context"]["db"] == {"statement": "SELECT name from users", "type": "sql"}
140+
assert span["context"]["db"] == {"statement": "SELECT name from users", "type": "sql", "instance": "testkeyspace"}
103141
assert span["context"]["destination"] == {
104142
"address": socket.gethostbyname(os.environ.get("CASSANDRA_HOST", "localhost")),
105143
"port": 9042,
106-
"service": {"name": "", "resource": "cassandra", "type": ""},
144+
"service": {"name": "", "resource": "cassandra/testkeyspace", "type": ""},
107145
}
146+
assert span["context"]["service"]["target"]["type"] == "cassandra"
147+
assert span["context"]["service"]["target"]["name"] == "testkeyspace"
108148

109149

110150
def test_select_simple_statement(instrument, cassandra_session, elasticapm_client):
@@ -118,7 +158,7 @@ def test_select_simple_statement(instrument, cassandra_session, elasticapm_clien
118158
assert span["subtype"] == "cassandra"
119159
assert span["action"] == "query"
120160
assert span["name"] == "SELECT FROM users"
121-
assert span["context"]["db"] == {"statement": "SELECT name from users", "type": "sql"}
161+
assert span["context"]["db"] == {"statement": "SELECT name from users", "type": "sql", "instance": "testkeyspace"}
122162

123163

124164
def test_select_prepared_statement(instrument, cassandra_session, elasticapm_client):
@@ -132,7 +172,7 @@ def test_select_prepared_statement(instrument, cassandra_session, elasticapm_cli
132172
assert span["subtype"] == "cassandra"
133173
assert span["action"] == "query"
134174
assert span["name"] == "SELECT FROM users"
135-
assert span["context"]["db"] == {"statement": "SELECT name from users", "type": "sql"}
175+
assert span["context"]["db"] == {"statement": "SELECT name from users", "type": "sql", "instance": "testkeyspace"}
136176

137177

138178
def test_signature_create_keyspace():

tests/instrumentation/pymongo_tests.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,10 @@ def test_collection_count_documents(instrument, elasticapm_client, mongo_databas
121121
assert span["context"]["destination"] == {
122122
"address": os.environ.get("MONGODB_HOST", "localhost"),
123123
"port": int(os.environ.get("MONGODB_PORT", 27017)),
124-
"service": {"name": "", "resource": "mongodb", "type": ""},
124+
"service": {"name": "", "resource": "mongodb/elasticapm_test", "type": ""},
125125
}
126+
assert span["context"]["service"]["target"]["type"] == "mongodb"
127+
assert span["context"]["service"]["target"]["name"] == "elasticapm_test"
126128

127129

128130
@pytest.mark.skipif(pymongo.version_tuple < (3, 7), reason="New in 3.7")
@@ -143,8 +145,10 @@ def test_collection_estimated_document_count(instrument, elasticapm_client, mong
143145
assert span["context"]["destination"] == {
144146
"address": os.environ.get("MONGODB_HOST", "localhost"),
145147
"port": int(os.environ.get("MONGODB_PORT", 27017)),
146-
"service": {"name": "", "resource": "mongodb", "type": ""},
148+
"service": {"name": "", "resource": "mongodb/elasticapm_test", "type": ""},
147149
}
150+
assert span["context"]["service"]["target"]["type"] == "mongodb"
151+
assert span["context"]["service"]["target"]["name"] == "elasticapm_test"
148152

149153

150154
@pytest.mark.integrationtest
@@ -254,7 +258,7 @@ def test_collection_find(instrument, elasticapm_client, mongo_database):
254258
assert span["context"]["destination"] == {
255259
"address": os.environ.get("MONGODB_HOST", "localhost"),
256260
"port": int(os.environ.get("MONGODB_PORT", 27017)),
257-
"service": {"name": "", "resource": "mongodb", "type": ""},
261+
"service": {"name": "", "resource/elasticapm_test": "mongodb", "type": ""},
258262
}
259263

260264

@@ -276,7 +280,7 @@ def test_collection_find_one(instrument, elasticapm_client, mongo_database):
276280
assert span["context"]["destination"] == {
277281
"address": os.environ.get("MONGODB_HOST", "localhost"),
278282
"port": int(os.environ.get("MONGODB_PORT", 27017)),
279-
"service": {"name": "", "resource": "mongodb", "type": ""},
283+
"service": {"name": "", "resource": "mongodb/elasticapm_test", "type": ""},
280284
}
281285

282286

0 commit comments

Comments
 (0)