Skip to main content

A Python library for collecting and sending telemetry data to Malti server

Project description

Malti Python SDK

PyPI version Python versions License: MIT

A Python library for collecting and sending telemetry data to Malti server using any Starlette-compatible framework.

Features

  • ๐Ÿš€ High Performance: Asynchronous batch processing with connection pooling
  • ๐Ÿ”’ Thread-Safe: Designed for multi-worker applications
  • ๐ŸŽฏ Clean Mode: Automatically filters out bot traffic (401/404 responses)
  • ๐ŸŒŸ Multi-Framework: Works with any Starlette-compatible framework (FastAPI, Starlette, Responder, etc.)
  • ๐Ÿ“Š Rich Telemetry: Collects method, endpoint, status, response time, consumer, and context
  • ๐Ÿ”„ Automatic Batching: Efficient batching with overflow protection
  • โšก Non-Blocking: Telemetry collection doesn't impact request performance
  • ๐Ÿ›ก๏ธ Retry Logic: Exponential backoff for failed requests
  • ๐ŸŽ›๏ธ Configurable: Extensive environment variable configuration
  • ๐Ÿ”ง Framework Optimized: Enhanced integrations for popular frameworks
  • ๐ŸŒ IP Consumer Extraction: Optional IP address extraction from X-Forwarded-For with anonymization

Installation

pip install malti-telemetry

Quick Start

FastAPI Integration

from fastapi import FastAPI
from malti_telemetry.middleware import MaltiMiddleware

app = FastAPI()

# Add telemetry middleware (route patterns automatically extracted!)
app.add_middleware(MaltiMiddleware)

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id, "name": "John Doe"}

# Recorded as: method=GET, endpoint="/users/{user_id}"

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app)

Starlette Integration

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.responses import JSONResponse
from malti_telemetry.middleware import MaltiMiddleware

app = Starlette()

# Add telemetry middleware (lifespan auto-injected!)
app.add_middleware(Middleware(MaltiMiddleware))

@app.route("/users/{user_id}")
async def get_user(request):
    user_id = request.path_params["user_id"]
    return JSONResponse({"user_id": user_id, "name": "John Doe"})

# Recorded as: method=GET, endpoint="/users/{user_id}"

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app)

Responder Integration

from responder import API
from malti_telemetry.middleware import MaltiMiddleware

api = API()

# Add telemetry middleware (generic Starlette middleware works with Responder)
api.add_middleware(MaltiMiddleware)

@api.route("/users/{user_id}")
async def get_user(req, resp, *, user_id):
    resp.media = {"user_id": user_id, "name": "John Doe"}

Generic Starlette Middleware

from starlette.applications import Starlette
from starlette.middleware import Middleware
from malti_telemetry.middleware import MaltiMiddleware

app = Starlette()

# Add telemetry middleware (works with any Starlette framework)
app.add_middleware(Middleware(MaltiMiddleware))

@app.route("/api/data")
async def get_data(request):
    return JSONResponse({"data": "example"})

Environment Configuration

Set these environment variables before starting your application:

export MALTI_API_KEY="your-api-key-here"
export MALTI_SERVICE_NAME="my-fastapi-app"
export MALTI_URL="https://your-malti-server.muzy.dev"
export MALTI_NODE="production-node-1"

# Optional: Enable IP address consumer extraction
export MALTI_USE_IP_AS_CONSUMER=true
export MALTI_IP_ANONYMIZE=true

Configuration

Environment Variables

Variable Default Description
MALTI_API_KEY (required) Your Malti API key
MALTI_SERVICE_NAME "unknown-service" Name of your service
MALTI_URL "http://localhost:8000" Malti server URL
MALTI_NODE "unknown-node" Node identifier
MALTI_BATCH_SIZE 500 Records per batch
MALTI_BATCH_INTERVAL 60.0 Seconds between batch sends
MALTI_MAX_RETRIES 3 Max retry attempts
MALTI_RETRY_DELAY 1.0 Base retry delay (seconds)
MALTI_HTTP_TIMEOUT 30.0 HTTP request timeout
MALTI_MAX_KEEPALIVE_CONNECTIONS 5 Max keepalive connections
MALTI_MAX_CONNECTIONS 10 Max total connections
MALTI_OVERFLOW_THRESHOLD_PERCENT 90.0 Buffer overflow threshold
MALTI_CLEAN_MODE true Ignore 401/404 responses
MALTI_USE_IP_AS_CONSUMER false Use IP address as consumer fallback
MALTI_IP_ANONYMIZE false Anonymize IP addresses (simple octet masking)

Programmatic Configuration

from malti_telemetry import configure_malti

configure_malti(
    service_name="my-service",
    api_key="your-api-key",
    malti_url="https://api.malti.muzy.dev",
    node="prod-web-01",
    batch_size=1000,
    clean_mode=True,
    use_ip_as_consumer=True,
    ip_anonymize=True
)

Advanced Usage

Framework-Specific Features

FastAPI Features

Route Pattern Extraction: Automatic conversion of actual paths to route patterns:

  • /users/123 โ†’ /users/{user_id}
  • /api/v1/posts/456/comments โ†’ /api/v1/posts/{post_id}/comments
  • Works with nested routes and mount points

Context Information: Add context using FastAPI's request state:

from fastapi import Request

@app.route("/users/{user_id}")
async def get_user(request):
    user_id = request.path_params["user_id"]
    if user_id < 1000:
        request.state.context = "legacy"
    else:
        request.state.context = "current"
    return JSONResponse({"user_id": user_id, "name": "John Doe"})

Starlette Features

Automatic Lifespan Management: Telemetry system starts/stops automatically:

from starlette.applications import Starlette
from starlette.middleware import Middleware
from malti_telemetry.middleware import MaltiMiddleware

app = Starlette()
app.add_middleware(Middleware(MaltiMiddleware))  # Lifespan auto-injected!

# No need for manual lifespan management!

Route Pattern Extraction: Automatically extracts route patterns from Starlette routing:

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.routing import Route, Mount
from malti_telemetry.middleware import MaltiMiddleware

app = Starlette()
app.add_middleware(Middleware(MaltiMiddleware))

async def user_handler(request):
    return JSONResponse({"user_id": request.path_params["user_id"]})

async def post_handler(request):
    return JSONResponse({"post_id": request.path_params["post_id"]})

# Works with all Starlette routing patterns
app.routes = [
    Route("/api/v1/users/{user_id}", endpoint=user_handler),
    Mount("/api/v2", routes=[
        Route("/posts/{post_id}", endpoint=post_handler),
    ]),
]

# Automatically recorded as:
# method=GET, endpoint="/api/v1/users/{user_id}"
# method=GET, endpoint="/api/v2/posts/{post_id}"

Consumer Identification

Malti automatically extracts consumer information from headers:

  1. x-consumer-id header
  2. x-user-id header
  3. consumer-id header
  4. user-id header

IP Address as Consumer: When no consumer headers are found, you can configure Malti to use IP addresses as a fallback:

# Enable IP address extraction from X-Forwarded-For header
configure_malti(
    use_ip_as_consumer=True,
    ip_anonymize=True  # Optional: anonymize IPs for privacy
)

# Or via environment variables
export MALTI_USE_IP_AS_CONSUMER=true
export MALTI_IP_ANONYMIZE=true

IP Anonymization: When enabled, IP addresses are anonymized using simple octet masking:

  • IPv4: 192.168.1.100 โ†’ 192.168.1.xxx
  • IPv6: 2001:db8:85a3:8d3:1319:8a2e:370:7348 โ†’ 2001:db8:85a3:8d3:xxxx:xxxx:xxxx:xxxx

IP Extraction Priority:

  1. X-Forwarded-For header (uses first/leftmost IP)
  2. Direct client IP (fallback)

Custom Consumer Extraction: Set consumer information in your framework:

# FastAPI
@app.middleware("http")
async def set_consumer(request: Request, call_next):
    request.state.malti_consumer = "app"
    return await call_next(request)

# Starlette
@app.middleware("http")
async def set_consumer(request, call_next):
    # Add consumer to ASGI scope
    request.scope["state"]["malti_consumer"] = "api"
    response = await call_next(request)
    return response

Manual Telemetry Recording

from malti_telemetry import get_telemetry_system

telemetry = get_telemetry_system()

# Record a custom event
telemetry.record_request(
    method="GET",
    endpoint="/api/custom",
    status=200,
    response_time=150,
    consumer="custom-client",
    context="manual-recording"
)

Statistics and Monitoring

from malti_telemetry import get_malti_stats

stats = get_malti_stats()
print(stats)
# {
#     'total_added': 1250,
#     'total_sent': 1200,
#     'total_failed': 50,
#     'current_size': 50,
#     'max_size': 25000,
#     'service_name': 'my-service',
#     'running': True
# }

Supported Frameworks

Malti Telemetry works with any Starlette-compatible framework:

  • FastAPI: Enhanced route pattern extraction and request.state integration
  • Starlette: Base middleware with full functionality and lifespan management
  • Responder: Works with generic Starlette middleware
  • Any ASGI framework: Generic middleware for custom implementations

Architecture

Core Components

  1. TelemetryCollector: Collects HTTP request telemetry data
  2. BatchSender: Sends batched telemetry data to Malti server
  3. TelemetrySystem: Combines collector and sender with unified interface
  4. TelemetryBuffer: Thread-safe buffer for storing records

Worker Process Model

Each FastAPI/Uvicorn worker process gets its own telemetry system instance:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Worker Process  โ”‚    โ”‚ Worker Process  โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚    โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚TelemetrySys โ”‚ โ”‚    โ”‚ โ”‚TelemetrySys โ”‚ โ”‚
โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚    โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚
โ”‚ โ”‚ โ”‚Buffer   โ”‚ โ”‚ โ”‚    โ”‚ โ”‚ โ”‚Buffer   โ”‚ โ”‚ โ”‚
โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚    โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚
โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚    โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚
โ”‚ โ”‚ โ”‚Sender   โ”‚ โ”‚ โ”‚    โ”‚ โ”‚ โ”‚Sender   โ”‚ โ”‚ โ”‚
โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚    โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚    โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Development

Setup

git clone https://github.com/muzy/malti-telemetry.git
cd python/
pip install -e ".[dev]"

Testing

pytest

Code Quality

black malti_telemetry/
isort malti_telemetry/
mypy malti_telemetry/
flake8 malti_telemetry/

License

MIT License - see LICENSE file for details.

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

malti_telemetry-1.0.4.tar.gz (26.0 kB view details)

Uploaded Source

Built Distribution

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

malti_telemetry-1.0.4-py3-none-any.whl (15.3 kB view details)

Uploaded Python 3

File details

Details for the file malti_telemetry-1.0.4.tar.gz.

File metadata

  • Download URL: malti_telemetry-1.0.4.tar.gz
  • Upload date:
  • Size: 26.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for malti_telemetry-1.0.4.tar.gz
Algorithm Hash digest
SHA256 c0b09e7914c71e90e6aa1e7ee0315023db0ab9b5b499dd70efacb04a5cdfb0d7
MD5 3097f868c9cb5bad1cf3ad46171d6735
BLAKE2b-256 ca8981d70e54fc2657183f6bd5ec0e4b4436158d559373ec09c84b23d4ee6301

See more details on using hashes here.

Provenance

The following attestation bundles were made for malti_telemetry-1.0.4.tar.gz:

Publisher: publish-to-pypi.yml on muzy/malti-telemetry

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file malti_telemetry-1.0.4-py3-none-any.whl.

File metadata

File hashes

Hashes for malti_telemetry-1.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 84bb140cd1ba2cb8982dfc26ef590901deb72ed03e6da5503ae6107c15538915
MD5 64a9e74a4bf73d7d3435e77aa5844532
BLAKE2b-256 ba935c43dbd8636310da15ade683b5d0a5d0e59f097d01720aa99844398de752

See more details on using hashes here.

Provenance

The following attestation bundles were made for malti_telemetry-1.0.4-py3-none-any.whl:

Publisher: publish-to-pypi.yml on muzy/malti-telemetry

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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