From cf8c885bc55cc6a4844d04ddd3bc11b62954f5e0 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 1 May 2020 17:44:05 -0400 Subject: [PATCH 01/27] feat: add jinja2 instrumentation --- ext/opentelemetry-ext-jinja2/CHANGELOG.md | 3 + ext/opentelemetry-ext-jinja2/LICENSE | 201 ++++++++++++++++++ ext/opentelemetry-ext-jinja2/MANIFEST.in | 9 + ext/opentelemetry-ext-jinja2/README.rst | 21 ++ ext/opentelemetry-ext-jinja2/setup.cfg | 53 +++++ ext/opentelemetry-ext-jinja2/setup.py | 33 +++ .../src/opentelemetry/ext/jinja2/__init__.py | 134 ++++++++++++ .../src/opentelemetry/ext/jinja2/version.py | 15 ++ .../tests/__init__.py | 0 .../tests/conftest.py | 25 +++ .../tests/templates/base.html | 1 + .../tests/templates/template.html | 2 + .../tests/test_jinja2.py | 129 +++++++++++ tox.ini | 10 + 14 files changed, 636 insertions(+) create mode 100644 ext/opentelemetry-ext-jinja2/CHANGELOG.md create mode 100644 ext/opentelemetry-ext-jinja2/LICENSE create mode 100644 ext/opentelemetry-ext-jinja2/MANIFEST.in create mode 100644 ext/opentelemetry-ext-jinja2/README.rst create mode 100644 ext/opentelemetry-ext-jinja2/setup.cfg create mode 100644 ext/opentelemetry-ext-jinja2/setup.py create mode 100644 ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py create mode 100644 ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/version.py create mode 100644 ext/opentelemetry-ext-jinja2/tests/__init__.py create mode 100644 ext/opentelemetry-ext-jinja2/tests/conftest.py create mode 100644 ext/opentelemetry-ext-jinja2/tests/templates/base.html create mode 100644 ext/opentelemetry-ext-jinja2/tests/templates/template.html create mode 100644 ext/opentelemetry-ext-jinja2/tests/test_jinja2.py diff --git a/ext/opentelemetry-ext-jinja2/CHANGELOG.md b/ext/opentelemetry-ext-jinja2/CHANGELOG.md new file mode 100644 index 0000000000..1512c42162 --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +## Unreleased diff --git a/ext/opentelemetry-ext-jinja2/LICENSE b/ext/opentelemetry-ext-jinja2/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ext/opentelemetry-ext-jinja2/MANIFEST.in b/ext/opentelemetry-ext-jinja2/MANIFEST.in new file mode 100644 index 0000000000..aed3e33273 --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/MANIFEST.in @@ -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 diff --git a/ext/opentelemetry-ext-jinja2/README.rst b/ext/opentelemetry-ext-jinja2/README.rst new file mode 100644 index 0000000000..ac8afd0a75 --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/README.rst @@ -0,0 +1,21 @@ +OpenTelemetry jinja2 integration +====================================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-jinja2.svg + :target: https://pypi.org/project/opentelemetry-ext-jinja2/ + +Installation +------------ + +:: + + pip install opentelemetry-ext-jinja2 + + +References +---------- + +* `OpenTelemetry jinja2 integration `_ +* `OpenTelemetry Project `_ diff --git a/ext/opentelemetry-ext-jinja2/setup.cfg b/ext/opentelemetry-ext-jinja2/setup.cfg new file mode 100644 index 0000000000..505e267069 --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/setup.cfg @@ -0,0 +1,53 @@ +# 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-ext-jinja2 +description = OpenTelemetry jinja2 integration +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/ext/opentelemetry-ext-jinja2 +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.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-api == 0.7.dev0 + opentelemetry-auto-instrumentation == 0.7.dev0 + wrapt >= 1.0.0, < 2.0.0 + +[options.extras_require] +test = + jinja2~=2.7 + opentelemetry-test == 0.7.dev0 + +[options.packages.find] +where = src diff --git a/ext/opentelemetry-ext-jinja2/setup.py b/ext/opentelemetry-ext-jinja2/setup.py new file mode 100644 index 0000000000..c972398bf8 --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/setup.py @@ -0,0 +1,33 @@ +# 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. +import os + +import setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "ext", "jinja2", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup( + version=PACKAGE_INFO["__version__"], + entry_points={ + "opentelemetry_instrumentor": [ + "jinja2 = opentelemetry.ext.jinja2:Jinja2Instrumentor" + ] + }, +) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py new file mode 100644 index 0000000000..93ecbcbb12 --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -0,0 +1,134 @@ +# 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. + +""" + +Usage +----- + +The OpenTelemetry ``jinja2`` integration traces templates loading, compilation and rendering. + +.. code-block:: python + +Instrumentation example:: + + from opentelemetry.ext.jinja2 import Jinja2Instrumentor + Jinja2Instrumentor().instrument() # This needs to be executed before importing jinja2 + from jinja2 import Environment, FileSystemLoader + + env = Environment( + loader=FileSystemLoader("templates") + ) + template = env.get_template('mytemplate.html') + +API +--- +""" + +import logging + +import jinja2 +from wrapt import ObjectProxy +from wrapt import wrap_function_wrapper as _wrap + +from opentelemetry import trace +from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.ext.jinja2.version import __version__ +from opentelemetry.trace.status import Status, StatusCanonicalCode + +logger = logging.getLogger(__name__) + +DEFAULT_TEMPLATE_NAME = "" + + +def _wrap_render(wrapped, instance, args, kwargs): + """Wrap `Template.render()` or `Template.generate()` + """ + template_name = instance.name or DEFAULT_TEMPLATE_NAME + tracer = trace.get_tracer(__name__, __version__) + with tracer.start_as_current_span( + "jinja2.render", kind=trace.SpanKind.INTERNAL + ) as span: + try: + return wrapped(*args, **kwargs) + finally: + span.set_attribute("component", "template") + span.set_attribute("jinja2.template_name", template_name) + + +def _wrap_compile(wrapped, _, args, kwargs): + if len(args) > 1: + template_name = args[1] + else: + template_name = kwargs.get("name", DEFAULT_TEMPLATE_NAME) + + tracer = trace.get_tracer(__name__, __version__) + with tracer.start_as_current_span( + "jinja2.compile", kind=trace.SpanKind.INTERNAL + ) as span: + try: + return wrapped(*args, **kwargs) + finally: + span.set_attribute("component", "template") + span.set_attribute("jinja2.template_name", template_name) + + +def _wrap_load_template(wrapped, _, args, kwargs): + template_name = kwargs.get("name", args[0]) + tracer = trace.get_tracer(__name__, __version__) + with tracer.start_as_current_span( + "jinja2.load", kind=trace.SpanKind.INTERNAL + ) as span: + template = None + try: + template = wrapped(*args, **kwargs) + return template + finally: + span.set_attribute("component", "template") + span.set_attribute("jinja2.template_name", template_name) + if template: + span.set_attribute("jinja2.template_path", template.filename) + + +def _unwrap(obj, attr): + func = getattr(obj, attr, None) + if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"): + setattr(obj, attr, func.__wrapped__) + + +class Jinja2Instrumentor(BaseInstrumentor): + """A instrumentor for jinja2 + + See `BaseInstrumentor` + """ + + def __init__(self): + super().__init__() + self._original_jinja2_tempalte_render = None + + def _instrument(self, **kwargs): + _wrap(jinja2, "environment.Template.render", _wrap_render) + _wrap(jinja2, "environment.Template.generate", _wrap_render) + _wrap(jinja2, "environment.Environment.compile", _wrap_compile) + _wrap( + jinja2, + "environment.Environment._load_template", + _wrap_load_template, + ) + + def _uninstrument(self, **kwargs): + _unwrap(jinja2.Template, "render") + _unwrap(jinja2.Template, "generate") + _unwrap(jinja2.Environment, "compile") + _unwrap(jinja2.Environment, "_load_template") diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/version.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/version.py new file mode 100644 index 0000000000..86c61362ab --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/version.py @@ -0,0 +1,15 @@ +# 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. + +__version__ = "0.7.dev0" diff --git a/ext/opentelemetry-ext-jinja2/tests/__init__.py b/ext/opentelemetry-ext-jinja2/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/opentelemetry-ext-jinja2/tests/conftest.py b/ext/opentelemetry-ext-jinja2/tests/conftest.py new file mode 100644 index 0000000000..cdcd27c67e --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/tests/conftest.py @@ -0,0 +1,25 @@ +# 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. + +from opentelemetry.ext.jinja2 import Jinja2Instrumentor + +_INSTRUMENTOR = Jinja2Instrumentor() + + +def pytest_sessionstart(session): # pylint: disable=unused-argument + _INSTRUMENTOR.instrument() + + +def pytest_sessionfinish(session): # pylint: disable=unused-argument + _INSTRUMENTOR.uninstrument() diff --git a/ext/opentelemetry-ext-jinja2/tests/templates/base.html b/ext/opentelemetry-ext-jinja2/tests/templates/base.html new file mode 100644 index 0000000000..05490d0c02 --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/tests/templates/base.html @@ -0,0 +1 @@ +Message: {% block content %}{% endblock %} diff --git a/ext/opentelemetry-ext-jinja2/tests/templates/template.html b/ext/opentelemetry-ext-jinja2/tests/templates/template.html new file mode 100644 index 0000000000..ab28182415 --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/tests/templates/template.html @@ -0,0 +1,2 @@ +{% extends 'base.html' %} +{% block content %}Hello {{name}}!{% endblock %} diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py new file mode 100644 index 0000000000..789d5cbb6e --- /dev/null +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -0,0 +1,129 @@ +# 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. + +import os + +import jinja2 + +from opentelemetry import trace as trace_api +from opentelemetry.test.test_base import TestBase + +TEST_DIR = os.path.dirname(os.path.realpath(__file__)) +TMPL_DIR = os.path.join(TEST_DIR, "templates") + + +class TestJinja2Instrumentor(TestBase): + def setUp(self): + super().setUp() + # prevent cache effects when using Template('code...') + # pylint: disable=protected-access + jinja2.environment._spontaneous_environments.clear() + + def test_render_inline_template(self): + template = jinja2.environment.Template("Hello {{name}}!") + self.assertEqual(template.render(name="Jinja"), "Hello Jinja!") + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 2) + + self.assertEqual(spans[0].name, "jinja2.compile") + self.assertEqual(spans[0].kind, trace_api.SpanKind.INTERNAL) + self.assertEqual( + spans[0].attributes, + {"component": "template", "jinja2.template_name": ""}, + ) + + self.assertEqual(spans[1].name, "jinja2.render") + self.assertEqual(spans[1].kind, trace_api.SpanKind.INTERNAL) + self.assertEqual( + spans[1].attributes, + {"component": "template", "jinja2.template_name": ""}, + ) + + def test_generate_inline_template(self): + template = jinja2.environment.Template("Hello {{name}}!") + self.assertEqual( + "".join(template.generate(name="Jinja")), "Hello Jinja!" + ) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 2) + + self.assertEqual(spans[0].name, "jinja2.compile") + self.assertEqual(spans[0].kind, trace_api.SpanKind.INTERNAL) + self.assertEqual( + spans[0].attributes, + {"component": "template", "jinja2.template_name": ""}, + ) + + self.assertEqual(spans[1].name, "jinja2.render") + self.assertEqual(spans[1].kind, trace_api.SpanKind.INTERNAL) + self.assertEqual( + spans[1].attributes, + {"component": "template", "jinja2.template_name": ""}, + ) + + def test_file_template(self): + loader = jinja2.loaders.FileSystemLoader(TMPL_DIR) + env = jinja2.Environment(loader=loader) + template = env.get_template("template.html") + self.assertEqual( + template.render(name="Jinja"), "Message: Hello Jinja!" + ) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 5) + + self.assertEqual(spans[0].name, "jinja2.compile") + self.assertEqual(spans[1].name, "jinja2.load") + self.assertEqual(spans[2].name, "jinja2.compile") + self.assertEqual(spans[3].name, "jinja2.load") + self.assertEqual(spans[4].name, "jinja2.render") + + self.assertEqual( + spans[0].attributes, + { + "component": "template", + "jinja2.template_name": "template.html", + }, + ) + self.assertEqual( + spans[1].attributes, + { + "component": "template", + "jinja2.template_name": "template.html", + "jinja2.template_path": os.path.join( + TMPL_DIR, "template.html" + ), + }, + ) + self.assertEqual( + spans[2].attributes, + {"component": "template", "jinja2.template_name": "base.html"}, + ) + self.assertEqual( + spans[3].attributes, + { + "component": "template", + "jinja2.template_name": "base.html", + "jinja2.template_path": os.path.join(TMPL_DIR, "base.html"), + }, + ) + self.assertEqual( + spans[4].attributes, + { + "component": "template", + "jinja2.template_name": "template.html", + }, + ) diff --git a/tox.ini b/tox.ini index 1570df787c..4d02bfce0e 100644 --- a/tox.ini +++ b/tox.ini @@ -40,6 +40,10 @@ envlist = py3{4,5,6,7,8}-test-ext-requests pypy3-test-ext-requests + ; opentelemetry-ext-jinja2 + py3{4,5,6,7,8}-test-ext-jinja2 + pypy3-test-ext-jinja2 + ; opentelemetry-ext-jaeger py3{4,5,6,7,8}-test-ext-jaeger pypy3-test-ext-jaeger @@ -125,6 +129,7 @@ changedir = test-auto-instrumentation: opentelemetry-auto-instrumentation/tests test-ext-grpc: ext/opentelemetry-ext-grpc/tests test-ext-requests: ext/opentelemetry-ext-requests/tests + test-ext-jinja2: ext/opentelemetry-ext-jinja2/tests test-ext-jaeger: ext/opentelemetry-ext-jaeger/tests test-ext-dbapi: ext/opentelemetry-ext-dbapi/tests test-ext-mysql: ext/opentelemetry-ext-mysql/tests @@ -198,6 +203,11 @@ commands_pre = requests: pip install {toxinidir}/opentelemetry-auto-instrumentation requests: pip install {toxinidir}/ext/opentelemetry-ext-requests[test] + jinja2: pip install {toxinidir}/opentelemetry-sdk + jinja2: pip install {toxinidir}/opentelemetry-auto-instrumentation + jinja2: pip install {toxinidir}/tests/util + jinja2: pip install {toxinidir}/ext/opentelemetry-ext-jinja2[test] + jaeger: pip install {toxinidir}/ext/opentelemetry-ext-jaeger opentracing-shim: pip install {toxinidir}/ext/opentelemetry-ext-opentracing-shim From 5b604dc576c03fb0408888d094c0ae4cfa929cdd Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Mon, 4 May 2020 23:51:34 -0400 Subject: [PATCH 02/27] fix attributes --- .../src/opentelemetry/ext/jinja2/__init__.py | 45 +++++++++---------- .../tests/test_jinja2.py | 29 +++--------- 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index 93ecbcbb12..49148cb4c2 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -49,56 +49,51 @@ logger = logging.getLogger(__name__) +ATTRIBUTE_JINJA2_TEMPLATE_NAME = "jinja2.template_name" +ATTRIBUTE_JINJA2_TEMPLATE_PATH = "jinja2.template_path" DEFAULT_TEMPLATE_NAME = "" def _wrap_render(wrapped, instance, args, kwargs): """Wrap `Template.render()` or `Template.generate()` """ - template_name = instance.name or DEFAULT_TEMPLATE_NAME tracer = trace.get_tracer(__name__, __version__) + template_name = instance.name or DEFAULT_TEMPLATE_NAME + attributes = {ATTRIBUTE_JINJA2_TEMPLATE_NAME: template_name} with tracer.start_as_current_span( - "jinja2.render", kind=trace.SpanKind.INTERNAL - ) as span: - try: - return wrapped(*args, **kwargs) - finally: - span.set_attribute("component", "template") - span.set_attribute("jinja2.template_name", template_name) + "jinja2.render", kind=trace.SpanKind.INTERNAL, attributes=attributes + ): + return wrapped(*args, **kwargs) def _wrap_compile(wrapped, _, args, kwargs): - if len(args) > 1: - template_name = args[1] - else: - template_name = kwargs.get("name", DEFAULT_TEMPLATE_NAME) - tracer = trace.get_tracer(__name__, __version__) + template_name = ( + args[1] if len(args) > 1 else kwargs.get("name", DEFAULT_TEMPLATE_NAME) + ) + attributes = {ATTRIBUTE_JINJA2_TEMPLATE_NAME: template_name} with tracer.start_as_current_span( - "jinja2.compile", kind=trace.SpanKind.INTERNAL - ) as span: - try: - return wrapped(*args, **kwargs) - finally: - span.set_attribute("component", "template") - span.set_attribute("jinja2.template_name", template_name) + "jinja2.compile", kind=trace.SpanKind.INTERNAL, attributes=attributes + ): + return wrapped(*args, **kwargs) def _wrap_load_template(wrapped, _, args, kwargs): - template_name = kwargs.get("name", args[0]) tracer = trace.get_tracer(__name__, __version__) + template_name = kwargs.get("name", args[0]) + attributes = {ATTRIBUTE_JINJA2_TEMPLATE_NAME: template_name} with tracer.start_as_current_span( - "jinja2.load", kind=trace.SpanKind.INTERNAL + "jinja2.load", kind=trace.SpanKind.INTERNAL, attributes=attributes ) as span: template = None try: template = wrapped(*args, **kwargs) return template finally: - span.set_attribute("component", "template") - span.set_attribute("jinja2.template_name", template_name) if template: - span.set_attribute("jinja2.template_path", template.filename) + span.set_attribute( + ATTRIBUTE_JINJA2_TEMPLATE_PATH, template.filename + ) def _unwrap(obj, attr): diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 789d5cbb6e..725ab0f7ec 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -40,15 +40,13 @@ def test_render_inline_template(self): self.assertEqual(spans[0].name, "jinja2.compile") self.assertEqual(spans[0].kind, trace_api.SpanKind.INTERNAL) self.assertEqual( - spans[0].attributes, - {"component": "template", "jinja2.template_name": ""}, + spans[0].attributes, {"jinja2.template_name": ""}, ) self.assertEqual(spans[1].name, "jinja2.render") self.assertEqual(spans[1].kind, trace_api.SpanKind.INTERNAL) self.assertEqual( - spans[1].attributes, - {"component": "template", "jinja2.template_name": ""}, + spans[1].attributes, {"jinja2.template_name": ""}, ) def test_generate_inline_template(self): @@ -63,15 +61,13 @@ def test_generate_inline_template(self): self.assertEqual(spans[0].name, "jinja2.compile") self.assertEqual(spans[0].kind, trace_api.SpanKind.INTERNAL) self.assertEqual( - spans[0].attributes, - {"component": "template", "jinja2.template_name": ""}, + spans[0].attributes, {"jinja2.template_name": ""}, ) self.assertEqual(spans[1].name, "jinja2.render") self.assertEqual(spans[1].kind, trace_api.SpanKind.INTERNAL) self.assertEqual( - spans[1].attributes, - {"component": "template", "jinja2.template_name": ""}, + spans[1].attributes, {"jinja2.template_name": ""}, ) def test_file_template(self): @@ -92,16 +88,11 @@ def test_file_template(self): self.assertEqual(spans[4].name, "jinja2.render") self.assertEqual( - spans[0].attributes, - { - "component": "template", - "jinja2.template_name": "template.html", - }, + spans[0].attributes, {"jinja2.template_name": "template.html"}, ) self.assertEqual( spans[1].attributes, { - "component": "template", "jinja2.template_name": "template.html", "jinja2.template_path": os.path.join( TMPL_DIR, "template.html" @@ -109,21 +100,15 @@ def test_file_template(self): }, ) self.assertEqual( - spans[2].attributes, - {"component": "template", "jinja2.template_name": "base.html"}, + spans[2].attributes, {"jinja2.template_name": "base.html"}, ) self.assertEqual( spans[3].attributes, { - "component": "template", "jinja2.template_name": "base.html", "jinja2.template_path": os.path.join(TMPL_DIR, "base.html"), }, ) self.assertEqual( - spans[4].attributes, - { - "component": "template", - "jinja2.template_name": "template.html", - }, + spans[4].attributes, {"jinja2.template_name": "template.html"}, ) From 8f6790efd94fbe409c89c9ecb5c81d9cc54697b6 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Mon, 4 May 2020 23:56:19 -0400 Subject: [PATCH 03/27] remove unused --- .../src/opentelemetry/ext/jinja2/__init__.py | 4 ---- tox.ini | 2 -- 2 files changed, 6 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index 49148cb4c2..995862516f 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -108,10 +108,6 @@ class Jinja2Instrumentor(BaseInstrumentor): See `BaseInstrumentor` """ - def __init__(self): - super().__init__() - self._original_jinja2_tempalte_render = None - def _instrument(self, **kwargs): _wrap(jinja2, "environment.Template.render", _wrap_render) _wrap(jinja2, "environment.Template.generate", _wrap_render) diff --git a/tox.ini b/tox.ini index 4d02bfce0e..f3651fb6e9 100644 --- a/tox.ini +++ b/tox.ini @@ -203,9 +203,7 @@ commands_pre = requests: pip install {toxinidir}/opentelemetry-auto-instrumentation requests: pip install {toxinidir}/ext/opentelemetry-ext-requests[test] - jinja2: pip install {toxinidir}/opentelemetry-sdk jinja2: pip install {toxinidir}/opentelemetry-auto-instrumentation - jinja2: pip install {toxinidir}/tests/util jinja2: pip install {toxinidir}/ext/opentelemetry-ext-jinja2[test] jaeger: pip install {toxinidir}/ext/opentelemetry-ext-jaeger From 7b1d4988bcdb0f1e3931a7167c7879e7f5804633 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Tue, 5 May 2020 09:19:05 -0400 Subject: [PATCH 04/27] docs --- docs/ext/jinja2/jinja2.rst | 7 +++++++ ext/opentelemetry-ext-jinja2/CHANGELOG.md | 2 ++ .../src/opentelemetry/ext/jinja2/__init__.py | 19 +++++++++++-------- 3 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 docs/ext/jinja2/jinja2.rst diff --git a/docs/ext/jinja2/jinja2.rst b/docs/ext/jinja2/jinja2.rst new file mode 100644 index 0000000000..d9b461627d --- /dev/null +++ b/docs/ext/jinja2/jinja2.rst @@ -0,0 +1,7 @@ +OpenTelemetry Jinja2 Instrumentation +==================================== + +.. automodule:: opentelemetry.ext.jinja2 + :members: + :undoc-members: + :show-inheritance: diff --git a/ext/opentelemetry-ext-jinja2/CHANGELOG.md b/ext/opentelemetry-ext-jinja2/CHANGELOG.md index 1512c42162..bd0f6b54ae 100644 --- a/ext/opentelemetry-ext-jinja2/CHANGELOG.md +++ b/ext/opentelemetry-ext-jinja2/CHANGELOG.md @@ -1,3 +1,5 @@ # Changelog ## Unreleased + +- Add jinja2 instrumentation ([#643](https://github.com/open-telemetry/opentelemetry-python/pull/643)) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index 995862516f..5bce3bc81b 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -17,19 +17,22 @@ Usage ----- -The OpenTelemetry ``jinja2`` integration traces templates loading, compilation and rendering. +The OpenTelemetry ``jinja2`` integration traces templates loading, compilation +and rendering. -.. code-block:: python +Usage +----- -Instrumentation example:: +.. code-block:: python - from opentelemetry.ext.jinja2 import Jinja2Instrumentor - Jinja2Instrumentor().instrument() # This needs to be executed before importing jinja2 from jinja2 import Environment, FileSystemLoader + from opentelemetry.ext.jinja2 import Jinja2Instrumentor - env = Environment( - loader=FileSystemLoader("templates") - ) + trace.set_tracer_provider(TracerProvider()) + + Jinja2Instrumentor().instrument() + + env = Environment(loader=FileSystemLoader("templates")) template = env.get_template('mytemplate.html') API From fb3cbf9937013244c5aef62b8183e51fca337c58 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 11:45:25 -0400 Subject: [PATCH 05/27] Update ext/opentelemetry-ext-jinja2/README.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/README.rst b/ext/opentelemetry-ext-jinja2/README.rst index ac8afd0a75..09e74e21d3 100644 --- a/ext/opentelemetry-ext-jinja2/README.rst +++ b/ext/opentelemetry-ext-jinja2/README.rst @@ -1,5 +1,5 @@ OpenTelemetry jinja2 integration -====================================== +================================ |pypi| From d3432546410c9f0e52bd492d10ebac6fb7056a46 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 11:45:35 -0400 Subject: [PATCH 06/27] Update ext/opentelemetry-ext-jinja2/setup.cfg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/setup.cfg b/ext/opentelemetry-ext-jinja2/setup.cfg index 505e267069..c142e51cf7 100644 --- a/ext/opentelemetry-ext-jinja2/setup.cfg +++ b/ext/opentelemetry-ext-jinja2/setup.cfg @@ -19,7 +19,7 @@ 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/ext/opentelemetry-ext-jinja2 +url = https://github.com/open-telemetry/opentelemetry-python/tree/master/ext/opentelemetry-ext-jinja2 platforms = any license = Apache-2.0 classifiers = From 70a2d84cc421279bf86d23d173af31497e5aa473 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 11:52:17 -0400 Subject: [PATCH 07/27] Update ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- .../src/opentelemetry/ext/jinja2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index 5bce3bc81b..abf0efb166 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -106,7 +106,7 @@ def _unwrap(obj, attr): class Jinja2Instrumentor(BaseInstrumentor): - """A instrumentor for jinja2 + """An instrumentor for jinja2 See `BaseInstrumentor` """ From 2e0cdcbc20471b31ffc84f48284270a624da3ba8 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 11:53:49 -0400 Subject: [PATCH 08/27] move entrypoint --- ext/opentelemetry-ext-jinja2/setup.cfg | 4 ++++ ext/opentelemetry-ext-jinja2/setup.py | 9 +-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/setup.cfg b/ext/opentelemetry-ext-jinja2/setup.cfg index c142e51cf7..7ff1ac17e1 100644 --- a/ext/opentelemetry-ext-jinja2/setup.cfg +++ b/ext/opentelemetry-ext-jinja2/setup.cfg @@ -51,3 +51,7 @@ test = [options.packages.find] where = src + +[options.entry_points] +opentelemetry_instrumentor = + jinja2 = opentelemetry.ext.jinja2:Jinja2Instrumentor diff --git a/ext/opentelemetry-ext-jinja2/setup.py b/ext/opentelemetry-ext-jinja2/setup.py index c972398bf8..323c1b3353 100644 --- a/ext/opentelemetry-ext-jinja2/setup.py +++ b/ext/opentelemetry-ext-jinja2/setup.py @@ -23,11 +23,4 @@ with open(VERSION_FILENAME) as f: exec(f.read(), PACKAGE_INFO) -setuptools.setup( - version=PACKAGE_INFO["__version__"], - entry_points={ - "opentelemetry_instrumentor": [ - "jinja2 = opentelemetry.ext.jinja2:Jinja2Instrumentor" - ] - }, -) +setuptools.setup(version=PACKAGE_INFO["__version__"]) From 8c609a03a1f584a5bcebc87867f5644aed30de72 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 11:57:07 -0400 Subject: [PATCH 09/27] remove conftest.py --- .../tests/conftest.py | 25 ------------------- .../tests/test_jinja2.py | 7 ++++++ 2 files changed, 7 insertions(+), 25 deletions(-) delete mode 100644 ext/opentelemetry-ext-jinja2/tests/conftest.py diff --git a/ext/opentelemetry-ext-jinja2/tests/conftest.py b/ext/opentelemetry-ext-jinja2/tests/conftest.py deleted file mode 100644 index cdcd27c67e..0000000000 --- a/ext/opentelemetry-ext-jinja2/tests/conftest.py +++ /dev/null @@ -1,25 +0,0 @@ -# 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. - -from opentelemetry.ext.jinja2 import Jinja2Instrumentor - -_INSTRUMENTOR = Jinja2Instrumentor() - - -def pytest_sessionstart(session): # pylint: disable=unused-argument - _INSTRUMENTOR.instrument() - - -def pytest_sessionfinish(session): # pylint: disable=unused-argument - _INSTRUMENTOR.uninstrument() diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 725ab0f7ec..e36f6313d5 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -18,6 +18,8 @@ from opentelemetry import trace as trace_api from opentelemetry.test.test_base import TestBase +from opentelemetry.ext.jinja2 import Jinja2Instrumentor + TEST_DIR = os.path.dirname(os.path.realpath(__file__)) TMPL_DIR = os.path.join(TEST_DIR, "templates") @@ -26,10 +28,15 @@ class TestJinja2Instrumentor(TestBase): def setUp(self): super().setUp() + self.instrumentor = Jinja2Instrumentor() + self.instrumentor.instrument() # prevent cache effects when using Template('code...') # pylint: disable=protected-access jinja2.environment._spontaneous_environments.clear() + def tearDown(self): + self.instrumentor.uninstrument() + def test_render_inline_template(self): template = jinja2.environment.Template("Hello {{name}}!") self.assertEqual(template.render(name="Jinja"), "Hello Jinja!") From aa43501ab7367c10a6c18d074689ca921d219215 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 13:02:55 -0400 Subject: [PATCH 10/27] pass tracer to wrapped --- .../src/opentelemetry/ext/jinja2/__init__.py | 57 ++++++++++++++----- .../tests/test_jinja2.py | 3 +- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index abf0efb166..5694be00be 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -27,6 +27,7 @@ from jinja2 import Environment, FileSystemLoader from opentelemetry.ext.jinja2 import Jinja2Instrumentor + from opentelemetry.trace import TracerProvider trace.set_tracer_provider(TracerProvider()) @@ -38,6 +39,7 @@ API --- """ +# pylint: disable=no-value-for-parameter import logging @@ -45,9 +47,9 @@ from wrapt import ObjectProxy from wrapt import wrap_function_wrapper as _wrap -from opentelemetry import trace from opentelemetry.auto_instrumentation.instrumentor import BaseInstrumentor from opentelemetry.ext.jinja2.version import __version__ +from opentelemetry.trace import SpanKind, get_tracer from opentelemetry.trace.status import Status, StatusCanonicalCode logger = logging.getLogger(__name__) @@ -57,36 +59,60 @@ DEFAULT_TEMPLATE_NAME = "" -def _wrap_render(wrapped, instance, args, kwargs): +def with_tracer_wrapper(func): + """Helper for providing tracer for wrapper functions. + + Usage:: + + @with_tracer + def my_wrapper(tracer, wrapped, instance, args, kwargs): + # Do tracing stuff + pass + + def instrument(): + tracer = get_tracer(__name__) + wrap(mod, "Class.function", my_wrapper(tracer)) + """ + + def _with_tracer(tracer): + def wrapper(wrapped, instance, args, kwargs): + return func(tracer, wrapped, instance, args, kwargs) + + return wrapper + + return _with_tracer + + +@with_tracer_wrapper +def _wrap_render(tracer, wrapped, instance, args, kwargs): """Wrap `Template.render()` or `Template.generate()` """ - tracer = trace.get_tracer(__name__, __version__) template_name = instance.name or DEFAULT_TEMPLATE_NAME attributes = {ATTRIBUTE_JINJA2_TEMPLATE_NAME: template_name} with tracer.start_as_current_span( - "jinja2.render", kind=trace.SpanKind.INTERNAL, attributes=attributes + "jinja2.render", kind=SpanKind.INTERNAL, attributes=attributes ): return wrapped(*args, **kwargs) -def _wrap_compile(wrapped, _, args, kwargs): - tracer = trace.get_tracer(__name__, __version__) +@with_tracer_wrapper +def _wrap_compile(tracer, wrapped, _, args, kwargs): template_name = ( args[1] if len(args) > 1 else kwargs.get("name", DEFAULT_TEMPLATE_NAME) ) attributes = {ATTRIBUTE_JINJA2_TEMPLATE_NAME: template_name} with tracer.start_as_current_span( - "jinja2.compile", kind=trace.SpanKind.INTERNAL, attributes=attributes + "jinja2.compile", kind=SpanKind.INTERNAL, attributes=attributes ): return wrapped(*args, **kwargs) -def _wrap_load_template(wrapped, _, args, kwargs): - tracer = trace.get_tracer(__name__, __version__) +@with_tracer_wrapper +def _wrap_load_template(tracer, wrapped, _, args, kwargs): template_name = kwargs.get("name", args[0]) attributes = {ATTRIBUTE_JINJA2_TEMPLATE_NAME: template_name} with tracer.start_as_current_span( - "jinja2.load", kind=trace.SpanKind.INTERNAL, attributes=attributes + "jinja2.load", kind=SpanKind.INTERNAL, attributes=attributes ) as span: template = None try: @@ -112,13 +138,16 @@ class Jinja2Instrumentor(BaseInstrumentor): """ def _instrument(self, **kwargs): - _wrap(jinja2, "environment.Template.render", _wrap_render) - _wrap(jinja2, "environment.Template.generate", _wrap_render) - _wrap(jinja2, "environment.Environment.compile", _wrap_compile) + tracer_provider = kwargs.get("tracer_provider") + tracer = get_tracer(__name__, __version__, tracer_provider) + + _wrap(jinja2, "environment.Template.render", _wrap_render(tracer)) + _wrap(jinja2, "environment.Template.generate", _wrap_render(tracer)) + _wrap(jinja2, "environment.Environment.compile", _wrap_compile(tracer)) _wrap( jinja2, "environment.Environment._load_template", - _wrap_load_template, + _wrap_load_template(tracer), ) def _uninstrument(self, **kwargs): diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index e36f6313d5..e602f2cf51 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -17,9 +17,8 @@ import jinja2 from opentelemetry import trace as trace_api -from opentelemetry.test.test_base import TestBase from opentelemetry.ext.jinja2 import Jinja2Instrumentor - +from opentelemetry.test.test_base import TestBase TEST_DIR = os.path.dirname(os.path.realpath(__file__)) TMPL_DIR = os.path.join(TEST_DIR, "templates") From 752c9599dfd1d7304f3a6bf91f70469fbef1a761 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 13:53:04 -0400 Subject: [PATCH 11/27] fix doc --- .../src/opentelemetry/ext/jinja2/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index 5694be00be..0e702e0af3 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -27,6 +27,7 @@ from jinja2 import Environment, FileSystemLoader from opentelemetry.ext.jinja2 import Jinja2Instrumentor + from opentelemetry import trace from opentelemetry.trace import TracerProvider trace.set_tracer_provider(TracerProvider()) From 86b60e23f69ac2ea0fe5bdaa16f0db5aae2fe489 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 15:24:24 -0400 Subject: [PATCH 12/27] add parent-child tests --- .../tests/test_jinja2.py | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index e602f2cf51..fdb3a4a6c1 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -19,6 +19,7 @@ from opentelemetry import trace as trace_api from opentelemetry.ext.jinja2 import Jinja2Instrumentor from opentelemetry.test.test_base import TestBase +from opentelemetry.trace import get_tracer TEST_DIR = os.path.dirname(os.path.realpath(__file__)) TMPL_DIR = os.path.join(TEST_DIR, "templates") @@ -32,16 +33,22 @@ def setUp(self): # prevent cache effects when using Template('code...') # pylint: disable=protected-access jinja2.environment._spontaneous_environments.clear() + self.tracer = get_tracer(__name__) def tearDown(self): self.instrumentor.uninstrument() def test_render_inline_template(self): - template = jinja2.environment.Template("Hello {{name}}!") - self.assertEqual(template.render(name="Jinja"), "Hello Jinja!") + with self.tracer.start_as_current_span("test"): + template = jinja2.environment.Template("Hello {{name}}!") + self.assertEqual(template.render(name="Jinja"), "Hello Jinja!") spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 2) + self.assertEqual(len(spans), 3) + + self.assertEqual(spans[0].parent, spans[2].get_context()) + self.assertEqual(spans[1].parent, spans[2].get_context()) + self.assertIsNone(spans[2].parent) self.assertEqual(spans[0].name, "jinja2.compile") self.assertEqual(spans[0].kind, trace_api.SpanKind.INTERNAL) @@ -56,13 +63,18 @@ def test_render_inline_template(self): ) def test_generate_inline_template(self): - template = jinja2.environment.Template("Hello {{name}}!") - self.assertEqual( - "".join(template.generate(name="Jinja")), "Hello Jinja!" - ) + with self.tracer.start_as_current_span("test"): + template = jinja2.environment.Template("Hello {{name}}!") + self.assertEqual( + "".join(template.generate(name="Jinja")), "Hello Jinja!" + ) spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 2) + self.assertEqual(len(spans), 3) + + self.assertEqual(spans[0].parent, spans[2].get_context()) + self.assertEqual(spans[1].parent, spans[2].get_context()) + self.assertIsNone(spans[2].parent) self.assertEqual(spans[0].name, "jinja2.compile") self.assertEqual(spans[0].kind, trace_api.SpanKind.INTERNAL) @@ -77,15 +89,23 @@ def test_generate_inline_template(self): ) def test_file_template(self): - loader = jinja2.loaders.FileSystemLoader(TMPL_DIR) - env = jinja2.Environment(loader=loader) - template = env.get_template("template.html") - self.assertEqual( - template.render(name="Jinja"), "Message: Hello Jinja!" - ) + with self.tracer.start_as_current_span("test"): + loader = jinja2.loaders.FileSystemLoader(TMPL_DIR) + env = jinja2.Environment(loader=loader) + template = env.get_template("template.html") + self.assertEqual( + template.render(name="Jinja"), "Message: Hello Jinja!" + ) spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 5) + self.assertEqual(len(spans), 6) + + self.assertEqual(spans[0].parent, spans[1].get_context()) + self.assertEqual(spans[1].parent, spans[5].get_context()) + self.assertEqual(spans[2].parent, spans[3].get_context()) + self.assertEqual(spans[3].parent, spans[4].get_context()) + self.assertEqual(spans[4].parent, spans[5].get_context()) + self.assertIsNone(spans[5].parent) self.assertEqual(spans[0].name, "jinja2.compile") self.assertEqual(spans[1].name, "jinja2.load") From 485e8c1563581a953faf2842dbd29b98c10fb2b3 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 15:25:38 -0400 Subject: [PATCH 13/27] Update ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- .../src/opentelemetry/ext/jinja2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index 0e702e0af3..cba1affdee 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -35,7 +35,7 @@ Jinja2Instrumentor().instrument() env = Environment(loader=FileSystemLoader("templates")) - template = env.get_template('mytemplate.html') + template = env.get_template("mytemplate.html") API --- From df5b207f366306ed22c38294d3b903cba3c72a18 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 15:45:07 -0400 Subject: [PATCH 14/27] add test for uninstrumented --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index fdb3a4a6c1..3eeaffdd25 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -138,3 +138,12 @@ def test_file_template(self): self.assertEqual( spans[4].attributes, {"jinja2.template_name": "template.html"}, ) + + def test_uninstrumented(self): + self.instrumentor.uninstrument() + + jinja2.environment.Template("Hello {{name}}!") + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 0) + + self.instrumentor.instrument() From 490800f2015c2564ddb39c80f334900846b9233c Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Thu, 7 May 2020 15:52:17 -0400 Subject: [PATCH 15/27] fix typo --- .../src/opentelemetry/ext/jinja2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index cba1affdee..64c148f48e 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -65,7 +65,7 @@ def with_tracer_wrapper(func): Usage:: - @with_tracer + @with_tracer_wrapper def my_wrapper(tracer, wrapped, instance, args, kwargs): # Do tracing stuff pass From 3ae97643c937fc49fcc641474a7ab62801a31aea Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:30:46 -0400 Subject: [PATCH 16/27] use instrumentor as singleton --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 3eeaffdd25..99d9080f7f 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -28,15 +28,14 @@ class TestJinja2Instrumentor(TestBase): def setUp(self): super().setUp() - self.instrumentor = Jinja2Instrumentor() - self.instrumentor.instrument() + Jinja2Instrumentor().instrument() # prevent cache effects when using Template('code...') # pylint: disable=protected-access jinja2.environment._spontaneous_environments.clear() self.tracer = get_tracer(__name__) def tearDown(self): - self.instrumentor.uninstrument() + Jinja2Instrumentor().uninstrument() def test_render_inline_template(self): with self.tracer.start_as_current_span("test"): @@ -140,10 +139,10 @@ def test_file_template(self): ) def test_uninstrumented(self): - self.instrumentor.uninstrument() + Jinja2Instrumentor().uninstrument() jinja2.environment.Template("Hello {{name}}!") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 0) - self.instrumentor.instrument() + Jinja2Instrumentor().instrument() From d7a675eeffbf3f5b0b279d27ca93658ec3b53d87 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:31:10 -0400 Subject: [PATCH 17/27] Update ext/opentelemetry-ext-jinja2/tests/test_jinja2.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 99d9080f7f..f35b6c674f 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -45,8 +45,8 @@ def test_render_inline_template(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 3) - self.assertEqual(spans[0].parent, spans[2].get_context()) - self.assertEqual(spans[1].parent, spans[2].get_context()) + self.assertIs(spans[0].parent, spans[2].get_context()) + self.assertIs(spans[1].parent, spans[2].get_context()) self.assertIsNone(spans[2].parent) self.assertEqual(spans[0].name, "jinja2.compile") From aa29c9cc29b918754313a1dee51ff6db616b6dca Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:31:18 -0400 Subject: [PATCH 18/27] Update ext/opentelemetry-ext-jinja2/tests/test_jinja2.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index f35b6c674f..29469e01e8 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -50,7 +50,7 @@ def test_render_inline_template(self): self.assertIsNone(spans[2].parent) self.assertEqual(spans[0].name, "jinja2.compile") - self.assertEqual(spans[0].kind, trace_api.SpanKind.INTERNAL) + self.assertIs(spans[0].kind, trace_api.SpanKind.INTERNAL) self.assertEqual( spans[0].attributes, {"jinja2.template_name": ""}, ) From 1bd7c4cfc6d1194c7b85a10dddbf34f0b1884e1b Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:31:25 -0400 Subject: [PATCH 19/27] Update ext/opentelemetry-ext-jinja2/tests/test_jinja2.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 29469e01e8..9100e5a898 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -71,8 +71,8 @@ def test_generate_inline_template(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 3) - self.assertEqual(spans[0].parent, spans[2].get_context()) - self.assertEqual(spans[1].parent, spans[2].get_context()) + self.assertIs(spans[0].parent, spans[2].get_context()) + self.assertIs(spans[1].parent, spans[2].get_context()) self.assertIsNone(spans[2].parent) self.assertEqual(spans[0].name, "jinja2.compile") From 546c8ba9413806fb7e033290ac1641932576e10b Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:31:33 -0400 Subject: [PATCH 20/27] Update ext/opentelemetry-ext-jinja2/tests/test_jinja2.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 9100e5a898..684af62f92 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -76,7 +76,7 @@ def test_generate_inline_template(self): self.assertIsNone(spans[2].parent) self.assertEqual(spans[0].name, "jinja2.compile") - self.assertEqual(spans[0].kind, trace_api.SpanKind.INTERNAL) + self.assertIs(spans[0].kind, trace_api.SpanKind.INTERNAL) self.assertEqual( spans[0].attributes, {"jinja2.template_name": ""}, ) From 6cfb17bc0ccbfbc0e96f2f0620f0075fbffa2fc5 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:31:48 -0400 Subject: [PATCH 21/27] Update ext/opentelemetry-ext-jinja2/tests/test_jinja2.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 684af62f92..0149ec7a36 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -99,11 +99,11 @@ def test_file_template(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 6) - self.assertEqual(spans[0].parent, spans[1].get_context()) - self.assertEqual(spans[1].parent, spans[5].get_context()) - self.assertEqual(spans[2].parent, spans[3].get_context()) - self.assertEqual(spans[3].parent, spans[4].get_context()) - self.assertEqual(spans[4].parent, spans[5].get_context()) + self.assertIs(spans[0].parent, spans[1].get_context()) + self.assertIs(spans[1].parent, spans[5].get_context()) + self.assertIs(spans[2].parent, spans[3].get_context()) + self.assertIs(spans[3].parent, spans[4].get_context()) + self.assertIs(spans[4].parent, spans[5].get_context()) self.assertIsNone(spans[5].parent) self.assertEqual(spans[0].name, "jinja2.compile") From 2f0fd4f85d05a5520d847a0d4c7b5c851c84264a Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:31:55 -0400 Subject: [PATCH 22/27] Update ext/opentelemetry-ext-jinja2/tests/test_jinja2.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 0149ec7a36..f3215ba1f9 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -82,7 +82,7 @@ def test_generate_inline_template(self): ) self.assertEqual(spans[1].name, "jinja2.render") - self.assertEqual(spans[1].kind, trace_api.SpanKind.INTERNAL) + self.assertIs(spans[1].kind, trace_api.SpanKind.INTERNAL) self.assertEqual( spans[1].attributes, {"jinja2.template_name": ""}, ) From f15482053b379e2530d4691956bdc9b410b5e55a Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:32:04 -0400 Subject: [PATCH 23/27] Update ext/opentelemetry-ext-jinja2/tests/test_jinja2.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mauricio Vásquez --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index f3215ba1f9..f155121ddb 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -56,7 +56,7 @@ def test_render_inline_template(self): ) self.assertEqual(spans[1].name, "jinja2.render") - self.assertEqual(spans[1].kind, trace_api.SpanKind.INTERNAL) + self.assertIs(spans[1].kind, trace_api.SpanKind.INTERNAL) self.assertEqual( spans[1].attributes, {"jinja2.template_name": ""}, ) From db44456599bdf8c31df2dfde3123ee3f90ec7745 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:33:34 -0400 Subject: [PATCH 24/27] call tearDown --- ext/opentelemetry-ext-jinja2/tests/test_jinja2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index f155121ddb..537e18f378 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -35,6 +35,7 @@ def setUp(self): self.tracer = get_tracer(__name__) def tearDown(self): + super().tearDown() Jinja2Instrumentor().uninstrument() def test_render_inline_template(self): From ed1789074d9cb65de99a1ce3aba5d9f9239dd3f7 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:35:42 -0400 Subject: [PATCH 25/27] make decorator private --- .../src/opentelemetry/ext/jinja2/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index 64c148f48e..c164e7d032 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -60,7 +60,7 @@ DEFAULT_TEMPLATE_NAME = "" -def with_tracer_wrapper(func): +def _with_tracer_wrapper(func): """Helper for providing tracer for wrapper functions. Usage:: @@ -84,7 +84,7 @@ def wrapper(wrapped, instance, args, kwargs): return _with_tracer -@with_tracer_wrapper +@_with_tracer_wrapper def _wrap_render(tracer, wrapped, instance, args, kwargs): """Wrap `Template.render()` or `Template.generate()` """ @@ -96,7 +96,7 @@ def _wrap_render(tracer, wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) -@with_tracer_wrapper +@_with_tracer_wrapper def _wrap_compile(tracer, wrapped, _, args, kwargs): template_name = ( args[1] if len(args) > 1 else kwargs.get("name", DEFAULT_TEMPLATE_NAME) @@ -108,7 +108,7 @@ def _wrap_compile(tracer, wrapped, _, args, kwargs): return wrapped(*args, **kwargs) -@with_tracer_wrapper +@_with_tracer_wrapper def _wrap_load_template(tracer, wrapped, _, args, kwargs): template_name = kwargs.get("name", args[0]) attributes = {ATTRIBUTE_JINJA2_TEMPLATE_NAME: template_name} From 6392bb5d4b2477b28e22352ef884aaed692303ec Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Fri, 8 May 2020 08:44:15 -0400 Subject: [PATCH 26/27] remove unnecessary doc --- .../src/opentelemetry/ext/jinja2/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py index c164e7d032..4aabb832ba 100644 --- a/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py +++ b/ext/opentelemetry-ext-jinja2/src/opentelemetry/ext/jinja2/__init__.py @@ -62,17 +62,6 @@ def _with_tracer_wrapper(func): """Helper for providing tracer for wrapper functions. - - Usage:: - - @with_tracer_wrapper - def my_wrapper(tracer, wrapped, instance, args, kwargs): - # Do tracing stuff - pass - - def instrument(): - tracer = get_tracer(__name__) - wrap(mod, "Class.function", my_wrapper(tracer)) """ def _with_tracer(tracer): From 4353a8df375e763483a56ea466a587e2709104eb Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Mon, 11 May 2020 07:49:09 -0400 Subject: [PATCH 27/27] feedback --- ext/opentelemetry-ext-jinja2/setup.cfg | 3 +- .../tests/test_jinja2.py | 119 ++++++++++++------ 2 files changed, 80 insertions(+), 42 deletions(-) diff --git a/ext/opentelemetry-ext-jinja2/setup.cfg b/ext/opentelemetry-ext-jinja2/setup.cfg index 7ff1ac17e1..8aa18bab8e 100644 --- a/ext/opentelemetry-ext-jinja2/setup.cfg +++ b/ext/opentelemetry-ext-jinja2/setup.cfg @@ -35,18 +35,17 @@ classifiers = Programming Language :: Python :: 3.8 [options] -python_requires = >=3.4 package_dir= =src packages=find_namespace: install_requires = opentelemetry-api == 0.7.dev0 opentelemetry-auto-instrumentation == 0.7.dev0 + jinja2~=2.7 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - jinja2~=2.7 opentelemetry-test == 0.7.dev0 [options.packages.find] diff --git a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py index 537e18f378..1a2da437c9 100644 --- a/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py +++ b/ext/opentelemetry-ext-jinja2/tests/test_jinja2.py @@ -38,32 +38,43 @@ def tearDown(self): super().tearDown() Jinja2Instrumentor().uninstrument() - def test_render_inline_template(self): - with self.tracer.start_as_current_span("test"): + def test_render_inline_template_with_root(self): + with self.tracer.start_as_current_span("root"): template = jinja2.environment.Template("Hello {{name}}!") self.assertEqual(template.render(name="Jinja"), "Hello Jinja!") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 3) - self.assertIs(spans[0].parent, spans[2].get_context()) - self.assertIs(spans[1].parent, spans[2].get_context()) - self.assertIsNone(spans[2].parent) + render, template, root = spans + + self.assertIs(render.parent, root.get_context()) + self.assertIs(template.parent, root.get_context()) + self.assertIsNone(root.parent) + + def test_render_inline_template(self): + template = jinja2.environment.Template("Hello {{name}}!") + self.assertEqual(template.render(name="Jinja"), "Hello Jinja!") + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 2) + + template, render = spans - self.assertEqual(spans[0].name, "jinja2.compile") - self.assertIs(spans[0].kind, trace_api.SpanKind.INTERNAL) + self.assertEqual(template.name, "jinja2.compile") + self.assertIs(template.kind, trace_api.SpanKind.INTERNAL) self.assertEqual( - spans[0].attributes, {"jinja2.template_name": ""}, + template.attributes, {"jinja2.template_name": ""}, ) - self.assertEqual(spans[1].name, "jinja2.render") - self.assertIs(spans[1].kind, trace_api.SpanKind.INTERNAL) + self.assertEqual(render.name, "jinja2.render") + self.assertIs(render.kind, trace_api.SpanKind.INTERNAL) self.assertEqual( - spans[1].attributes, {"jinja2.template_name": ""}, + render.attributes, {"jinja2.template_name": ""}, ) - def test_generate_inline_template(self): - with self.tracer.start_as_current_span("test"): + def test_generate_inline_template_with_root(self): + with self.tracer.start_as_current_span("root"): template = jinja2.environment.Template("Hello {{name}}!") self.assertEqual( "".join(template.generate(name="Jinja")), "Hello Jinja!" @@ -72,24 +83,37 @@ def test_generate_inline_template(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 3) - self.assertIs(spans[0].parent, spans[2].get_context()) - self.assertIs(spans[1].parent, spans[2].get_context()) - self.assertIsNone(spans[2].parent) + template, generate, root = spans + + self.assertIs(generate.parent, root.get_context()) + self.assertIs(template.parent, root.get_context()) + self.assertIsNone(root.parent) - self.assertEqual(spans[0].name, "jinja2.compile") - self.assertIs(spans[0].kind, trace_api.SpanKind.INTERNAL) + def test_generate_inline_template(self): + template = jinja2.environment.Template("Hello {{name}}!") self.assertEqual( - spans[0].attributes, {"jinja2.template_name": ""}, + "".join(template.generate(name="Jinja")), "Hello Jinja!" ) - self.assertEqual(spans[1].name, "jinja2.render") - self.assertIs(spans[1].kind, trace_api.SpanKind.INTERNAL) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 2) + + template, generate = spans + + self.assertEqual(template.name, "jinja2.compile") + self.assertIs(template.kind, trace_api.SpanKind.INTERNAL) self.assertEqual( - spans[1].attributes, {"jinja2.template_name": ""}, + template.attributes, {"jinja2.template_name": ""}, ) - def test_file_template(self): - with self.tracer.start_as_current_span("test"): + self.assertEqual(generate.name, "jinja2.render") + self.assertIs(generate.kind, trace_api.SpanKind.INTERNAL) + self.assertEqual( + generate.attributes, {"jinja2.template_name": ""}, + ) + + def test_file_template_with_root(self): + with self.tracer.start_as_current_span("root"): loader = jinja2.loaders.FileSystemLoader(TMPL_DIR) env = jinja2.Environment(loader=loader) template = env.get_template("template.html") @@ -100,24 +124,39 @@ def test_file_template(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 6) - self.assertIs(spans[0].parent, spans[1].get_context()) - self.assertIs(spans[1].parent, spans[5].get_context()) - self.assertIs(spans[2].parent, spans[3].get_context()) - self.assertIs(spans[3].parent, spans[4].get_context()) - self.assertIs(spans[4].parent, spans[5].get_context()) - self.assertIsNone(spans[5].parent) + compile2, load2, compile1, load1, render, root = spans + + self.assertIs(compile2.parent, load2.get_context()) + self.assertIs(load2.parent, root.get_context()) + self.assertIs(compile1.parent, load1.get_context()) + self.assertIs(load1.parent, render.get_context()) + self.assertIs(render.parent, root.get_context()) + self.assertIsNone(root.parent) + + def test_file_template(self): + loader = jinja2.loaders.FileSystemLoader(TMPL_DIR) + env = jinja2.Environment(loader=loader) + template = env.get_template("template.html") + self.assertEqual( + template.render(name="Jinja"), "Message: Hello Jinja!" + ) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 5) + + compile2, load2, compile1, load1, render = spans - self.assertEqual(spans[0].name, "jinja2.compile") - self.assertEqual(spans[1].name, "jinja2.load") - self.assertEqual(spans[2].name, "jinja2.compile") - self.assertEqual(spans[3].name, "jinja2.load") - self.assertEqual(spans[4].name, "jinja2.render") + self.assertEqual(compile2.name, "jinja2.compile") + self.assertEqual(load2.name, "jinja2.load") + self.assertEqual(compile1.name, "jinja2.compile") + self.assertEqual(load1.name, "jinja2.load") + self.assertEqual(render.name, "jinja2.render") self.assertEqual( - spans[0].attributes, {"jinja2.template_name": "template.html"}, + compile2.attributes, {"jinja2.template_name": "template.html"}, ) self.assertEqual( - spans[1].attributes, + load2.attributes, { "jinja2.template_name": "template.html", "jinja2.template_path": os.path.join( @@ -126,17 +165,17 @@ def test_file_template(self): }, ) self.assertEqual( - spans[2].attributes, {"jinja2.template_name": "base.html"}, + compile1.attributes, {"jinja2.template_name": "base.html"}, ) self.assertEqual( - spans[3].attributes, + load1.attributes, { "jinja2.template_name": "base.html", "jinja2.template_path": os.path.join(TMPL_DIR, "base.html"), }, ) self.assertEqual( - spans[4].attributes, {"jinja2.template_name": "template.html"}, + render.attributes, {"jinja2.template_name": "template.html"}, ) def test_uninstrumented(self):