Skip to main content

A lightweight Python logging handler for sending logs to Sumo Logic HTTP Sources.

Project description

sumo-logger

A lightweight and production-ready Python package for sending application logs to Sumo Logic HTTP Sources using the built-in logging module.


📘 Overview

sumo-logger makes it easy to stream your Python logs directly to Sumo Logic with minimal setup.It extends Python's native logging system, allowing you to:

  • Centralize logs across microservices or distributed apps.
  • Automatically retry failed requests with exponential backoff.
  • Send structured JSON logs containing rich metadata (timestamp, level, file, function, etc.).
  • Use it as a drop-in replacement for your existing logging setup.

📦 Installation

You can install sumo-logger directly from PyPI:

pip install sumo-logger

Requirements:

  • Python 3.8 or later
  • requests (installed automatically as a dependency)

You'll also need a Sumo Logic HTTP Source presigned URL, created on the Sumo Logic side (Manage Data → Collection → set up an HTTP Source). It looks like:

https://endpoint1.collection.us2.sumologic.com/receiver/v1/http/<long-token>

🚀 Quick Start

from sumo_logger import setup_sumo_logger

SUMO_URL = "https://endpoint1.collection.us2.sumologic.com/receiver/v1/http/XXXXXXXX"

logger = setup_sumo_logger(SUMO_URL, "YOUR_LOGGER_NAME")

logger.info("Service started successfully")
logger.warning("Cache miss for key=%s", cache_key)
logger.error("Failed to process order", exc_info=True)

💡 Tip: "YOUR_LOGGER_NAME" becomes the logger's name (record.name) and is what shows up as the logger field in Sumo Logic — use something identifiable, like your service or module name, e.g. "billing-service" or "orders.api".


📚 API Reference

setup_sumo_logger(http_url, module_name=None, log_level=logging.INFO)

Creates (or reuses) a standard logging.Logger and attaches a SumoHttpHandler to it. This is the only function most users ever need.

Parameter Type Default Description
http_url str required Your Sumo Logic HTTP Source presigned URL. RaisesValueError if empty/None.
module_name str | None None Name given to the logger. If omitted, sumo-logger inspects the call stack and auto-detects the name of the calling module.
log_level int logging.INFO Minimum severity this logger will process (e.g.logging.DEBUG, logging.WARNING).

Returns: a standard logging.Logger instance — so every normal method works: .debug(), .info(), .warning(), .error(), .critical(), .exception().

Safe to call more than once: If you call setup_sumo_logger() again for the same module, it will not attach a duplicate handler — it checks whether a SumoHttpHandler is already registered on that logger first.


SumoHttpHandler(http_source_url, max_retries=5, backoff_factor=1)

The logging.Handler subclass that actually performs the HTTP POST to Sumo Logic. setup_sumo_logger() creates one of these for you, but you can also use it directly if you want full control (e.g. attaching it to an existing logger, or combining it with other handlers).

Parameter Type Default Description
http_source_url str required The Sumo Logic HTTP Source URL. RaisesValueError if empty.
max_retries int 5 How many times to retry a send after a 429 (rate limit) response.
backoff_factor int 1 Base seconds for exponential backoff:delay = backoff_factor × 2^attempt.
import logging
from sumo_logger import SumoHttpHandler

logger = logging.getLogger("my.custom.logger")
logger.setLevel(logging.DEBUG)
logger.addHandler(SumoHttpHandler(SUMO_URL, max_retries=3, backoff_factor=2))

set_request_id(request_id=None) / get_request_id()

Located in sumo_logger.context. These use a contextvars.ContextVar under the hood, so the request ID is automatically attached to record.request_id on every log line — no need to pass it manually to each log call.

Parameter Type Default Description
request_id str | None None set_request_id(): if omitted, a random UUID4 is generated and used.
from sumo_logger import setup_sumo_logger
from sumo_logger.context import set_request_id

logger = setup_sumo_logger(SUMO_URL, "orders-service")

def handle_request(payload):
    req_id = set_request_id()          # generates + stores a UUID4
    logger.info("Handling request")     # request_id is attached automatically
    process(payload)
    logger.info("Request complete")

def process(payload):
    # same request_id is still active here — no need to pass it around
    logger.debug("Processing payload: %s", payload)

🔎 Why this matters: Because the request ID rides on a ContextVar, it correctly follows a single request/task through nested function calls (and async code) without being passed as a parameter everywhere — every log line from that flow will carry the same request_id in Sumo Logic, so you can filter one request's full trace.


📤 What Actually Gets Sent to Sumo Logic

Every log call is serialized into this exact JSON structure before being POSTed:

{
  "timestamp": "2026-07-09T14:32:01.512384",
  "level": "INFO",
  "logger": "orders-service",
  "message": "Handling request",
  "pathname": "/app/orders/handlers.py",
  "funcName": "handle_request",
  "request_id": "3f1c9e2a-7b44-4e9d-9c31-df1a6b0c9d21"
}
Field Type Description
timestamp str ISO-8601 timestamp derived from the log record's creation time.
level str Log level name:DEBUG, INFO, WARNING, ERROR, CRITICAL.
logger str The logger name (yourmodule_name / YOUR_LOGGER_NAME).
message str The formatted log message.
pathname str Full file path where the log call was made.
funcName str Name of the function where the log call was made.
request_id str | null Current request ID if set viaset_request_id(), otherwise null.

Content-Type is always application/json, and the POST timeout is fixed at 5 seconds.


🔁 Retry & Backoff Behavior

If Sumo Logic responds with HTTP 429 (rate limited), the handler retries with exponential backoff:

delay = backoff_factor × 2^attempt   (attempt = 0, 1, 2, ...)

With the defaults (backoff_factor=1, max_retries=5), delays are approximately: 1s → 2s → 4s → 8s → 16s.

After the final retry fails, sumo-logger prints "Maximum retries reached. Log not sent to Sumo Logic." and gives up on that particular record — it does not raise an exception, so your application keeps running.

⚠️ Other failure modes: Any other HTTP error, connection error, or unexpected exception during the send is caught, printed to stdout, and the handler moves on without crashing your app. Logging failures are intentionally non-fatal.


✅ Best Practices

  • Call setup_sumo_logger() once per module, near the top of the file — it is idempotent, so importing that module multiple times won't create duplicate handlers.
  • Use a distinct, human-readable module_name per service (e.g. "payments-api", "worker.email") so you can filter easily inside Sumo Logic.
  • Call set_request_id() at the entry point of each request/job (e.g. in middleware or at the top of a task handler) rather than deep inside business logic.
  • Keep your presigned HTTP Source URL out of source control — load it from an environment variable or secrets manager, not a hard-coded string.
  • Use logger.exception("message") inside except blocks instead of logger.error(...) when you want the traceback captured in the message text.
  • Since every log call blocks briefly for the HTTP POST (up to a 5-second timeout, plus retries), avoid setting log_level=logging.DEBUG in high-throughput production hot paths unless you also raise backoff_factor / lower max_retries deliberately.

🛠️ Troubleshooting

ValueError: HTTP URL is required to initialize Sumo logger You called setup_sumo_logger() with an empty string or None as http_url. Double-check the environment variable or config value you're passing in actually resolves to a URL.

Logs aren't showing up in Sumo Logic

  • Confirm the presigned URL is still valid (Sumo Logic HTTP Sources can be deleted/rotated on the Sumo side).
  • Check stdout for "HTTP error", "Connection error", or "Maximum retries reached" messages printed by the handler — these indicate the POST failed silently from your app's perspective.
  • Make sure the logger's log_level (and any parent logger's level) isn't filtering out the messages you expect to see, e.g. calling .debug() on a logger set to logging.INFO.

Seeing duplicate log entries in Sumo Logic This usually means setup_sumo_logger() was called for the same module_name in a way that bypassed the duplicate-handler check — for example, if two different module_name values were used for what is logically the same logger. Standardize on one module_name per logical component.

request_id is null in Sumo Logic set_request_id() was never called in that code path. The context variable defaults to None until it's explicitly set — call set_request_id() at the start of the relevant request/task.


🧩 Full Working Example

import logging
import os
from sumo_logger import setup_sumo_logger
from sumo_logger.context import set_request_id

SUMO_URL = os.environ["SUMO_HTTP_SOURCE_URL"]

logger = setup_sumo_logger(SUMO_URL, "inventory-service", log_level=logging.INFO)


def restock_item(item_id, quantity):
    set_request_id()
    logger.info("Restock started for item_id=%s qty=%s", item_id, quantity)
    try:
        _update_inventory(item_id, quantity)
        logger.info("Restock completed for item_id=%s", item_id)
    except Exception:
        logger.exception("Restock failed for item_id=%s", item_id)
        raise


def _update_inventory(item_id, quantity):
    logger.debug("Writing %s units to inventory table", quantity)
    # ... database write here ...

Every function signature, default value, and JSON field in this document was read directly from the sumo_logger 1.2.3 wheel (setup_logger.py, handler.py, context.py) — not inferred.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sumo_logger-1.2.3.tar.gz (11.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sumo_logger-1.2.3-py3-none-any.whl (8.1 kB view details)

Uploaded Python 3

File details

Details for the file sumo_logger-1.2.3.tar.gz.

File metadata

  • Download URL: sumo_logger-1.2.3.tar.gz
  • Upload date:
  • Size: 11.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for sumo_logger-1.2.3.tar.gz
Algorithm Hash digest
SHA256 d5862f11d860359214bf5b1d12486a10fb4c6461500ba5f3822b5a4f1b1d5e0c
MD5 aef6f38a31b8166ceb7103946945fd78
BLAKE2b-256 1a0da714918a5ee46edf0a47b1d0b1e37ca5e6fe89678d8f0b9e72e1019bd2e5

See more details on using hashes here.

File details

Details for the file sumo_logger-1.2.3-py3-none-any.whl.

File metadata

  • Download URL: sumo_logger-1.2.3-py3-none-any.whl
  • Upload date:
  • Size: 8.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for sumo_logger-1.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 f55b3896b2b9a7654e219dfd196588f8216756fce22cb1d59c6e20cdfd63d375
MD5 e26d7c89011e790b149fefb8680d8d3e
BLAKE2b-256 7c7fea439c919b401f718d07c7a6390b4d0af0fc419b37eb7db3f94cf4cf2af1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page