Skip to content

Commit d026893

Browse files
authored
Merge branch 'main' into ruff
2 parents c500ea7 + a36587c commit d026893

File tree

8 files changed

+273
-17
lines changed

8 files changed

+273
-17
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
([#4154](https://github.com/open-telemetry/opentelemetry-python/pull/4154))
1414
- sdk: Add support for log formatting
1515
([#4137](https://github.com/open-telemetry/opentelemetry-python/pull/4166))
16+
- sdk: Add Host resource detector
17+
([#4182](https://github.com/open-telemetry/opentelemetry-python/pull/4182))
1618
- sdk: Implementation of exemplars
1719
([#4094](https://github.com/open-telemetry/opentelemetry-python/pull/4094))
1820
- Implement events sdk
@@ -23,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2325
([#4206](https://github.com/open-telemetry/opentelemetry-python/pull/4206))
2426
- Update environment variable descriptions to match signal
2527
([#4222](https://github.com/open-telemetry/opentelemetry-python/pull/4222))
28+
- Record logger name as the instrumentation scope name
29+
([#4208](https://github.com/open-telemetry/opentelemetry-python/pull/4208))
2630

2731
## Version 1.27.0/0.48b0 (2024-08-28)
2832

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import logging
2+
3+
import pytest
4+
5+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
6+
from opentelemetry.sdk._logs.export import (
7+
InMemoryLogExporter,
8+
SimpleLogRecordProcessor,
9+
)
10+
11+
12+
def _set_up_logging_handler(level):
13+
logger_provider = LoggerProvider()
14+
exporter = InMemoryLogExporter()
15+
processor = SimpleLogRecordProcessor(exporter=exporter)
16+
logger_provider.add_log_record_processor(processor)
17+
handler = LoggingHandler(level=level, logger_provider=logger_provider)
18+
return handler
19+
20+
21+
def _create_logger(handler, name):
22+
logger = logging.getLogger(name)
23+
logger.addHandler(handler)
24+
return logger
25+
26+
27+
@pytest.mark.parametrize("num_loggers", [1, 10, 100, 1000])
28+
def test_simple_get_logger_different_names(benchmark, num_loggers):
29+
handler = _set_up_logging_handler(level=logging.DEBUG)
30+
loggers = [
31+
_create_logger(handler, str(f"logger_{i}")) for i in range(num_loggers)
32+
]
33+
34+
def benchmark_get_logger():
35+
for index in range(1000):
36+
loggers[index % num_loggers].warning("test message")
37+
38+
benchmark(benchmark_get_logger)

opentelemetry-sdk/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ console = "opentelemetry.sdk.trace.export:ConsoleSpanExporter"
6868
otel = "opentelemetry.sdk.resources:OTELResourceDetector"
6969
process = "opentelemetry.sdk.resources:ProcessResourceDetector"
7070
os = "opentelemetry.sdk.resources:OsResourceDetector"
71+
host = "opentelemetry.sdk.resources:_HostResourceDetector"
7172

7273
[project.urls]
7374
Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-sdk"

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import traceback
2222
import warnings
2323
from os import environ
24+
from threading import Lock
2425
from time import time_ns
2526
from typing import Any, Callable, Optional, Tuple, Union # noqa
2627

@@ -470,9 +471,6 @@ def __init__(
470471
) -> None:
471472
super().__init__(level=level)
472473
self._logger_provider = logger_provider or get_logger_provider()
473-
self._logger = get_logger(
474-
__name__, logger_provider=self._logger_provider
475-
)
476474

477475
@staticmethod
478476
def _get_attributes(record: logging.LogRecord) -> Attributes:
@@ -557,6 +555,7 @@ def _translate(self, record: logging.LogRecord) -> LogRecord:
557555
"WARN" if record.levelname == "WARNING" else record.levelname
558556
)
559557

558+
logger = get_logger(record.name, logger_provider=self._logger_provider)
560559
return LogRecord(
561560
timestamp=timestamp,
562561
observed_timestamp=observered_timestamp,
@@ -566,7 +565,7 @@ def _translate(self, record: logging.LogRecord) -> LogRecord:
566565
severity_text=level_name,
567566
severity_number=severity_number,
568567
body=body,
569-
resource=self._logger.resource,
568+
resource=logger.resource,
570569
attributes=attributes,
571570
)
572571

@@ -576,14 +575,17 @@ def emit(self, record: logging.LogRecord) -> None:
576575
577576
The record is translated to OTel format, and then sent across the pipeline.
578577
"""
579-
if not isinstance(self._logger, NoOpLogger):
580-
self._logger.emit(self._translate(record))
578+
logger = get_logger(record.name, logger_provider=self._logger_provider)
579+
if not isinstance(logger, NoOpLogger):
580+
logger.emit(self._translate(record))
581581

582582
def flush(self) -> None:
583583
"""
584-
Flushes the logging output. Skip flushing if logger is NoOp.
584+
Flushes the logging output. Skip flushing if logging_provider has no force_flush method.
585585
"""
586-
if not isinstance(self._logger, NoOpLogger):
586+
if hasattr(self._logger_provider, "force_flush") and callable(
587+
self._logger_provider.force_flush
588+
):
587589
self._logger_provider.force_flush()
588590

589591

@@ -641,26 +643,20 @@ def __init__(
641643
self._at_exit_handler = None
642644
if shutdown_on_exit:
643645
self._at_exit_handler = atexit.register(self.shutdown)
646+
self._logger_cache = {}
647+
self._logger_cache_lock = Lock()
644648

645649
@property
646650
def resource(self):
647651
return self._resource
648652

649-
def get_logger(
653+
def _get_logger_no_cache(
650654
self,
651655
name: str,
652656
version: Optional[str] = None,
653657
schema_url: Optional[str] = None,
654658
attributes: Optional[Attributes] = None,
655659
) -> Logger:
656-
if self._disabled:
657-
_logger.warning("SDK is disabled.")
658-
return NoOpLogger(
659-
name,
660-
version=version,
661-
schema_url=schema_url,
662-
attributes=attributes,
663-
)
664660
return Logger(
665661
self._resource,
666662
self._multi_log_record_processor,
@@ -672,6 +668,41 @@ def get_logger(
672668
),
673669
)
674670

671+
def _get_logger_cached(
672+
self,
673+
name: str,
674+
version: Optional[str] = None,
675+
schema_url: Optional[str] = None,
676+
) -> Logger:
677+
with self._logger_cache_lock:
678+
key = (name, version, schema_url)
679+
if key in self._logger_cache:
680+
return self._logger_cache[key]
681+
682+
self._logger_cache[key] = self._get_logger_no_cache(
683+
name, version, schema_url
684+
)
685+
return self._logger_cache[key]
686+
687+
def get_logger(
688+
self,
689+
name: str,
690+
version: Optional[str] = None,
691+
schema_url: Optional[str] = None,
692+
attributes: Optional[Attributes] = None,
693+
) -> Logger:
694+
if self._disabled:
695+
_logger.warning("SDK is disabled.")
696+
return NoOpLogger(
697+
name,
698+
version=version,
699+
schema_url=schema_url,
700+
attributes=attributes,
701+
)
702+
if attributes is None:
703+
return self._get_logger_cached(name, version, schema_url)
704+
return self._get_logger_no_cache(name, version, schema_url, attributes)
705+
675706
def add_log_record_processor(
676707
self, log_record_processor: LogRecordProcessor
677708
):

opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import logging
6161
import os
6262
import platform
63+
import socket
6364
import sys
6465
import typing
6566
from json import dumps
@@ -105,6 +106,7 @@
105106
FAAS_VERSION = ResourceAttributes.FAAS_VERSION
106107
FAAS_INSTANCE = ResourceAttributes.FAAS_INSTANCE
107108
HOST_NAME = ResourceAttributes.HOST_NAME
109+
HOST_ARCH = ResourceAttributes.HOST_ARCH
108110
HOST_TYPE = ResourceAttributes.HOST_TYPE
109111
HOST_IMAGE_NAME = ResourceAttributes.HOST_IMAGE_NAME
110112
HOST_IMAGE_ID = ResourceAttributes.HOST_IMAGE_ID
@@ -470,6 +472,20 @@ def detect(self) -> "Resource":
470472
)
471473

472474

475+
class _HostResourceDetector(ResourceDetector):
476+
"""
477+
The HostResourceDetector detects the hostname and architecture attributes.
478+
"""
479+
480+
def detect(self) -> "Resource":
481+
return Resource(
482+
{
483+
HOST_NAME: socket.gethostname(),
484+
HOST_ARCH: platform.machine(),
485+
}
486+
)
487+
488+
473489
def get_aggregated_resources(
474490
detectors: typing.List["ResourceDetector"],
475491
initial_resource: typing.Optional[Resource] = None,

opentelemetry-sdk/tests/logs/test_export.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ def test_simple_log_record_processor_default_level(self):
7171
self.assertEqual(
7272
warning_log_record.severity_number, SeverityNumber.WARN
7373
)
74+
self.assertEqual(
75+
finished_logs[0].instrumentation_scope.name, "default_level"
76+
)
7477

7578
def test_simple_log_record_processor_custom_level(self):
7679
exporter = InMemoryLogExporter()
@@ -104,6 +107,12 @@ def test_simple_log_record_processor_custom_level(self):
104107
self.assertEqual(
105108
fatal_log_record.severity_number, SeverityNumber.FATAL
106109
)
110+
self.assertEqual(
111+
finished_logs[0].instrumentation_scope.name, "custom_level"
112+
)
113+
self.assertEqual(
114+
finished_logs[1].instrumentation_scope.name, "custom_level"
115+
)
107116

108117
def test_simple_log_record_processor_trace_correlation(self):
109118
exporter = InMemoryLogExporter()
@@ -129,6 +138,9 @@ def test_simple_log_record_processor_trace_correlation(self):
129138
self.assertEqual(
130139
log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags
131140
)
141+
self.assertEqual(
142+
finished_logs[0].instrumentation_scope.name, "trace_correlation"
143+
)
132144
exporter.clear()
133145

134146
tracer = trace.TracerProvider().get_tracer(__name__)
@@ -140,6 +152,10 @@ def test_simple_log_record_processor_trace_correlation(self):
140152
self.assertEqual(log_record.body, "Critical message within span")
141153
self.assertEqual(log_record.severity_text, "CRITICAL")
142154
self.assertEqual(log_record.severity_number, SeverityNumber.FATAL)
155+
self.assertEqual(
156+
finished_logs[0].instrumentation_scope.name,
157+
"trace_correlation",
158+
)
143159
span_context = span.get_span_context()
144160
self.assertEqual(log_record.trace_id, span_context.trace_id)
145161
self.assertEqual(log_record.span_id, span_context.span_id)
@@ -166,6 +182,9 @@ def test_simple_log_record_processor_shutdown(self):
166182
self.assertEqual(
167183
warning_log_record.severity_number, SeverityNumber.WARN
168184
)
185+
self.assertEqual(
186+
finished_logs[0].instrumentation_scope.name, "shutdown"
187+
)
169188
exporter.clear()
170189
logger_provider.shutdown()
171190
with self.assertLogs(level=logging.WARNING):
@@ -206,6 +225,10 @@ def test_simple_log_record_processor_different_msg_types(self):
206225
for item in finished_logs
207226
]
208227
self.assertEqual(expected, emitted)
228+
for item in finished_logs:
229+
self.assertEqual(
230+
item.instrumentation_scope.name, "different_msg_types"
231+
)
209232

210233
def test_simple_log_record_processor_different_msg_types_with_formatter(
211234
self,
@@ -428,6 +451,8 @@ def test_shutdown(self):
428451
for item in finished_logs
429452
]
430453
self.assertEqual(expected, emitted)
454+
for item in finished_logs:
455+
self.assertEqual(item.instrumentation_scope.name, "shutdown")
431456

432457
def test_force_flush(self):
433458
exporter = InMemoryLogExporter()
@@ -447,6 +472,9 @@ def test_force_flush(self):
447472
log_record = finished_logs[0].log_record
448473
self.assertEqual(log_record.body, "Earth is burning")
449474
self.assertEqual(log_record.severity_number, SeverityNumber.FATAL)
475+
self.assertEqual(
476+
finished_logs[0].instrumentation_scope.name, "force_flush"
477+
)
450478

451479
def test_log_record_processor_too_many_logs(self):
452480
exporter = InMemoryLogExporter()
@@ -465,6 +493,8 @@ def test_log_record_processor_too_many_logs(self):
465493
self.assertTrue(log_record_processor.force_flush())
466494
finised_logs = exporter.get_finished_logs()
467495
self.assertEqual(len(finised_logs), 1000)
496+
for item in finised_logs:
497+
self.assertEqual(item.instrumentation_scope.name, "many_logs")
468498

469499
def test_with_multiple_threads(self):
470500
exporter = InMemoryLogExporter()
@@ -492,6 +522,8 @@ def bulk_log_and_flush(num_logs):
492522

493523
finished_logs = exporter.get_finished_logs()
494524
self.assertEqual(len(finished_logs), 2415)
525+
for item in finished_logs:
526+
self.assertEqual(item.instrumentation_scope.name, "threads")
495527

496528
@unittest.skipUnless(
497529
hasattr(os, "fork"),

0 commit comments

Comments
 (0)