Skip to content

Instantly share code, notes, and snippets.

@jasonnerothin
Last active October 31, 2025 03:42
Show Gist options
  • Select an option

  • Save jasonnerothin/f50a974638f5f80627538df02b7109a6 to your computer and use it in GitHub Desktop.

Select an option

Save jasonnerothin/f50a974638f5f80627538df02b7109a6 to your computer and use it in GitHub Desktop.
OLTP logging exporter
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