Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add instrumentation for remoulade #1082

Merged
merged 39 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
56a7c2d
feat: add basic remoulade instrumentation
ben-natan Apr 20, 2022
73da355
feat: add basic test
ben-natan Apr 20, 2022
9cc3e2c
feat: add basic tests
ben-natan Apr 27, 2022
fc79f80
feat: add basic attributes
ben-natan Apr 27, 2022
8eab917
feat: pass context in options["trace_ctx"] instead of options
ben-natan Apr 27, 2022
53698aa
feat: add retries and retries tests
ben-natan Apr 27, 2022
b5ca442
fix: copy pasta
ben-natan Apr 28, 2022
918405b
feat: add uninstrument logic (wait for remoulade release)
ben-natan Apr 29, 2022
5e4e3e1
feat: add uninstrument
ben-natan May 3, 2022
51b932e
chore: clean
ben-natan May 3, 2022
8bcadcd
fix: typo and run tox
ben-natan May 3, 2022
ebf3066
fix: add docs-requirements.txt
ben-natan May 3, 2022
36fff81
Merge branch 'main' into add-remoulade
ben-natan May 3, 2022
d1f7ed4
chore: set version to 0.30b1
ben-natan May 3, 2022
94ab40a
docs: changelog update
ben-natan May 3, 2022
236002b
feat: add auto-instrumentation
ben-natan May 6, 2022
9d5abd3
feat: add RemouladeGetter
ben-natan May 6, 2022
d41444a
Merge branch 'main' into add-remoulade
ben-natan May 6, 2022
acd7884
test: add test_getter.py
ben-natan May 6, 2022
75d2efe
chore: add copyright
ben-natan May 6, 2022
4e2f1c6
fix: remoulade version
ben-natan May 6, 2022
8507a74
fix: readme spaces
ben-natan May 6, 2022
38d8622
Merge branch 'main' into add-remoulade
srikanthccv May 10, 2022
dc67bcc
fix: lint and opentelemetry-api version
ben-natan May 11, 2022
18f487e
fix: typo in remoulade version
ben-natan May 16, 2022
f730c5f
Merge branch 'main' into add-remoulade
srikanthccv May 17, 2022
b745608
fix: python >= 3.7
ben-natan May 18, 2022
2fdd776
Merge branch 'main' into add-remoulade
ben-natan May 18, 2022
6b3718e
chore: bump to 31b0
ben-natan May 18, 2022
4c2b4c0
docs: update CHANGELOG.md
ben-natan May 18, 2022
911c142
run tox -e generate
ben-natan May 18, 2022
4c2c478
lint
ben-natan May 18, 2022
9471334
Izchen code review
ben-natan May 19, 2022
0fc6b65
srikanthccv code review
ben-natan May 19, 2022
b18e951
remove pypy3 test
ben-natan May 20, 2022
2a963f5
Merge branch 'main' into add-remoulade
ben-natan May 25, 2022
314ee1a
31b0
ben-natan May 30, 2022
e6ecadd
Merge branch 'main' into add-remoulade
ben-natan May 30, 2022
e1a268c
Merge branch 'main' into add-remoulade
lzchen May 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.11.1-0.30b1...HEAD)

### Fixed
- `opentelemetry-instrumentation-aiohttp-client` make span attributes available to sampler
- `opentelemetry-instrumentation-aiohttp-clientopentelemetry-instrumentation-aiohttp-client` make span attributes available to sampler
([1072](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1072))
- `opentelemetry-instrumentation-aws-lambda` Fixed an issue - in some rare cases (API GW proxy integration test)
headers are set to None, breaking context propagators.
Expand All @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- `opentelemetry-instrument` and `opentelemetry-bootstrap` now include a `--version` flag
([#1065](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1065))
- `opentelemetry-instrumentation-remoulade` Initial release
([#1082](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1082))


## [1.11.1-0.30b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.11.1-0.30b1) - 2022-04-21

Expand Down
1 change: 1 addition & 0 deletions docs-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pymongo~=3.1
PyMySQL~=0.9.3
pyramid>=1.7
redis>=2.6
remoulade>=0.49
sqlalchemy>=1.0
tornado>=5.1.1
ddtrace>=0.34.0
Expand Down
1 change: 1 addition & 0 deletions instrumentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
| [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 |
| [opentelemetry-instrumentation-pyramid](./opentelemetry-instrumentation-pyramid) | pyramid >= 1.7 |
| [opentelemetry-instrumentation-redis](./opentelemetry-instrumentation-redis) | redis >= 2.6 |
| [opentelemetry-instrumentation-remoulade](./opentelemetry-instrumentation-remoulade) | remoulade >= 0.49 |
| [opentelemetry-instrumentation-requests](./opentelemetry-instrumentation-requests) | requests ~= 2.0 |
| [opentelemetry-instrumentation-sklearn](./opentelemetry-instrumentation-sklearn) | scikit-learn ~= 0.24.0 |
| [opentelemetry-instrumentation-sqlalchemy](./opentelemetry-instrumentation-sqlalchemy) | sqlalchemy |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
graft src
graft tests
global-exclude *.pyc
global-exclude *.pyo
global-exclude __pycache__/*
include CHANGELOG.md
include MANIFEST.in
include README.rst
include LICENSE
59 changes: 59 additions & 0 deletions instrumentation/opentelemetry-instrumentation-remoulade/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
OpenTelemetry Remoulade Instrumentation
===================================
ben-natan marked this conversation as resolved.
Show resolved Hide resolved

|pypi|

.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-remoulade.svg
:target: https://pypi.org/project/opentelemetry-instrumentation-remoulade/

This library allows tracing requests made by the Remoulade library.

Installation
------------

::

pip install opentelemetry-instrumentation-remoulade

Usage
-----

* Start broker backend

::

docker run -p 5672:5672 rabbitmq

* Run instrumented actor

.. code-block:: python

from remoulade.brokers.rabbitmq import RabbitmqBroker
import remoulade

broker = RabbitmqBroker()
remoulade.set_broker(broker)

RemouladeInstrumentor().instrument()

@remoulade.actor
def multiply(x, y):
return x * y

broker.declare_actor(count_words)

multiply.send(43, 51)


Setting up tracing
--------------------
The ``instrument()`` method of the RemouladeInstrumentor should always be called after the broker is set, because the instrumentation is attached to the broker.



References
----------

* `OpenTelemetry Remoulade Instrumentation <https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/remoulade/remoulade.html>`_
* `OpenTelemetry Project <https://opentelemetry.io/>`_
* `OpenTelemetry Python Examples <https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples>`_
57 changes: 57 additions & 0 deletions instrumentation/opentelemetry-instrumentation-remoulade/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
[metadata]
name = opentelemetry-instrumentation-remoulade
description = OpenTelemetry Remoulade instrumentation
long_description = file: README.rst
long_description_content_type = text/x-rst
author = OpenTelemetry Authors
author_email = cncf-opentelemetry-contributors@lists.cncf.io
url = https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-remoulade
platforms = any
license = Apache-2.0
classifiers =
Development Status :: 4 - Beta
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
ben-natan marked this conversation as resolved.
Show resolved Hide resolved
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10

[options]
python_requires = >=3.6
package_dir=
=src
packages=find_namespace:
install_requires =
opentelemetry-api ~= 1.3
ben-natan marked this conversation as resolved.
Show resolved Hide resolved
opentelemetry-semantic-conventions == 0.30b1
opentelemetry-instrumentation == 0.30b1

[options.extras_require]
test =
opentelemetry-test-utils == 0.30b1
opentelemetry-sdk ~= 1.3

[options.packages.find]
where = src

[options.entry_points]
opentelemetry_instrumentor =
remoulade = opentelemetry.instrumentation.remoulade:RemouladeInstrumentor
99 changes: 99 additions & 0 deletions instrumentation/opentelemetry-instrumentation-remoulade/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM templates/instrumentation_setup.py.txt.
# RUN `python scripts/generate_setup.py` TO REGENERATE.


import distutils.cmd
import json
import os
from configparser import ConfigParser

import setuptools

config = ConfigParser()
config.read("setup.cfg")

# We provide extras_require parameter to setuptools.setup later which
# overwrites the extras_require section from setup.cfg. To support extras_require
# section in setup.cfg, we load it here and merge it with the extras_require param.
extras_require = {}
if "options.extras_require" in config:
for key, value in config["options.extras_require"].items():
extras_require[key] = [v for v in value.split("\n") if v.strip()]

BASE_DIR = os.path.dirname(__file__)
PACKAGE_INFO = {}

VERSION_FILENAME = os.path.join(
BASE_DIR,
"src",
"opentelemetry",
"instrumentation",
"remoulade",
"version.py",
)
with open(VERSION_FILENAME, encoding="utf-8") as f:
exec(f.read(), PACKAGE_INFO)

PACKAGE_FILENAME = os.path.join(
BASE_DIR,
"src",
"opentelemetry",
"instrumentation",
"remoulade",
"package.py",
)
with open(PACKAGE_FILENAME, encoding="utf-8") as f:
exec(f.read(), PACKAGE_INFO)

# Mark any instruments/runtime dependencies as test dependencies as well.
extras_require["instruments"] = PACKAGE_INFO["_instruments"]
test_deps = extras_require.get("test", [])
for dep in extras_require["instruments"]:
test_deps.append(dep)

extras_require["test"] = test_deps


class JSONMetadataCommand(distutils.cmd.Command):

description = (
"print out package metadata as JSON. This is used by OpenTelemetry dev scripts to ",
"auto-generate code in other places",
)
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
metadata = {
"name": config["metadata"]["name"],
"version": PACKAGE_INFO["__version__"],
"instruments": PACKAGE_INFO["_instruments"],
}
print(json.dumps(metadata))


setuptools.setup(
cmdclass={"meta": JSONMetadataCommand},
version=PACKAGE_INFO["__version__"],
extras_require=extras_require,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import logging
from typing import Collection

from remoulade import Middleware, broker

from opentelemetry import trace
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.remoulade import utils
from opentelemetry.instrumentation.remoulade.package import _instruments
from opentelemetry.instrumentation.remoulade.version import __version__
from opentelemetry.propagate import extract, inject
from opentelemetry.semconv.trace import SpanAttributes

_MESSAGE_TAG_KEY = "remoulade.action"
ben-natan marked this conversation as resolved.
Show resolved Hide resolved
_MESSAGE_SEND = "send"
_MESSAGE_RUN = "run"

_MESSAGE_NAME_KEY = "remoulade.actor_name"


class InstrumentationMiddleware(Middleware):
ben-natan marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, _tracer):
self._tracer = _tracer
self._span_registry = {}

def before_process_message(self, _broker, message):
trace_ctx = extract(message.options["trace_ctx"])
ben-natan marked this conversation as resolved.
Show resolved Hide resolved
retry_count = message.options.get("retries")

operation_name = (
"remoulade/process"
if retry_count is None
else f"remoulade/process(retry-{retry_count})"
)

span = self._tracer.start_span(
operation_name, kind=trace.SpanKind.CONSUMER, context=trace_ctx
)

if retry_count is not None:
ben-natan marked this conversation as resolved.
Show resolved Hide resolved
span.set_attribute("retry_count", retry_count)
ben-natan marked this conversation as resolved.
Show resolved Hide resolved

activation = trace.use_span(span, end_on_exit=True)
activation.__enter__()

utils.attach_span(
self._span_registry, message.message_id, (span, activation)
)

def after_process_message(
self, _broker, message, *, result=None, exception=None
):
span, activation = utils.retrieve_span(
self._span_registry, message.message_id
)

if span is None:
# no existing span found for message_id
return

if span.is_recording():
span.set_attribute(_MESSAGE_TAG_KEY, _MESSAGE_RUN)
lzchen marked this conversation as resolved.
Show resolved Hide resolved
span.set_attribute(_MESSAGE_NAME_KEY, message.actor_name)
pass

activation.__exit__(None, None, None)
utils.detach_span(self._span_registry, message.message_id)

def before_enqueue(self, _broker, message, delay):
retry_count = message.options.get("retries")

operation_name = (
ben-natan marked this conversation as resolved.
Show resolved Hide resolved
"remoulade/send"
if retry_count is None
else f"remoulade/send(retry-{retry_count})"
)

span = self._tracer.start_span(
operation_name, kind=trace.SpanKind.PRODUCER
)

if retry_count is not None:
span.set_attribute("retry_count", retry_count)

if span.is_recording():
span.set_attribute(_MESSAGE_TAG_KEY, _MESSAGE_SEND)
lzchen marked this conversation as resolved.
Show resolved Hide resolved
span.set_attribute(
SpanAttributes.MESSAGING_MESSAGE_ID, message.message_id
)
span.set_attribute(_MESSAGE_NAME_KEY, message.actor_name)
pass

activation = trace.use_span(span, end_on_exit=True)
activation.__enter__()

utils.attach_span(
self._span_registry,
message.message_id,
(span, activation),
is_publish=True,
)

if "trace_ctx" not in message.options:
message.options["trace_ctx"] = {}
inject(message.options["trace_ctx"])
oxeye-nikolay marked this conversation as resolved.
Show resolved Hide resolved

def after_enqueue(self, _broker, message, delay, exception=None):
_, activation = utils.retrieve_span(
self._span_registry, message.message_id, is_publish=True
)

if activation is None:
# no existing span found for message_id
return

activation.__exit__(None, None, None)
utils.detach_span(
self._span_registry, message.message_id, is_publish=True
)


class RemouladeInstrumentor(BaseInstrumentor):
def instrumentation_dependencies(self) -> Collection[str]:
return _instruments

def _instrument(self, **kwargs):
tracer_provider = kwargs.get("tracer_provider")

self._tracer = trace.get_tracer(__name__, __version__, tracer_provider)

instrumentation_middleware = InstrumentationMiddleware(self._tracer)
broker.get_broker().add_middleware(instrumentation_middleware)
oxeye-nikolay marked this conversation as resolved.
Show resolved Hide resolved

def _uninstrument(self, **kwargs):
broker.get_broker().remove_middleware(InstrumentationMiddleware)
Loading