Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c01f308

Browse files
alrexsrikanthccv
authored andcommitted
add a ConsoleExporter for logging (open-telemetry#2099)
Co-authored-by: Srikanth Chekuri <[email protected]>
1 parent 4714a4e commit c01f308

File tree

3 files changed

+126
-6
lines changed

3 files changed

+126
-6
lines changed

opentelemetry-sdk/src/opentelemetry/sdk/logs/__init__.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import abc
1616
import atexit
1717
import concurrent.futures
18+
import json
1819
import logging
1920
import os
2021
import threading
@@ -25,8 +26,13 @@
2526
)
2627
from opentelemetry.sdk.logs.severity import SeverityNumber, std_to_otlp
2728
from opentelemetry.sdk.resources import Resource
29+
from opentelemetry.sdk.util import ns_to_iso_str
2830
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
29-
from opentelemetry.trace import get_current_span
31+
from opentelemetry.trace import (
32+
format_span_id,
33+
format_trace_id,
34+
get_current_span,
35+
)
3036
from opentelemetry.trace.span import TraceFlags
3137
from opentelemetry.util._providers import _load_provider
3238
from opentelemetry.util._time import _time_ns
@@ -72,6 +78,25 @@ def __eq__(self, other: object) -> bool:
7278
return NotImplemented
7379
return self.__dict__ == other.__dict__
7480

81+
def to_json(self) -> str:
82+
return json.dumps(
83+
{
84+
"body": self.body,
85+
"name": self.name,
86+
"severity_number": repr(self.severity_number),
87+
"severity_text": self.severity_text,
88+
"attributes": self.attributes,
89+
"timestamp": ns_to_iso_str(self.timestamp),
90+
"trace_id": "0x{}".format(format_trace_id(self.trace_id)),
91+
"span_id": "0x{}".format(format_span_id(self.span_id)),
92+
"trace_flags": self.trace_flags,
93+
"resource": repr(self.resource.attributes)
94+
if self.resource
95+
else "",
96+
},
97+
indent=4,
98+
)
99+
75100

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

opentelemetry-sdk/src/opentelemetry/sdk/logs/export/__init__.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
import collections
1717
import enum
1818
import logging
19+
import sys
1920
import threading
20-
from typing import Deque, List, Optional, Sequence
21+
from os import linesep
22+
from typing import IO, Callable, Deque, List, Optional, Sequence
2123

2224
from opentelemetry.context import attach, detach, set_value
23-
from opentelemetry.sdk.logs import LogData, LogProcessor
25+
from opentelemetry.sdk.logs import LogData, LogProcessor, LogRecord
2426
from opentelemetry.util._time import _time_ns
2527

2628
_logger = logging.getLogger(__name__)
@@ -60,6 +62,33 @@ def shutdown(self):
6062
"""
6163

6264

65+
class ConsoleExporter(LogExporter):
66+
"""Implementation of :class:`LogExporter` that prints log records to the
67+
console.
68+
69+
This class can be used for diagnostic purposes. It prints the exported
70+
log records to the console STDOUT.
71+
"""
72+
73+
def __init__(
74+
self,
75+
out: IO = sys.stdout,
76+
formatter: Callable[[LogRecord], str] = lambda record: record.to_json()
77+
+ linesep,
78+
):
79+
self.out = out
80+
self.formatter = formatter
81+
82+
def export(self, batch: Sequence[LogData]):
83+
for data in batch:
84+
self.out.write(self.formatter(data.log_record))
85+
self.out.flush()
86+
return LogExportResult.SUCCESS
87+
88+
def shutdown(self):
89+
pass
90+
91+
6392
class SimpleLogProcessor(LogProcessor):
6493
"""This is an implementation of LogProcessor which passes
6594
received logs in the export-friendly LogData representation to the

opentelemetry-sdk/tests/logs/test_export.py

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,31 @@
1414

1515
# pylint: disable=protected-access
1616
import logging
17+
import os
18+
import time
1719
import unittest
1820
from concurrent.futures import ThreadPoolExecutor
19-
from unittest.mock import Mock
21+
from unittest.mock import Mock, patch
2022

2123
from opentelemetry.sdk import trace
22-
from opentelemetry.sdk.logs import LogEmitterProvider, OTLPHandler
23-
from opentelemetry.sdk.logs.export import BatchLogProcessor, SimpleLogProcessor
24+
from opentelemetry.sdk.logs import (
25+
LogData,
26+
LogEmitterProvider,
27+
LogRecord,
28+
OTLPHandler,
29+
)
30+
from opentelemetry.sdk.logs.export import (
31+
BatchLogProcessor,
32+
ConsoleExporter,
33+
SimpleLogProcessor,
34+
)
2435
from opentelemetry.sdk.logs.export.in_memory_log_exporter import (
2536
InMemoryLogExporter,
2637
)
2738
from opentelemetry.sdk.logs.severity import SeverityNumber
39+
from opentelemetry.sdk.resources import Resource as SDKResource
40+
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
41+
from opentelemetry.trace import TraceFlags
2842
from opentelemetry.trace.span import INVALID_SPAN_CONTEXT
2943

3044

@@ -254,3 +268,55 @@ def bulk_log_and_flush(num_logs):
254268

255269
finished_logs = exporter.get_finished_logs()
256270
self.assertEqual(len(finished_logs), 2415)
271+
272+
273+
class TestConsoleExporter(unittest.TestCase):
274+
def test_export(self): # pylint: disable=no-self-use
275+
"""Check that the console exporter prints log records."""
276+
log_data = LogData(
277+
log_record=LogRecord(
278+
timestamp=int(time.time() * 1e9),
279+
trace_id=2604504634922341076776623263868986797,
280+
span_id=5213367945872657620,
281+
trace_flags=TraceFlags(0x01),
282+
severity_text="WARN",
283+
severity_number=SeverityNumber.WARN,
284+
name="name",
285+
body="Zhengzhou, We have a heaviest rains in 1000 years",
286+
resource=SDKResource({"key": "value"}),
287+
attributes={"a": 1, "b": "c"},
288+
),
289+
instrumentation_info=InstrumentationInfo(
290+
"first_name", "first_version"
291+
),
292+
)
293+
exporter = ConsoleExporter()
294+
# Mocking stdout interferes with debugging and test reporting, mock on
295+
# the exporter instance instead.
296+
297+
with patch.object(exporter, "out") as mock_stdout:
298+
exporter.export([log_data])
299+
mock_stdout.write.assert_called_once_with(
300+
log_data.log_record.to_json() + os.linesep
301+
)
302+
303+
self.assertEqual(mock_stdout.write.call_count, 1)
304+
self.assertEqual(mock_stdout.flush.call_count, 1)
305+
306+
def test_export_custom(self): # pylint: disable=no-self-use
307+
"""Check that console exporter uses custom io, formatter."""
308+
mock_record_str = Mock(str)
309+
310+
def formatter(record): # pylint: disable=unused-argument
311+
return mock_record_str
312+
313+
mock_stdout = Mock()
314+
exporter = ConsoleExporter(out=mock_stdout, formatter=formatter)
315+
log_data = LogData(
316+
log_record=LogRecord(),
317+
instrumentation_info=InstrumentationInfo(
318+
"first_name", "first_version"
319+
),
320+
)
321+
exporter.export([log_data])
322+
mock_stdout.write.assert_called_once_with(mock_record_str)

0 commit comments

Comments
 (0)