Skip to content

Commit 56a7c2d

Browse files
committed
feat: add basic remoulade instrumentation
1 parent 9ad4155 commit 56a7c2d

File tree

10 files changed

+347
-0
lines changed

10 files changed

+347
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
graft src
2+
graft tests
3+
global-exclude *.pyc
4+
global-exclude *.pyo
5+
global-exclude __pycache__/*
6+
include CHANGELOG.md
7+
include MANIFEST.in
8+
include README.rst
9+
include LICENSE
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
OpenTelemetry Redis Instrumentation
2+
===================================
3+
4+
|pypi|
5+
6+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-redis.svg
7+
:target: https://pypi.org/project/opentelemetry-instrumentation-redis/
8+
9+
This library allows tracing requests made by the Redis library.
10+
11+
Installation
12+
------------
13+
14+
::
15+
16+
pip install opentelemetry-instrumentation-redis
17+
18+
Usage
19+
-----
20+
21+
* Start broker backend
22+
23+
::
24+
25+
docker run -p 5672:5672 rabbitmq
26+
27+
* Run instrumented actor
28+
29+
.. code-block:: python
30+
31+
from remoulade.brokers.rabbitmq import RabbitmqBroker
32+
import remoulade
33+
34+
broker = RabbitmqBroker()
35+
remoulade.set_broker(broker)
36+
37+
RemouladeInstrumentor().instrument()
38+
39+
@remoulade.actor
40+
def multiply(x, y):
41+
return x * y
42+
43+
broker.declare_actor(count_words)
44+
45+
multiply.send(43, 51)
46+
47+
48+
Setting up tracing
49+
--------------------
50+
The ``instrument()`` method of the RemouladeInstrumentor should always be called after the broker is set, because the instrumentation is attached to the broker.
51+
52+
53+
54+
References
55+
----------
56+
57+
* `OpenTelemetry Redis Instrumentation <https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/redis/redis.html>`_
58+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
59+
* `OpenTelemetry Python Examples <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples>`_
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
[metadata]
16+
name = opentelemetry-instrumentation-remoulade
17+
description = OpenTelemetry remoulade instrumentation
18+
long_description = file: README.rst
19+
long_description_content_type = text/x-rst
20+
author = OpenTelemetry Authors
21+
author_email = [email protected]
22+
url = https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-remoulade
23+
platforms = any
24+
license = Apache-2.0
25+
classifiers =
26+
Development Status :: 4 - Beta
27+
Intended Audience :: Developers
28+
License :: OSI Approved :: Apache Software License
29+
Programming Language :: Python
30+
Programming Language :: Python :: 3
31+
Programming Language :: Python :: 3.6
32+
Programming Language :: Python :: 3.7
33+
Programming Language :: Python :: 3.8
34+
Programming Language :: Python :: 3.9
35+
Programming Language :: Python :: 3.10
36+
37+
[options]
38+
python_requires = >=3.6
39+
package_dir=
40+
=src
41+
packages=find_namespace:
42+
install_requires =
43+
opentelemetry-api ~= 1.3
44+
opentelemetry-semantic-conventions == 0.30b0
45+
opentelemetry-instrumentation == 0.30b0
46+
wrapt >= 1.12.1
47+
48+
[options.extras_require]
49+
test =
50+
opentelemetry-test-utils == 0.30b0
51+
opentelemetry-sdk ~= 1.3
52+
53+
[options.packages.find]
54+
where = src
55+
56+
[options.entry_points]
57+
opentelemetry_instrumentor =
58+
redis = opentelemetry.instrumentation.remoulade:RemouladeInstrumentor
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM templates/instrumentation_setup.py.txt.
17+
# RUN `python scripts/generate_setup.py` TO REGENERATE.
18+
19+
20+
import distutils.cmd
21+
import json
22+
import os
23+
from configparser import ConfigParser
24+
25+
import setuptools
26+
27+
config = ConfigParser()
28+
config.read("setup.cfg")
29+
30+
# We provide extras_require parameter to setuptools.setup later which
31+
# overwrites the extras_require section from setup.cfg. To support extras_require
32+
# section in setup.cfg, we load it here and merge it with the extras_require param.
33+
extras_require = {}
34+
if "options.extras_require" in config:
35+
for key, value in config["options.extras_require"].items():
36+
extras_require[key] = [v for v in value.split("\n") if v.strip()]
37+
38+
BASE_DIR = os.path.dirname(__file__)
39+
PACKAGE_INFO = {}
40+
41+
VERSION_FILENAME = os.path.join(
42+
BASE_DIR, "src", "opentelemetry", "instrumentation", "remoulade", "version.py"
43+
)
44+
with open(VERSION_FILENAME, encoding="utf-8") as f:
45+
exec(f.read(), PACKAGE_INFO)
46+
47+
PACKAGE_FILENAME = os.path.join(
48+
BASE_DIR, "src", "opentelemetry", "instrumentation", "remoulade", "package.py"
49+
)
50+
with open(PACKAGE_FILENAME, encoding="utf-8") as f:
51+
exec(f.read(), PACKAGE_INFO)
52+
53+
# Mark any instruments/runtime dependencies as test dependencies as well.
54+
extras_require["instruments"] = PACKAGE_INFO["_instruments"]
55+
test_deps = extras_require.get("test", [])
56+
for dep in extras_require["instruments"]:
57+
test_deps.append(dep)
58+
59+
extras_require["test"] = test_deps
60+
61+
62+
class JSONMetadataCommand(distutils.cmd.Command):
63+
64+
description = (
65+
"print out package metadata as JSON. This is used by OpenTelemetry dev scripts to ",
66+
"auto-generate code in other places",
67+
)
68+
user_options = []
69+
70+
def initialize_options(self):
71+
pass
72+
73+
def finalize_options(self):
74+
pass
75+
76+
def run(self):
77+
metadata = {
78+
"name": config["metadata"]["name"],
79+
"version": PACKAGE_INFO["__version__"],
80+
"instruments": PACKAGE_INFO["_instruments"],
81+
}
82+
print(json.dumps(metadata))
83+
84+
85+
setuptools.setup(
86+
cmdclass={"meta": JSONMetadataCommand},
87+
version=PACKAGE_INFO["__version__"],
88+
extras_require=extras_require,
89+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import logging
2+
from typing import Collection
3+
4+
from remoulade import Middleware, broker
5+
6+
from opentelemetry import trace
7+
from opentelemetry.instrumentation.remoulade import utils
8+
from opentelemetry.instrumentation.remoulade.version import __version__
9+
from opentelemetry.instrumentation.remoulade.package import _instruments
10+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
11+
from opentelemetry.propagate import extract, inject
12+
13+
14+
class InstrumentationMiddleware(Middleware):
15+
def __init__(self, _tracer):
16+
self._tracer = _tracer
17+
self._span_registry = {}
18+
19+
def before_process_message(self, _broker, message):
20+
trace_ctx = extract(message.options) # FIXME: extract/inject in message.option["trace_ctx"]
21+
operation_name = "remoulade/process"
22+
23+
span = self._tracer.start_span(operation_name, kind=trace.SpanKind.CONSUMER, context=trace_ctx)
24+
25+
activation = trace.use_span(span, end_on_exit=True)
26+
activation.__enter__()
27+
28+
utils.attach_span(self._span_registry, message.message_id, (span, activation))
29+
30+
def after_process_message(self, _broker, message, *, result=None, exception=None):
31+
span, activation = utils.retrieve_span(self._span_registry, message.message_id)
32+
33+
if span is None:
34+
# no existing span found for message_id
35+
return
36+
37+
if span.is_recording():
38+
pass
39+
40+
activation.__exit__(None, None, None)
41+
utils.detach_span(self._span_registry, message.message_id)
42+
43+
def before_enqueue(self, _broker, message, delay):
44+
operation_name = "remoulade/send"
45+
46+
span = self._tracer.start_span(operation_name, kind=trace.SpanKind.PRODUCER)
47+
48+
if span.is_recording():
49+
# span.set_attribute("TEST_ATTRIBUTE", "TEST_VALUE")
50+
pass
51+
52+
activation = trace.use_span(span, end_on_exit=True)
53+
activation.__enter__()
54+
55+
utils.attach_span(self._span_registry, message.message_id, (span, activation), is_publish=True)
56+
57+
inject(message.options) # FIXME: extract/inject in message.option["trace_ctx"]
58+
59+
def after_enqueue(self, _broker, message, delay, exception=None):
60+
_, activation = utils.retrieve_span(self._span_registry, message.message_id, is_publish=True)
61+
62+
if activation is None:
63+
# no existing span found for message_id
64+
return
65+
66+
activation.__exit__(None, None, None)
67+
utils.detach_span(self._span_registry, message.message_id, is_publish=True)
68+
69+
70+
class RemouladeInstrumentor(BaseInstrumentor):
71+
def instrumentation_dependencies(self) -> Collection[str]:
72+
return _instruments
73+
74+
def _instrument(self, **kwargs):
75+
tracer_provider = kwargs.get("tracer_provider")
76+
77+
self._tracer = trace.get_tracer(__name__, __version__, tracer_provider)
78+
79+
instrumentation_middleware = InstrumentationMiddleware(self._tracer)
80+
broker.get_broker().add_middleware(instrumentation_middleware)
81+
82+
def _uninstrument(self, **kwargs):
83+
return
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
_instruments = ("remoulade >= 0.48",)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
def attach_span(span_registry, message_id, span, is_publish=False):
2+
span_registry[(message_id, is_publish)] = span
3+
4+
5+
def detach_span(span_registry, message_id, is_publish=False):
6+
span_registry.pop((message_id, is_publish))
7+
8+
9+
def retrieve_span(span_registry, message_id, is_publish=False):
10+
return span_registry.get((message_id, is_publish), (None, None))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
__version__ = "0.30b0"

opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@
112112
"library": "redis >= 2.6",
113113
"instrumentation": "opentelemetry-instrumentation-redis==0.30b0",
114114
},
115+
"remoulade": {
116+
"library": "remoulade >= 0.48",
117+
"instrumentation": "opentelemetry-instrumentation-remoulade==0.30b0",
118+
},
115119
"requests": {
116120
"library": "requests ~= 2.0",
117121
"instrumentation": "opentelemetry-instrumentation-requests==0.30b0",

tox.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ envlist =
156156
py3{6,7,8,9,10}-test-instrumentation-redis
157157
pypy3-test-instrumentation-redis
158158

159+
; opentelemetry-instrumentation-remoulade
160+
py3{6,7,8,9,10}-test-instrumentation-remoualde
161+
pypy3-test-instrumentation-remoulade
162+
159163
; opentelemetry-instrumentation-celery
160164
py3{6,7,8,9,10}-test-instrumentation-celery
161165
pypy3-test-instrumentation-celery

0 commit comments

Comments
 (0)