Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial overall structure and classes for logs sdk #1894

Merged
merged 4 commits into from
Jun 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import atexit
from typing import Any, Optional

from opentelemetry.sdk.logs.severity import SeverityNumber
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
from opentelemetry.trace.span import TraceFlags
from opentelemetry.util.types import Attributes


class LogRecord:
"""A LogRecord instance represents an event being logged.

LogRecord instances are created and emitted via `LogEmitter`
every time something is logged. They contain all the information
pertinent to the event being logged.
"""

def __init__(
self,
timestamp: Optional[int] = None,
trace_id: Optional[int] = None,
span_id: Optional[int] = None,
trace_flags: Optional[TraceFlags] = None,
severity_text: Optional[str] = None,
severity_number: Optional[SeverityNumber] = None,
name: Optional[str] = None,
body: Optional[Any] = None,
resource: Optional[Resource] = None,
attributes: Optional[Attributes] = None,
):
self.timestamp = timestamp
self.trace_id = trace_id
self.span_id = span_id
self.trace_flags = trace_flags
self.severity_text = severity_text
self.severity_number = severity_number
self.name = name
self.body = body
self.resource = resource
self.attributes = attributes

def __eq__(self, other: object) -> bool:
if not isinstance(other, LogRecord):
return NotImplemented
return self.__dict__ == other.__dict__


class LogData:
"""Readable LogRecord data plus associated InstrumentationLibrary."""

def __init__(
self,
log_record: LogRecord,
instrumentation_info: InstrumentationInfo,
):
self.log_record = log_record
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might need to be read-only in the future. Could save for separate PR.

self.instrumentation_info = instrumentation_info


class LogProcessor(abc.ABC):
"""Interface to hook the log record emitting action.

Log processors can be registered directly using
:func:`LogEmitterProvider.add_log_processor` and they are invoked
in the same order as they were registered.
codeboten marked this conversation as resolved.
Show resolved Hide resolved
"""

@abc.abstractmethod
def emit(self, log_data: LogData):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if the SpanProcessor would also have a method called emit for consistency. No action required from this comment, just a thought.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what would emit mean in the context of span?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect it to mean the same thing as in the context of logs and metrics, that we're emitting the data from the processor. Currently this is called "on_end" in the span processor.

"""Emits the `LogData`"""

@abc.abstractmethod
def shutdown(self):
"""Called when a :class:`opentelemetry.sdk.logs.LogEmitter` is shutdown"""

@abc.abstractmethod
def force_flush(self, timeout_millis: int = 30000):
"""Export all the received logs to the configured Exporter that have not yet
been exported.

Args:
timeout_millis: The maximum amount of time to wait for logs to be
exported.

Returns:
False if the timeout is exceeded, True otherwise.
"""


class LogEmitter:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a subclass of LogProcessor?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I added a diagram below. I hope it helps.

# TODO: Add multi_log_processor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add documentation as to what a log emitter is

def __init__(
self,
resource: Resource,
instrumentation_info: InstrumentationInfo,
):
self._resource = resource
self._instrumentation_info = instrumentation_info

def emit(self, record: LogRecord):
ocelotl marked this conversation as resolved.
Show resolved Hide resolved
# TODO: multi_log_processor.emit
pass

def flush(self):
# TODO: multi_log_processor.force_flush
pass


class LogEmitterProvider:
# TODO: Add multi_log_processor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto to documentation here.

def __init__(
self,
resource: Resource = Resource.create(),
shutdown_on_exit: bool = True,
):
self._resource = resource
self._at_exit_handler = None
if shutdown_on_exit:
self._at_exit_handler = atexit.register(self.shutdown)

def get_log_emitter(
self,
instrumenting_module_name: str,
instrumenting_module_verison: str = "",
) -> LogEmitter:
return LogEmitter(
self._resource,
InstrumentationInfo(
instrumenting_module_name, instrumenting_module_verison
),
)

def add_log_processor(self, log_processor: LogProcessor):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this weird? I mean, this is a method of the LogEmitterProvider class

Copy link
Member Author

@srikanthccv srikanthccv Jun 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand your question. How else would we add a log processor?

"""Registers a new :class:`LogProcessor` for this `LogEmitterProvider` instance.

The log processors are invoked in the same order they are registered.
"""
# TODO: multi_log_processor.add_log_processor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO necessary here?


def shutdown(self):
"""Shuts down the log processors."""
# TODO: multi_log_processor.shutdown
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO necessary here?

if self._at_exit_handler is not None:
atexit.unregister(self._at_exit_handler)
self._at_exit_handler = None

def force_flush(self, timeout_millis: int = 30000) -> bool:
"""Force flush the log processors.

Args:
timeout_millis: The maximum amount of time to wait for logs to be
exported.

Returns:
True if all the log processors flushes the logs within timeout,
False otherwise.
"""
# TODO: multi_log_processor.force_flush
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO necessary here?

53 changes: 53 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/logs/export/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import enum
from typing import Sequence

from opentelemetry.sdk.logs import LogData


class LogExportResult(enum.Enum):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this part of the spec?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think this will be eventually added to spec. Even I can send PR to get this ExportResult included in spec if you want.

SUCCESS = 0
FAILURE = 1


class LogExporter(abc.ABC):
"""Interface for exporting logs.

Interface to be implemented by services that want to export logs received
in their own format.

To export data this MUST be registered to the :class`opentelemetry.sdk.logs.LogEmitter` using a
log processor.
"""

@abc.abstractmethod
def export(self, batch: Sequence[LogData]):
"""Exports a batch of logs.

Args:
batch: The list of `LogData` objects to be exported

Returns:
The result of the export
"""

@abc.abstractmethod
def shutdown(self):
"""Shuts down the exporter.

Called when the SDK is shut down.
"""
56 changes: 56 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/logs/severity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import enum


class SeverityNumber(enum.Enum):
"""Numerical value of severity.

Smaller numerical values correspond to less severe events
(such as debug events), larger numerical values correspond
to more severe events (such as errors and critical events).

See the `Log Data Model`_ spec for more info and how to map the
severity from source format to OTLP Model.

.. _Log Data Model:
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-severitynumber
"""

UNSPECIFIED = 0
TRACE = 1
TRACE2 = 2
TRACE3 = 3
TRACE4 = 4
DEBUG = 5
DEBUG2 = 6
DEBUG3 = 7
DEBUG4 = 8
INFO = 9
INFO2 = 10
INFO3 = 11
INFO4 = 12
WARN = 13
WARN2 = 14
WARN3 = 15
WARN4 = 16
ERROR = 17
ERROR2 = 18
ERROR3 = 19
ERROR4 = 20
FATAL = 21
FATAL2 = 22
FATAL3 = 23
FATAL4 = 24