Skip to content

Commit

Permalink
Add support for user defined attributes in OTLPHandler (open-telemetr…
Browse files Browse the repository at this point in the history
  • Loading branch information
adriangb authored and lzchen committed Oct 15, 2021
1 parent b972db0 commit 8ef53c0
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#1893](https://github.com/open-telemetry/opentelemetry-python/pull/1893))

### Added
- Give OTLPHandler the ability to process attributes
([#1952](https://github.com/open-telemetry/opentelemetry-python/pull/1952))
- Add global LogEmitterProvider and convenience function get_log_emitter
([#1901](https://github.com/open-telemetry/opentelemetry-python/pull/1901))
- Add OTLPHandler for standard library logging module
Expand Down
45 changes: 42 additions & 3 deletions opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,20 +244,59 @@ def force_flush(self, timeout_millis: int = 30000) -> bool:
return True


# skip natural LogRecord attributes
# http://docs.python.org/library/logging.html#logrecord-attributes
_RESERVED_ATTRS = frozenset(
(
"asctime",
"args",
"created",
"exc_info",
"exc_text",
"filename",
"funcName",
"getMessage",
"levelname",
"levelno",
"lineno",
"module",
"msecs",
"msg",
"name",
"pathname",
"process",
"processName",
"relativeCreated",
"stack_info",
"thread",
"threadName",
)
)


class OTLPHandler(logging.Handler):
"""A handler class which writes logging records, in OTLP format, to
a network destination or file.
"""

def __init__(self, level=logging.NOTSET, log_emitter=None) -> None:
def __init__(
self,
level=logging.NOTSET,
log_emitter=None,
) -> None:
super().__init__(level=level)
self._log_emitter = log_emitter or get_log_emitter(__name__)

@staticmethod
def _get_attributes(record: logging.LogRecord) -> Attributes:
return {
k: v for k, v in vars(record).items() if k not in _RESERVED_ATTRS
}

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
attributes: Attributes = {}
attributes = self._get_attributes(record)
severity_number = std_to_otlp(record.levelno)
return LogRecord(
timestamp=timestamp,
Expand Down
12 changes: 12 additions & 0 deletions opentelemetry-sdk/tests/logs/test_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ def test_log_record_no_span_context(self):
log_record.trace_flags, INVALID_SPAN_CONTEXT.trace_flags
)

def test_log_record_user_attributes(self):
"""Attributes can be injected into logs by adding them to the LogRecord"""
emitter_mock = Mock(spec=LogEmitter)
logger = get_logger(log_emitter=emitter_mock)
# Assert emit gets called for warning message
logger.warning("Warning message", extra={"http.status_code": 200})
args, _ = emitter_mock.emit.call_args_list[0]
log_record = args[0]

self.assertIsNotNone(log_record)
self.assertEqual(log_record.attributes, {"http.status_code": 200})

def test_log_record_trace_correlation(self):
emitter_mock = Mock(spec=LogEmitter)
logger = get_logger(log_emitter=emitter_mock)
Expand Down

0 comments on commit 8ef53c0

Please sign in to comment.