sloplog 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +204 -0
- package/dist/codegen.d.ts +43 -0
- package/dist/codegen.js +500 -0
- package/dist/collectors/betterstack.d.ts +47 -0
- package/dist/collectors/betterstack.js +74 -0
- package/dist/collectors/composite.d.ts +1 -0
- package/dist/collectors/composite.js +1 -0
- package/dist/collectors/file.d.ts +1 -0
- package/dist/collectors/file.js +1 -0
- package/dist/collectors/filtered.d.ts +1 -0
- package/dist/collectors/filtered.js +1 -0
- package/dist/collectors/index.d.ts +102 -0
- package/dist/collectors/index.js +127 -0
- package/dist/collectors/sentry.d.ts +46 -0
- package/dist/collectors/sentry.js +45 -0
- package/dist/collectors/stdio.d.ts +1 -0
- package/dist/collectors/stdio.js +1 -0
- package/dist/generated/partials.d.ts +40 -0
- package/dist/generated/partials.js +3 -0
- package/dist/index.d.ts +199 -0
- package/dist/index.js +354 -0
- package/dist/originator/index.d.ts +150 -0
- package/dist/originator/index.js +217 -0
- package/dist/partials.d.ts +154 -0
- package/dist/partials.js +52 -0
- package/dist/registry.d.ts +89 -0
- package/dist/registry.js +44 -0
- package/dist/wevt-0.0.1-py3-none-any.whl +0 -0
- package/dist/wevt-0.0.1.tar.gz +0 -0
- package/dist/wevt-0.0.2-py3-none-any.whl +0 -0
- package/dist/wevt-0.0.2.tar.gz +0 -0
- package/package.json +101 -0
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# sloplog - A python and typescript library for constructing wide events
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
## Quick start (TypeScript)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { partial, registry, z, service, wideEvent, httpOriginator } from 'sloplog';
|
|
11
|
+
import { stdioCollector } from 'sloplog/collectors/stdio';
|
|
12
|
+
|
|
13
|
+
const user = partial('user', {
|
|
14
|
+
id: z.string(),
|
|
15
|
+
tier: z.enum(['free', 'pro']),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const request = partial('request', {
|
|
19
|
+
method: z.string(),
|
|
20
|
+
durationMs: z.number(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const reg = registry([user, request]);
|
|
24
|
+
|
|
25
|
+
const collector = stdioCollector();
|
|
26
|
+
const originator = httpOriginator(new Request('https://example.com'));
|
|
27
|
+
|
|
28
|
+
const evt = wideEvent(reg, service({ name: 'my-service' }), originator, collector);
|
|
29
|
+
|
|
30
|
+
evt.partial(user({ id: 'user_123', tier: 'pro' }));
|
|
31
|
+
evt.log(request({ method: 'GET', durationMs: 120 }));
|
|
32
|
+
evt.log('cache miss', { key: 'user_123' }, 'warn');
|
|
33
|
+
evt.error(new Error('boom'));
|
|
34
|
+
await evt.flush();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick start (Python)
|
|
38
|
+
|
|
39
|
+
```py
|
|
40
|
+
import asyncio
|
|
41
|
+
from sloplog import service, wideevent, cron_originator
|
|
42
|
+
from sloplog.collectors import stdio_collector
|
|
43
|
+
|
|
44
|
+
async def main() -> None:
|
|
45
|
+
collector = stdio_collector()
|
|
46
|
+
originator = cron_originator("*/5 * * * *", "cleanup-job")
|
|
47
|
+
evt = wideevent(service({"name": "my-service"}), originator, collector)
|
|
48
|
+
|
|
49
|
+
evt.log("cache miss", {"key": "user_123"}, "warn")
|
|
50
|
+
evt.error(Exception("boom"))
|
|
51
|
+
evt.span("refresh-cache", lambda: None)
|
|
52
|
+
await evt.flush()
|
|
53
|
+
|
|
54
|
+
asyncio.run(main())
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## The structure of a sloplog WideEvent
|
|
58
|
+
|
|
59
|
+
Each wide event includes:
|
|
60
|
+
|
|
61
|
+
1. WideEventBase: `eventId`, `traceId`, `service`, and `originator`
|
|
62
|
+
2. WideEventPartials: structured payloads keyed by partial type
|
|
63
|
+
|
|
64
|
+
Partials are defined up front to keep fields consistent across services. A wide event log
|
|
65
|
+
will look like:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const evt = {
|
|
69
|
+
eventId: 'evt_...',
|
|
70
|
+
traceId: 'trace_...',
|
|
71
|
+
service: {
|
|
72
|
+
name: 'my-rest-service',
|
|
73
|
+
version: '1.0.0',
|
|
74
|
+
sloplogVersion: '0.0.3',
|
|
75
|
+
sloplogLanguage: 'typescript',
|
|
76
|
+
pod_id: 'v8a4ad',
|
|
77
|
+
},
|
|
78
|
+
originator: {
|
|
79
|
+
type: 'http',
|
|
80
|
+
originatorId: 'orig_...',
|
|
81
|
+
method: 'POST',
|
|
82
|
+
path: '/foo',
|
|
83
|
+
},
|
|
84
|
+
user: { type: 'user', id: 'user_123', tier: 'pro' },
|
|
85
|
+
request: { type: 'request', method: 'POST', durationMs: 120 },
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Collectors
|
|
90
|
+
|
|
91
|
+
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.:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
/**
|
|
95
|
+
* Simple collector to log the event in the console
|
|
96
|
+
*/
|
|
97
|
+
import { stdioCollector } from 'sloplog/collectors/stdio';
|
|
98
|
+
|
|
99
|
+
const collector = stdioCollector();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Python:
|
|
103
|
+
|
|
104
|
+
```py
|
|
105
|
+
from sloplog.collectors import stdio_collector
|
|
106
|
+
|
|
107
|
+
collector = stdio_collector()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Included collectors: `stdio`, `file`, `composite`, `filtered`, `betterstack`, `sentry` (requires optional `@sentry/node` peer dependency).
|
|
111
|
+
|
|
112
|
+
### WideEventBase
|
|
113
|
+
|
|
114
|
+
The WideEventBase type contains:
|
|
115
|
+
|
|
116
|
+
1. `eventId`, which uniquely identifies your event
|
|
117
|
+
2. `traceId`, which stays constant across a distributed trace
|
|
118
|
+
3. `originator`, an external thing that triggered your service (HTTP request, cron trigger, etc)
|
|
119
|
+
4. `service`, where an event is emitted from (use `service()` to add sloplog defaults)
|
|
120
|
+
|
|
121
|
+
`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()`.
|
|
122
|
+
|
|
123
|
+
### WideEventPartial
|
|
124
|
+
|
|
125
|
+
Partials are added to a WideEvent via:
|
|
126
|
+
|
|
127
|
+
- `event.partial(partial)` for structured partials
|
|
128
|
+
- `event.log(partial)` as an alias for `partial()`
|
|
129
|
+
- `event.log("message", data?, level?)` to emit `log_message` (level defaults to `info`, data is JSON-stringified)
|
|
130
|
+
- `event.error(error)` to emit an `error` partial
|
|
131
|
+
- `event.span(name, fn)` / `event.spanStart(name)` / `event.spanEnd(name)`
|
|
132
|
+
|
|
133
|
+
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.
|
|
134
|
+
|
|
135
|
+
## Built-in partials (separate module)
|
|
136
|
+
|
|
137
|
+
sloplog ships a small set of built-in partials for convenience. These are intentionally separate from the core API and may change.
|
|
138
|
+
|
|
139
|
+
TypeScript:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { builtInRegistry, builtInPartialMetadata } from 'sloplog/partials';
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Python:
|
|
146
|
+
|
|
147
|
+
```py
|
|
148
|
+
from sloplog.partials import GeneratedRegistry, PARTIAL_METADATA
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Built-in partial names: `error` (repeatable + always-sample), `log_message` (with `level`), `span`, `sloplog_usage_error`.
|
|
152
|
+
|
|
153
|
+
## Registry + codegen
|
|
154
|
+
|
|
155
|
+
Define your registry in a `sloplog.reg.ts` file (or any path you prefer):
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { partial, registry, z } from 'sloplog';
|
|
159
|
+
|
|
160
|
+
const user = partial('user', {
|
|
161
|
+
userId: z.string(),
|
|
162
|
+
subscriptionLevel: z.string(),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export default registry([user]);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Pass the registry as the first argument to `wideEvent()` to infer types and extract metadata:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const evt = wideEvent(registry, service({ name: 'my-service' }), originator, collector);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Generate Python + JSON Schema outputs with the `config()` helper:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { config } from 'sloplog/codegen';
|
|
178
|
+
|
|
179
|
+
await config({
|
|
180
|
+
registry: './sloplog.reg.ts',
|
|
181
|
+
outDir: './generated',
|
|
182
|
+
outputs: ['python', 'jsonschema'],
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`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:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
await config({
|
|
190
|
+
registry: './sloplog.reg.ts',
|
|
191
|
+
outputs: { python: 'types.py', jsonschema: false },
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
If your registry is a TypeScript file, run the script with a TS runtime like `tsx` or `ts-node`.
|
|
196
|
+
|
|
197
|
+
# prior art
|
|
198
|
+
|
|
199
|
+
- an open source example of my proto-logging library: https://github.com/cloudflare/mcp-server-cloudflare/tree/eb24e3bba8be7b682aa721d34918ff0954f1254a/packages/mcp-observability
|
|
200
|
+
- https://boristane.com/blog/observability-wide-events-101/
|
|
201
|
+
- https://isburmistrov.substack.com/p/all-you-need-is-wide-events-not-metrics
|
|
202
|
+
- https://jeremymorrell.dev/blog/a-practitioners-guide-to-wide-events/
|
|
203
|
+
- https://charity.wtf/2024/08/07/is-it-time-to-version-observability-signs-point-to-yes/
|
|
204
|
+
- https://loggingsucks.com/
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code generation utilities for sloplog partials
|
|
3
|
+
*
|
|
4
|
+
* Generates TypeScript, Python, and JSON Schema from partial definitions
|
|
5
|
+
*/
|
|
6
|
+
import type { ZodRawShape } from 'zod';
|
|
7
|
+
import type { PartialDefinition, Registry } from './registry.js';
|
|
8
|
+
/**
|
|
9
|
+
* Generate full JSON Schema for a registry
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateJsonSchema(reg: Registry<PartialDefinition<string, ZodRawShape>[]>): object;
|
|
12
|
+
/**
|
|
13
|
+
* Generate TypeScript types for a registry
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateTypeScript(reg: Registry<PartialDefinition<string, ZodRawShape>[]>): string;
|
|
16
|
+
/**
|
|
17
|
+
* Generate Python TypedDicts for a registry
|
|
18
|
+
*/
|
|
19
|
+
export declare function generatePython(reg: Registry<PartialDefinition<string, ZodRawShape>[]>): string;
|
|
20
|
+
export type CodegenOutputKind = 'python' | 'jsonschema';
|
|
21
|
+
export type CodegenOutputs = CodegenOutputKind[] | {
|
|
22
|
+
python?: string | false;
|
|
23
|
+
jsonschema?: string | false;
|
|
24
|
+
};
|
|
25
|
+
export interface CodegenConfig {
|
|
26
|
+
registry: string | Registry<PartialDefinition<string, ZodRawShape>[]>;
|
|
27
|
+
outDir?: string;
|
|
28
|
+
outputs?: CodegenOutputs;
|
|
29
|
+
}
|
|
30
|
+
export interface CodegenResult {
|
|
31
|
+
python?: {
|
|
32
|
+
path: string;
|
|
33
|
+
code: string;
|
|
34
|
+
};
|
|
35
|
+
jsonschema?: {
|
|
36
|
+
path: string;
|
|
37
|
+
code: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate outputs from a sloplog registry (Python + JSON schema).
|
|
42
|
+
*/
|
|
43
|
+
export declare function config(config: CodegenConfig): Promise<CodegenResult>;
|