Skip to main content

A Python library for constructing wide events

Project description

sloplog - A python and typescript library for constructing wide events

When constructing wide events for my services, I've found myself constructing essentially the same library again and again. I end up constructing a mediocre semi-structured wide event library.

The core idea is taken from a wide array of prior art (see below) on wide events. We have structured logs which will eventually be queried. The structured logging part isn't particularly hard, but I've found that it's nice to have a single place where my log structure is defined.

Quick start (TypeScript)

import { partial, registry, z, service, wideEvent, httpOriginator } from 'sloplog';
import { stdioCollector } from 'sloplog/collectors/stdio';

const user = partial('user', {
  id: z.string(),
  tier: z.enum(['free', 'pro']),
});

const request = partial('request', {
  method: z.string(),
  durationMs: z.number(),
});

const reg = registry([user, request]);

const collector = stdioCollector();
const originator = httpOriginator(new Request('https://example.com'));

const evt = wideEvent(reg, service({ name: 'my-service' }), originator, collector);

evt.partial(user({ id: 'user_123', tier: 'pro' }));
evt.log(request({ method: 'GET', durationMs: 120 }));
evt.log('cache miss', { key: 'user_123' }, 'warn');
evt.error(new Error('boom'));
await evt.flush();

Quick start (Python)

import asyncio
from sloplog import service, wideevent, cron_originator
from sloplog.collectors import stdio_collector

async def main() -> None:
    collector = stdio_collector()
    originator = cron_originator("*/5 * * * *", "cleanup-job")
    evt = wideevent(service({"name": "my-service"}), originator, collector)

    evt.log("cache miss", {"key": "user_123"}, "warn")
    evt.error(Exception("boom"))
    evt.span("refresh-cache", lambda: None)
    await evt.flush()

asyncio.run(main())

The structure of a sloplog WideEvent

Each wide event includes:

  1. WideEventBase: eventId, traceId, service, and originator
  2. WideEventPartials: structured payloads keyed by partial type

Partials are defined up front to keep fields consistent across services. A wide event log will look like:

const evt = {
  eventId: 'evt_...',
  traceId: 'trace_...',
  service: {
    name: 'my-rest-service',
    version: '1.0.0',
    sloplogVersion: '0.0.3',
    sloplogLanguage: 'typescript',
    pod_id: 'v8a4ad',
  },
  originator: {
    type: 'http',
    originatorId: 'orig_...',
    method: 'POST',
    path: '/foo',
  },
  user: { type: 'user', id: 'user_123', tier: 'pro' },
  request: { type: 'request', method: 'POST', durationMs: 120 },
};

Collectors

Collectors flush wide event logs. The goal is that you can adapt the format and flush the logs wherever you want. Import collectors via subpaths, e.g.:

/**
 * Simple collector to log the event in the console
 */
import { stdioCollector } from 'sloplog/collectors/stdio';

const collector = stdioCollector();

Python:

from sloplog.collectors import stdio_collector

collector = stdio_collector()

Included collectors: stdio, file, composite, filtered, betterstack, sentry (requires optional @sentry/node peer dependency).

WideEventBase

The WideEventBase type contains:

  1. eventId, which uniquely identifies your event
  2. traceId, which stays constant across a distributed trace
  3. originator, an external thing that triggered your service (HTTP request, cron trigger, etc)
  4. service, where an event is emitted from (use service() to add sloplog defaults)

httpOriginator() returns { originator, traceId }. You can pass that object directly to wideEvent() and the trace ID will be picked up automatically. In Python, starlette_http_originator() and flask_http_originator() return { originator, trace_id } and can be passed directly to wideevent().

WideEventPartial

Partials are added to a WideEvent via:

  • event.partial(partial) for structured partials
  • event.log(partial) as an alias for partial()
  • event.log("message", data?, level?) to emit log_message (level defaults to info, data is JSON-stringified)
  • event.error(error) to emit an error partial
  • event.span(name, fn) / event.spanStart(name) / event.spanEnd(name)

Partials are always preferred over log_message for structured data. Usage errors (partial overwrites or span misuse) are emitted as sloplog_usage_error on flush.

Built-in partials (separate module)

sloplog ships a small set of built-in partials for convenience. These are intentionally separate from the core API and may change.

TypeScript:

import { builtInRegistry, builtInPartialMetadata } from 'sloplog/partials';

Python:

from sloplog.partials import GeneratedRegistry, PARTIAL_METADATA

Built-in partial names: error (repeatable + always-sample), log_message (with level), span, sloplog_usage_error.

Registry + codegen

Define your registry in a sloplog.reg.ts file (or any path you prefer):

import { partial, registry, z } from 'sloplog';

const user = partial('user', {
  userId: z.string(),
  subscriptionLevel: z.string(),
});

export default registry([user]);

Pass the registry as the first argument to wideEvent() to infer types and extract metadata:

const evt = wideEvent(registry, service({ name: 'my-service' }), originator, collector);

Generate Python + JSON Schema outputs with the config() helper:

import { config } from 'sloplog/codegen';

await config({
  registry: './sloplog.reg.ts',
  outDir: './generated',
  outputs: ['python', 'jsonschema'],
});

registry can be a registry object or a path to a module exporting one. Defaults write ./generated/sloplog.py and ./generated/sloplog.json. Disable or rename outputs via:

await config({
  registry: './sloplog.reg.ts',
  outputs: { python: 'types.py', jsonschema: false },
});

If your registry is a TypeScript file, run the script with a TS runtime like tsx or ts-node.

prior art

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

sloplog-0.0.3.tar.gz (65.2 kB view details)

Uploaded Source

Built Distribution

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

sloplog-0.0.3-py3-none-any.whl (14.8 kB view details)

Uploaded Python 3

File details

Details for the file sloplog-0.0.3.tar.gz.

File metadata

  • Download URL: sloplog-0.0.3.tar.gz
  • Upload date:
  • Size: 65.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for sloplog-0.0.3.tar.gz
Algorithm Hash digest
SHA256 860902979eca865eb6578d3b2f4d9156de0fbf48d2150b2f657e60ea03bf7c3e
MD5 6f2e6fea1efb11646acc0e5803caff15
BLAKE2b-256 55aa4d2b8b1ab0c3b616744feb81ed01e87c71f7725eec26bc283ea38bfeae38

See more details on using hashes here.

File details

Details for the file sloplog-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: sloplog-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 14.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for sloplog-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 f7f980784b56ec862fec85f9d6a29b5f5a38a734fe11d35ad32dc8187b06bcfa
MD5 c25c5d1e3c1cf5f758fefafd25fde505
BLAKE2b-256 c3a94c6aae84ab9d8622cf033497f16fab4e6681944574e292a3c9cc418d285f

See more details on using hashes here.

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