__init__.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. # Copyright The OpenTelemetry Authors
  2. # SPDX-License-Identifier: Apache-2.0
  3. """
  4. The OpenTelemetry logging API describes the classes used to generate logs and events.
  5. The :class:`.LoggerProvider` provides users access to the :class:`.Logger`.
  6. This module provides abstract (i.e. unimplemented) classes required for
  7. logging, and a concrete no-op implementation :class:`.NoOpLogger` that allows applications
  8. to use the API package alone without a supporting implementation.
  9. To get a logger, you need to provide the package name from which you are
  10. calling the logging APIs to OpenTelemetry by calling `LoggerProvider.get_logger`
  11. with the calling module name and the version of your package.
  12. The following code shows how to obtain a logger using the global :class:`.LoggerProvider`::
  13. from opentelemetry._logs import get_logger
  14. logger = get_logger("example-logger")
  15. .. versionadded:: 1.15.0
  16. """
  17. from __future__ import annotations
  18. from abc import ABC, abstractmethod
  19. from logging import getLogger
  20. from os import environ
  21. from time import time_ns
  22. from typing import cast, overload
  23. from typing_extensions import deprecated
  24. from opentelemetry._logs.severity import SeverityNumber
  25. from opentelemetry.context import get_current
  26. from opentelemetry.context.context import Context
  27. from opentelemetry.environment_variables import _OTEL_PYTHON_LOGGER_PROVIDER
  28. from opentelemetry.trace import get_current_span
  29. from opentelemetry.trace.span import TraceFlags
  30. from opentelemetry.util._once import Once
  31. from opentelemetry.util._providers import _load_provider
  32. from opentelemetry.util.types import AnyValue, _ExtendedAttributes
  33. _logger = getLogger(__name__)
  34. class LogRecord(ABC):
  35. """A LogRecord instance represents an event being logged.
  36. LogRecord instances are created and emitted via `Logger`
  37. every time something is logged. They contain all the information
  38. pertinent to the event being logged.
  39. """
  40. @overload
  41. def __init__(
  42. self,
  43. *,
  44. timestamp: int | None = None,
  45. observed_timestamp: int | None = None,
  46. context: Context | None = None,
  47. severity_text: str | None = None,
  48. severity_number: SeverityNumber | None = None,
  49. body: AnyValue = None,
  50. attributes: _ExtendedAttributes | None = None,
  51. event_name: str | None = None,
  52. exception: BaseException | None = None,
  53. ) -> None: ...
  54. @overload
  55. @deprecated(
  56. "LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead."
  57. )
  58. def __init__(
  59. self,
  60. *,
  61. timestamp: int | None = None,
  62. observed_timestamp: int | None = None,
  63. trace_id: int | None = None,
  64. span_id: int | None = None,
  65. trace_flags: TraceFlags | None = None,
  66. severity_text: str | None = None,
  67. severity_number: SeverityNumber | None = None,
  68. body: AnyValue = None,
  69. attributes: _ExtendedAttributes | None = None,
  70. ) -> None: ...
  71. def __init__(
  72. self,
  73. *,
  74. timestamp: int | None = None,
  75. observed_timestamp: int | None = None,
  76. context: Context | None = None,
  77. trace_id: int | None = None,
  78. span_id: int | None = None,
  79. trace_flags: TraceFlags | None = None,
  80. severity_text: str | None = None,
  81. severity_number: SeverityNumber | None = None,
  82. body: AnyValue = None,
  83. attributes: _ExtendedAttributes | None = None,
  84. event_name: str | None = None,
  85. exception: BaseException | None = None,
  86. ) -> None:
  87. if not context:
  88. context = get_current()
  89. span_context = get_current_span(context).get_span_context()
  90. self.timestamp = timestamp
  91. if observed_timestamp is None:
  92. observed_timestamp = time_ns()
  93. self.observed_timestamp = observed_timestamp
  94. self.context = context
  95. self.trace_id = trace_id or span_context.trace_id
  96. self.span_id = span_id or span_context.span_id
  97. self.trace_flags = trace_flags or span_context.trace_flags
  98. self.severity_text = severity_text
  99. self.severity_number = severity_number
  100. self.body = body
  101. self.attributes = attributes
  102. self.event_name = event_name
  103. self.exception = exception
  104. class Logger(ABC):
  105. """Handles emitting events and logs via `LogRecord`."""
  106. def __init__(
  107. self,
  108. name: str,
  109. version: str | None = None,
  110. schema_url: str | None = None,
  111. attributes: _ExtendedAttributes | None = None,
  112. ) -> None:
  113. super().__init__()
  114. self._name = name
  115. self._version = version
  116. self._schema_url = schema_url
  117. self._attributes = attributes
  118. @overload
  119. def emit(
  120. self,
  121. *,
  122. timestamp: int | None = None,
  123. observed_timestamp: int | None = None,
  124. context: Context | None = None,
  125. severity_number: SeverityNumber | None = None,
  126. severity_text: str | None = None,
  127. body: AnyValue | None = None,
  128. attributes: _ExtendedAttributes | None = None,
  129. event_name: str | None = None,
  130. exception: BaseException | None = None,
  131. ) -> None: ...
  132. @overload
  133. def emit(
  134. self,
  135. record: LogRecord,
  136. ) -> None: ...
  137. @abstractmethod
  138. def emit(
  139. self,
  140. record: LogRecord | None = None,
  141. *,
  142. timestamp: int | None = None,
  143. observed_timestamp: int | None = None,
  144. context: Context | None = None,
  145. severity_number: SeverityNumber | None = None,
  146. severity_text: str | None = None,
  147. body: AnyValue | None = None,
  148. attributes: _ExtendedAttributes | None = None,
  149. event_name: str | None = None,
  150. exception: BaseException | None = None,
  151. ) -> None:
  152. """Emits a :class:`LogRecord` representing a log to the processing pipeline."""
  153. class NoOpLogger(Logger):
  154. """The default Logger used when no Logger implementation is available.
  155. All operations are no-op.
  156. """
  157. @overload
  158. def emit(
  159. self,
  160. *,
  161. timestamp: int | None = None,
  162. observed_timestamp: int | None = None,
  163. context: Context | None = None,
  164. severity_number: SeverityNumber | None = None,
  165. severity_text: str | None = None,
  166. body: AnyValue | None = None,
  167. attributes: _ExtendedAttributes | None = None,
  168. event_name: str | None = None,
  169. exception: BaseException | None = None,
  170. ) -> None: ...
  171. @overload
  172. def emit( # pylint:disable=arguments-differ
  173. self,
  174. record: LogRecord,
  175. ) -> None: ...
  176. def emit(
  177. self,
  178. record: LogRecord | None = None,
  179. *,
  180. timestamp: int | None = None,
  181. observed_timestamp: int | None = None,
  182. context: Context | None = None,
  183. severity_number: SeverityNumber | None = None,
  184. severity_text: str | None = None,
  185. body: AnyValue | None = None,
  186. attributes: _ExtendedAttributes | None = None,
  187. event_name: str | None = None,
  188. exception: BaseException | None = None,
  189. ) -> None:
  190. pass
  191. class ProxyLogger(Logger):
  192. def __init__( # pylint: disable=super-init-not-called
  193. self,
  194. name: str,
  195. version: str | None = None,
  196. schema_url: str | None = None,
  197. attributes: _ExtendedAttributes | None = None,
  198. ):
  199. self._name = name
  200. self._version = version
  201. self._schema_url = schema_url
  202. self._attributes = attributes
  203. self._real_logger: Logger | None = None
  204. self._noop_logger = NoOpLogger(name)
  205. @property
  206. def _logger(self) -> Logger:
  207. if self._real_logger:
  208. return self._real_logger
  209. if _LOGGER_PROVIDER:
  210. self._real_logger = _LOGGER_PROVIDER.get_logger(
  211. self._name,
  212. self._version,
  213. self._schema_url,
  214. self._attributes,
  215. )
  216. return self._real_logger
  217. return self._noop_logger
  218. @overload
  219. def emit(
  220. self,
  221. *,
  222. timestamp: int | None = None,
  223. observed_timestamp: int | None = None,
  224. context: Context | None = None,
  225. severity_number: SeverityNumber | None = None,
  226. severity_text: str | None = None,
  227. body: AnyValue | None = None,
  228. attributes: _ExtendedAttributes | None = None,
  229. event_name: str | None = None,
  230. exception: BaseException | None = None,
  231. ) -> None: ...
  232. @overload
  233. def emit( # pylint:disable=arguments-differ
  234. self,
  235. record: LogRecord,
  236. ) -> None: ...
  237. def emit(
  238. self,
  239. record: LogRecord | None = None,
  240. *,
  241. timestamp: int | None = None,
  242. observed_timestamp: int | None = None,
  243. context: Context | None = None,
  244. severity_number: SeverityNumber | None = None,
  245. severity_text: str | None = None,
  246. body: AnyValue | None = None,
  247. attributes: _ExtendedAttributes | None = None,
  248. event_name: str | None = None,
  249. exception: BaseException | None = None,
  250. ) -> None:
  251. if record:
  252. self._logger.emit(record)
  253. else:
  254. self._logger.emit(
  255. timestamp=timestamp,
  256. observed_timestamp=observed_timestamp,
  257. context=context,
  258. severity_number=severity_number,
  259. severity_text=severity_text,
  260. body=body,
  261. attributes=attributes,
  262. event_name=event_name,
  263. exception=exception,
  264. )
  265. class LoggerProvider(ABC):
  266. """
  267. LoggerProvider is the entry point of the API. It provides access to Logger instances.
  268. """
  269. @abstractmethod
  270. def get_logger(
  271. self,
  272. name: str,
  273. version: str | None = None,
  274. schema_url: str | None = None,
  275. attributes: _ExtendedAttributes | None = None,
  276. ) -> Logger:
  277. """Returns a `Logger` for use by the given instrumentation library.
  278. For any two calls with identical parameters, it is undefined whether the same
  279. or different `Logger` instances are returned.
  280. This function may return different `Logger` types (e.g. a no-op logger
  281. vs. a functional logger).
  282. Args:
  283. name: The name of the instrumenting module, package or class.
  284. This should *not* be the name of the module, package or class that is
  285. instrumented but the name of the code doing the instrumentation.
  286. E.g., instead of ``"requests"``, use
  287. ``"opentelemetry.instrumentation.requests"``.
  288. For log sources which define a logger name (e.g. logging.Logger.name)
  289. the Logger Name should be recorded as the instrumentation scope name.
  290. version: Optional. The version string of the
  291. instrumenting library. Usually this should be the same as
  292. ``importlib.metadata.version(instrumenting_library_name)``.
  293. schema_url: Optional. Specifies the Schema URL of the emitted telemetry.
  294. attributes: Optional. Specifies the instrumentation scope attributes to
  295. associate with emitted telemetry.
  296. """
  297. class NoOpLoggerProvider(LoggerProvider):
  298. """The default LoggerProvider used when no LoggerProvider implementation is available."""
  299. def get_logger(
  300. self,
  301. name: str,
  302. version: str | None = None,
  303. schema_url: str | None = None,
  304. attributes: _ExtendedAttributes | None = None,
  305. ) -> Logger:
  306. """Returns a NoOpLogger."""
  307. return NoOpLogger(
  308. name, version=version, schema_url=schema_url, attributes=attributes
  309. )
  310. class ProxyLoggerProvider(LoggerProvider):
  311. def get_logger(
  312. self,
  313. name: str,
  314. version: str | None = None,
  315. schema_url: str | None = None,
  316. attributes: _ExtendedAttributes | None = None,
  317. ) -> Logger:
  318. if _LOGGER_PROVIDER:
  319. return _LOGGER_PROVIDER.get_logger(
  320. name,
  321. version=version,
  322. schema_url=schema_url,
  323. attributes=attributes,
  324. )
  325. return ProxyLogger(
  326. name,
  327. version=version,
  328. schema_url=schema_url,
  329. attributes=attributes,
  330. )
  331. _LOGGER_PROVIDER_SET_ONCE = Once()
  332. _LOGGER_PROVIDER: LoggerProvider | None = None
  333. _PROXY_LOGGER_PROVIDER = ProxyLoggerProvider()
  334. def get_logger_provider() -> LoggerProvider:
  335. """Gets the current global :class:`~.LoggerProvider` object."""
  336. global _LOGGER_PROVIDER # pylint: disable=global-variable-not-assigned
  337. if _LOGGER_PROVIDER is None:
  338. if _OTEL_PYTHON_LOGGER_PROVIDER not in environ:
  339. return _PROXY_LOGGER_PROVIDER
  340. logger_provider: LoggerProvider = _load_provider( # type: ignore
  341. _OTEL_PYTHON_LOGGER_PROVIDER, "logger_provider"
  342. )
  343. _set_logger_provider(logger_provider, log=False)
  344. # _LOGGER_PROVIDER will have been set by one thread
  345. return cast("LoggerProvider", _LOGGER_PROVIDER)
  346. def _set_logger_provider(logger_provider: LoggerProvider, log: bool) -> None:
  347. def set_lp() -> None:
  348. global _LOGGER_PROVIDER # pylint: disable=global-statement
  349. _LOGGER_PROVIDER = logger_provider
  350. did_set = _LOGGER_PROVIDER_SET_ONCE.do_once(set_lp)
  351. if log and not did_set:
  352. _logger.warning("Overriding of current LoggerProvider is not allowed")
  353. def set_logger_provider(logger_provider: LoggerProvider) -> None:
  354. """Sets the current global :class:`~.LoggerProvider` object.
  355. This can only be done once, a warning will be logged if any further attempt
  356. is made.
  357. """
  358. _set_logger_provider(logger_provider, log=True)
  359. def get_logger(
  360. instrumenting_module_name: str,
  361. instrumenting_library_version: str = "",
  362. logger_provider: LoggerProvider | None = None,
  363. schema_url: str | None = None,
  364. attributes: _ExtendedAttributes | None = None,
  365. ) -> Logger:
  366. """Returns a `Logger` for use within a python process.
  367. This function is a convenience wrapper for
  368. opentelemetry.sdk._logs.LoggerProvider.get_logger.
  369. If logger_provider param is omitted the current configured one is used.
  370. """
  371. if logger_provider is None:
  372. logger_provider = get_logger_provider()
  373. return logger_provider.get_logger(
  374. instrumenting_module_name,
  375. instrumenting_library_version,
  376. schema_url,
  377. attributes,
  378. )