Python SDK
Zero-dependency Python client for GoodLogs. Supports auto-batching, context managers, and background flushing.
Installation
pip install goodlogs
Quick Start
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
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
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
# 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
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
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
| Parameter | Default | Description |
|---|---|---|
| api_key | (required) | API key — gl_sk_* for logs + events |
| endpoint | Auto | API URL (auto-resolved from key region) |
| batch_size | 50 | Flush after N items |
| flush_interval | 5.0 | Auto-flush interval in seconds |
| default_context | {} | Properties merged into every log |
| disabled | False | Disable all tracking |
| on_error | None | Error callback: (Exception) -> None |
| use_envelope | False | Use the unified /v1/envelope pipeline (enables errors, traces, vitals). Will become the default once the legacy endpoints retire. |
| release | None | Release version attached to errors + spans |
| environment | None | Environment tag (production, staging, …) |
Errors
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
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
# 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
# FastAPI
from fastapi import FastAPI
from goodlogs import GoodLogs, goodlogs_fastapi
app = FastAPI()
gl = GoodLogs("gl_sk_...", use_envelope=True)
goodlogs_fastapi(gl, app)
# 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
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.
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.
# 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.
# 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
gl.identify("user_123") # Set user ID
gl.get_distinct_id() # Get current ID
gl.reset() # New anonymous ID
Flushing
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
| Field | Value |
|---|---|
| $session_id | Auto-generated session ID |
| $distinct_id | User or anonymous ID |
| $goodlogs_sdk | "python" |
| $goodlogs_sdk_version | SDK version |
| $os | Operating system (platform.system()) |
| $python_version | Python version |
| code_location | Auto-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.
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.
# 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.
# 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:
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
# 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
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
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
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
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})")