Skip to content

Commit cad15de

Browse files
author
Liudmila Molkova
committed
Move events to logs API
1 parent 2ddb076 commit cad15de

File tree

12 files changed

+356
-704
lines changed

12 files changed

+356
-704
lines changed

opentelemetry-api/src/opentelemetry/_events/py.typed

Whitespace-only changes.

opentelemetry-api/src/opentelemetry/_logs/__init__.py

Lines changed: 317 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,325 @@
3333
.. versionadded:: 1.15.0
3434
"""
3535

36-
from opentelemetry._logs._internal import (
37-
Logger,
38-
LoggerProvider,
39-
LogRecord,
40-
NoOpLogger,
41-
NoOpLoggerProvider,
42-
get_logger,
43-
get_logger_provider,
44-
set_logger_provider,
45-
)
4636
from opentelemetry._logs.severity import SeverityNumber, std_to_otel
4737

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

0 commit comments

Comments
 (0)