|
15 | 15 | from functools import cached_property |
16 | 16 | from logging import getLogger |
17 | 17 | from os import environ |
| 18 | +from typing import Callable |
18 | 19 |
|
| 20 | +from opentelemetry._logs import LoggerProvider, get_logger_provider |
19 | 21 | from opentelemetry.instrumentation.dependencies import ( |
20 | 22 | DependencyConflictError, |
21 | 23 | get_dist_dependency_conflicts, |
@@ -187,3 +189,74 @@ def _load_configurators(): |
187 | 189 | except Exception as exc: # pylint: disable=broad-except |
188 | 190 | _logger.exception("Configuration of %s failed", entry_point.name) |
189 | 191 | raise exc |
| 192 | + |
| 193 | + |
| 194 | +# FIXME: move to proper place |
| 195 | +_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED = ( |
| 196 | + "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED" |
| 197 | +) |
| 198 | + |
| 199 | + |
| 200 | +LoggingSetupT = Callable[[LoggerProvider], None] |
| 201 | + |
| 202 | + |
| 203 | +# FIXME: how should call these things? Logging integrations? |
| 204 | +def _load_logging_integrations(): |
| 205 | + entry_point_finder = _EntryPointDistFinder() |
| 206 | + # this assumes we are called after sdk has been setup |
| 207 | + logger_provider = get_logger_provider() |
| 208 | + |
| 209 | + enabled_logging_integrations = [] |
| 210 | + if ( |
| 211 | + environ.get(_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED, "false") |
| 212 | + .strip() |
| 213 | + .lower() |
| 214 | + == "true" |
| 215 | + ): |
| 216 | + enabled_logging_integrations.append("logging") |
| 217 | + |
| 218 | + for entry_point in entry_points(group="opentelemetry_logging_handlers"): |
| 219 | + # TODO: add exclusions once we move to enabled by default |
| 220 | + if entry_point.name not in enabled_logging_integrations: |
| 221 | + continue |
| 222 | + |
| 223 | + try: |
| 224 | + entry_point_dist = entry_point_finder.dist_for(entry_point) |
| 225 | + conflict = get_dist_dependency_conflicts(entry_point_dist) |
| 226 | + if conflict: |
| 227 | + _logger.debug( |
| 228 | + "Skipping logging handler %s: %s", |
| 229 | + entry_point.name, |
| 230 | + conflict, |
| 231 | + ) |
| 232 | + continue |
| 233 | + |
| 234 | + # we expect a function that takes the logger_provider |
| 235 | + logging_setup_func: LoggingSetupT = entry_point.load() |
| 236 | + logging_setup_func(logger_provider) |
| 237 | + _logger.debug("Loaded logging integration %s", entry_point.name) |
| 238 | + except ModuleNotFoundError as exc: |
| 239 | + # ModuleNotFoundError is raised when the library is not installed |
| 240 | + # and the logging integration is not required to be loaded. |
| 241 | + _logger.debug( |
| 242 | + "Skipping logging integration %s: %s", |
| 243 | + entry_point.name, |
| 244 | + exc.msg, |
| 245 | + ) |
| 246 | + continue |
| 247 | + except ImportError: |
| 248 | + # in scenarios using the kubernetes operator to do autoinstrumentation |
| 249 | + # logging integrations (usually requiring binary extensions) may fail to load |
| 250 | + # because the injected autoinstrumentation code does not match the application |
| 251 | + # environment regarding python version, libc, etc... In this case it's better |
| 252 | + # to skip the single logging integration rather than failing to load everything |
| 253 | + # so treat differently ImportError than the rest of exceptions |
| 254 | + _logger.exception( |
| 255 | + "Importing of %s failed, skipping it", entry_point.name |
| 256 | + ) |
| 257 | + continue |
| 258 | + except Exception as exc: # pylint: disable=broad-except |
| 259 | + _logger.exception( |
| 260 | + "Logging integration of %s failed", entry_point.name |
| 261 | + ) |
| 262 | + raise exc |
0 commit comments