Skip to content

Commit 18eaa37

Browse files
committed
Add logger caching by name, version, schema_url
1 parent 8259ec5 commit 18eaa37

File tree

3 files changed

+142
-14
lines changed

3 files changed

+142
-14
lines changed
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, 10000])
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 i in range(10000):
36+
loggers[i % num_loggers].warning("test message")
37+
38+
benchmark(benchmark_get_logger)

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
import threading
2121
import traceback
2222
import warnings
23-
from functools import lru_cache
2423
from os import environ
24+
from threading import Lock
2525
from time import time_ns
2626
from typing import Any, Callable, Optional, Tuple, Union # noqa
2727

@@ -582,9 +582,12 @@ def emit(self, record: logging.LogRecord) -> None:
582582

583583
def flush(self) -> None:
584584
"""
585-
Flushes the logging output. Skip flushing if logger is NoOp.
585+
Flushes the logging output. Skip flushing if logging_provider has no force_flush method.
586586
"""
587-
self._logger_provider.force_flush()
587+
if hasattr(self._logger_provider, "force_flush") and callable(
588+
self._logger_provider.force_flush
589+
):
590+
self._logger_provider.force_flush()
588591

589592

590593
class Logger(APILogger):
@@ -641,12 +644,13 @@ def __init__(
641644
self._at_exit_handler = None
642645
if shutdown_on_exit:
643646
self._at_exit_handler = atexit.register(self.shutdown)
647+
self._logger_cache = {}
648+
self._logger_cache_lock = Lock()
644649

645650
@property
646651
def resource(self):
647652
return self._resource
648653

649-
@lru_cache(maxsize=None)
650654
def get_logger(
651655
self,
652656
name: str,
@@ -662,16 +666,28 @@ def get_logger(
662666
schema_url=schema_url,
663667
attributes=attributes,
664668
)
665-
return Logger(
666-
self._resource,
667-
self._multi_log_record_processor,
668-
InstrumentationScope(
669-
name,
670-
version,
671-
schema_url,
672-
attributes,
673-
),
674-
)
669+
key = (name, version, schema_url)
670+
# Fast path if the logger is already in the cache, return it
671+
if key in self._logger_cache:
672+
return self._logger_cache[key]
673+
674+
# Lock to prevent race conditions when registering loggers with the same key
675+
with self._logger_cache_lock:
676+
# Check again in case another thread added the logger while waiting
677+
if key in self._logger_cache:
678+
return self._logger_cache[key]
679+
680+
self._logger_cache[key] = Logger(
681+
self._resource,
682+
self._multi_log_record_processor,
683+
InstrumentationScope(
684+
name,
685+
version,
686+
schema_url,
687+
attributes,
688+
),
689+
)
690+
return self._logger_cache[key]
675691

676692
def add_log_record_processor(
677693
self, log_record_processor: LogRecordProcessor
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import logging
2+
import unittest
3+
4+
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
5+
from opentelemetry.sdk._logs.export import (
6+
InMemoryLogExporter,
7+
SimpleLogRecordProcessor,
8+
)
9+
10+
11+
def set_up_logging_handler(level):
12+
logger_provider = LoggerProvider()
13+
exporter = InMemoryLogExporter()
14+
processor = SimpleLogRecordProcessor(exporter=exporter)
15+
logger_provider.add_log_record_processor(processor)
16+
handler = LoggingHandler(level=level, logger_provider=logger_provider)
17+
return handler, logger_provider
18+
19+
20+
def create_logger(handler, name):
21+
logger = logging.getLogger(name)
22+
logger.addHandler(handler)
23+
return logger
24+
25+
26+
class TestLogProviderCache(unittest.TestCase):
27+
28+
def test_get_logger_single_handler(self):
29+
handler, logger_provider = set_up_logging_handler(level=logging.DEBUG)
30+
31+
logger = create_logger(handler, "test_logger")
32+
33+
logger.warning("test message")
34+
35+
self.assertEqual(1, len(logger_provider._logger_cache))
36+
self.assertTrue(
37+
("test_logger", "", None) in logger_provider._logger_cache
38+
)
39+
40+
rounds = 100
41+
for _ in range(rounds):
42+
logger.warning("test message")
43+
44+
self.assertEqual(1, len(logger_provider._logger_cache))
45+
self.assertTrue(
46+
("test_logger", "", None) in logger_provider._logger_cache
47+
)
48+
49+
def test_get_logger_multiple_loggers(self):
50+
handler, logger_provider = set_up_logging_handler(level=logging.DEBUG)
51+
52+
num_loggers = 10
53+
loggers = [create_logger(handler, str(i)) for i in range(num_loggers)]
54+
55+
for logger in loggers:
56+
logger.warning("test message")
57+
58+
self.assertEqual(num_loggers, len(logger_provider._logger_cache))
59+
print(logger_provider._logger_cache)
60+
for logger in loggers:
61+
self.assertTrue(
62+
(logger.name, "", None) in logger_provider._logger_cache
63+
)
64+
65+
rounds = 100
66+
for _ in range(rounds):
67+
for logger in loggers:
68+
logger.warning("test message")
69+
70+
self.assertEqual(num_loggers, len(logger_provider._logger_cache))
71+
for logger in loggers:
72+
self.assertTrue(
73+
(logger.name, "", None) in logger_provider._logger_cache
74+
)

0 commit comments

Comments
 (0)