Skip to content

Commit da15594

Browse files
authored
Fixup LogRecord.emit signature (#4737)
* Fixup Logger.emit signature * Use overload and deprecate the Logger.emit interface with just logrecord * Test and fixes * Remember to add resource when emitting * Lint fixes * Add changelog * Type cleanups * Silence pylint warnings * Drop deprecations
1 parent b63f643 commit da15594

File tree

6 files changed

+248
-23
lines changed

6 files changed

+248
-23
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
- Add `rstcheck` to pre-commit to stop introducing invalid RST
1616
([#4755](https://github.com/open-telemetry/opentelemetry-python/pull/4755))
17+
- logs: extend Logger.emit to accept separated keyword arguments
18+
([#4737](https://github.com/open-telemetry/opentelemetry-python/pull/4737))
1719

1820
## Version 1.37.0/0.58b0 (2025-09-11)
1921

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

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
.. versionadded:: 1.15.0
3434
"""
3535

36+
from __future__ import annotations
37+
3638
from abc import ABC, abstractmethod
3739
from logging import getLogger
3840
from os import environ
@@ -143,8 +145,40 @@ def __init__(
143145
self._schema_url = schema_url
144146
self._attributes = attributes
145147

148+
@overload
149+
def emit(
150+
self,
151+
*,
152+
timestamp: int | None = None,
153+
observed_timestamp: int | None = None,
154+
context: Context | None = None,
155+
severity_number: SeverityNumber | None = None,
156+
severity_text: str | None = None,
157+
body: AnyValue | None = None,
158+
attributes: _ExtendedAttributes | None = None,
159+
event_name: str | None = None,
160+
) -> None: ...
161+
162+
@overload
163+
def emit(
164+
self,
165+
record: LogRecord,
166+
) -> None: ...
167+
146168
@abstractmethod
147-
def emit(self, record: "LogRecord") -> None:
169+
def emit(
170+
self,
171+
record: LogRecord | None = None,
172+
*,
173+
timestamp: int | None = None,
174+
observed_timestamp: int | None = None,
175+
context: Context | None = None,
176+
severity_number: SeverityNumber | None = None,
177+
severity_text: str | None = None,
178+
body: AnyValue | None = None,
179+
attributes: _ExtendedAttributes | None = None,
180+
event_name: str | None = None,
181+
) -> None:
148182
"""Emits a :class:`LogRecord` representing a log to the processing pipeline."""
149183

150184

@@ -154,7 +188,39 @@ class NoOpLogger(Logger):
154188
All operations are no-op.
155189
"""
156190

157-
def emit(self, record: "LogRecord") -> None:
191+
@overload
192+
def emit(
193+
self,
194+
*,
195+
timestamp: int | None = None,
196+
observed_timestamp: int | None = None,
197+
context: Context | None = None,
198+
severity_number: SeverityNumber | None = None,
199+
severity_text: str | None = None,
200+
body: AnyValue | None = None,
201+
attributes: _ExtendedAttributes | None = None,
202+
event_name: str | None = None,
203+
) -> None: ...
204+
205+
@overload
206+
def emit( # pylint:disable=arguments-differ
207+
self,
208+
record: LogRecord,
209+
) -> None: ...
210+
211+
def emit(
212+
self,
213+
record: LogRecord | None = None,
214+
*,
215+
timestamp: int | None = None,
216+
observed_timestamp: int | None = None,
217+
context: Context | None = None,
218+
severity_number: SeverityNumber | None = None,
219+
severity_text: str | None = None,
220+
body: AnyValue | None = None,
221+
attributes: _ExtendedAttributes | None = None,
222+
event_name: str | None = None,
223+
) -> None:
158224
pass
159225

160226

@@ -188,8 +254,52 @@ def _logger(self) -> Logger:
188254
return self._real_logger
189255
return self._noop_logger
190256

191-
def emit(self, record: LogRecord) -> None:
192-
self._logger.emit(record)
257+
@overload
258+
def emit(
259+
self,
260+
*,
261+
timestamp: int | None = None,
262+
observed_timestamp: int | None = None,
263+
context: Context | None = None,
264+
severity_number: SeverityNumber | None = None,
265+
severity_text: str | None = None,
266+
body: AnyValue | None = None,
267+
attributes: _ExtendedAttributes | None = None,
268+
event_name: str | None = None,
269+
) -> None: ...
270+
271+
@overload
272+
def emit( # pylint:disable=arguments-differ
273+
self,
274+
record: LogRecord,
275+
) -> None: ...
276+
277+
def emit(
278+
self,
279+
record: LogRecord | None = None,
280+
*,
281+
timestamp: int | None = None,
282+
observed_timestamp: int | None = None,
283+
context: Context | None = None,
284+
severity_number: SeverityNumber | None = None,
285+
severity_text: str | None = None,
286+
body: AnyValue | None = None,
287+
attributes: _ExtendedAttributes | None = None,
288+
event_name: str | None = None,
289+
) -> None:
290+
if record:
291+
self._logger.emit(record)
292+
else:
293+
self._logger.emit(
294+
timestamp=timestamp,
295+
observed_timestamp=observed_timestamp,
296+
context=context,
297+
severity_number=severity_number,
298+
severity_text=severity_text,
299+
body=body,
300+
attributes=attributes,
301+
event_name=event_name,
302+
)
193303

194304

195305
class LoggerProvider(ABC):

opentelemetry-api/tests/logs/test_proxy.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,19 @@ def get_logger(
3434

3535

3636
class LoggerTest(_logs.NoOpLogger):
37-
def emit(self, record: _logs.LogRecord) -> None:
37+
def emit(
38+
self,
39+
record: typing.Optional[_logs.LogRecord] = None,
40+
*,
41+
timestamp=None,
42+
observed_timestamp=None,
43+
context=None,
44+
severity_number=None,
45+
severity_text=None,
46+
body=None,
47+
attributes=None,
48+
event_name=None,
49+
) -> None:
3850
pass
3951

4052

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ def _get_attributes(record: logging.LogRecord) -> _ExtendedAttributes:
599599
)
600600
return attributes
601601

602-
def _translate(self, record: logging.LogRecord) -> LogRecord:
602+
def _translate(self, record: logging.LogRecord) -> dict:
603603
timestamp = int(record.created * 1e9)
604604
observered_timestamp = time_ns()
605605
attributes = self._get_attributes(record)
@@ -633,17 +633,15 @@ def _translate(self, record: logging.LogRecord) -> LogRecord:
633633
"WARN" if record.levelname == "WARNING" else record.levelname
634634
)
635635

636-
logger = get_logger(record.name, logger_provider=self._logger_provider)
637-
return LogRecord(
638-
timestamp=timestamp,
639-
observed_timestamp=observered_timestamp,
640-
context=get_current() or None,
641-
severity_text=level_name,
642-
severity_number=severity_number,
643-
body=body,
644-
resource=logger.resource,
645-
attributes=attributes,
646-
)
636+
return {
637+
"timestamp": timestamp,
638+
"observed_timestamp": observered_timestamp,
639+
"context": get_current() or None,
640+
"severity_text": level_name,
641+
"severity_number": severity_number,
642+
"body": body,
643+
"attributes": attributes,
644+
}
647645

648646
def emit(self, record: logging.LogRecord) -> None:
649647
"""
@@ -653,7 +651,7 @@ def emit(self, record: logging.LogRecord) -> None:
653651
"""
654652
logger = get_logger(record.name, logger_provider=self._logger_provider)
655653
if not isinstance(logger, NoOpLogger):
656-
logger.emit(self._translate(record))
654+
logger.emit(**self._translate(record))
657655

658656
def flush(self) -> None:
659657
"""
@@ -692,16 +690,63 @@ def __init__(
692690
def resource(self):
693691
return self._resource
694692

695-
def emit(self, record: APILogRecord):
693+
@overload
694+
def emit(
695+
self,
696+
*,
697+
timestamp: int | None = None,
698+
observed_timestamp: int | None = None,
699+
context: Context | None = None,
700+
severity_number: SeverityNumber | None = None,
701+
severity_text: str | None = None,
702+
body: AnyValue | None = None,
703+
attributes: _ExtendedAttributes | None = None,
704+
event_name: str | None = None,
705+
) -> None: ...
706+
707+
@overload
708+
def emit( # pylint:disable=arguments-differ
709+
self,
710+
record: APILogRecord,
711+
) -> None: ...
712+
713+
def emit(
714+
self,
715+
record: APILogRecord | None = None,
716+
*,
717+
timestamp: int | None = None,
718+
observed_timestamp: int | None = None,
719+
context: Context | None = None,
720+
severity_text: str | None = None,
721+
severity_number: SeverityNumber | None = None,
722+
body: AnyValue | None = None,
723+
attributes: _ExtendedAttributes | None = None,
724+
event_name: str | None = None,
725+
):
696726
"""Emits the :class:`LogData` by associating :class:`LogRecord`
697727
and instrumentation info.
698728
"""
699-
if not isinstance(record, LogRecord):
729+
730+
if not record:
731+
record = LogRecord(
732+
timestamp=timestamp,
733+
observed_timestamp=observed_timestamp,
734+
context=context,
735+
severity_text=severity_text,
736+
severity_number=severity_number,
737+
body=body,
738+
attributes=attributes,
739+
event_name=event_name,
740+
resource=self._resource,
741+
)
742+
elif not isinstance(record, LogRecord):
700743
# pylint:disable=protected-access
701744
record = LogRecord._from_api_log_record(
702745
record=record, resource=self._resource
703746
)
747+
704748
log_data = LogData(record, self._instrumentation_scope)
749+
705750
self._multi_log_record_processor.on_emit(log_data)
706751

707752

opentelemetry-sdk/tests/logs/test_logs.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818
from unittest.mock import Mock, patch
1919

2020
from opentelemetry._logs import LogRecord as APILogRecord
21-
from opentelemetry.sdk._logs import Logger, LoggerProvider, LogRecord
21+
from opentelemetry._logs import SeverityNumber
22+
from opentelemetry.context import get_current
23+
from opentelemetry.sdk._logs import (
24+
Logger,
25+
LoggerProvider,
26+
LogRecord,
27+
)
2228
from opentelemetry.sdk._logs._internal import (
2329
NoOpLogger,
2430
SynchronousMultiLogRecordProcessor,
@@ -116,6 +122,7 @@ def test_can_emit_logrecord(self):
116122
log_record_processor_mock.on_emit.assert_called_once()
117123
log_data = log_record_processor_mock.on_emit.call_args.args[0]
118124
self.assertTrue(isinstance(log_data.log_record, LogRecord))
125+
self.assertTrue(log_data.log_record is log_record)
119126

120127
def test_can_emit_api_logrecord(self):
121128
logger, log_record_processor_mock = self._get_logger()
@@ -126,4 +133,41 @@ def test_can_emit_api_logrecord(self):
126133
logger.emit(api_log_record)
127134
log_record_processor_mock.on_emit.assert_called_once()
128135
log_data = log_record_processor_mock.on_emit.call_args.args[0]
129-
self.assertTrue(isinstance(log_data.log_record, LogRecord))
136+
log_record = log_data.log_record
137+
self.assertTrue(isinstance(log_record, LogRecord))
138+
self.assertEqual(log_record.timestamp, None)
139+
self.assertEqual(log_record.observed_timestamp, 0)
140+
self.assertEqual(log_record.context, {})
141+
self.assertEqual(log_record.severity_number, None)
142+
self.assertEqual(log_record.severity_text, None)
143+
self.assertEqual(log_record.body, "a log line")
144+
self.assertEqual(log_record.attributes, {})
145+
self.assertEqual(log_record.event_name, None)
146+
self.assertEqual(log_record.resource, logger.resource)
147+
148+
def test_can_emit_with_keywords_arguments(self):
149+
logger, log_record_processor_mock = self._get_logger()
150+
151+
logger.emit(
152+
timestamp=100,
153+
observed_timestamp=101,
154+
context=get_current(),
155+
severity_number=SeverityNumber.WARN,
156+
severity_text="warn",
157+
body="a body",
158+
attributes={"some": "attributes"},
159+
event_name="event_name",
160+
)
161+
log_record_processor_mock.on_emit.assert_called_once()
162+
log_data = log_record_processor_mock.on_emit.call_args.args[0]
163+
log_record = log_data.log_record
164+
self.assertTrue(isinstance(log_record, LogRecord))
165+
self.assertEqual(log_record.timestamp, 100)
166+
self.assertEqual(log_record.observed_timestamp, 101)
167+
self.assertEqual(log_record.context, {})
168+
self.assertEqual(log_record.severity_number, SeverityNumber.WARN)
169+
self.assertEqual(log_record.severity_text, "warn")
170+
self.assertEqual(log_record.body, "a body")
171+
self.assertEqual(log_record.attributes, {"some": "attributes"})
172+
self.assertEqual(log_record.event_name, "event_name")
173+
self.assertEqual(log_record.resource, logger.resource)

opentelemetry-sdk/tests/test_configurator.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,19 @@ def __init__(self, name, resource, processor):
113113
self.resource = resource
114114
self.processor = processor
115115

116-
def emit(self, record):
116+
def emit(
117+
self,
118+
record=None,
119+
*,
120+
timestamp=None,
121+
observed_timestamp=None,
122+
context=None,
123+
severity_number=None,
124+
severity_text=None,
125+
body=None,
126+
attributes=None,
127+
event_name=None,
128+
):
117129
self.processor.emit(record)
118130

119131

0 commit comments

Comments
 (0)