Skip to content

Commit

Permalink
feat: Add ability to create manual spans for datadog.
Browse files Browse the repository at this point in the history
  • Loading branch information
dianakhuang committed Jul 30, 2024
1 parent 0833182 commit aa176dd
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 27 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Change Log

.. There should always be an "Unreleased" section for changes pending release.
[5.15.0] - 2024-07-29
---------------------
Added
~~~~~
* Added Datadog implementation of ``function_trace`` and allowed implementation to be configurable.

[5.14.2] - 2024-05-31
---------------------
Fixed
Expand Down
2 changes: 1 addition & 1 deletion edx_django_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
EdX utilities for Django Application development..
"""

__version__ = "5.14.2"
__version__ = "5.15.0"

default_app_config = (
"edx_django_utils.apps.EdxDjangoUtilsConfig"
Expand Down
2 changes: 1 addition & 1 deletion edx_django_utils/monitoring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
MonitoringMemoryMiddleware
)
from .internal.transactions import (
function_trace,
get_current_transaction,
ignore_transaction,
set_monitoring_transaction_name
)
from .internal.utils import (
accumulate,
background_task,
function_trace,
increment,
record_exception,
set_custom_attribute,
Expand Down
22 changes: 22 additions & 0 deletions edx_django_utils/monitoring/internal/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ def record_exception(self):
Record the exception that is currently being handled.
"""

@abstractmethod
def trace(self, name):
"""
Create a manual trace with the given name. The underlying implementation is generally
assumed to be a contextmanager, and the level above calls it as such.
"""


class NewRelicBackend(TelemetryBackend):
"""
Expand Down Expand Up @@ -77,6 +84,13 @@ def record_exception(self):
# https://docs.newrelic.com/docs/apm/agents/python-agent/python-agent-api/recordexception-python-agent-api/
newrelic.agent.record_exception()

def trace(self, name):
if newrelic.version_info[0] >= 5:
return newrelic.agent.FunctionTrace(name)

Check warning on line 89 in edx_django_utils/monitoring/internal/backends.py

View check run for this annotation

Codecov / codecov/patch

edx_django_utils/monitoring/internal/backends.py#L89

Added line #L89 was not covered by tests
else:
nr_transaction = newrelic.agent.current_transaction()
return newrelic.agent.FunctionTrace(nr_transaction, name)

Check warning on line 92 in edx_django_utils/monitoring/internal/backends.py

View check run for this annotation

Codecov / codecov/patch

edx_django_utils/monitoring/internal/backends.py#L91-L92

Added lines #L91 - L92 were not covered by tests


class OpenTelemetryBackend(TelemetryBackend):
"""
Expand All @@ -89,6 +103,7 @@ def __init__(self):
# If import fails, the backend won't be used.
from opentelemetry import trace
self.otel_trace = trace
self.otel_tracer = trace.get_tracer(__name__)

def set_attribute(self, key, value):
# Sets the value on the current span, not necessarily the root
Expand All @@ -98,6 +113,10 @@ def set_attribute(self, key, value):
def record_exception(self):
self.otel_trace.get_current_span().record_exception(sys.exc_info()[1])

def trace(self, name):
# Currently, this is not implemented.
pass

Check warning on line 118 in edx_django_utils/monitoring/internal/backends.py

View check run for this annotation

Codecov / codecov/patch

edx_django_utils/monitoring/internal/backends.py#L118

Added line #L118 was not covered by tests


class DatadogBackend(TelemetryBackend):
"""
Expand All @@ -119,6 +138,9 @@ def record_exception(self):
if span := self.dd_tracer.current_span():
span.set_traceback()

def trace(self, name):
return self.dd_tracer.trace(name)

Check warning on line 142 in edx_django_utils/monitoring/internal/backends.py

View check run for this annotation

Codecov / codecov/patch

edx_django_utils/monitoring/internal/backends.py#L142

Added line #L142 was not covered by tests


# We're using an lru_cache instead of assigning the result to a variable on
# module load. With the default settings (pointing to a TelemetryBackend
Expand Down
25 changes: 0 additions & 25 deletions edx_django_utils/monitoring/internal/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
Please remember to expose any new methods in the `__init__.py` file.
"""

from contextlib import contextmanager

try:
import newrelic.agent
except ImportError:
Expand Down Expand Up @@ -41,28 +38,6 @@ def ignore_transaction():
newrelic.agent.ignore_transaction()


@contextmanager
def function_trace(function_name):
"""
Wraps a chunk of code that we want to appear as a separate, explicit,
segment in our monitoring tools.
"""
# Not covering this because if we mock it, we're not really testing anything
# anyway. If something did break, it should show up in tests for apps that
# use this code with newrelic enabled, on whatever version of newrelic they
# run.
if newrelic: # pragma: no cover
if newrelic.version_info[0] >= 5:
with newrelic.agent.FunctionTrace(function_name):
yield
else:
nr_transaction = newrelic.agent.current_transaction()
with newrelic.agent.FunctionTrace(nr_transaction, function_name):
yield
else:
yield


class MonitoringTransaction():
"""
Represents a monitoring transaction (likely the current transaction).
Expand Down
18 changes: 18 additions & 0 deletions edx_django_utils/monitoring/internal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
At this time, the custom monitoring will only be reported to New Relic.
"""
from contextlib import ExitStack, contextmanager

from .backends import configured_backends
from .middleware import CachedCustomMonitoringMiddleware

Expand Down Expand Up @@ -92,6 +94,22 @@ def record_exception():
backend.record_exception()


@contextmanager
def function_trace(function_name):
"""
Wraps a chunk of code that we want to appear as a separate, explicit,
segment in our monitoring tools.
"""
# Not covering this because if we mock it, we're not really testing anything
# anyway. If something did break, it should show up in tests for apps that
# use this code with whatever uses it.
# ExitStack handles the underlying context managers.
with ExitStack() as stack:
for backend in configured_backends():
stack.enter_context(backend.trace(function_name))
yield

Check warning on line 110 in edx_django_utils/monitoring/internal/utils.py

View check run for this annotation

Codecov / codecov/patch

edx_django_utils/monitoring/internal/utils.py#L109-L110

Added lines #L109 - L110 were not covered by tests


def background_task(*args, **kwargs):
"""
Handles monitoring for background tasks that are not passed in through the web server like
Expand Down

0 comments on commit aa176dd

Please sign in to comment.