Skip to content

Commit 3cdbfa7

Browse files
Add OTEL_AWS_APPLICATION_SIGNALS_ENABLED check for EMF dimensions
Add Service and Environment dimensions when Application Signals is enabled (OTEL_AWS_APPLICATION_SIGNALS_ENABLED=true), regardless of platform. Environment dimension can be customized via deployment.environment resource attribute, defaulting to "lambda:default" if not set. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent fabb1da commit 3cdbfa7

File tree

3 files changed

+112
-33
lines changed

3 files changed

+112
-33
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/_aws_resource_attribute_configurator.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
# SPDX-License-Identifier: Apache-2.0
33
from amazon.opentelemetry.distro._aws_span_processing_util import UNKNOWN_SERVICE
44
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
5+
from opentelemetry.semconv.resource import ResourceAttributes
56

67
# As per https://opentelemetry.io/docs/specs/semconv/resource/#service, if service name is not specified, SDK defaults
78
# the service name to unknown_service:<process name> or just unknown_service.
89
_OTEL_UNKNOWN_SERVICE_PREFIX: str = "unknown_service"
910

11+
# Default environment value when deployment.environment is not set
12+
_DEFAULT_ENVIRONMENT: str = "lambda:default"
13+
1014

1115
def get_service_attribute(resource: Resource) -> (str, bool):
1216
"""Service is always derived from SERVICE_NAME"""
@@ -17,3 +21,23 @@ def get_service_attribute(resource: Resource) -> (str, bool):
1721
return UNKNOWN_SERVICE, True
1822

1923
return service, False
24+
25+
26+
def get_environment_attribute(resource: Resource) -> str:
27+
"""
28+
Get environment from resource's deployment.environment attribute.
29+
30+
Args:
31+
resource: The OTel Resource to extract environment from
32+
33+
Returns:
34+
The deployment.environment value if set, otherwise "lambda:default"
35+
"""
36+
if resource is None:
37+
return _DEFAULT_ENVIRONMENT
38+
39+
environment = resource.attributes.get(ResourceAttributes.DEPLOYMENT_ENVIRONMENT)
40+
if environment:
41+
return environment
42+
43+
return _DEFAULT_ENVIRONMENT

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/exporter/aws/metrics/base_emf_exporter.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
import json
77
import logging
88
import math
9+
import os
910
import time
1011
from abc import ABC, abstractmethod
1112
from collections import defaultdict
1213
from typing import Any, Dict, List, Optional, Tuple
1314

14-
from amazon.opentelemetry.distro._aws_resource_attribute_configurator import get_service_attribute
15+
from amazon.opentelemetry.distro._aws_resource_attribute_configurator import (
16+
get_environment_attribute,
17+
get_service_attribute,
18+
)
1519
from opentelemetry.sdk.metrics import Counter
1620
from opentelemetry.sdk.metrics import Histogram as HistogramInstr
1721
from opentelemetry.sdk.metrics import ObservableCounter, ObservableGauge, ObservableUpDownCounter, UpDownCounter
@@ -33,9 +37,6 @@
3337

3438
logger = logging.getLogger(__name__)
3539

36-
# Constants for Lambda EMF dimensions
37-
LAMBDA_ENVIRONMENT_DEFAULT = "lambda:default"
38-
3940

4041
class MetricRecord:
4142
"""The metric data unified representation of all OTel metrics for OTel to CW EMF conversion."""
@@ -193,23 +194,32 @@ def _get_dimension_names(self, attributes: Attributes) -> List[str]:
193194

194195
def _add_lambda_dimensions(self, emf_log: Dict, dimension_names: List[str], resource: Resource) -> List[str]:
195196
"""
196-
Add Service and Environment dimensions for Lambda environments.
197+
Add Service and Environment dimensions when Application Signals is enabled.
197198
198199
Args:
199200
emf_log: The EMF log dictionary to add dimension values to
200201
dimension_names: The current list of dimension names
201202
resource: The OTel Resource to extract service name from
202203
203204
Returns:
204-
Updated list of dimension names with Service and Environment prepended (if Lambda)
205+
Updated list of dimension names with Service and Environment prepended
205206
"""
206-
if not self._is_lambda:
207+
# Only add dimensions when Application Signals is enabled
208+
app_signals_enabled = (
209+
os.environ.get(
210+
"OTEL_AWS_APPLICATION_SIGNALS_ENABLED",
211+
os.environ.get("OTEL_AWS_APP_SIGNALS_ENABLED", "false"),
212+
).lower()
213+
== "true"
214+
)
215+
if not app_signals_enabled:
207216
return dimension_names
208217

209218
service_name, _ = get_service_attribute(resource) if resource else ("UnknownService", True)
219+
environment = get_environment_attribute(resource)
210220
# Add dimension values to the EMF log root
211221
emf_log["Service"] = service_name
212-
emf_log["Environment"] = LAMBDA_ENVIRONMENT_DEFAULT
222+
emf_log["Environment"] = environment
213223
# Add Service and Environment to dimension names (at the beginning for consistency)
214224
return ["Service", "Environment"] + dimension_names
215225

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/exporter/aws/metrics/test_base_emf_exporter.py

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4+
import os
45
import unittest
5-
from unittest.mock import Mock
6+
from unittest.mock import Mock, patch
67

78
from amazon.opentelemetry.distro.exporter.aws.metrics.base_emf_exporter import BaseEmfExporter, MetricRecord
89
from opentelemetry.sdk.metrics.export import MetricExportResult
@@ -300,25 +301,25 @@ def test_is_lambda_initialization(self):
300301
exporter = ConcreteEmfExporter(is_lambda=True)
301302
self.assertTrue(exporter._is_lambda)
302303

303-
def test_create_emf_log_with_lambda_dimensions(self):
304-
"""Test EMF log creation includes Service and Environment dimensions in Lambda mode."""
305-
# Create Lambda exporter
306-
lambda_exporter = ConcreteEmfExporter(namespace="TestNamespace", is_lambda=True)
304+
@patch.dict(os.environ, {"OTEL_AWS_APPLICATION_SIGNALS_ENABLED": "true"})
305+
def test_create_emf_log_with_app_signals_dimensions(self):
306+
"""Test EMF log creation includes Service and Environment dimensions when app signals enabled."""
307+
exporter = ConcreteEmfExporter(namespace="TestNamespace")
307308

308309
# Create a simple metric record
309-
record = lambda_exporter._create_metric_record("test_metric", "Count", "Test")
310+
record = exporter._create_metric_record("test_metric", "Count", "Test")
310311
record.value = 50.0
311312
record.timestamp = 1234567890
312313
record.attributes = {"env": "test"}
313314

314315
records = [record]
315-
resource = Resource.create({"service.name": "my-lambda-service"})
316+
resource = Resource.create({"service.name": "my-service"})
316317

317-
result = lambda_exporter._create_emf_log(records, resource, 1234567890)
318+
result = exporter._create_emf_log(records, resource, 1234567890)
318319

319320
# Check that Service and Environment dimensions are added
320321
self.assertIn("Service", result)
321-
self.assertEqual(result["Service"], "my-lambda-service")
322+
self.assertEqual(result["Service"], "my-service")
322323
self.assertIn("Environment", result)
323324
self.assertEqual(result["Environment"], "lambda:default")
324325

@@ -331,21 +332,21 @@ def test_create_emf_log_with_lambda_dimensions(self):
331332
self.assertEqual(dimensions[0], "Service")
332333
self.assertEqual(dimensions[1], "Environment")
333334

334-
def test_create_emf_log_without_lambda_dimensions(self):
335-
"""Test EMF log creation does NOT include Service and Environment dimensions when not Lambda."""
336-
# Create non-Lambda exporter (default)
337-
non_lambda_exporter = ConcreteEmfExporter(namespace="TestNamespace", is_lambda=False)
335+
def test_create_emf_log_without_app_signals_dimensions(self):
336+
"""Test EMF log creation does NOT include Service and Environment dimensions when app signals disabled."""
337+
# App signals not enabled (default)
338+
exporter = ConcreteEmfExporter(namespace="TestNamespace")
338339

339340
# Create a simple metric record
340-
record = non_lambda_exporter._create_metric_record("test_metric", "Count", "Test")
341+
record = exporter._create_metric_record("test_metric", "Count", "Test")
341342
record.value = 50.0
342343
record.timestamp = 1234567890
343344
record.attributes = {"env": "test"}
344345

345346
records = [record]
346347
resource = Resource.create({"service.name": "my-service"})
347348

348-
result = non_lambda_exporter._create_emf_log(records, resource, 1234567890)
349+
result = exporter._create_emf_log(records, resource, 1234567890)
349350

350351
# Check that Service and Environment dimensions are NOT added
351352
self.assertNotIn("Service", result)
@@ -357,11 +358,12 @@ def test_create_emf_log_without_lambda_dimensions(self):
357358
self.assertNotIn("Service", dimensions)
358359
self.assertNotIn("Environment", dimensions)
359360

360-
def test_create_emf_log_lambda_with_unknown_service(self):
361-
"""Test EMF log creation uses UnknownService when service.name is not set in Lambda mode."""
362-
lambda_exporter = ConcreteEmfExporter(namespace="TestNamespace", is_lambda=True)
361+
@patch.dict(os.environ, {"OTEL_AWS_APPLICATION_SIGNALS_ENABLED": "true"})
362+
def test_create_emf_log_with_unknown_service(self):
363+
"""Test EMF log creation uses UnknownService when service.name is not set."""
364+
exporter = ConcreteEmfExporter(namespace="TestNamespace")
363365

364-
record = lambda_exporter._create_metric_record("test_metric", "Count", "Test")
366+
record = exporter._create_metric_record("test_metric", "Count", "Test")
365367
record.value = 50.0
366368
record.timestamp = 1234567890
367369
record.attributes = {}
@@ -370,29 +372,72 @@ def test_create_emf_log_lambda_with_unknown_service(self):
370372
# Resource with unknown_service prefix
371373
resource = Resource.create({"service.name": "unknown_service:python"})
372374

373-
result = lambda_exporter._create_emf_log(records, resource, 1234567890)
375+
result = exporter._create_emf_log(records, resource, 1234567890)
374376

375377
# Should fall back to UnknownService
376378
self.assertEqual(result["Service"], "UnknownService")
377379
self.assertEqual(result["Environment"], "lambda:default")
378380

379-
def test_create_emf_log_lambda_with_none_resource(self):
380-
"""Test EMF log creation handles None resource in Lambda mode."""
381-
lambda_exporter = ConcreteEmfExporter(namespace="TestNamespace", is_lambda=True)
381+
@patch.dict(os.environ, {"OTEL_AWS_APPLICATION_SIGNALS_ENABLED": "true"})
382+
def test_create_emf_log_with_none_resource(self):
383+
"""Test EMF log creation handles None resource gracefully."""
384+
exporter = ConcreteEmfExporter(namespace="TestNamespace")
382385

383-
record = lambda_exporter._create_metric_record("test_metric", "Count", "Test")
386+
record = exporter._create_metric_record("test_metric", "Count", "Test")
384387
record.value = 50.0
385388
record.timestamp = 1234567890
386389
record.attributes = {}
387390

388391
records = [record]
389392

390-
result = lambda_exporter._create_emf_log(records, None, 1234567890)
393+
result = exporter._create_emf_log(records, None, 1234567890)
391394

392395
# Should handle None resource gracefully
393396
self.assertEqual(result["Service"], "UnknownService")
394397
self.assertEqual(result["Environment"], "lambda:default")
395398

399+
@patch.dict(os.environ, {"OTEL_AWS_APP_SIGNALS_ENABLED": "true"})
400+
def test_create_emf_log_with_deprecated_app_signals_env_var(self):
401+
"""Test EMF log includes dimensions when using deprecated OTEL_AWS_APP_SIGNALS_ENABLED."""
402+
exporter = ConcreteEmfExporter(namespace="TestNamespace")
403+
404+
record = exporter._create_metric_record("test_metric", "Count", "Test")
405+
record.value = 50.0
406+
record.timestamp = 1234567890
407+
record.attributes = {}
408+
409+
records = [record]
410+
resource = Resource.create({"service.name": "my-service"})
411+
412+
result = exporter._create_emf_log(records, resource, 1234567890)
413+
414+
# Should add dimensions with deprecated env var
415+
self.assertIn("Service", result)
416+
self.assertEqual(result["Service"], "my-service")
417+
self.assertIn("Environment", result)
418+
self.assertEqual(result["Environment"], "lambda:default")
419+
420+
@patch.dict(os.environ, {"OTEL_AWS_APPLICATION_SIGNALS_ENABLED": "true"})
421+
def test_create_emf_log_with_custom_deployment_environment(self):
422+
"""Test EMF log uses deployment.environment from resource when set."""
423+
exporter = ConcreteEmfExporter(namespace="TestNamespace")
424+
425+
record = exporter._create_metric_record("test_metric", "Count", "Test")
426+
record.value = 50.0
427+
record.timestamp = 1234567890
428+
record.attributes = {}
429+
430+
records = [record]
431+
resource = Resource.create({"service.name": "my-service", "deployment.environment": "production"})
432+
433+
result = exporter._create_emf_log(records, resource, 1234567890)
434+
435+
# Should use custom deployment.environment
436+
self.assertIn("Service", result)
437+
self.assertEqual(result["Service"], "my-service")
438+
self.assertIn("Environment", result)
439+
self.assertEqual(result["Environment"], "production")
440+
396441

397442
if __name__ == "__main__":
398443
unittest.main()

0 commit comments

Comments
 (0)