-
Notifications
You must be signed in to change notification settings - Fork 802
Description
What problem do you want to solve?
I have an app fully instrumented with OTEL. However, for development work I'd like to have some human-readable output in the console rather than being forced to use a trace sink. opentelemetry-exporter-richconsole is really nice for this.
For this use case however, the output is a little verbose. For example
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.richconsole import RichConsoleSpanExporter
trace.set_tracer_provider(TracerProvider())
console_exporter = RichConsoleSpanExporter()
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(console_exporter)
)
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("main-operation") as main_span:
main_span.set_attribute("user.name", "pmeier")
with tracer.start_as_current_span("child-operation-1") as child_span_1:
child_span_1.set_attribute("foo", "bar")
# Doing some work in child 1
with tracer.start_as_current_span("child-operation-2") as child_span_2:
child_span_2.add_event("Starting data processing")
# Doing some work in child 2
child_span_2.add_event("Finished data processing")
child_span_2.set_attribute("processing.success", True)prints
Trace 72b561402d24aee84a061d3a44543214
└── [10:56:27.322557] main-operation, span 61f4b4c2f4dee29e
├── Kind : INTERNAL
├── Attributes :
│ └── user.name : pmeier
├── Resources :
│ ├── telemetry.sdk.language : python
│ ├── telemetry.sdk.name : opentelemetry
│ ├── telemetry.sdk.version : 1.37.0
│ └── service.name : unknown_service
├── [10:56:27.322611] child-operation-1, span 3341446358921c9f
│ ├── Kind : INTERNAL
│ ├── Attributes :
│ │ └── foo : bar
│ └── Resources :
│ ├── telemetry.sdk.language : python
│ ├── telemetry.sdk.name : opentelemetry
│ ├── telemetry.sdk.version : 1.37.0
│ └── service.name : unknown_service
└── [10:56:27.322651] child-operation-2, span 6be6ca7fe8e546f5
├── Kind : INTERNAL
├── Events :
│ ├── Starting data processing
│ └── Finished data processing
├── Attributes :
│ └── processing.success : True
└── Resources :
├── telemetry.sdk.language : python
├── telemetry.sdk.name : opentelemetry
├── telemetry.sdk.version : 1.37.0
└── service.name : unknown_service
I understand that the resource information is important in general, but for this use case it just adds five unnecessary lines per span to the output and in turn drowning the relevant information.
Describe the solution you'd like
I'd like to have a suppress_resources: bool = False flag to suppress the resources, e.g. RichConsoleSpanExporter(suppress_resources=True)
Describe alternatives you've considered
Looking at the code, there is already some logic to only optionally render the resources:
Lines 136 to 139 in 34db73e
| if span.resource: | |
| resources = child.add( | |
| label=Text.from_markup("[bold cyan]Resources :[/bold cyan] ") | |
| ) |
However, unless I'm missing something, ReadableSpan().resource can only be a Resource() and that in turn will never evaluate to False. Meaning, with the standard components, the check is unnecessary.
However, this opens up a workaround: by defining a resource that evaluates to False, we can suppress the resources:
from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.sdk.trace.export import SpanExporter
from opentelemetry.exporter.richconsole import RichConsoleSpanExporter
from opentelemetry.sdk.resources import Resource
class FalsyResource(Resource):
def __bool__(self) -> bool:
return False
FALSY_RESOURCE = FalsyResource({})
class FalsyResourceSpanExporterWrapper(SpanExporter):
def __init__(self, exporter: SpanExporter):
self.wrapped_exporter = exporter
def export(self, spans):
return self.wrapped_exporter.export(
[
ReadableSpan(
name=span.name,
context=span.context,
parent=span.parent,
resource=FALSY_RESOURCE,
attributes=span.attributes,
events=span.events,
links=span.links,
kind=span.kind,
start_time=span.start_time,
end_time=span.end_time,
status=span.status,
instrumentation_scope=span.instrumentation_scope,
)
for span in spans
]
)
def shutdown(self) -> None:
self.wrapped_exporter.shutdown()
def force_flush(self, timeout_millis: int = 30000) -> bool:
return self.wrapped_exporter.force_flush(timeout_millis)
console_exporter = FalsyResourceSpanExporterWrapper(RichConsoleSpanExporter())
...Trace 97ba84d5dd7cfc5f1824b47315440ad5
└── [11:10:51.177077] main-operation, span 2d6b319be01d4cba
├── Kind : INTERNAL
├── Attributes :
│ └── user.name : pmeier
├── [11:10:51.177119] child-operation-1, span b69158f47b74aca9
│ ├── Kind : INTERNAL
│ └── Attributes :
│ └── foo : bar
└── [11:10:51.177159] child-operation-2, span 8cdf87c40a85f964
├── Kind : INTERNAL
├── Events :
│ ├── Starting data processing
│ └── Finished data processing
└── Attributes :
└── processing.success : True
This way I can keep the full resource information when sending information to a trace sink, but for my console the noise is significantly reduced.
I'm not against this workaround, but it does seem a little brittle as it relies on an unnecessary check in the source that might be removed at any time.
Additional Context
No response
Would you like to implement a fix?
Yes
Tip
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.