Python SDK

Zero-dependency Python client for GoodLogs. Supports auto-batching, context managers, and background flushing.

Installation

bash
pip install goodlogs

Quick Start

python
from goodlogs import GoodLogs

gl = GoodLogs("gl_sk_eu_your_secret_key")

# Track events — pass any properties
gl.track("signup", {"plan": "pro", "referrer": "google"}, distinct_id="user_123")

# Send logs — pass any properties as a flat dict
gl.error("Payment failed", {"orderId": "ord_123", "errorCode": "card_declined"})
gl.info("User signed up", {"userId": "user_123", "method": "email"})

# Or use keyword arguments
gl.warn("Rate limit approaching", current=95, limit=100, service="api")

# Flush before exit
gl.flush()

Context Manager

python
with GoodLogs("gl_sk_eu_...") as gl:
    gl.track("job_started")
    # ... your code ...
    gl.info("Job completed", duration_ms=1234)
# auto-flushes on exit

Framework Examples

FastAPI

python
from fastapi import FastAPI, Request
from goodlogs import GoodLogs

app = FastAPI()
gl = GoodLogs("gl_sk_eu_...", default_context={"service": "api", "env": "prod"})

@app.middleware("http")
async def log_requests(request: Request, call_next):
    response = await call_next(request)
    gl.info("Request", path=request.url.path, method=request.method, status=response.status_code)
    return response

@app.post("/checkout")
async def checkout(order: dict):
    try:
        result = process_payment(order)
        gl.track("purchase", {"amount": order["amount"], "plan": order["plan"]})
        return result
    except Exception as e:
        gl.error("Checkout failed", error=str(e), order_id=order.get("id"))
        raise

@app.on_event("shutdown")
async def shutdown():
    gl.flush()

Django

python
# settings.py
from goodlogs import GoodLogs
gl = GoodLogs("gl_sk_eu_...", default_context={"service": "web"})

# views.py
from django.conf import settings

def signup_view(request):
    user = create_user(request.POST)
    settings.gl.track("signup", {"method": "email"}, distinct_id=str(user.id))
    settings.gl.info("User signed up", user_id=str(user.id))
    return redirect("/dashboard")

Flask

python
from flask import Flask, request
from goodlogs import GoodLogs

app = Flask(__name__)
gl = GoodLogs("gl_sk_eu_...", default_context={"service": "api"})

@app.route("/api/orders", methods=["POST"])
def create_order():
    data = request.json
    gl.track("order_created", {"amount": data["amount"]}, distinct_id=data["user_id"])
    return {"status": "ok"}

Script / CLI

python
from goodlogs import GoodLogs

with GoodLogs("gl_sk_eu_...", batch_size=1) as gl:
    gl.info("Migration started", table="users", batch=500)
    migrate_users()
    gl.info("Migration completed", rows_migrated=12345)
# auto-flush on exit

Configuration

ParameterDefaultDescription
api_key(required)API key — gl_sk_* for logs + events
endpointAutoAPI URL (auto-resolved from key region)
batch_size50Flush after N items
flush_interval5.0Auto-flush interval in seconds
default_context{}Properties merged into every log
disabledFalseDisable all tracking
on_errorNoneError callback: (Exception) -> None
use_envelopeFalseUse the unified /v1/envelope pipeline (enables errors, traces, vitals). Will become the default once the legacy endpoints retire.
releaseNoneRelease version attached to errors + spans
environmentNoneEnvironment tag (production, staging, …)

Errors

python
import goodlogs
gl = goodlogs.GoodLogs("gl_sk_...", use_envelope=True, release="v1.4.2")

try:
    charge_card()
except Exception as e:
    gl.capture_exception(
        e,
        tags={"feature": "checkout"},
        fingerprint=["payment-flow"],
        user_id="usr_123",
    )

gl.capture_message("hit unreachable branch", level="warning")

# Rolling breadcrumb trail (max 50)
gl.add_breadcrumb(category="ui.click", message="Buy")

capture_exception walks __traceback__ (newest frame first), marks frames outside site-packages / dist-packages as in_app, and captures __cause__ / __context__ chained exceptions into extra.chain.

See Errors & Issues for fingerprinting, regression detection, and the zero-config log-to-issue scanner.

Tracing

python
with gl.start_transaction(name="/checkout", op="http.server") as tx:
    tx.set_attribute("user.id", user_id)
    with tx.start_child(op="db.query", name="SELECT users") as db_span:
        db_span.add_event("query.start")
        run_query()

Span is a context manager — on exit it auto-finishes and flips status to error if the block raised.

Framework helpers

python
# Flask
from flask import Flask
from goodlogs import GoodLogs, goodlogs_flask, instrument_requests
app = Flask(__name__)
gl = GoodLogs("gl_sk_...", use_envelope=True)
instrument_requests(gl)     # outbound requests.get/post → http.client spans
goodlogs_flask(gl, app)     # inbound → http.server spans
python
# FastAPI
from fastapi import FastAPI
from goodlogs import GoodLogs, goodlogs_fastapi
app = FastAPI()
gl = GoodLogs("gl_sk_...", use_envelope=True)
goodlogs_fastapi(gl, app)
python
# Django (settings.py)
from goodlogs import GoodLogs
GOODLOGS = GoodLogs("gl_sk_...", use_envelope=True)

# myapp/tracing.py
from django.conf import settings
from goodlogs import goodlogs_django_middleware
goodlogs_middleware = goodlogs_django_middleware(settings.GOODLOGS)

# settings.py
MIDDLEWARE = ["myapp.tracing.goodlogs_middleware", ...]

All three middleware adapters honour incoming W3C traceparent so multi-service traces stitch together.

Web Vitals

python
gl.capture_vital("lcp", 2300, route="/checkout")

Rating is computed locally from web.dev thresholds. Use the browser SDK for automatic capture; the Python helper exists for SSR / synthetic / longtask observers. See Web Vitals.

Continuous Profiling

101 Hz sampling thread using sys._current_frames(). Ships chunks every 60 seconds.

python
from goodlogs.profiling import start_profiling, ProfilingOptions

handle = start_profiling(gl, ProfilingOptions(
    service="my-api",
    release="v1.2.3",
    environment="production",
))
# handle.stop() to end profiling and flush the final chunk

Runs as a daemon thread, excludes its own thread ID from sampling. See Profiling for full options and chunk format.

Logging

gl.log(severity, message, properties?, **kwargs)

Pass any properties as a dict or keyword arguments. Everything is stored flat and searchable via GQL.

python
# Dict style
gl.error("DB timeout", {"host": "db-1.internal", "timeout_ms": 5000})

# Keyword style
gl.error("DB timeout", host="db-1.internal", timeout_ms=5000)

# Mixed
gl.error("DB timeout", {"host": "db-1.internal"}, timeout_ms=5000)

# Severity shortcuts
gl.debug("Cache hit", key="user:123")
gl.info("User signed up", user_id="u_456")
gl.warn("Disk space low", usage_pct=92)
gl.error("Payment declined", order_id="ord_789")
gl.fatal("Process crashed", signal="SIGKILL")

Tip: Just pass any properties you want — they're all stored flat and searchable. No need for metadata/context wrappers.

Event Tracking

gl.track(event, properties?, distinct_id?)

Track a product analytics event. distinct_id is a dedicated parameter for user identity.

python
# Basic event
gl.track("page_view")

# With properties
gl.track("purchase", {"plan": "pro", "amount": 29.99})

# With user identity
gl.track("signup", {"method": "google"}, distinct_id="user_123")

# distinct_id is stored as 'distinct_id' in properties — query via GQL:
# from:events distinct_id:user_123

Tip: Pass distinct_id as a keyword argument — it's stored in properties and queryable via GQL as distinct_id.

Identity

python
gl.identify("user_123")     # Set user ID
gl.get_distinct_id()        # Get current ID
gl.reset()                  # New anonymous ID

Flushing

python
gl.flush()   # Send all queued data now
gl.close()   # Flush + stop background timer

Warning: Always call gl.flush() or gl.close() before process exit, or use the context manager (with block).

Auto-Injected Fields

FieldValue
$session_idAuto-generated session ID
$distinct_idUser or anonymous ID
$goodlogs_sdk"python"
$goodlogs_sdk_versionSDK version
$osOperating system (platform.system())
$python_versionPython version
code_locationAuto-captured caller function@file:line

Tip: The SDK uses urllib from the standard library — zero dependencies. Errors are silently handled to never crash your application.

Programmatic API Client

The GoodLogsClient provides access to all read/write endpoints. Requires a secret API key.

python
from goodlogs import GoodLogsClient

client = GoodLogsClient(api_key="gl_sk_us_...")

Query Logs

Use GQL to query logs — it supports filtering, full-text search, aggregation, and time ranges.

python
# Search error logs from the last hour
result = client.gql("severity:error last:1h")
for row in result["data"]:
    print(f"[{row['severity']}] {row['message']}")

# Full-text search
search = client.gql("'payment failed' last:24h limit:20")

# Count errors by message
grouped = client.gql("count by message | where severity = 'error' | last 1h")

Run GQL Queries

All data querying uses POST /v1/gql — one endpoint for everything.

python
# Single query
result = client.gql("count | where severity = 'error' | last 1h")
print(result["data"])

# Natural language → GQL
nl = client.nl2gql("Show me the most common errors today")
data = client.gql(nl["gql"])

Batch Queries

Fetch multiple metrics in a single request:

python
results = client.gql_batch([
    "from:events | count_distinct(distinct_id) | last:1d",
    "from:events | count | last:30d",
    "from:events | count by event_name | top 10 | last:30d",
])

# Each result: {"status": "ok", "query": ..., "data": ...} or {"status": "error", "error": "msg"}
for r in results:
    if r["status"] == "ok":
        print(r["data"])

Alerts

python
# List alerts
alerts = client.alerts.list()

# Create
alert = client.alerts.create(
    name="High Error Rate",
    metric="error_count",
    threshold=100,
    window_minutes=15,
    severity="critical",
)

# Update
client.alerts.update(alert["id"], threshold=200, enabled=True)

# Mute for 1 hour
client.alerts.mute(alert["id"], 60)

# Alert timeline via GQL
timeline = client.gql("from:alert_events | last 7d | sort triggered_at desc | limit 20")

# Delete
client.alerts.delete(alert["id"])

SLOs

python
slo = client.slos.create(name="API Availability", target_percent=99.9)
slos = client.slos.list()
client.slos.update(slo["id"], target_percent=99.95)
client.slos.delete(slo["id"])

AI Chat

python
response = client.ai.chat("Why are error rates spiking?")
print(response["reply"])

# Continue conversation
followup = client.ai.chat("Show me the errors", session_id=response["session_id"])

Schema & Info

python
schema = client.schema()
print("Events:", [e["name"] for e in schema["events"]])

info = client.info()
print(f"Project: {info['project']['name']}, Plan: {info['org']['plan']}")

Error Handling

python
from goodlogs import GoodLogsClient, GoodLogsApiError

try:
    client.alerts.create(name="Test", metric="x", threshold=0)
except GoodLogsApiError as e:
    print(f"API error {e.status}: {e} ({e.code})")