fix(otel): recognize inherited logging handlers before assigning formatters
Maintainers usually reply within 1 day
@yeongseon is already working on this.
Since Oct 18, 2026.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- azure, python
- Domain
- backend, observability
Research direction
Read src/azure_functions_logging/_setup.py and src/azure_functions_logging/_host_config.py, then inspect the regression in tests/test_setup.py and how it covers the direct SDK handler. Add coverage using an inherited handler and the real SDK in-memory exporter, while preserving support when OTel is absent. Done means both recognition sites agree, the subclass keeps its structured body, and a normal stream handler still receives the formatter.
Written by the indexing model from the issue text.
Description
Context
At reviewed commit 5343aa68b745e94a40041941f5f35915181ec4ff (0.15.3), Azure-mode setup_logging(functions_formatter=...) recognizes an OpenTelemetry handler only when the concrete class's module starts with opentelemetry.. An ordinary application subclass of the real SDK LoggingHandler therefore loses the protection added in #511.
The subclass still is an OpenTelemetry handler and exports records through the SDK. However, setup installs the Functions formatter on it. A structured mapping log body becomes a JSON string containing another log envelope; the original mapping is reduced to Python repr inside its message. Handler detection also misses the subclass, so no formatter diagnostic is emitted.
Priority is medium because the failure is limited to inherited/customized OTel handlers; using the direct SDK class or configuring the formatter only on the non-OTel output avoids it.
Reproduction
Run in a fresh process with the reviewed package, Python 3.12.14, and opentelemetry-sdk==1.45.1 installed. This uses the real SDK with an in-memory exporter; no Azure resource, exporter endpoint, or credential is involved.
import json
import logging
import os
import warnings
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import InMemoryLogRecordExporter, SimpleLogRecordProcessor
from azure_functions_logging import JsonFormatter, setup_logging
class ApplicationLoggingHandler(LoggingHandler):
pass
os.environ['FUNCTIONS_WORKER_RUNTIME'] = 'python'
os.environ['PYTHON_ENABLE_OPENTELEMETRY'] = '1'
exporter = InMemoryLogRecordExporter()
provider = LoggerProvider()
provider.add_log_record_processor(SimpleLogRecordProcessor(exporter))
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
handler = ApplicationLoggingHandler(logger_provider=provider)
root = logging.getLogger()
root.handlers = [handler]
root.setLevel(logging.INFO)
logger = logging.getLogger('review.otel')
logger.info({'event': 'before', 'count': 1})
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter('always')
setup_logging(functions_formatter=JsonFormatter())
logger.info({'event': 'after', 'count': 1})
print(json.dumps({
'handler_module': type(handler).__module__,
'is_otel_handler': isinstance(handler, LoggingHandler),
'formatter_installed': type(handler.formatter).__name__,
'warnings': [str(item.message) for item in captured],
'bodies': [item.log_record.body for item in exporter.get_finished_logs()],
}, indent=2, default=str))
root.handlers = []
provider.shutdown()
Observed:
is_otel_handleristrue.formatter_installedisJsonFormatter, andwarningsis empty.- The first exported body is the mapping
{"event": "before", "count": 1}. - The second body is a string such as
{"timestamp": "...", "level": "INFO", "message": "{'event': 'after', 'count': 1}", ...}.
Expected: applying functions_formatter preserves the SDK subclass's native structured body, just as it does for an instance of LoggingHandler itself. Non-OTel handlers should continue receiving the selected formatter.
Cause / fix direction: both formatter selection in _setup.py and handler detection in _host_config.py inspect only type(handler).__module__. Share a capability/ancestry-aware recognition helper, preserving the package's import-free behavior when OTel is absent. An MRO-aware check can recognize supported inherited handlers without adding a mandatory OTel import. Keep detection narrow enough not to treat unrelated logging handlers as OTel handlers.
Acceptance Checklist
- A real SDK
LoggingHandlersubclass defined in an application module retains an unformatted mapping body after Azure-mode setup. - Direct SDK handlers and supported instrumentation logging handlers retain their current behavior.
- Formatter selection and configuration diagnostics use the same handler-recognition rule.
- A coexisting normal stream handler still receives
functions_formatter. - Add a regression with the actual SDK and in-memory exporter, including the inherited handler case.
- Importing/configuring the library without the optional OTel package remains supported.
Out of scope
- Changing OpenTelemetry's serialization/export behavior or creating an exporter.
- Making arbitrary wrapper classes transparently delegate formatters.
- Changing root handler ownership or the root logger level in Azure mode.
- Reopening the direct-SDK-class case fixed by #511; this is the remaining subclass path.
References
- Dominant language
- Python
- Stars
- 15
- Forks
- 0
- Avg merge
- 7h 53m
- Merged PRs (30d)
- 90
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from yeongseon/azure-functions-logging-python
-
documentation good first issue priority: low size: XS
Difficulty 1/5 1-3 hours Newbie friendliness 88/100
yeongseon/azure-functions-logging-python#647 ·
Maintainers usually reply within 1 day
-
ci priority: high size: S
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
yeongseon/azure-functions-logging-python#625 · 2 comments ·
Maintainers usually reply within 1 day
-
fix(deps): cap untested future azure-functions SDK majorsPossibly taken @bfs2021 claimed this 6 days ago. Openbug good first issue priority: medium size: XS
Difficulty 3/5 1-2 days Newbie friendliness 25/100
yeongseon/azure-functions-logging-python#646 ·
Maintainers usually reply within 1 day
All issues in yeongseon/azure-functions-logging-python
Similar issues
-
New InternshipOpennew_internship
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
get_graph warns that the oneway column is missing when no way has a oneway tagPossibly taken @HTenkanen claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
cython/cython#8110 · 2 comments ·
Maintainers usually reply within 1 day
-
fix: security 🔐 style: article/section 📝 system: data-mgmt 🗃️ system: security 🔐 system: software 💻
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
uabrc/uabrc.github.io#1267 ·
-
Link Checker ReportOpenBot Type: Bug Type: Documentation
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day