Skip to content

Commit

Permalink
TEST: require faas semconv opt-in
Browse files Browse the repository at this point in the history
  • Loading branch information
tammy-baylis-swi committed Mar 20, 2024
1 parent 078002f commit 74af9ce
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ def custom_event_context_extractor(lambda_event):
from wrapt import wrap_function_wrapper

from opentelemetry.context.context import Context
from opentelemetry.instrumentation._semconv import (
_get_schema_url,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilityMode,
_OpenTelemetryStabilitySignalType,
_set_cloud_resource_id,
_set_faas_invocation_id,
)
from opentelemetry.instrumentation.aws_lambda.package import _instruments
from opentelemetry.instrumentation.aws_lambda.version import __version__
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
Expand Down Expand Up @@ -287,12 +295,15 @@ def _instrument(
tracer_provider: TracerProvider = None,
disable_aws_context_propagation: bool = False,
meter_provider: MeterProvider = None,
sem_conv_opt_in_mode: _OpenTelemetryStabilityMode = _OpenTelemetryStabilityMode.DEFAULT,
):
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
call_wrapped, instance, args, kwargs
):
schema_url = _get_schema_url(sem_conv_opt_in_mode)

orig_handler_name = ".".join(
[wrapped_module_name, wrapped_function_name]
)
Expand Down Expand Up @@ -328,7 +339,7 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
__name__,
__version__,
tracer_provider,
schema_url="https://opentelemetry.io/schemas/1.11.0",
schema_url=schema_url,
)

with tracer.start_as_current_span(
Expand All @@ -339,31 +350,20 @@ def _instrumented_lambda_handler_call( # noqa pylint: disable=too-many-branches
if span.is_recording():
lambda_context = args[1]
# NOTE: The specs mention an exception here, allowing the
# `SpanAttributes.CLOUD_RESOURCE_ID` attribute to be set as a span
# `ResourceAttributes.CLOUD_RESOURCE_ID` attribute to be set as a span
# attribute instead of a resource attribute.
#
# See more:
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#resource-detector
span.set_attribute(
SpanAttributes.CLOUD_RESOURCE_ID,
_set_cloud_resource_id(
span,
lambda_context.invoked_function_arn,
sem_conv_opt_in_mode,
)
span.set_attribute(
SpanAttributes.FAAS_INVOCATION_ID,
_set_faas_invocation_id(
span,
lambda_context.aws_request_id,
)

# NOTE: `cloud.account.id` can be parsed from the ARN as the fifth item when splitting on `:`
#
# See more:
# https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#all-triggers
account_id = lambda_context.invoked_function_arn.split(":")[4]

# TODO: Update key with semconvgen 1.23.0
# https://github.com/open-telemetry/semantic-conventions/issues/551
span.set_attribute(
"cloud.account.id",
account_id,
sem_conv_opt_in_mode,
)

exception = None
Expand Down Expand Up @@ -454,6 +454,9 @@ def _instrument(self, **kwargs):
will try to read the context from the `_X_AMZN_TRACE_ID` environment
variable set by Lambda, set this to `True` to disable this behavior.
"""
semconv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
_OpenTelemetryStabilitySignalType.FAAS,
)
lambda_handler = os.environ.get(ORIG_HANDLER, os.environ.get(_HANDLER))
# pylint: disable=attribute-defined-outside-init
(
Expand Down Expand Up @@ -494,6 +497,7 @@ def _instrument(self, **kwargs):
tracer_provider=kwargs.get("tracer_provider"),
disable_aws_context_propagation=disable_aws_context_propagation,
meter_provider=kwargs.get("meter_provider"),
sem_conv_opt_in_mode=semconv_opt_in_mode,
)

def _uninstrument(self, **kwargs):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
)

from opentelemetry.environment_variables import OTEL_PROPAGATORS
from opentelemetry.instrumentation._semconv import (
_OTEL_SEMCONV_STABILITY_OPT_IN_KEY,
_OpenTelemetrySemanticConventionStability,
)
from opentelemetry.instrumentation.aws_lambda import (
_HANDLER,
_X_AMZN_TRACE_ID,
Expand All @@ -37,6 +41,7 @@
TRACE_ID_FIRST_PART_LENGTH,
TRACE_ID_VERSION,
)
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.test_base import TestBase
from opentelemetry.trace import NoOpTracerProvider, SpanKind, StatusCode
Expand All @@ -53,7 +58,7 @@ def __init__(self, aws_request_id, invoked_function_arn):

MOCK_LAMBDA_CONTEXT = MockLambdaContext(
aws_request_id="mock_aws_request_id",
invoked_function_arn="arn:aws:lambda:us-east-1:123456:function:myfunction:myalias",
invoked_function_arn="arn://mock-lambda-function-arn",
)

MOCK_XRAY_TRACE_ID = 0x5FB7331105E8BB83207FA31D4D9CDB4C
Expand Down Expand Up @@ -106,6 +111,7 @@ def setUp(self):
"os.environ",
{_HANDLER: "tests.mocks.lambda_function.handler"},
)
_OpenTelemetrySemanticConventionStability._initialized = False
self.common_env_patch.start()

# NOTE: Whether AwsLambdaInstrumentor().instrument() is run is decided
Expand Down Expand Up @@ -144,9 +150,8 @@ def test_active_tracing(self):
self.assertSpanHasAttributes(
span,
{
SpanAttributes.CLOUD_RESOURCE_ID: MOCK_LAMBDA_CONTEXT.invoked_function_arn,
SpanAttributes.FAAS_INVOCATION_ID: MOCK_LAMBDA_CONTEXT.aws_request_id,
"cloud.account.id": MOCK_LAMBDA_CONTEXT.invoked_function_arn.split(":")[4],
ResourceAttributes.FAAS_ID: MOCK_LAMBDA_CONTEXT.invoked_function_arn,
SpanAttributes.FAAS_EXECUTION: MOCK_LAMBDA_CONTEXT.aws_request_id,
},
)

Expand All @@ -159,6 +164,74 @@ def test_active_tracing(self):

test_env_patch.stop()

def test_active_tracing_semconv_opt_in(self):
test_env_patch = mock.patch.dict(
"os.environ",
{
**os.environ,
# Using Active tracing
_X_AMZN_TRACE_ID: MOCK_XRAY_TRACE_CONTEXT_SAMPLED,
# Opt into new semconv
_OTEL_SEMCONV_STABILITY_OPT_IN_KEY: "faas",
},
)
test_env_patch.start()

AwsLambdaInstrumentor().instrument()

mock_execute_lambda()

spans = self.memory_exporter.get_finished_spans()

assert spans

self.assertEqual(len(spans), 1)
span = spans[0]
self.assertSpanHasAttributes(
span,
{
ResourceAttributes.CLOUD_RESOURCE_ID: MOCK_LAMBDA_CONTEXT.invoked_function_arn,
SpanAttributes.FAAS_INVOCATION_ID: MOCK_LAMBDA_CONTEXT.aws_request_id,
},
)

test_env_patch.stop()

def test_active_tracing_semconv_opt_in_dup(self):
test_env_patch = mock.patch.dict(
"os.environ",
{
**os.environ,
# Using Active tracing
_X_AMZN_TRACE_ID: MOCK_XRAY_TRACE_CONTEXT_SAMPLED,
# Opt into new semconv
_OTEL_SEMCONV_STABILITY_OPT_IN_KEY: "faas/dup",
},
)
test_env_patch.start()

AwsLambdaInstrumentor().instrument()

mock_execute_lambda()

spans = self.memory_exporter.get_finished_spans()

assert spans

self.assertEqual(len(spans), 1)
span = spans[0]
self.assertSpanHasAttributes(
span,
{
ResourceAttributes.FAAS_ID: MOCK_LAMBDA_CONTEXT.invoked_function_arn,
ResourceAttributes.CLOUD_RESOURCE_ID: MOCK_LAMBDA_CONTEXT.invoked_function_arn,
SpanAttributes.FAAS_EXECUTION: MOCK_LAMBDA_CONTEXT.aws_request_id,
SpanAttributes.FAAS_INVOCATION_ID: MOCK_LAMBDA_CONTEXT.aws_request_id,
},
)

test_env_patch.stop()

def test_parent_context_from_lambda_event(self):
@dataclass
class TestCase:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import threading
from enum import Enum

from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.semconv.trace import SpanAttributes

# TODO: will come through semconv package once updated
Expand Down Expand Up @@ -144,19 +145,52 @@ def _set_http_network_protocol_version(result, version, sem_conv_opt_in_mode):
)


def _set_cloud_resource_id(span, cr_id, sem_conv_opt_in_mode):
if _report_old_faas(sem_conv_opt_in_mode):
span.set_attribute(
ResourceAttributes.FAAS_ID,
cr_id,
)
if _report_new_faas(sem_conv_opt_in_mode):
span.set_attribute(
ResourceAttributes.CLOUD_RESOURCE_ID,
cr_id,
)


def _set_faas_invocation_id(span, fi_id, sem_conv_opt_in_mode):
if _report_old_faas(sem_conv_opt_in_mode):
span.set_attribute(
SpanAttributes.FAAS_EXECUTION,
fi_id,
)
if _report_new_faas(sem_conv_opt_in_mode):
span.set_attribute(
SpanAttributes.FAAS_INVOCATION_ID,
fi_id,
)


_OTEL_SEMCONV_STABILITY_OPT_IN_KEY = "OTEL_SEMCONV_STABILITY_OPT_IN"


class _OpenTelemetryStabilitySignalType:
HTTP = "http"
FAAS = "faas"


class _OpenTelemetryStabilityMode(Enum):
# http - emit the new, stable HTTP and networking conventions ONLY
HTTP = "http"
# http/dup - emit both the old and the stable HTTP and networking conventions
HTTP_DUP = "http/dup"
# default - continue emitting old experimental HTTP and networking conventions

# faas - emit the new, stable FAAS aconventions ONLY
FAAS = "faas"
# faas/dup - emit both the old and the stable FAAS conventions
FAAS_DUP = "faas/dup"

# default - continue emitting all old experimental conventions
DEFAULT = "default"


Expand All @@ -168,6 +202,14 @@ def _report_old(mode):
return mode.name != _OpenTelemetryStabilityMode.HTTP.name


def _report_new_faas(mode):
return mode.name != _OpenTelemetryStabilityMode.DEFAULT.name


def _report_old_faas(mode):
return mode.name != _OpenTelemetryStabilityMode.FAAS.name


class _OpenTelemetrySemanticConventionStability:
_initialized = False
_lock = threading.Lock()
Expand All @@ -178,12 +220,13 @@ def _initialize(cls):
with _OpenTelemetrySemanticConventionStability._lock:
if not _OpenTelemetrySemanticConventionStability._initialized:
# Users can pass in comma delimited string for opt-in options
# Only values for http stability are supported for now
# Only values for http, faas stability are supported for now
opt_in = os.environ.get(_OTEL_SEMCONV_STABILITY_OPT_IN_KEY, "")
opt_in_list = []
if opt_in:
opt_in_list = [s.strip() for s in opt_in.split(",")]
http_opt_in = _OpenTelemetryStabilityMode.DEFAULT
faas_opt_in = _OpenTelemetryStabilityMode.DEFAULT
if opt_in_list:
# Process http opt-in
# http/dup takes priority over http
Expand All @@ -194,9 +237,23 @@ def _initialize(cls):
http_opt_in = _OpenTelemetryStabilityMode.HTTP_DUP
elif _OpenTelemetryStabilityMode.HTTP.value in opt_in_list:
http_opt_in = _OpenTelemetryStabilityMode.HTTP

# Process faas opt-in
# faas/dup takes priority over faas
if (
_OpenTelemetryStabilityMode.FAAS_DUP.value
in opt_in_list
):
faas_opt_in = _OpenTelemetryStabilityMode.FAAS_DUP
elif _OpenTelemetryStabilityMode.FAAS.value in opt_in_list:
faas_opt_in = _OpenTelemetryStabilityMode.FAAS

_OpenTelemetrySemanticConventionStability._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
_OpenTelemetryStabilitySignalType.HTTP
] = http_opt_in
_OpenTelemetrySemanticConventionStability._OTEL_SEMCONV_STABILITY_SIGNAL_MAPPING[
_OpenTelemetryStabilitySignalType.FAAS
] = faas_opt_in
_OpenTelemetrySemanticConventionStability._initialized = True

@classmethod
Expand Down

0 comments on commit 74af9ce

Please sign in to comment.