Last active
October 31, 2025 03:42
-
-
Save jasonnerothin/f50a974638f5f80627538df02b7109a6 to your computer and use it in GitHub Desktop.
OLTP logging exporter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import logging | |
| import os | |
| import time | |
| from opentelemetry._logs import set_logger_provider | |
| from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler | |
| from opentelemetry.sdk._logs.export import BatchLogRecordProcessor | |
| from opentelemetry.sdk.resources import Resource | |
| from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter | |
| # Configuration | |
| OTEL_COLLECTOR_URL = os.environ.get( | |
| "OTEL_COLLECTOR_BASE_URL", | |
| "http://localhost:4318" | |
| ) | |
| def configure_otlp_logger(service_name: str, log_level: int = logging.INFO): | |
| """ | |
| Configures the root logger to send logs to both an OTLP HTTP collector | |
| (with internal nanosecond precision) and standard output (with maximum | |
| available precision shown). | |
| Args: | |
| service_name: Name of the service for telemetry | |
| log_level: Logging level (default: INFO) | |
| Returns: | |
| Configured root logger instance | |
| """ | |
| # 1. Define service metadata | |
| resource = Resource.create({ | |
| "service.name": service_name, | |
| }) | |
| # 2. Create and set logger provider | |
| logger_provider = LoggerProvider(resource=resource) | |
| set_logger_provider(logger_provider) | |
| # 3. Configure OTLP exporter | |
| log_exporter = OTLPLogExporter( | |
| endpoint=f"{OTEL_COLLECTOR_URL}/v1/logs", | |
| timeout=5, | |
| ) | |
| # 4. Add batch processor | |
| processor = BatchLogRecordProcessor(log_exporter) | |
| logger_provider.add_log_record_processor(processor) | |
| # 5. Create OTLP logging handler | |
| # The LoggingHandler ensures nanosecond precision is passed to the collector. | |
| otlp_handler = LoggingHandler( | |
| level=log_level, | |
| logger_provider=logger_provider, | |
| ) | |
| # 6. Configure root logger | |
| root_logger = logging.getLogger() | |
| root_logger.setLevel(log_level) | |
| # Clear existing handlers | |
| for handler in root_logger.handlers[:]: | |
| root_logger.removeHandler(handler) | |
| # 7. Add OTLP handler for collector | |
| root_logger.addHandler(otlp_handler) | |
| # 8. Add console handler for stdout | |
| stream_handler = logging.StreamHandler() | |
| # *** REVISED CHANGE FOR RELIABLE PRECISION DISPLAY IN CONSOLE *** | |
| # We use a custom LogRecord factory to inject nanoseconds into the record | |
| # to guarantee the full precision is available for the formatter. | |
| # Custom LogRecord Factory for high-resolution timing | |
| class HighPrecisionFormatter(logging.Formatter): | |
| def formatTime(self, record, datefmt=None): | |
| # The standard LogRecord's 'created' (record.created) has | |
| # microsecond resolution on Python 3.9+. We use that directly. | |
| ct = self.converter(record.created) | |
| # Format the time part (e.g., '21:21:27') | |
| if datefmt: | |
| time_str = time.strftime(datefmt, ct) | |
| else: | |
| time_str = time.strftime("%Y-%m-%d %H:%M:%S", ct) | |
| # Manually append the microsecond portion (e.g., '.123456') | |
| s = f"{record.created - int(record.created):.6f}"[1:] | |
| return f"{time_str}{s}" | |
| # Use a simpler format string now that the precision is handled in formatTime | |
| stream_formatter = HighPrecisionFormatter( | |
| fmt=f'%(asctime)s [{service_name}] %(levelname)s: %(message)s', | |
| datefmt='%Y-%m-%d %H:%M:%S' # Only specify up to seconds here | |
| ) | |
| stream_handler.setFormatter(stream_formatter) | |
| root_logger.addHandler(stream_handler) | |
| logging.info(f"OTLP Logging initialized for {service_name}. Endpoint: {OTEL_COLLECTOR_URL}/v1/logs") | |
| return root_logger |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment