Skip to content

Commit 489bca3

Browse files
xrmxpmcollins
andauthored
Move logger handlers to opentelemetry-instrumentation-logging (#4210)
* Move logging integrations into auto-instrumentation * opentelemetry-instrumentation-logging: move the sdk logging handler here And hook it up via entry point * Add header and future annotations import * Rename entry point group to opentelemetry_logging_integrations * Consider setting up the LoggingHandler as a normal instrumentation * Fix typo * Add missing import * Copy handler tests from core * More work towards green tests * Cleanup properly after loggingHandler tests * Quite hard to expect a mock to setup the handler * Call removehandler also on local loggers No change in practice * Fix wrong noop test * Move to our own env var for controlling autoinstrumentation * Copy handler benchmark from sdk * Document the new environment variables * Add changelog * Please pylint * Added warning about coexistence with sdk code * Reword a bit * Assert that the LoggingHandler has not been setup in uninstrumented test * Add manual handling of auto instrumentation and code attributes logging * Update instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/constants.py * Apply suggestions from code review Co-authored-by: Pablo Collins <pablo.collins@gmail.com> * Apply more Pablo feedback --------- Co-authored-by: Pablo Collins <pablo.collins@gmail.com>
1 parent c907282 commit 489bca3

File tree

10 files changed

+1316
-10
lines changed

10 files changed

+1316
-10
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6060
([#4141](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4141))
6161
- `opentelemetry-instrumentation-pyramid`: pass request attributes at span creation
6262
([#4139](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4139))
63+
- `opentelemetry-instrumentation-logging`: Move there the SDK LoggingHandler
64+
([#4210](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4210))
6365

6466
### Fixed
6567

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest-benchmark==4.0.0
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
17+
import pytest
18+
19+
from opentelemetry.instrumentation.logging.handler import LoggingHandler
20+
from opentelemetry.sdk._logs import LoggerProvider
21+
from opentelemetry.sdk._logs.export import (
22+
InMemoryLogRecordExporter,
23+
SimpleLogRecordProcessor,
24+
)
25+
26+
27+
def _set_up_logging_handler(level):
28+
logger_provider = LoggerProvider()
29+
exporter = InMemoryLogRecordExporter()
30+
processor = SimpleLogRecordProcessor(exporter=exporter)
31+
logger_provider.add_log_record_processor(processor)
32+
handler = LoggingHandler(level=level, logger_provider=logger_provider)
33+
return handler
34+
35+
36+
def _create_logger(handler, name):
37+
logger = logging.getLogger(name)
38+
logger.addHandler(handler)
39+
return logger
40+
41+
42+
@pytest.mark.parametrize("num_loggers", [1, 10, 100, 1000])
43+
def test_simple_get_logger_different_names(benchmark, num_loggers):
44+
handler = _set_up_logging_handler(level=logging.DEBUG)
45+
loggers = [
46+
_create_logger(handler, str(f"logger_{i}")) for i in range(num_loggers)
47+
]
48+
49+
def benchmark_get_logger():
50+
for index in range(1000):
51+
loggers[index % num_loggers].warning("test message")
52+
53+
benchmark(benchmark_get_logger)

instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/__init__.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
# pylint: disable=empty-docstring,no-value-for-parameter,no-member,no-name-in-module
1616

1717
"""
18-
The OpenTelemetry `logging` integration automatically injects tracing context into
18+
The OpenTelemetry `logging` instrumentation automatically instruments Python logging
19+
system with an handler to convert log messages into OpenTelemetry logs.
20+
You can disable this setting `OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION` to `false`.
21+
22+
The OpenTelemetry `logging` integration can inject tracing context into
1923
log statements, though it is opt-in and must be enabled explicitly by setting the
2024
environment variable `OTEL_PYTHON_LOG_CORRELATION` to `true`.
2125
@@ -59,16 +63,22 @@
5963
from os import environ
6064
from typing import Collection
6165

66+
from opentelemetry._logs import get_logger_provider
6267
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
6368
from opentelemetry.instrumentation.logging.constants import (
6469
_MODULE_DOC,
6570
DEFAULT_LOGGING_FORMAT,
6671
)
6772
from opentelemetry.instrumentation.logging.environment_variables import (
73+
OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION,
74+
OTEL_PYTHON_LOG_CODE_ATTRIBUTES,
6875
OTEL_PYTHON_LOG_CORRELATION,
6976
OTEL_PYTHON_LOG_FORMAT,
7077
OTEL_PYTHON_LOG_LEVEL,
7178
)
79+
from opentelemetry.instrumentation.logging.handler import (
80+
_setup_logging_handler,
81+
)
7282
from opentelemetry.instrumentation.logging.package import _instruments
7383
from opentelemetry.trace import (
7484
INVALID_SPAN,
@@ -86,6 +96,8 @@
8696
"error": logging.ERROR,
8797
}
8898

99+
_logger = logging.getLogger(__name__)
100+
89101

90102
class LoggingInstrumentor(BaseInstrumentor): # pylint: disable=empty-docstring
91103
__doc__ = f"""An instrumentor for stdlib logging module.
@@ -120,6 +132,7 @@ def log_hook(span: Span, record: LogRecord):
120132

121133
_old_factory = None
122134
_log_hook = None
135+
_logging_handler = None
123136

124137
def instrumentation_dependencies(self) -> Collection[str]:
125138
return _instruments
@@ -199,7 +212,53 @@ def record_factory(*args, **kwargs):
199212

200213
logging.setLogRecordFactory(record_factory)
201214

215+
# Here we need to handle 3 scenarios:
216+
# - the sdk logging handler is enabled and we should do no nothing
217+
# - the sdk logging handler is not enabled and we should setup the handler by default
218+
# - the sdk logging handler is not enabled and the user do not want we setup the handler
219+
sdk_autoinstrumentation_env_var = (
220+
environ.get(
221+
"OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED", "notset"
222+
)
223+
.strip()
224+
.lower()
225+
)
226+
if sdk_autoinstrumentation_env_var == "true":
227+
_logger.warning(
228+
"Skipping installation of LoggingHandler from "
229+
"`opentelemetry-instrumentation-logging` to avoid duplicate logs. "
230+
"The SDK's deprecated LoggingHandler is already active "
231+
"(OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true). To migrate, unset "
232+
"this environment variable. The SDK's handler will be removed in a future release."
233+
)
234+
elif kwargs.get(
235+
"enable_log_auto_instrumentation",
236+
environ.get(OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION, "true")
237+
.strip()
238+
.lower()
239+
== "true",
240+
):
241+
log_code_attributes = kwargs.get(
242+
"log_code_attributes",
243+
environ.get(OTEL_PYTHON_LOG_CODE_ATTRIBUTES, "false")
244+
.strip()
245+
.lower()
246+
== "true",
247+
)
248+
logger_provider = get_logger_provider()
249+
handler = _setup_logging_handler(
250+
logger_provider=logger_provider,
251+
log_code_attributes=log_code_attributes,
252+
)
253+
LoggingInstrumentor._logging_handler = handler
254+
202255
def _uninstrument(self, **kwargs):
203256
if LoggingInstrumentor._old_factory:
204257
logging.setLogRecordFactory(LoggingInstrumentor._old_factory)
205258
LoggingInstrumentor._old_factory = None
259+
260+
if LoggingInstrumentor._logging_handler:
261+
logging.getLogger().removeHandler(
262+
LoggingInstrumentor._logging_handler
263+
)
264+
LoggingInstrumentor._logging_handler = None

instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/constants.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,22 @@
1616

1717

1818
_MODULE_DOC = """
19-
The OpenTelemetry ``logging`` integration automatically injects tracing context into log statements.
19+
The OpenTelemetry ``logging`` instrumentation automatically instruments Python logging
20+
with a handler to convert Python log messages into OpenTelemetry logs and export them.
21+
You can disable this by setting ``OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION`` to ``false``.
22+
23+
.. warning::
24+
25+
This package provides a logging handler to replace the deprecated one in ``opentelemetry-sdk``.
26+
Therefore if you have ``opentelemetry-instrumentation-logging`` installed, you don't need to set the
27+
``OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED`` environment variable to ``true``.
28+
By default, this instrumentation does not add ``code`` namespace attributes as the SDK's logger does, but adding them can be enabled by using the
29+
``OTEL_PYTHON_LOG_CODE_ATTRIBUTES`` environment variable.
30+
31+
Enable trace context injection
32+
------------------------------
33+
34+
The OpenTelemetry ``logging`` integration can also be configured to inject tracing context into log statements.
2035
2136
The integration registers a custom log record factory with the the standard library logging module that automatically inject
2237
tracing context into log record objects. Optionally, the integration can also call ``logging.basicConfig()`` to set a logging
@@ -35,19 +50,25 @@
3550
3651
{default_logging_format}
3752
38-
Enable trace context injection
39-
------------------------------
40-
4153
The integration is opt-in and must be enabled explicitly by setting the environment variable ``OTEL_PYTHON_LOG_CORRELATION`` to ``true``.
42-
43-
The integration always registers the custom factory that injects the tracing context into the log record objects. Setting
44-
``OTEL_PYTHON_LOG_CORRELATION`` to ``true`` calls ``logging.basicConfig()`` to set a logging format that actually makes
54+
Setting ``OTEL_PYTHON_LOG_CORRELATION`` to ``true`` calls ``logging.basicConfig()`` to set a logging format that actually makes
4555
use of the injected variables.
4656
47-
4857
Environment variables
4958
---------------------
5059
60+
.. envvar:: OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION
61+
62+
Set this env var to ``false`` to skip installing the logging handler provided by this package.
63+
64+
The default value is ``true``.
65+
66+
.. envvar:: OTEL_PYTHON_CODE_ATTRIBUTES
67+
68+
Set this env var to ``true`` to add ``code`` attributes (``code.file.path``, ``code.function.name``, ``code.line.number``) to OpenTelemetry logs, referencing the Python source location that emitted each log message.
69+
70+
The default value is ``false``.
71+
5172
.. envvar:: OTEL_PYTHON_LOG_CORRELATION
5273
5374
This env var must be set to ``true`` in order to enable trace context injection into logs by calling ``logging.basicConfig()`` and

instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/environment_variables.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION = "OTEL_PYTHON_LOG_AUTO_INSTRUMENTATION"
16+
OTEL_PYTHON_LOG_CODE_ATTRIBUTES = "OTEL_PYTHON_LOG_CODE_ATTRIBUTES"
1517
OTEL_PYTHON_LOG_CORRELATION = "OTEL_PYTHON_LOG_CORRELATION"
1618
OTEL_PYTHON_LOG_FORMAT = "OTEL_PYTHON_LOG_FORMAT"
1719
OTEL_PYTHON_LOG_LEVEL = "OTEL_PYTHON_LOG_LEVEL"

0 commit comments

Comments
 (0)