Skip to content

Commit b57420e

Browse files
Renamed subaccount id attribute
1 parent 5057100 commit b57420e

3 files changed

Lines changed: 120 additions & 2 deletions

File tree

src/sap_cloud_sdk/core/telemetry/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def create_resource_attributes_from_env() -> dict:
135135
- service.instance.id (from HOSTNAME, defaults to "unknown")
136136
- deployment.environment.name (from APPFND_CONHOS_ENVIRONMENT, defaults to "unknown")
137137
- cloud.region (from APPFND_CONHOS_REGION, defaults to "unknown")
138-
- sap.cld.subaccount_id (from APPFND_CONHOS_SUBACCOUNTID, defaults to "unknown")
138+
- sap.cloud.provider.subaccount_id (from APPFND_CONHOS_SUBACCOUNTID, defaults to "unknown")
139139
- sap.cld.system_role (from APPFND_CONHOS_SYSTEM_ROLE, defaults to "ZAFT")
140140
- sap.cloud_sdk.name (constant: "SAP Cloud SDK for Python")
141141
- sap.cloud_sdk.language (constant: "python")

src/sap_cloud_sdk/core/telemetry/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
ATTR_USER_ID = "user.id"
1818

1919
# Attribute keys - SAP standard
20-
ATTR_SAP_SUBACCOUNT_ID = "sap.cld.subaccount_id"
20+
ATTR_SAP_SUBACCOUNT_ID = "sap.cloud.provider.subaccount_id"
2121
ATTR_SAP_TENANT_ID = "sap.tenancy.tenant_id"
2222
ATTR_SAP_SYSTEM_ROLE = "sap.cld.system_role"
2323
ATTR_SAP_TRIGGER_TYPE = "sap.ai.agent.trigger.type"

tests/core/unit/auditlog_ng/unit/test_client.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
import pytest
1010

11+
from opentelemetry import context as context_api, trace
12+
from opentelemetry.sdk._logs import LoggerProvider
13+
from opentelemetry.sdk._logs.export import InMemoryLogRecordExporter, SimpleLogRecordProcessor
14+
from opentelemetry.sdk.resources import Resource
15+
from opentelemetry.trace import NonRecordingSpan, SpanContext, TraceFlags
16+
1117
from sap_cloud_sdk.core.auditlog_ng.client import AuditClient, _fill_common_from_auth_context
1218
from sap_cloud_sdk.core.auditlog_ng.config import AuditLogNGConfig, SCHEMA_URL
1319
from sap_cloud_sdk.core.auditlog_ng.exceptions import ValidationError
@@ -392,3 +398,115 @@ def test_does_not_overwrite_explicit_timestamp(self):
392398
def test_event_without_common_field_is_skipped(self):
393399
event = MagicMock(spec=[]) # no 'common' attribute
394400
_fill_common_from_auth_context(event) # must not raise
401+
402+
403+
# ---------------------------------------------------------------------------
404+
# Helpers shared by TestTraceSpanCorrelation
405+
# ---------------------------------------------------------------------------
406+
407+
_TRACE_ID = 0xABCD1234ABCD1234ABCD1234ABCD1234
408+
_SPAN_ID = 0x1234ABCD1234ABCD
409+
410+
411+
def _make_real_client_with_exporter() -> tuple[AuditClient, InMemoryLogRecordExporter]:
412+
"""Build an AuditClient backed by an InMemoryLogRecordExporter (no mocks)."""
413+
exporter = InMemoryLogRecordExporter()
414+
provider = LoggerProvider(
415+
resource=Resource.create(
416+
{
417+
"service.name": "test",
418+
"sap.ucl.deployment_id": "dep-1",
419+
"sap.ucl.system_namespace": "ns-1",
420+
}
421+
)
422+
)
423+
provider.add_log_record_processor(SimpleLogRecordProcessor(exporter))
424+
425+
config = _make_config()
426+
# Bypass the real gRPC exporter and inject our in-memory provider directly.
427+
with patch("sap_cloud_sdk.core.auditlog_ng.client.GRPCLogExporter"), \
428+
patch("sap_cloud_sdk.core.auditlog_ng.client.LoggerProvider", return_value=provider):
429+
client = AuditClient(config)
430+
431+
return client, exporter
432+
433+
434+
def _make_valid_event(tenant_id: str = "tenant-123") -> pb.DataAccess:
435+
event = pb.DataAccess()
436+
event.common.tenant_id = tenant_id
437+
event.common.user_initiator_id = "user@example.com"
438+
event.common.timestamp.FromDatetime(datetime.now(timezone.utc))
439+
event.channel_type = "API"
440+
event.channel_id = "ch-001"
441+
event.object_type = "Resource"
442+
event.object_id = "obj-001"
443+
event.attribute = "attr-001"
444+
return event
445+
446+
447+
class TestTraceSpanCorrelation:
448+
"""Verify that trace_id and span_id from an active OTel span are
449+
automatically captured on emitted log records."""
450+
451+
def test_active_span_correlates_trace_and_span_id(self):
452+
client, exporter = _make_real_client_with_exporter()
453+
454+
span_ctx = SpanContext(
455+
trace_id=_TRACE_ID,
456+
span_id=_SPAN_ID,
457+
is_remote=False,
458+
trace_flags=TraceFlags(TraceFlags.SAMPLED),
459+
)
460+
span = NonRecordingSpan(span_ctx)
461+
ctx = trace.set_span_in_context(span)
462+
token = context_api.attach(ctx)
463+
try:
464+
client.send(_make_valid_event())
465+
finally:
466+
context_api.detach(token)
467+
468+
records = exporter.get_finished_logs()
469+
assert len(records) == 1
470+
log = records[0].log_record
471+
assert log.trace_id == _TRACE_ID
472+
assert log.span_id == _SPAN_ID
473+
assert log.trace_flags == TraceFlags(TraceFlags.SAMPLED)
474+
475+
def test_no_active_span_yields_zero_ids(self):
476+
client, exporter = _make_real_client_with_exporter()
477+
478+
# Ensure no span is active (detach any inherited context).
479+
empty_ctx = context_api.Context()
480+
token = context_api.attach(empty_ctx)
481+
try:
482+
client.send(_make_valid_event())
483+
finally:
484+
context_api.detach(token)
485+
486+
records = exporter.get_finished_logs()
487+
assert len(records) == 1
488+
log = records[0].log_record
489+
assert log.trace_id == 0
490+
assert log.span_id == 0
491+
492+
def test_log_record_carries_audit_attributes_alongside_trace_context(self):
493+
client, exporter = _make_real_client_with_exporter()
494+
495+
span_ctx = SpanContext(
496+
trace_id=_TRACE_ID,
497+
span_id=_SPAN_ID,
498+
is_remote=False,
499+
trace_flags=TraceFlags(TraceFlags.SAMPLED),
500+
)
501+
token = context_api.attach(trace.set_span_in_context(NonRecordingSpan(span_ctx)))
502+
try:
503+
event_id = client.send(_make_valid_event())
504+
finally:
505+
context_api.detach(token)
506+
507+
log = exporter.get_finished_logs()[0].log_record
508+
assert log.trace_id == _TRACE_ID
509+
assert log.span_id == _SPAN_ID
510+
assert log.attributes["cloudevents.event_id"] == event_id
511+
assert log.attributes["sap.tenancy.tenant_id"] == "tenant-123"
512+
assert log.attributes["sap.auditlogging.mime_type"] == "application/protobuf"

0 commit comments

Comments
 (0)