增加环绕侦察场景适配

This commit is contained in:
2026-01-08 15:44:38 +08:00
parent 3eba1f962b
commit 10c5bb5a8a
5441 changed files with 40219 additions and 379695 deletions

View File

@@ -22,13 +22,13 @@ from __future__ import annotations
import logging
import logging.config
import os
import warnings
from abc import ABC, abstractmethod
from os import environ
from typing import Any, Callable, Mapping, Sequence, Type, Union
from typing_extensions import Literal
from opentelemetry._events import set_event_logger_provider
from opentelemetry._logs import set_logger_provider
from opentelemetry.environment_variables import (
OTEL_LOGS_EXPORTER,
@@ -37,9 +37,11 @@ from opentelemetry.environment_variables import (
OTEL_TRACES_EXPORTER,
)
from opentelemetry.metrics import set_meter_provider
from opentelemetry.sdk._events import EventLoggerProvider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor, LogExporter
from opentelemetry.sdk._logs.export import (
BatchLogRecordProcessor,
LogRecordExporter,
)
from opentelemetry.sdk.environment_variables import (
_OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED,
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
@@ -97,7 +99,7 @@ ExporterArgsMap = Mapping[
Type[SpanExporter],
Type[MetricExporter],
Type[MetricReader],
Type[LogExporter],
Type[LogRecordExporter],
],
Mapping[str, Any],
]
@@ -250,7 +252,7 @@ def _init_metrics(
def _init_logging(
exporters: dict[str, Type[LogExporter]],
exporters: dict[str, Type[LogRecordExporter]],
resource: Resource | None = None,
setup_logging_handler: bool = True,
exporter_args_map: ExporterArgsMap | None = None,
@@ -265,8 +267,19 @@ def _init_logging(
BatchLogRecordProcessor(exporter_class(**exporter_args))
)
event_logger_provider = EventLoggerProvider(logger_provider=provider)
set_event_logger_provider(event_logger_provider)
# silence warnings from internal users until we drop the deprecated Events API
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=DeprecationWarning)
# pylint: disable=import-outside-toplevel
from opentelemetry._events import ( # noqa: PLC0415
set_event_logger_provider,
)
from opentelemetry.sdk._events import ( # noqa: PLC0415
EventLoggerProvider,
)
event_logger_provider = EventLoggerProvider(logger_provider=provider)
set_event_logger_provider(event_logger_provider)
if setup_logging_handler:
# Add OTel handler
@@ -309,7 +322,7 @@ def _import_exporters(
) -> tuple[
dict[str, Type[SpanExporter]],
dict[str, Union[Type[MetricExporter], Type[MetricReader]]],
dict[str, Type[LogExporter]],
dict[str, Type[LogRecordExporter]],
]:
trace_exporters = {}
metric_exporters = {}
@@ -345,7 +358,7 @@ def _import_exporters(
) in _import_config_components(
log_exporter_names, "opentelemetry_logs_exporter"
):
if issubclass(exporter_impl, LogExporter):
if issubclass(exporter_impl, LogRecordExporter):
log_exporters[exporter_name] = exporter_impl
else:
raise RuntimeError(f"{exporter_name} is not a log exporter")

View File

@@ -13,26 +13,31 @@
# limitations under the License.
import logging
import warnings
from time import time_ns
from typing import Optional
from typing_extensions import deprecated
from opentelemetry import trace
from opentelemetry._events import Event
from opentelemetry._events import EventLogger as APIEventLogger
from opentelemetry._events import EventLoggerProvider as APIEventLoggerProvider
from opentelemetry._logs import NoOpLogger, SeverityNumber, get_logger_provider
from opentelemetry.sdk._logs import (
LogDeprecatedInitWarning,
Logger,
LoggerProvider,
from opentelemetry._logs import (
LogRecord,
NoOpLogger,
SeverityNumber,
get_logger_provider,
)
from opentelemetry.sdk._logs import Logger, LoggerProvider
from opentelemetry.util.types import _ExtendedAttributes
_logger = logging.getLogger(__name__)
@deprecated(
"You should use `Logger` instead. "
"Deprecated since version 1.39.0 and will be removed in a future release."
)
class EventLogger(APIEventLogger):
def __init__(
self,
@@ -58,25 +63,24 @@ class EventLogger(APIEventLogger):
return
span_context = trace.get_current_span().get_span_context()
# silence deprecation warnings from internal users
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=LogDeprecatedInitWarning)
log_record = LogRecord(
timestamp=event.timestamp or time_ns(),
observed_timestamp=None,
trace_id=event.trace_id or span_context.trace_id,
span_id=event.span_id or span_context.span_id,
trace_flags=event.trace_flags or span_context.trace_flags,
severity_text=None,
severity_number=event.severity_number or SeverityNumber.INFO,
body=event.body,
resource=getattr(self._logger, "resource", None),
attributes=event.attributes,
)
log_record = LogRecord(
timestamp=event.timestamp or time_ns(),
observed_timestamp=None,
trace_id=event.trace_id or span_context.trace_id,
span_id=event.span_id or span_context.span_id,
trace_flags=event.trace_flags or span_context.trace_flags,
severity_text=None,
severity_number=event.severity_number or SeverityNumber.INFO,
body=event.body,
attributes=event.attributes,
)
self._logger.emit(log_record)
@deprecated(
"You should use `LoggerProvider` instead. "
"Deprecated since version 1.39.0 and will be removed in a future release."
)
class EventLoggerProvider(APIEventLoggerProvider):
def __init__(self, logger_provider: Optional[LoggerProvider] = None):
self._logger_provider = logger_provider or get_logger_provider()

View File

@@ -12,27 +12,28 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from opentelemetry.sdk._logs._internal import (
LogData,
LogDeprecatedInitWarning,
LogDroppedAttributesWarning,
Logger,
LoggerProvider,
LoggingHandler,
LogLimits,
LogRecord,
LogRecordDroppedAttributesWarning,
LogRecordLimits,
LogRecordProcessor,
ReadableLogRecord,
ReadWriteLogRecord,
)
__all__ = [
"LogData",
"Logger",
"LoggerProvider",
"LoggingHandler",
"LogLimits",
"LogRecord",
"LogRecordLimits",
"LogRecordProcessor",
"LogDeprecatedInitWarning",
"LogDroppedAttributesWarning",
"LogRecordDroppedAttributesWarning",
"ReadableLogRecord",
"ReadWriteLogRecord",
]

View File

@@ -22,6 +22,7 @@ import logging
import threading
import traceback
import warnings
from dataclasses import dataclass, field
from os import environ
from threading import Lock
from time import time_ns
@@ -31,8 +32,8 @@ from typing_extensions import deprecated
from opentelemetry._logs import Logger as APILogger
from opentelemetry._logs import LoggerProvider as APILoggerProvider
from opentelemetry._logs import LogRecord as APILogRecord
from opentelemetry._logs import (
LogRecord,
NoOpLogger,
SeverityNumber,
get_logger,
@@ -54,13 +55,9 @@ from opentelemetry.semconv.attributes import exception_attributes
from opentelemetry.trace import (
format_span_id,
format_trace_id,
get_current_span,
)
from opentelemetry.trace.span import TraceFlags
from opentelemetry.util.types import AnyValue, _ExtendedAttributes
_logger = logging.getLogger(__name__)
_DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT = 128
_ENV_VALUE_UNSET = ""
@@ -72,7 +69,7 @@ class BytesEncoder(json.JSONEncoder):
return super().default(o)
class LogDroppedAttributesWarning(UserWarning):
class LogRecordDroppedAttributesWarning(UserWarning):
"""Custom warning to indicate dropped log attributes due to limits.
This class is used to filter and handle these specific warnings separately
@@ -81,22 +78,17 @@ class LogDroppedAttributesWarning(UserWarning):
"""
warnings.simplefilter("once", LogDroppedAttributesWarning)
warnings.simplefilter("once", LogRecordDroppedAttributesWarning)
class LogDeprecatedInitWarning(UserWarning):
"""Custom warning to indicate that deprecated and soon to be deprecated Log classes was used.
This class is used to filter and handle these specific warnings separately
from other warnings, ensuring that they are only shown once without
interfering with default user warnings.
"""
@deprecated(
"Use LogRecordDroppedAttributesWarning. Since logs are not stable yet this WILL be removed in future releases."
)
class LogDroppedAttributesWarning(LogRecordDroppedAttributesWarning):
pass
warnings.simplefilter("once", LogDeprecatedInitWarning)
class LogLimits:
class LogRecordLimits:
"""This class is based on a SpanLimits class in the Tracing module.
This class represents the limits that should be enforced on recorded data such as events, links, attributes etc.
@@ -176,202 +168,125 @@ class LogLimits:
return value
class LogRecord(APILogRecord):
"""A LogRecord instance represents an event being logged.
@deprecated(
"Use LogRecordLimits. Since logs are not stable yet this WILL be removed in future releases."
)
class LogLimits(LogRecordLimits):
pass
LogRecord instances are created and emitted via `Logger`
every time something is logged. They contain all the information
pertinent to the event being logged.
"""
@overload
def __init__(
self,
timestamp: int | None = None,
observed_timestamp: int | None = None,
context: Context | None = None,
severity_text: str | None = None,
severity_number: SeverityNumber | None = None,
body: AnyValue | None = None,
resource: Resource | None = None,
attributes: _ExtendedAttributes | None = None,
limits: LogLimits | None = None,
event_name: str | None = None,
): ...
@dataclass(frozen=True)
class ReadableLogRecord:
"""Readable LogRecord should be kept exactly in-sync with ReadWriteLogRecord, only difference is the frozen=True param."""
@overload
@deprecated(
"LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead." # noqa: E501
)
def __init__(
self,
timestamp: int | None = None,
observed_timestamp: int | None = None,
trace_id: int | None = None,
span_id: int | None = None,
trace_flags: TraceFlags | None = None,
severity_text: str | None = None,
severity_number: SeverityNumber | None = None,
body: AnyValue | None = None,
resource: Resource | None = None,
attributes: _ExtendedAttributes | None = None,
limits: LogLimits | None = None,
): ...
log_record: LogRecord
resource: Resource
instrumentation_scope: InstrumentationScope | None = None
limits: LogRecordLimits | None = None
def __init__( # pylint:disable=too-many-locals
self,
timestamp: int | None = None,
observed_timestamp: int | None = None,
context: Context | None = None,
trace_id: int | None = None,
span_id: int | None = None,
trace_flags: TraceFlags | None = None,
severity_text: str | None = None,
severity_number: SeverityNumber | None = None,
body: AnyValue | None = None,
resource: Resource | None = None,
attributes: _ExtendedAttributes | None = None,
limits: LogLimits | None = None,
event_name: str | None = None,
):
warnings.warn(
"LogRecord will be removed in 1.39.0 and replaced by ReadWriteLogRecord and ReadableLogRecord",
LogDeprecatedInitWarning,
stacklevel=2,
)
if not context:
context = get_current()
if trace_id or span_id or trace_flags:
warnings.warn(
"LogRecord init with `trace_id`, `span_id`, and/or `trace_flags` is deprecated since 1.35.0. Use `context` instead.",
LogDeprecatedInitWarning,
stacklevel=2,
)
span = get_current_span(context)
span_context = span.get_span_context()
# Use default LogLimits if none provided
if limits is None:
limits = LogLimits()
super().__init__(
**{
"timestamp": timestamp,
"observed_timestamp": observed_timestamp,
"context": context,
"trace_id": trace_id or span_context.trace_id,
"span_id": span_id or span_context.span_id,
"trace_flags": trace_flags or span_context.trace_flags,
"severity_text": severity_text,
"severity_number": severity_number,
"body": body,
"attributes": BoundedAttributes(
maxlen=limits.max_attributes,
attributes=attributes if bool(attributes) else None,
immutable=False,
max_value_len=limits.max_attribute_length,
extended_attributes=True,
),
"event_name": event_name,
}
)
self.resource = (
resource if isinstance(resource, Resource) else Resource.create({})
)
if self.dropped_attributes > 0:
warnings.warn(
"Log record attributes were dropped due to limits",
LogDroppedAttributesWarning,
stacklevel=2,
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, LogRecord):
return NotImplemented
return self.__dict__ == other.__dict__
@property
def dropped_attributes(self) -> int:
if isinstance(self.log_record.attributes, BoundedAttributes):
return self.log_record.attributes.dropped
return 0
def to_json(self, indent: int | None = 4) -> str:
return json.dumps(
{
"body": self.body,
"severity_number": self.severity_number.value
if self.severity_number is not None
"body": self.log_record.body,
"severity_number": self.log_record.severity_number.value
if self.log_record.severity_number is not None
else None,
"severity_text": self.severity_text,
"severity_text": self.log_record.severity_text,
"attributes": (
dict(self.attributes) if bool(self.attributes) else None
dict(self.log_record.attributes)
if bool(self.log_record.attributes)
else None
),
"dropped_attributes": self.dropped_attributes,
"timestamp": ns_to_iso_str(self.timestamp)
if self.timestamp is not None
"timestamp": ns_to_iso_str(self.log_record.timestamp)
if self.log_record.timestamp is not None
else None,
"observed_timestamp": ns_to_iso_str(self.observed_timestamp),
"observed_timestamp": ns_to_iso_str(
self.log_record.observed_timestamp
),
"trace_id": (
f"0x{format_trace_id(self.trace_id)}"
if self.trace_id is not None
f"0x{format_trace_id(self.log_record.trace_id)}"
if self.log_record.trace_id is not None
else ""
),
"span_id": (
f"0x{format_span_id(self.span_id)}"
if self.span_id is not None
f"0x{format_span_id(self.log_record.span_id)}"
if self.log_record.span_id is not None
else ""
),
"trace_flags": self.trace_flags,
"trace_flags": self.log_record.trace_flags,
"resource": json.loads(self.resource.to_json()),
"event_name": self.event_name if self.event_name else "",
"event_name": self.log_record.event_name
if self.log_record.event_name
else "",
},
indent=indent,
cls=BytesEncoder,
)
@dataclass
class ReadWriteLogRecord:
"""A ReadWriteLogRecord instance represents an event being logged.
ReadWriteLogRecord instances are created and emitted via `Logger`
every time something is logged. They contain all the information
pertinent to the event being logged.
"""
log_record: LogRecord
resource: Resource | None = Resource.create({})
instrumentation_scope: InstrumentationScope | None = None
limits: LogRecordLimits = field(default_factory=LogRecordLimits)
def __post_init__(self):
self.log_record.attributes = BoundedAttributes(
maxlen=self.limits.max_attributes,
attributes=self.log_record.attributes
if self.log_record.attributes
else None,
immutable=False,
max_value_len=self.limits.max_attribute_length,
extended_attributes=True,
)
if self.dropped_attributes > 0:
warnings.warn(
"Log record attributes were dropped due to limits",
LogRecordDroppedAttributesWarning,
stacklevel=2,
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, ReadWriteLogRecord):
return NotImplemented
return self.__dict__ == other.__dict__
@property
def dropped_attributes(self) -> int:
attributes: BoundedAttributes = cast(
BoundedAttributes, self.attributes
)
if attributes:
return attributes.dropped
if isinstance(self.log_record.attributes, BoundedAttributes):
return self.log_record.attributes.dropped
return 0
@classmethod
def _from_api_log_record(
cls, *, record: APILogRecord, resource: Resource
) -> LogRecord:
cls,
*,
record: LogRecord,
resource: Resource,
instrumentation_scope: InstrumentationScope | None = None,
) -> ReadWriteLogRecord:
return cls(
timestamp=record.timestamp,
observed_timestamp=record.observed_timestamp,
context=record.context,
trace_id=record.trace_id,
span_id=record.span_id,
trace_flags=record.trace_flags,
severity_text=record.severity_text,
severity_number=record.severity_number,
body=record.body,
attributes=record.attributes,
event_name=record.event_name,
log_record=record,
resource=resource,
instrumentation_scope=instrumentation_scope,
)
class LogData:
"""Readable LogRecord data plus associated InstrumentationLibrary."""
def __init__(
self,
log_record: LogRecord,
instrumentation_scope: InstrumentationScope,
):
warnings.warn(
"LogData will be removed in 1.39.0 and replaced by ReadWriteLogRecord and ReadableLogRecord",
LogDeprecatedInitWarning,
stacklevel=2,
)
self.log_record = log_record
self.instrumentation_scope = instrumentation_scope
class LogRecordProcessor(abc.ABC):
"""Interface to hook the log record emitting action.
@@ -381,15 +296,15 @@ class LogRecordProcessor(abc.ABC):
"""
@abc.abstractmethod
def on_emit(self, log_data: LogData):
"""Emits the `LogData`"""
def on_emit(self, log_record: ReadWriteLogRecord):
"""Emits the `ReadWriteLogRecord`"""
@abc.abstractmethod
def shutdown(self):
"""Called when a :class:`opentelemetry.sdk._logs.Logger` is shutdown"""
@abc.abstractmethod
def force_flush(self, timeout_millis: int = 30000):
def force_flush(self, timeout_millis: int = 30000) -> bool:
"""Export all the received logs to the configured Exporter that have not yet
been exported.
@@ -425,9 +340,9 @@ class SynchronousMultiLogRecordProcessor(LogRecordProcessor):
with self._lock:
self._log_record_processors += (log_record_processor,)
def on_emit(self, log_data: LogData) -> None:
def on_emit(self, log_record: ReadWriteLogRecord) -> None:
for lp in self._log_record_processors:
lp.on_emit(log_data)
lp.on_emit(log_record)
def shutdown(self) -> None:
"""Shutdown the log processors one by one"""
@@ -499,8 +414,8 @@ class ConcurrentMultiLogRecordProcessor(LogRecordProcessor):
for future in futures:
future.result()
def on_emit(self, log_data: LogData):
self._submit_and_wait(lambda lp: lp.on_emit, log_data)
def on_emit(self, log_record: ReadWriteLogRecord):
self._submit_and_wait(lambda lp: lp.on_emit, log_record)
def shutdown(self):
self._submit_and_wait(lambda lp: lp.shutdown)
@@ -575,8 +490,8 @@ class LoggingHandler(logging.Handler):
def __init__(
self,
level=logging.NOTSET,
logger_provider=None,
level: int = logging.NOTSET,
logger_provider: APILoggerProvider | None = None,
) -> None:
super().__init__(level=level)
self._logger_provider = logger_provider or get_logger_provider()
@@ -609,7 +524,7 @@ class LoggingHandler(logging.Handler):
)
return attributes
def _translate(self, record: logging.LogRecord) -> dict:
def _translate(self, record: logging.LogRecord) -> LogRecord:
timestamp = int(record.created * 1e9)
observered_timestamp = time_ns()
attributes = self._get_attributes(record)
@@ -643,15 +558,15 @@ class LoggingHandler(logging.Handler):
"WARN" if record.levelname == "WARNING" else record.levelname
)
return {
"timestamp": timestamp,
"observed_timestamp": observered_timestamp,
"context": get_current() or None,
"severity_text": level_name,
"severity_number": severity_number,
"body": body,
"attributes": attributes,
}
return LogRecord(
timestamp=timestamp,
observed_timestamp=observered_timestamp,
context=get_current() or None,
severity_text=level_name,
severity_number=severity_number,
body=body,
attributes=attributes,
)
def emit(self, record: logging.LogRecord) -> None:
"""
@@ -661,18 +576,18 @@ class LoggingHandler(logging.Handler):
"""
logger = get_logger(record.name, logger_provider=self._logger_provider)
if not isinstance(logger, NoOpLogger):
logger.emit(**self._translate(record))
logger.emit(self._translate(record))
def flush(self) -> None:
"""
Flushes the logging output. Skip flushing if logging_provider has no force_flush method.
"""
if hasattr(self._logger_provider, "force_flush") and callable(
self._logger_provider.force_flush
self._logger_provider.force_flush # type: ignore[reportAttributeAccessIssue]
):
# This is done in a separate thread to avoid a potential deadlock, for
# details see https://github.com/open-telemetry/opentelemetry-python/pull/4636.
thread = threading.Thread(target=self._logger_provider.force_flush)
thread = threading.Thread(target=self._logger_provider.force_flush) # type: ignore[reportAttributeAccessIssue]
thread.start()
@@ -700,9 +615,10 @@ class Logger(APILogger):
def resource(self):
return self._resource
@overload
# pylint: disable=arguments-differ
def emit(
self,
record: LogRecord | None = None,
*,
timestamp: int | None = None,
observed_timestamp: int | None = None,
@@ -712,55 +628,41 @@ class Logger(APILogger):
body: AnyValue | None = None,
attributes: _ExtendedAttributes | None = None,
event_name: str | None = None,
) -> None: ...
@overload
def emit( # pylint:disable=arguments-differ
self,
record: APILogRecord,
) -> None: ...
def emit(
self,
record: APILogRecord | None = None,
*,
timestamp: int | None = None,
observed_timestamp: int | None = None,
context: Context | None = None,
severity_text: str | None = None,
severity_number: SeverityNumber | None = None,
body: AnyValue | None = None,
attributes: _ExtendedAttributes | None = None,
event_name: str | None = None,
):
"""Emits the :class:`LogData` by associating :class:`LogRecord`
and instrumentation info.
) -> None:
"""Emits the :class:`ReadWriteLogRecord` by setting instrumentation scope
and forwarding to the processor.
"""
# silence deprecation warnings from internal users
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=LogDeprecatedInitWarning)
if not record:
record = LogRecord(
timestamp=timestamp,
observed_timestamp=observed_timestamp,
context=context,
severity_text=severity_text,
severity_number=severity_number,
body=body,
attributes=attributes,
event_name=event_name,
resource=self._resource,
)
elif not isinstance(record, LogRecord):
# If a record is provided, use it directly
if record is not None:
if not isinstance(record, ReadWriteLogRecord):
# pylint:disable=protected-access
record = LogRecord._from_api_log_record(
record=record, resource=self._resource
writable_record = ReadWriteLogRecord._from_api_log_record(
record=record,
resource=self._resource,
instrumentation_scope=self._instrumentation_scope,
)
else:
writable_record = record
else:
# Create a record from individual parameters
log_record = LogRecord(
timestamp=timestamp,
observed_timestamp=observed_timestamp,
context=context,
severity_number=severity_number,
severity_text=severity_text,
body=body,
attributes=attributes,
event_name=event_name,
)
# pylint:disable=protected-access
writable_record = ReadWriteLogRecord._from_api_log_record(
record=log_record,
resource=self._resource,
instrumentation_scope=self._instrumentation_scope,
)
log_data = LogData(record, self._instrumentation_scope)
self._multi_log_record_processor.on_emit(log_data)
self._multi_log_record_processor.on_emit(writable_record)
class LoggerProvider(APILoggerProvider):
@@ -831,7 +733,7 @@ class LoggerProvider(APILoggerProvider):
version: str | None = None,
schema_url: str | None = None,
attributes: _ExtendedAttributes | None = None,
) -> Logger:
) -> APILogger:
if self._disabled:
return NoOpLogger(
name,

View File

@@ -20,6 +20,8 @@ import sys
from os import environ, linesep
from typing import IO, Callable, Optional, Sequence
from typing_extensions import deprecated
from opentelemetry.context import (
_SUPPRESS_INSTRUMENTATION_KEY,
attach,
@@ -27,9 +29,9 @@ from opentelemetry.context import (
set_value,
)
from opentelemetry.sdk._logs import (
LogData,
LogRecord,
LogRecordProcessor,
ReadableLogRecord,
ReadWriteLogRecord,
)
from opentelemetry.sdk._shared_internal import BatchProcessor, DuplicateFilter
from opentelemetry.sdk.environment_variables import (
@@ -38,6 +40,7 @@ from opentelemetry.sdk.environment_variables import (
OTEL_BLRP_MAX_QUEUE_SIZE,
OTEL_BLRP_SCHEDULE_DELAY,
)
from opentelemetry.sdk.resources import Resource
_DEFAULT_SCHEDULE_DELAY_MILLIS = 5000
_DEFAULT_MAX_EXPORT_BATCH_SIZE = 512
@@ -50,12 +53,20 @@ _logger = logging.getLogger(__name__)
_logger.addFilter(DuplicateFilter())
class LogRecordExportResult(enum.Enum):
SUCCESS = 0
FAILURE = 1
@deprecated(
"Use LogRecordExportResult. Since logs are not stable yet this WILL be removed in future releases."
)
class LogExportResult(enum.Enum):
SUCCESS = 0
FAILURE = 1
class LogExporter(abc.ABC):
class LogRecordExporter(abc.ABC):
"""Interface for exporting logs.
Interface to be implemented by services that want to export logs received
in their own format.
@@ -64,10 +75,12 @@ class LogExporter(abc.ABC):
"""
@abc.abstractmethod
def export(self, batch: Sequence[LogData]):
def export(
self, batch: Sequence[ReadableLogRecord]
) -> LogRecordExportResult:
"""Exports a batch of logs.
Args:
batch: The list of `LogData` objects to be exported
batch: The list of `ReadableLogRecord` objects to be exported
Returns:
The result of the export
"""
@@ -80,8 +93,15 @@ class LogExporter(abc.ABC):
"""
class ConsoleLogExporter(LogExporter):
"""Implementation of :class:`LogExporter` that prints log records to the
@deprecated(
"Use LogRecordExporter. Since logs are not stable yet this WILL be removed in future releases."
)
class LogExporter(LogRecordExporter):
pass
class ConsoleLogRecordExporter(LogRecordExporter):
"""Implementation of :class:`LogRecordExporter` that prints log records to the
console.
This class can be used for diagnostic purposes. It prints the exported
@@ -91,39 +111,59 @@ class ConsoleLogExporter(LogExporter):
def __init__(
self,
out: IO = sys.stdout,
formatter: Callable[[LogRecord], str] = lambda record: record.to_json()
+ linesep,
formatter: Callable[
[ReadableLogRecord], str
] = lambda record: record.to_json() + linesep,
):
self.out = out
self.formatter = formatter
def export(self, batch: Sequence[LogData]):
for data in batch:
self.out.write(self.formatter(data.log_record))
def export(self, batch: Sequence[ReadableLogRecord]):
for log_record in batch:
self.out.write(self.formatter(log_record))
self.out.flush()
return LogExportResult.SUCCESS
return LogRecordExportResult.SUCCESS
def shutdown(self):
pass
@deprecated(
"Use ConsoleLogRecordExporter. Since logs are not stable yet this WILL be removed in future releases."
)
class ConsoleLogExporter(ConsoleLogRecordExporter):
pass
class SimpleLogRecordProcessor(LogRecordProcessor):
"""This is an implementation of LogRecordProcessor which passes
received logs in the export-friendly LogData representation to the
configured LogExporter, as soon as they are emitted.
received logs directly to the configured LogRecordExporter, as soon as they are emitted.
"""
def __init__(self, exporter: LogExporter):
def __init__(self, exporter: LogRecordExporter):
self._exporter = exporter
self._shutdown = False
def on_emit(self, log_data: LogData):
def on_emit(self, log_record: ReadWriteLogRecord):
if self._shutdown:
_logger.warning("Processor is already shutdown, ignoring call")
return
token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
try:
self._exporter.export((log_data,))
# Convert ReadWriteLogRecord to ReadableLogRecord before exporting
# Note: resource should not be None at this point as it's set during Logger.emit()
resource = (
log_record.resource
if log_record.resource is not None
else Resource.create({})
)
readable_log_record = ReadableLogRecord(
log_record=log_record.log_record,
resource=resource,
instrumentation_scope=log_record.instrumentation_scope,
limits=log_record.limits,
)
self._exporter.export((readable_log_record,))
except Exception: # pylint: disable=broad-exception-caught
_logger.exception("Exception while exporting logs.")
detach(token)
@@ -138,8 +178,7 @@ class SimpleLogRecordProcessor(LogRecordProcessor):
class BatchLogRecordProcessor(LogRecordProcessor):
"""This is an implementation of LogRecordProcessor which creates batches of
received logs in the export-friendly LogData representation and
send to the configured LogExporter, as soon as they are emitted.
received logs and sends them to the configured LogRecordExporter.
`BatchLogRecordProcessor` is configurable with the following environment
variables which correspond to constructor parameters:
@@ -154,7 +193,7 @@ class BatchLogRecordProcessor(LogRecordProcessor):
def __init__(
self,
exporter: LogExporter,
exporter: LogRecordExporter,
schedule_delay_millis: float | None = None,
max_export_batch_size: int | None = None,
export_timeout_millis: float | None = None,
@@ -191,8 +230,21 @@ class BatchLogRecordProcessor(LogRecordProcessor):
"Log",
)
def on_emit(self, log_data: LogData) -> None:
return self._batch_processor.emit(log_data)
def on_emit(self, log_record: ReadWriteLogRecord) -> None:
# Convert ReadWriteLogRecord to ReadableLogRecord before passing to BatchProcessor
# Note: resource should not be None at this point as it's set during Logger.emit()
resource = (
log_record.resource
if log_record.resource is not None
else Resource.create({})
)
readable_log_record = ReadableLogRecord(
log_record=log_record.log_record,
resource=resource,
instrumentation_scope=log_record.instrumentation_scope,
limits=log_record.limits,
)
return self._batch_processor.emit(readable_log_record)
def shutdown(self):
return self._batch_processor.shutdown()

View File

@@ -15,12 +15,17 @@
import threading
import typing
from opentelemetry.sdk._logs import LogData
from opentelemetry.sdk._logs.export import LogExporter, LogExportResult
from typing_extensions import deprecated
from opentelemetry.sdk._logs import ReadableLogRecord
from opentelemetry.sdk._logs.export import (
LogRecordExporter,
LogRecordExportResult,
)
class InMemoryLogExporter(LogExporter):
"""Implementation of :class:`.LogExporter` that stores logs in memory.
class InMemoryLogRecordExporter(LogRecordExporter):
"""Implementation of :class:`.LogRecordExporter` that stores logs in memory.
This class can be used for testing purposes. It stores the exported logs
in a list in memory that can be retrieved using the
@@ -36,16 +41,25 @@ class InMemoryLogExporter(LogExporter):
with self._lock:
self._logs.clear()
def get_finished_logs(self) -> typing.Tuple[LogData, ...]:
def get_finished_logs(self) -> typing.Tuple[ReadableLogRecord, ...]:
with self._lock:
return tuple(self._logs)
def export(self, batch: typing.Sequence[LogData]) -> LogExportResult:
def export(
self, batch: typing.Sequence[ReadableLogRecord]
) -> LogRecordExportResult:
if self._stopped:
return LogExportResult.FAILURE
return LogRecordExportResult.FAILURE
with self._lock:
self._logs.extend(batch)
return LogExportResult.SUCCESS
return LogRecordExportResult.SUCCESS
def shutdown(self) -> None:
self._stopped = True
@deprecated(
"Use InMemoryLogRecordExporter. Since logs are not stable yet this WILL be removed in future releases."
)
class InMemoryLogExporter(InMemoryLogRecordExporter):
pass

View File

@@ -15,21 +15,29 @@
from opentelemetry.sdk._logs._internal.export import (
BatchLogRecordProcessor,
ConsoleLogExporter,
ConsoleLogRecordExporter,
LogExporter,
LogExportResult,
LogRecordExporter,
LogRecordExportResult,
SimpleLogRecordProcessor,
)
# The point module is not in the export directory to avoid a circular import.
from opentelemetry.sdk._logs._internal.export.in_memory_log_exporter import (
InMemoryLogExporter,
InMemoryLogRecordExporter,
)
__all__ = [
"BatchLogRecordProcessor",
"ConsoleLogExporter",
"ConsoleLogRecordExporter",
"LogExporter",
"LogRecordExporter",
"LogExportResult",
"LogRecordExportResult",
"SimpleLogRecordProcessor",
"InMemoryLogExporter",
"InMemoryLogRecordExporter",
]

View File

@@ -145,7 +145,7 @@ class ConsoleMetricExporter(MetricExporter):
self,
out: IO = stdout,
formatter: Callable[
["opentelemetry.sdk.metrics.export.MetricsData"], str
[MetricsData], str
] = lambda metrics_data: metrics_data.to_json() + linesep,
preferred_temporality: dict[type, AggregationTemporality]
| None = None,
@@ -362,7 +362,7 @@ class MetricReader(ABC):
@abstractmethod
def _receive_metrics(
self,
metrics_data: "opentelemetry.sdk.metrics.export.MetricsData",
metrics_data: MetricsData,
timeout_millis: float = 10_000,
**kwargs,
) -> None:
@@ -406,13 +406,11 @@ class InMemoryMetricReader(MetricReader):
preferred_aggregation=preferred_aggregation,
)
self._lock = RLock()
self._metrics_data: "opentelemetry.sdk.metrics.export.MetricsData" = (
None
)
self._metrics_data: MetricsData = None
def get_metrics_data(
self,
) -> Optional["opentelemetry.sdk.metrics.export.MetricsData"]:
) -> Optional[MetricsData]:
"""Reads and returns current metrics from the SDK"""
with self._lock:
self.collect()
@@ -422,7 +420,7 @@ class InMemoryMetricReader(MetricReader):
def _receive_metrics(
self,
metrics_data: "opentelemetry.sdk.metrics.export.MetricsData",
metrics_data: MetricsData,
timeout_millis: float = 10_000,
**kwargs,
) -> None:

View File

@@ -13,8 +13,10 @@
# limitations under the License.
from opentelemetry.sdk.metrics._internal.export import (
from opentelemetry.sdk.metrics._internal.aggregation import (
AggregationTemporality,
)
from opentelemetry.sdk.metrics._internal.export import (
ConsoleMetricExporter,
InMemoryMetricReader,
MetricExporter,

View File

@@ -63,7 +63,7 @@ class SpanExporter:
def export(
self, spans: typing.Sequence[ReadableSpan]
) -> "SpanExportResult":
) -> "SpanExportResult": # pyright: ignore[reportReturnType]
"""Exports a batch of telemetry data.
Args:
@@ -79,7 +79,7 @@ class SpanExporter:
Called when the SDK is shut down.
"""
def force_flush(self, timeout_millis: int = 30000) -> bool:
def force_flush(self, timeout_millis: int = 30000) -> bool: # pyright: ignore[reportReturnType]
"""Hint to ensure that the export of any spans the exporter has received
prior to the call to ForceFlush SHOULD be completed as soon as possible, preferably
before returning from this method.
@@ -102,7 +102,7 @@ class SimpleSpanProcessor(SpanProcessor):
pass
def on_end(self, span: ReadableSpan) -> None:
if not span.context.trace_flags.sampled:
if not (span.context and span.context.trace_flags.sampled):
return
token = attach(set_value(_SUPPRESS_INSTRUMENTATION_KEY, True))
try:
@@ -188,7 +188,7 @@ class BatchSpanProcessor(SpanProcessor):
pass
def on_end(self, span: ReadableSpan) -> None:
if not span.context.trace_flags.sampled:
if not (span.context and span.context.trace_flags.sampled):
return
self._batch_processor.emit(span)

View File

@@ -17,7 +17,7 @@ from typing import Optional
from typing_extensions import deprecated
from opentelemetry.attributes import BoundedAttributes
from opentelemetry.util.types import Attributes
from opentelemetry.util.types import Attributes, _ExtendedAttributes
class InstrumentationInfo:
@@ -94,7 +94,7 @@ class InstrumentationScope:
name: str,
version: Optional[str] = None,
schema_url: Optional[str] = None,
attributes: Optional[Attributes] = None,
attributes: Optional[_ExtendedAttributes] = None,
) -> None:
self._name = name
self._version = version

View File

@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "1.38.0"
__version__ = "1.39.1"