Skip to main content

LOZA Python SDK

Status: STABLE (v0.4.0) - Production-ready, full feature conformance

Full API conformance with specification is complete. See SDK_CONFORMANCE_CONTRACT.md for detailed guarantees.

loza-py is a collector-first Python SDK for wide events. It builds one structured event per operation (request, job, queue message, CLI run, cron run), then emits to your log/analytics backend.

Install

pip install -e .

Quick Start

import loza

# Configure the default logger
loza.configure(
    loza.production("checkout")
    .with_collector_endpoint("http://127.0.0.1:9308")
)

# Lifecycle
ctx = loza.start_event(loza.Params(event="checkout.request", kind="http", method="POST", path="/checkout"))
loza.enrich(ctx, loza.UserID("u_123"), loza.String("payment.provider", "stripe"))
loza.finish(ctx, "success", loza.Int("status_code", 200))
loza.emit(ctx)
loza.shutdown()

Core Lifecycle API

The main flow: start_event -> enrich -> checkpoint -> finish/finish_error -> emit

import loza

# Start an event (returns context carrying the event)
ctx = loza.start_event(loza.Params(
    event="checkout.request",
    kind="http",
    method="POST",
    path="/checkout",
    service="checkout",
))

# Typed starters
ctx = loza.start_http_event(loza.Params(event="http.request", method="GET", path="/health"))
ctx = loza.start_job_event(loza.Params(event="job.send_email"))
ctx = loza.start_queue_event(loza.Params(event="queue.process"))
ctx = loza.start_cli_event(loza.Params(event="cli.run"))
ctx = loza.start_cron_event(loza.Params(event="cron.tick"))

# Enrich (add attributes)
loza.enrich(ctx, loza.String("user.id", "u_123"), loza.Int("cart.items", 3))

# Append (alias for enrich)
loza.append(ctx, loza.String("key", "value"))

# Set (override)
loza.set(ctx, loza.String("status", "processing"))

# Merge into group
loza.merge(ctx, "payment", loza.String("provider", "stripe"), loza.Int("attempt", 1))

# Delete
loza.delete(ctx, "temp_field")

# Get
val = loza.get(ctx, "user.id")
group = loza.get_group(ctx, "payment")

# Checkpoint (timeline marker)
loza.checkpoint(ctx, "payment_started")
loza.checkpoint(ctx, "payment_finished", loza.String("provider", "stripe"))

# Finish
loza.finish(ctx, "success", loza.Int("status_code", 200))

# Or finish with error
try:
    process_payment()
except Exception as e:
    loza.finish_error(ctx, e, loza.Int("status_code", 500))

# Emit (sends to sink)
loza.emit(ctx)

# Flush (force buffered events)
loza.flush()

# Shutdown (drain pipeline)
loza.shutdown()

Attribute Constructors

import loza

loza.enrich(ctx,
    loza.String("user.id", "u_123"),
    loza.Int("cart.items", 3),
    loza.Int64("big_number", 9999999999),
    loza.Float64("price", 49.99),
    loza.Bool("premium", True),
    loza.Duration("timeout", timedelta(seconds=30)),
    loza.Any("metadata", {"key": "value"}),
    loza.Null("optional_field"),
)

# Groups (nested objects)
loza.enrich(ctx,
    loza.Group("user",
        loza.String("id", "u_123"),
        loza.String("email", "user@example.com"),
    ),
    loza.Group("payment",
        loza.String("provider", "stripe"),
        loza.Int("attempt", 1),
    ),
)

Dot keys expand into nested JSON:

loza.String("user.id", "u_123")  # -> {"user": {"id": "u_123"}}

Canonical Helpers

import loza

loza.enrich(ctx,
    loza.UserID("u_123"),
    loza.TenantID("t_456"),
    loza.WorkspaceID("w_789"),
    loza.OrganizationID("org_abc"),
    loza.SessionID("sess_xyz"),
    loza.RequestID("req_123"),
    loza.TraceID("trace_abc"),
    loza.SpanID("span_def"),
    loza.FeatureFlag("checkout_v2", "enabled"),
    loza.FeatureFlagBool("new_ui", True),
    loza.Experiment("pricing_test", "variant_b"),
)

Business/Domain Helpers

import loza

loza.enrich(ctx,
    loza.OrderID("ord_123"),
    loza.CartID("cart_456"),
    loza.ProductID("prod_789"),
    loza.CustomerID("cust_abc"),
    loza.Plan("pro"),
    loza.Currency("INR"),
    loza.Amount(4999),
    loza.Country("IN"),
    loza.Device("mobile"),
    loza.Platform("ios"),
    loza.AppVersion("2.1.0"),
)

Error Helpers

import loza

try:
    process()
except Exception as e:
    loza.finish_error(ctx, e,
        loza.ErrorType("ValidationError"),
        loza.ErrorCode("INVALID_INPUT"),
        loza.ErrorMessage(str(e)),
        loza.ErrorStack(traceback.format_exc()),
        loza.Retryable(False),
    )

Error output:

{
  "outcome": "error",
  "level": "error",
  "error": {
    "type": "ValidationError",
    "message": "...",
    "code": "INVALID_INPUT",
    "retryable": false
  }
}

Immediate Logs

One-shot events without requiring StartEvent:

import loza

loza.info("worker started", queue="emails")
loza.error("payment failed", provider="stripe", amount=4999)

Logger Instances

import loza

# Default API — configure once, use everywhere
loza.configure(loza.production("checkout").with_collector_endpoint("http://127.0.0.1:9308"))
loza.info("server started")

# Custom instance
logger = loza.create_loza(service="checkout-api", collector_endpoint="http://127.0.0.1:9308")
logger.info("custom instance ready")

# Alias -- same config, loza.alias metadata
audit = loza.alias("audit-service")
audit.info("audit trail started")

# Presets
cfg = loza.dev("checkout")       # pretty JSON, stdout, sync, debug level
cfg = loza.production("checkout") # compact JSON, stdout, async, info level
cfg = loza.test("checkout")       # sync, no sinks, debug level

# Instance methods
ctx = logger.start_event(loza.Params(event="checkout.request"))
logger.enrich(ctx, loza.String("key", "value"))
logger.finish(ctx, "success")
logger.emit(ctx)
logger.flush()
logger.shutdown()

# Immediate logs on instance
logger.info("started")
logger.error("failed")

Config API

import loza

cfg = loza.Config(
    service="checkout",
    version="1.2.0",
    environment="prod",
    region="ap-south-1",
    level=loza.LevelInfo,
    sinks=[loza.StdoutSink()],
    async_config=loza.AsyncConfig(enabled=True, queue_size=8192, workers=2),
    field_naming=loza.FieldNamingConfig(expand_dot_keys=True),
    security=loza.SecurityConfig(max_field_bytes=4096, max_event_bytes=262144),
)

# Builder style
cfg = (loza.production("checkout")
    .with_version("1.2.0")
    .with_environment("prod")
    .with_sink(loza.StdoutSink())
    .with_sampler(loza.SampleErrors())
    .with_redactor(loza.DefaultRedactor())
    .with_async(True)
    .with_duplicate_policy(loza.LastWins)
)

Levels

import loza

level = loza.ParseLevel("info")  # loza.LevelInfo

Sinks

import loza

cfg = loza.production("checkout").with_sink(loza.StdoutSink())
cfg = loza.production("checkout").with_sink(loza.FileSink("/var/log/app.log"))
cfg = loza.production("checkout").with_sink(loza.HTTPBatchSink("http://collector:9308/events"))

# For testing
sink, store = loza.MemorySink()
logger = loza.create_loza(service="checkout", sink=sink)
# ... use logger ...
events = store.events()

Sampling

import loza
from datetime import timedelta

cfg = loza.production("checkout").with_sampler(loza.SampleAll())
cfg = loza.production("checkout").with_sampler(loza.SampleRandom(0.01))  # 1% sampling
cfg = loza.production("checkout").with_sampler(loza.SampleErrors())
cfg = loza.production("checkout").with_sampler(loza.SampleSlowRequests(timedelta(milliseconds=500)))
cfg = loza.production("checkout").with_sampler(loza.SampleStatusCodes(500, 502, 503))
cfg = loza.production("checkout").with_sampler(loza.SampleRoutes("/checkout", "/payment"))
cfg = loza.production("checkout").with_sampler(loza.SampleUsers("u_1", "u_2"))
cfg = loza.production("checkout").with_sampler(loza.SampleRateLimited(100.0, 1.0))  # 100 events/sec

# Combinators
cfg = loza.production("checkout").with_sampler(
    loza.AnySampler(
        loza.SampleErrors(),
        loza.SampleSlowRequests(timedelta(milliseconds=500)),
        loza.SampleRandom(0.01),
    )
)

Redaction

import loza

cfg = loza.production("checkout").with_redactor(loza.DefaultRedactor())
cfg = loza.production("checkout").with_redactor(loza.RedactKeys("password", "token", "authorization"))
cfg = loza.production("checkout").with_redactor(loza.HashKeys("user.email"))

# Compose
cfg = loza.production("checkout").with_redactor(
    loza.ComposeRedactors(
        loza.DefaultRedactor(),
        loza.RedactKeys("password", "token"),
        loza.HashKeys("user.email"),
    )
)

# Mark fields as sensitive
loza.enrich(ctx,
    loza.SensitiveString("user.email", email),  # auto-redacted
    loza.MarkSensitive("credit_card", card_no),
    loza.HashString("user.ssn", ssn),
)

Schema

import loza

cfg = loza.production("checkout").with_schema(loza.DefaultSchema())
cfg = loza.production("checkout").with_schema(loza.FlatSchema())  # good for ClickHouse
cfg = loza.production("checkout").with_schema(loza.OTelLogSchema())
cfg = loza.production("checkout").with_schema(loza.ECSchema())

# Custom schema
def my_schema(ev):
    return {
        "ts": ev.timestamp(),
        "service": ev.service(),
        "requestId": ev.request_id(),
        "took": ev.duration_ms(),
        "fields": ev.attrs(),
    }

cfg = loza.production("checkout").with_schema(loza.CustomSchema(my_schema))

Duplicate Policy

import loza

cfg = loza.production("checkout").with_duplicate_policy(loza.CanonicalWins)  # default
cfg = loza.production("checkout").with_duplicate_policy(loza.LastWins)      # user attrs win
cfg = loza.production("checkout").with_duplicate_policy(loza.ErrorOnDuplicate)  # strict

Middleware

# Flask
from loza.middleware.flask.middleware import LozaMiddleware
app.wsgi_app = LozaMiddleware(app.wsgi_app, service="checkout")

# FastAPI / Starlette (ASGI)
from loza.middleware.asgi.middleware import Middleware as AsgiMiddleware
app.add_middleware(AsgiMiddleware, service="checkout")

# Django
from loza.middleware.django.middleware import LozaMiddleware
# Add to MIDDLEWARE in settings.py

Feature Flags

import loza

loza.enrich(ctx,
    loza.FeatureFlag("checkout_v2", "enabled"),
    loza.FeatureFlagBool("new_ui", True),
    loza.Experiment("pricing_test", "variant_b"),
)

Security

import loza

cfg = loza.production("checkout").with_security(loza.SecurityConfig(
    redact_by_default=True,
    allow_pii=False,
    max_field_bytes=4096,
    max_event_bytes=262144,
    max_attr_count=512,
    drop_oversized_events=True,
))

loza.enrich(ctx,
    loza.SensitiveString("user.email", email),
    loza.HashString("user.ssn", ssn),
)

Context Helpers

import loza

ev, ok = loza.FromContext(ctx)
if loza.HasEvent(ctx):
    eid = loza.EventID(ctx)
    rid = loza.RequestIDFromContext(ctx)

Testing

from loza.testkit.helpers import TestLogger, Capture, AssertEvent, AssertRedacted, AssertHasCheckpoint, CapturingLogger

# Test logger with memory sink
logger, store = TestLogger("test")

# Capture events
events = Capture(lambda: some_function())

# Assert
AssertEvent(events[0], user__id="u_123")
AssertRedacted(events[0], "password")
AssertHasCheckpoint(events[0], "payment_started")

# Context manager
with CapturingLogger("test") as (logger, store):
    ctx = logger.start_event(loza.Params(event="test"))
    logger.finish(ctx, "success")
    logger.emit(ctx)
events = store.events()

Testing / Conformance

python -m pytest -q
python ../../spec/conformance/runner.py --sdk python --group all

Docs

Cross-Language Parity

Feature Python Go JavaScript Rust
Module facade import loza import "loza" import loza use loza
Configure loza.configure() loza.Configure() loza.configure() loza::configure()
Start event loza.start_event() loza.StartEvent() loza.startEvent() loza::start_event()
Enrich loza.enrich() loza.Enrich() loza.enrich() loza::enrich()
Checkpoint loza.checkpoint() loza.Checkpoint() loza.checkpoint() loza::checkpoint()
Finish loza.finish() loza.Finish() loza.finish() loza::finish()
Emit loza.emit() loza.Emit() loza.emit() loza::emit()
Shutdown loza.shutdown() loza.Shutdown() loza.shutdown() loza::shutdown()
Create instance loza.create_loza() loza.NewLogger() loza.createLoza() loza::new()
Alias loza.alias() loza.Alias() loza.alias() loza::alias()
String attr loza.String() loza.String() loza.string() loza::String()
Int attr loza.Int() loza.Int() loza.int() loza::Int()
User ID loza.UserID() loza.UserID() loza.userID() loza::UserID()
Feature flag loza.FeatureFlag() loza.FeatureFlag() loza.featureFlag() loza::FeatureFlag()
Middleware loza.middleware.* loza/http loza/middleware loza::middleware
Test kit loza.testkit loza/testkit loza/testkit loza::testkit

Metadata

Release files for loza 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for loza 0.4.0
File Size Uploaded
loza-0.4.0.tar.gz 132.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for loza 0.4.0
File Interpreter ABI Platform
loza-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 255.7 kB

Release files / loza-0.4.0.tar.gz

Download URL loza-0.4.0.tar.gz
Size 132.4 kB
Tags Source
SHA-256 checksum
How to use checksums
260acd3e74d68f0d84e5eb5899dbdf4c123e494bf82b18a3906d7bb9fd9971d2
BLAKE2b-256 checksum
How to use checksums
8d0b8061dcd3219a893c08a986526ddf13ffbcb547dad95a2fc11ce5949ccd04
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / loza-0.4.0-py3-none-any.whl

Download URL loza-0.4.0-py3-none-any.whl
Size 123.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
502c423016c46f2df7d986f511548687a05a48d4ca564a347c27dfd8838a6458
BLAKE2b-256 checksum
How to use checksums
de512cfc92efe3153bc6b44736662c6cb0fb2af457aed06670f6092d12629ff8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.4.1

2 release files

This release

0.4.0 This release

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.2.6

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page