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 theloggerfield 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 aSumoHttpHandleris 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 samerequest_idin 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_nameper 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")insideexceptblocks instead oflogger.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.DEBUGin high-throughput production hot paths unless you also raisebackoff_factor/ lowermax_retriesdeliberately.
🛠️ 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 tologging.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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5862f11d860359214bf5b1d12486a10fb4c6461500ba5f3822b5a4f1b1d5e0c
|
|
| MD5 |
aef6f38a31b8166ceb7103946945fd78
|
|
| BLAKE2b-256 |
1a0da714918a5ee46edf0a47b1d0b1e37ca5e6fe89678d8f0b9e72e1019bd2e5
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f55b3896b2b9a7654e219dfd196588f8216756fce22cb1d59c6e20cdfd63d375
|
|
| MD5 |
e26d7c89011e790b149fefb8680d8d3e
|
|
| BLAKE2b-256 |
7c7fea439c919b401f718d07c7a6390b4d0af0fc419b37eb7db3f94cf4cf2af1
|