-
Notifications
You must be signed in to change notification settings - Fork 635
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 OTLPHandler for standard library logging module #1903
Add OTLPHandler for standard library logging module #1903
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good, just a typo and a question about naming. Ant thoughts on whether the OTLPHandler should be part of the SDK or a separate OTLP logging package?
@@ -111,6 +113,48 @@ def force_flush(self, timeout_millis: int = 30000): | |||
""" | |||
|
|||
|
|||
class OTLPHandler(logging.Handler): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be called an emitter rather than a handler? Or is this a convenience handler to allow users to add this handler to their python root handler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is one of the the handlers users can attach to root logger. I believe the name Handler makes more sense here.
@@ -121,6 +165,10 @@ def __init__( | |||
self._resource = resource | |||
self._instrumentation_info = instrumentation_info | |||
|
|||
@property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be on the provider
level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this should be on provider as well.
Co-authored-by: alrex <alrex.boten@gmail.com>
My initial thoughts were since this is Handler for logging module maybe we can provide this as a part of SDK and other third party libs as separate packages. No strong opinions. |
One other argument to provide OTLPHandler as part of the SDK so that the users doesn’t need to find yet another separate package for something common. |
…lemetry-python into logging-otlp-handler
|
||
|
||
_STD_TO_OTLP = { | ||
10: SeverityNumber.DEBUG, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come the interim levels defined like this? (11-19, 21-29, etc.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious will numbers in "std" ever be anything other than 0, 10, 20, 30, 40, 50?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This follows the log data model specification for mapping source format https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#mapping-of-severitynumber. The step value from one level to another level in logging module is 10 but the it is 5 in log data model, so it is clearly many-to-one mapping for some levels. Higher the value, higher the severity. In general these intermediary levels don't come into play but logging library allows programmers to define the custom log levels if they need, so this addresses that case. For example I would define some scary
b/w error
and critical
level like below
>>> import logging
>>> SCARY = 45
>>> logging.addLevelName(SCARY, "SCARY")
>>> def scary(self, message, *args, **kws):
... self._log(SCARY, message, args, **kws)
...
>>> logging.Logger.scary = scary
>>> logger = logging.getLogger(__name__)
>>> logger.scary("Boo")
Boo
def _translate(self, record: logging.LogRecord) -> LogRecord: | ||
timestamp = int(record.created * 1e9) | ||
span_context = get_current_span().get_span_context() | ||
# TODO: attributes (or resource attributes?) from record metadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, I believe attributes can be populated from both custom and resources.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So logging.LogRecord
https://docs.python.org/3/library/logging.html#logrecord-attributes has a number of attributes like funcName, filename, and processName etc.. I was wondering if we should just make these as OTLP log record attributes https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-attributes or log record resource attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For logging.LogRecord
attributes, it makes more sense to be part of the OTLP log record attributes.
When I was saying "custom" attributes, I meant the ability for users to add custom attributes to the LogRecord
themselves (it is a feature in OpenCensus as well), but may not be defined yet in the specs.
Not any concern for now, just something to be mindful of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logging.LogRecord
has an (optional) extra
attribute that holds arbitrary user-injected attributes.
This is not super well documented, but the docs can be found a couple paragraphs down from here.
From a users perspective, it looks like this:
logging.debug("msg", extra={...})
Would it be possible to map this to OTLP log record attributes? You could take the top-level attributes of extra
(which would be my preference), or require them to be nested under a specific key (e.g. logging.debug("msg", extra={"oltp-attributes": {...}})
).
self._log_emitter = log_emitter or get_log_emitter(__name__) | ||
|
||
def _translate(self, record: logging.LogRecord) -> LogRecord: | ||
timestamp = int(record.created * 1e9) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for your information, this PEP may be relevant here.
* Add initial overall structure and classes for logs sdk (#1894) * Add global LogEmitterProvider and convenience function get_log_emitter (#1901) * Add OTLPHandler for standard library logging module (#1903) * Add LogProcessors implementation (#1916) * Fix typos in test_handler.py (#1953) * Add support for OTLP Log exporter (#1943) * Add support for user defined attributes in OTLPHandler (#1952) * use timeout in force_flush (#2118) * use timeout in force_flush * fix lint * Update opentelemetry-sdk/src/opentelemetry/sdk/logs/export/__init__.py Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com> * fix lint Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com> * add a ConsoleExporter for logging (#2099) Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com> * Update SDK docs and Add example with OTEL collector logging (debug) exporter (#2050) * Fix exception in severity number transformation (#2208) * Fix exception with warning message transformation * Fix lint * Fix lint * fstring * Demonstrate how to set the Resource for LogEmitterProvider (#2209) * Demonstrate how to set the Resource for LogEmitterProvider Added a Resource to the logs example to make it more complete. Previously it was using the built-in Resource. Now it adds the service.name and service.instance.id attributes. The resulting emitted log records look like this: ``` Resource labels: -> telemetry.sdk.language: STRING(python) -> telemetry.sdk.name: STRING(opentelemetry) -> telemetry.sdk.version: STRING(1.5.0) -> service.name: STRING(shoppingcart) -> service.instance.id: STRING(instance-12) InstrumentationLibraryLogs #0 InstrumentationLibrary __main__ 0.1 LogRecord #0 Timestamp: 2021-10-14 18:33:43.425820928 +0000 UTC Severity: ERROR ShortName: Body: Hyderabad, we have a major problem. Trace ID: ce1577e4a703f42d569e72593ad71888 Span ID: f8908ac4258ceff6 Flags: 1 ``` * Fix linting * Use batch processor in example (#2225) * move logs to _logs (#2240) * move logs to _logs * fix lint * move log_exporter to _log_exporter as it's still experimental (#2252) Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com> Co-authored-by: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Co-authored-by: Leighton Chen <lechen@microsoft.com> Co-authored-by: Tigran Najaryan <4194920+tigrannajaryan@users.noreply.github.com> Co-authored-by: Owais Lone <owais@users.noreply.github.com>
Description
Part-3 of #1890. Builds on top of #1901. This adds OTLPHandler for logging module. Python logging module provides a way to send the log records to the one or more appropriate destinations. More info about handler https://docs.python.org/3/library/logging.html#handler-objects