```import os import logging import json import sys...
# support
n
Copy code
import os
import logging
import json
import sys
import structlog
from typing import Any, Dict, Optional
from opentelemetry import trace, _logs
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs._internal.export import BatchLogRecordProcessor
from opentelemetry.trace import Status, StatusCode
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.logging import LoggingInstrumentor

# --- ✅ Enable Fork Safety for macOS / multiprocessing ---
os.environ['OBJC_DISABLE_INITIALIZE_FORK_SAFETY'] = 'YES'

# --- ✅ Load Configuration ---
try:
    from app.config import settings
except ImportError:
    class FallbackSettings:
        JSON_LOGS = True
        LOG_LEVEL = "DEBUG"
        OTEL_SERVICE_NAME = "fastapi-app"
        OTEL_EXPORTER_OTLP_ENDPOINT = "<http://localhost:4317>"  # OpenTelemetry Collector


    settings = FallbackSettings()

LOG_LEVEL = os.getenv('LOG_LEVEL', settings.LOG_LEVEL).upper()
OTEL_SERVICE_NAME = os.getenv('OTEL_SERVICE_NAME', settings.OTEL_SERVICE_NAME)
OTEL_EXPORTER_OTLP_ENDPOINT = os.getenv('OTEL_EXPORTER_OTLP_ENDPOINT', "<http://localhost:4317>")
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")

# --- ✅ Configure OpenTelemetry Logger Provider (Direct to Collector) ---
logger_provider = LoggerProvider()
_logs.set_logger_provider(logger_provider)

# Set up OTLP Log Exporter (Directly to SigNoz/OpenTelemetry)
otlp_log_exporter = OTLPLogExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, insecure=True)
logger_provider.add_log_record_processor(BatchLogRecordProcessor(otlp_log_exporter))

# --- ✅ Configure OpenTelemetry Tracing (Direct to Collector) ---
tracer_provider = TracerProvider()
trace.set_tracer_provider(tracer_provider)

otlp_trace_exporter = OTLPSpanExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, insecure=True)
tracer_provider.add_span_processor(BatchSpanProcessor(otlp_trace_exporter))

# --- ✅ Instrument Python Logging with OpenTelemetry ---
LoggingInstrumentor().instrument(set_logging_format=True)


# --- ✅ OpenTelemetry Context Injection for Logs ---
def add_trace_context(_, __, event_dict: Dict[str, Any]) -> Dict[str, Any]:
    """Attach OpenTelemetry trace context to structured logs."""
    try:
        span = trace.get_current_span()
        if span:
            span_context = span.get_span_context()
            if span_context.is_valid:
                event_dict.update({
                    "trace_id": format(span_context.trace_id, "032x"),
                    "span_id": format(span_context.span_id, "016x"),
                    "service.name": OTEL_SERVICE_NAME,
                    "environment": ENVIRONMENT,
                })
    except Exception as e:
        event_dict["error.logging"] = str(e)
    return event_dict


# --- ✅ Configure Python Logging (Ensure Logs Flow to OpenTelemetry) ---
log_level_num = getattr(logging, LOG_LEVEL, logging.INFO)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', '%Y-%m-%d %H:%M:%S'))
console_handler.setLevel(log_level_num)

json_handler = logging.StreamHandler(sys.stderr)
json_handler.setLevel(log_level_num)

root_logger = logging.getLogger()
root_logger.setLevel(log_level_num)
root_logger.handlers = []
root_logger.addHandler(console_handler)
root_logger.addHandler(json_handler)

# Silence noisy loggers
for logger_name in ["urllib3.connectionpool", "urllib3", "asyncio", "charset_normalizer"]:
    logging.getLogger(logger_name).setLevel(logging.WARNING)

# --- ✅ Configure `structlog` ---
structlog.configure(
    processors=[
        structlog.contextvars.merge_contextvars,
        structlog.stdlib.filter_by_level,
        structlog.stdlib.add_logger_name,
        structlog.stdlib.add_log_level,
        structlog.processors.TimeStamper(fmt="iso", utc=True),
        add_trace_context,  # Attach OpenTelemetry trace info
        structlog.processors.format_exc_info,
        structlog.processors.JSONRenderer(),
    ],
    context_class=dict,
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    cache_logger_on_first_use=True,
)

# --- ✅ Initialize Structlog Logger ---
logger = structlog.get_logger(OTEL_SERVICE_NAME).bind(
    service=OTEL_SERVICE_NAME,
    environment=ENVIRONMENT,
)


# --- ✅ Context Manager for OpenTelemetry Tracing ---
class LogSpan:
    """Context manager to create spans and attach trace context to logs."""

    def __init__(self, name: str, attributes: Optional[Dict[str, Any]] = None):
        self.name = name
        self.attributes = attributes or {}
        self.tracer = trace.get_tracer(OTEL_SERVICE_NAME)
        self.span = None

    def __enter__(self):
        self.span = self.tracer.start_as_current_span(self.name)
        for key, value in self.attributes.items():
            self.span.set_attribute(key, value)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            self.span.set_status(Status(StatusCode.ERROR))
            self.span.record_exception(exc_val)
        self.span.end()
        return False


# --- ✅ Ensure Logs are Sent to OpenTelemetry ---
def ensure_otlp_logging():
    """Ensure logs and traces are properly sent to OpenTelemetry."""
    try:
        # Ensure tracing and logging are configured
        logger.info("✅ Successfully configured OpenTelemetry logging & tracing", endpoint=OTEL_EXPORTER_OTLP_ENDPOINT)
        return True
    except Exception as e:
        logger.error("❌ Failed to configure OpenTelemetry logging", error=str(e), exc_info=True)
        return False


# --- ✅ Test Log Messages (Ensure They Appear in OpenTelemetry Collector) ---
logger.debug("🐍 Debug message from structlog - Direct to OpenTelemetry")
logger.info("🚀 Structlog is now sending logs directly to OpenTelemetry Collector")

# --- ✅ Expose Logger for Use in Other Modules ---
__all__ = ['logger', 'LogSpan', 'ensure_otlp_logging']
Hi Team, I'm stuck in configuring up my own logger to send logs to signoz. I see API traces and sql traces which I guess is auto-instrumented. I want to see the logs logged by the above logger in signoz. Any thing that I'm doing wrong here. Or any simpler version of structlog that I can use to send logs. p.s- I'm using a self hosted version of signoz
n
Hey @Nikhil Anandani, are you following these docs: https://signoz.io/docs/logs-management/send-logs/application-logs/
n
Yes @Nagesh Bansal. I'm using pyhton auto instrumentation.