@owlmeans/viable-log 0.1.18-rc.1 → 0.1.18-rc.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 +148 -1
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/viable-log/SKILL.md +4 -4
- package/build/event/types.d.ts +13 -0
- package/build/event/types.d.ts.map +1 -0
- package/build/event/types.js +2 -0
- package/build/event/types.js.map +1 -0
- package/build/event.d.ts +5 -6
- package/build/event.d.ts.map +1 -1
- package/build/event.js +54 -52
- package/build/event.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/plugins.js +2 -2
- package/build/plugins.js.map +1 -1
- package/build/types.d.ts +2 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/event/types.ts +13 -0
- package/src/event.ts +60 -52
- package/src/index.ts +1 -0
- package/src/plugins.ts +2 -2
- package/src/types.ts +1 -1
- package/tests/viable-log.spec.ts +20 -20
package/README.md
CHANGED
|
@@ -1,3 +1,150 @@
|
|
|
1
|
+
# @owlmeans/viable-log
|
|
2
|
+
|
|
3
|
+
A generated (target) application's link to the OwlMeans platform (viable), expressed as two
|
|
4
|
+
`@owlmeans/log` plugins plus the wire contract the platform reads them with. `viablePreviewPlugin`
|
|
5
|
+
sends the target web's analytics events to the manager frame that embeds the preview;
|
|
6
|
+
`viableSlotPlugin` writes the target backend's error records and analytics events as marked JSON
|
|
7
|
+
lines on stdout, where the platform's publisher picks them up. A generated app registers both
|
|
8
|
+
unconditionally — each is inert wherever the platform is absent — and the platform side imports the
|
|
9
|
+
same package to parse what the app emits. An application that is not generated and run by viable has
|
|
10
|
+
no use for it: count events with `@owlmeans/web-log` (tag manager) instead. The `viable-log` skill
|
|
11
|
+
covers this package; the `logging` skill sets the policy for the log calls that feed it.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add @owlmeans/viable-log@^0.1.18-rc.3
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Concepts
|
|
20
|
+
|
|
21
|
+
- **Target application** — an app generated and hosted by viable. It logs only through
|
|
22
|
+
`@owlmeans/log`; these plugins decide what reaches the platform.
|
|
23
|
+
- **Preview channel** — `window[PREVIEW_REPORTER_FLAG]`, an object with `post(type, payload)`
|
|
24
|
+
installed by the platform's injected reporter in a framed preview. Absent in production.
|
|
25
|
+
- **Slot events** — a backend process run by the publisher with `OWLMEANS_SLOT_EVENTS=1`
|
|
26
|
+
(`SLOT_EVENTS_ENV`). Without that variable the slot plugin writes nothing.
|
|
27
|
+
- **Target event line** — `TARGET_EVENT_MARKER` (`'[owl:event] '`) followed by one JSON `TargetEvent`,
|
|
28
|
+
capped at `TARGET_EVENT_MAX` (16 KiB). Versioned by `TARGET_EVENT_VERSION`.
|
|
29
|
+
- **Untrusted input** — the platform treats every line as written by generated code: it validates
|
|
30
|
+
marker, JSON, version, kind and level, clips text fields and drops unknown ones.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### 1. Target web — register the preview plugin
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { addLogPlugin, logger } from '@owlmeans/log'
|
|
38
|
+
import { viablePreviewPlugin } from '@owlmeans/viable-log'
|
|
39
|
+
|
|
40
|
+
addLogPlugin(viablePreviewPlugin())
|
|
41
|
+
|
|
42
|
+
logger('ui').info('Opened', { page: 'home' }, { analytics: 'page_view' })
|
|
43
|
+
// in a framed preview: channel.post('OWLMEANS_PREVIEW_ANALYTICS', { event: 'page_view', scope: 'ui', data: { page: 'home' }, ... })
|
|
44
|
+
// anywhere else: nothing
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Browser errors need no plugin: a record carrying an `Error` reaches the reporter's own
|
|
48
|
+
`console.error` hook through the logger's console sink.
|
|
49
|
+
|
|
50
|
+
### 2. Target backend — register the slot plugin
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { addLogPlugin, logger } from '@owlmeans/log'
|
|
54
|
+
import { viableSlotPlugin } from '@owlmeans/viable-log'
|
|
55
|
+
|
|
56
|
+
addLogPlugin(viableSlotPlugin())
|
|
57
|
+
|
|
58
|
+
const log = logger('api')
|
|
59
|
+
log.error('Request failed', error) // → kind 'error' line
|
|
60
|
+
log.info('Signed up', { plan: 'free' }, { analytics: 'signup' }) // → kind 'analytics' line
|
|
61
|
+
log.warn('Slow query') // → no line (not error, not analytics)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Platform side — read a stdout line
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { targetEventHelper } from '@owlmeans/viable-log'
|
|
68
|
+
|
|
69
|
+
for (const line of chunk.split('\n')) {
|
|
70
|
+
const event = targetEventHelper.parseTargetEventLine(line) // marker may appear anywhere in the line
|
|
71
|
+
if (event == null) {
|
|
72
|
+
continue // ordinary output, or a rejected line
|
|
73
|
+
}
|
|
74
|
+
relay(event)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. Platform side — detect the preview channel
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { previewChannel, PREVIEW_ANALYTICS_TYPE } from '@owlmeans/viable-log'
|
|
82
|
+
|
|
83
|
+
previewChannel()?.post(PREVIEW_ANALYTICS_TYPE, payload)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`previewChannel()` returns `undefined` outside a browser and whenever the flag is not an object with
|
|
87
|
+
a `post` function.
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
### Plugins and helpers
|
|
92
|
+
|
|
93
|
+
| Symbol | Kind | Purpose |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `viablePreviewPlugin()` | function → `LogPlugin` | Name `'viable-preview'`; `track` posts each `AnalyticsEvent` as `PREVIEW_ANALYTICS_TYPE` through the preview channel |
|
|
96
|
+
| `viableSlotPlugin()` | function → `LogPlugin` | Name `'viable-slot'`; `log` emits error-level records, `track` emits analytics events, both as target event lines on `process.stdout` when `SLOT_EVENTS_ENV` is `'1'` |
|
|
97
|
+
| `previewChannel()` | function | The current document's `PreviewChannel`, or `undefined` |
|
|
98
|
+
| `targetEventHelper` | `TargetEventHelper` | Default instance of the line protocol |
|
|
99
|
+
| `createTargetEventHelper()` | function | Builds a `TargetEventHelper` |
|
|
100
|
+
| `targetEventHelper.targetEventLine(event)` | member | Serialise an event; an over-long one loses its stack, then its data, then is cut at `TARGET_EVENT_MAX` |
|
|
101
|
+
| `targetEventHelper.parseTargetEventLine(line)` | member | Validate and clip an untrusted line → `TargetEvent \| undefined` |
|
|
102
|
+
| `targetEventLine`, `parseTargetEventLine` | functions | Deprecated wrappers; use `targetEventHelper` |
|
|
103
|
+
|
|
104
|
+
### Constants
|
|
105
|
+
|
|
106
|
+
| Symbol | Value | Purpose |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `PREVIEW_REPORTER_FLAG` | `'__owlmeansPreviewReporter'` | `window` property holding the preview channel |
|
|
109
|
+
| `PREVIEW_ANALYTICS_TYPE` | `'OWLMEANS_PREVIEW_ANALYTICS'` | Message type of a preview analytics post |
|
|
110
|
+
| `TARGET_EVENT_MARKER` | `'[owl:event] '` | Prefix of a target event line |
|
|
111
|
+
| `SLOT_EVENTS_ENV` | `'OWLMEANS_SLOT_EVENTS'` | Environment variable the publisher sets to `1` |
|
|
112
|
+
| `TARGET_EVENT_VERSION` | `1` | Format version of `TargetEvent` |
|
|
113
|
+
| `TARGET_EVENT_MAX` | `16384` | Longest line written and accepted |
|
|
114
|
+
|
|
115
|
+
### Types
|
|
116
|
+
|
|
117
|
+
| Symbol | Shape |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `TargetEvent` | `{ v, kind, ts, level, scope, message, event?, data?, error?: { name, message, stack?, incidentId? } }` |
|
|
120
|
+
| `TargetEventKind` | `'error' \| 'analytics'` |
|
|
121
|
+
| `PreviewChannel` | `{ v: number, post(type, payload) }` |
|
|
122
|
+
| `PreviewAnalyticsPayload` | the `AnalyticsEvent` from `@owlmeans/log` |
|
|
123
|
+
| `TargetEventHelper` | `{ targetEventLine, parseTargetEventLine }` |
|
|
124
|
+
|
|
125
|
+
On parse, text fields are clipped: `scope` and `event` to 200 characters (`scope` falls back to
|
|
126
|
+
`'target'`), `message` to 2000, `error.stack` to 8000, `error.incidentId` to 100.
|
|
127
|
+
|
|
128
|
+
## Common pitfalls
|
|
129
|
+
|
|
130
|
+
- Never build a marked line or post a preview message from application code — log through
|
|
131
|
+
`@owlmeans/log` and let the plugins emit.
|
|
132
|
+
- Do not gate plugin registration on the environment; both plugins are already inert without the
|
|
133
|
+
channel or `OWLMEANS_SLOT_EVENTS=1`.
|
|
134
|
+
- The slot plugin forwards only `error` records; `warn` and lower reach the platform only when the
|
|
135
|
+
call also carries an `analytics` option.
|
|
136
|
+
- A stack or a large `data` may be dropped to fit `TARGET_EVENT_MAX` — keep the message meaningful
|
|
137
|
+
on its own.
|
|
138
|
+
- Changing any `TargetEvent` field shape requires a new `TARGET_EVENT_VERSION`; the parser rejects
|
|
139
|
+
every other version.
|
|
140
|
+
- Anything read from `parseTargetEventLine` is still user-generated content — do not render or log
|
|
141
|
+
it as trusted text.
|
|
142
|
+
|
|
143
|
+
## Related packages
|
|
144
|
+
|
|
145
|
+
- [`@owlmeans/log`](../log) — `logger`, `addLogPlugin`, `LogPlugin` and the analytics call option
|
|
146
|
+
- [`@owlmeans/web-log`](../web-log) — consent-gated analytics plugins for a tag manager
|
|
147
|
+
|
|
1
148
|
<!-- owlmeans:agent-guidance:start -->
|
|
2
149
|
## Agent guidance
|
|
3
150
|
|
|
@@ -6,7 +153,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
6
153
|
your project's skill store (`.agents/skills/`):
|
|
7
154
|
|
|
8
155
|
```sh
|
|
9
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
156
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.52
|
|
10
157
|
```
|
|
11
158
|
|
|
12
159
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/viable-log",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
4
|
+
"version": "0.1.18-rc.3",
|
|
5
|
+
"generatedAt": "2026-10-08T21:27:54.291Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: viable-log
|
|
3
|
-
description: How to use @owlmeans/viable-log — a generated (target) application's link to the OwlMeans Viable platform, as two @owlmeans/log plugins. viablePreviewPlugin sends the target web's analytics events to the manager frame through the preview reporter's channel; viableSlotPlugin writes the target backend's error records and analytics events as marked JSON stdout lines for the publisher to relay. Also the shared contract the platform reads them with (TARGET_EVENT_MARKER, parseTargetEventLine, PREVIEW_REPORTER_FLAG). Auto-invoked when wiring the platform link into a generated app's web or backend, importing viablePreviewPlugin, viableSlotPlugin or
|
|
3
|
+
description: How to use @owlmeans/viable-log — a generated (target) application's link to the OwlMeans Viable platform, as two @owlmeans/log plugins. viablePreviewPlugin sends the target web's analytics events to the manager frame through the preview reporter's channel; viableSlotPlugin writes the target backend's error records and analytics events as marked JSON stdout lines for the publisher to relay. Also the shared contract the platform reads them with (TARGET_EVENT_MARKER, targetEventHelper.parseTargetEventLine, PREVIEW_REPORTER_FLAG). Auto-invoked when wiring the platform link into a generated app's web or backend, importing viablePreviewPlugin, viableSlotPlugin or targetEventHelper, or changing how the preview reporter or the publisher takes events from a target.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/viable-log
|
|
9
9
|
|
|
10
10
|
**Layer:** Domain (depends on `@owlmeans/log` only)
|
|
11
|
-
**Install:** `"@owlmeans/viable-log": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/viable-log": "^0.1.18-rc.3"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
The platform is "just one of the plugins" of a target's logging: a generated app logs through
|
|
14
14
|
`@owlmeans/log`, and registers these two plugins so what it says reaches Viable. Both are inert
|
|
@@ -32,10 +32,10 @@ Errors in the **browser** need no plugin: a record carrying an `Error` reaches t
|
|
|
32
32
|
|
|
33
33
|
## The contract
|
|
34
34
|
|
|
35
|
-
`parseTargetEventLine(line)` is how the platform's publisher reads a stdout line — **the input is
|
|
35
|
+
`targetEventHelper.parseTargetEventLine(line)` is how the platform's publisher reads a stdout line — **the input is
|
|
36
36
|
untrusted, written by generated code**: the marker, JSON, version
|
|
37
37
|
(`TARGET_EVENT_VERSION`), kind and level are checked, every text field is clipped, unknown fields are
|
|
38
|
-
dropped. `targetEventLine(event)` writes one, cutting a stack and then data to stay under
|
|
38
|
+
dropped. `targetEventHelper.targetEventLine(event)` writes one, cutting a stack and then data to stay under
|
|
39
39
|
`TARGET_EVENT_MAX`. Change a field's shape only with a new `TARGET_EVENT_VERSION`. An application writes events only
|
|
40
40
|
through `@owlmeans/log` (`log.error(…)`, or a call with an `analytics` option) — never by building a
|
|
41
41
|
marked line or posting a preview message itself.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TargetEvent } from '../types.js';
|
|
2
|
+
/** The stdout line protocol a target's backend speaks to the platform's publisher. */
|
|
3
|
+
export interface TargetEventHelper {
|
|
4
|
+
/** The stdout line of an event. Over-long ones lose their stack, then their data — never their head. */
|
|
5
|
+
targetEventLine: (event: TargetEvent) => string;
|
|
6
|
+
/**
|
|
7
|
+
* The event a stdout line carries, or `undefined` when the line is not one — absent marker, broken
|
|
8
|
+
* JSON, unknown version, or a shape the platform does not accept. The input is untrusted: it is
|
|
9
|
+
* written by generated code, and nothing outside this function may read fields of it unchecked.
|
|
10
|
+
*/
|
|
11
|
+
parseTargetEventLine: (line: string) => TargetEvent | undefined;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/event/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC,wGAAwG;IACxG,eAAe,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAA;IAC/C;;;;OAIG;IACH,oBAAoB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAA;CAChE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/event/types.ts"],"names":[],"mappings":""}
|
package/build/event.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { TargetEvent } from './types.js';
|
|
2
|
-
|
|
2
|
+
import type { TargetEventHelper } from './event/types.js';
|
|
3
|
+
export declare const createTargetEventHelper: () => TargetEventHelper;
|
|
4
|
+
export declare const targetEventHelper: TargetEventHelper;
|
|
5
|
+
/** @deprecated compat:factory-refactor — use `targetEventHelper.targetEventLine(…)` */
|
|
3
6
|
export declare const targetEventLine: (event: TargetEvent) => string;
|
|
4
|
-
/**
|
|
5
|
-
* The event a stdout line carries, or `undefined` when the line is not one — absent marker, broken
|
|
6
|
-
* JSON, unknown version, or a shape the platform does not accept. The input is untrusted: it is
|
|
7
|
-
* written by generated code, and nothing outside this function may read fields of it unchecked.
|
|
8
|
-
*/
|
|
7
|
+
/** @deprecated compat:factory-refactor — use `targetEventHelper.parseTargetEventLine(…)` */
|
|
9
8
|
export declare const parseTargetEventLine: (line: string) => TargetEvent | undefined;
|
|
10
9
|
//# sourceMappingURL=event.d.ts.map
|
package/build/event.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAMzD,eAAO,MAAM,uBAAuB,QAAO,iBAoD1C,CAAA;AAED,eAAO,MAAM,iBAAiB,mBAA4B,CAAA;AAE1D,uFAAuF;AACvF,eAAO,MAAM,eAAe,UAAW,WAAW,KAAG,MAAkD,CAAA;AAEvG,4FAA4F;AAC5F,eAAO,MAAM,oBAAoB,SAAU,MAAM,KAAG,WAAW,GAAG,SACpB,CAAA"}
|
package/build/event.js
CHANGED
|
@@ -1,58 +1,60 @@
|
|
|
1
1
|
import { TARGET_EVENT_MARKER, TARGET_EVENT_MAX, TARGET_EVENT_VERSION } from './consts.js';
|
|
2
2
|
import { LEVELS } from './consts.local.js';
|
|
3
|
-
/** The stdout line of an event. Over-long ones lose their stack, then their data — never their head. */
|
|
4
|
-
export const targetEventLine = (event) => {
|
|
5
|
-
let line = TARGET_EVENT_MARKER + JSON.stringify(event);
|
|
6
|
-
if (line.length > TARGET_EVENT_MAX && event.error?.stack != null) {
|
|
7
|
-
line = TARGET_EVENT_MARKER + JSON.stringify({ ...event, error: { ...event.error, stack: undefined } });
|
|
8
|
-
}
|
|
9
|
-
if (line.length > TARGET_EVENT_MAX) {
|
|
10
|
-
line = TARGET_EVENT_MARKER + JSON.stringify({ ...event, data: undefined });
|
|
11
|
-
}
|
|
12
|
-
return line.length > TARGET_EVENT_MAX ? line.slice(0, TARGET_EVENT_MAX) : line;
|
|
13
|
-
};
|
|
14
3
|
const text = (value, max) => typeof value === 'string' ? value.slice(0, max) : undefined;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
v
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
:
|
|
4
|
+
export const createTargetEventHelper = () => {
|
|
5
|
+
const targetEventLine = (event) => {
|
|
6
|
+
let line = TARGET_EVENT_MARKER + JSON.stringify(event);
|
|
7
|
+
if (line.length > TARGET_EVENT_MAX && event.error?.stack != null) {
|
|
8
|
+
line = TARGET_EVENT_MARKER + JSON.stringify({ ...event, error: { ...event.error, stack: undefined } });
|
|
9
|
+
}
|
|
10
|
+
if (line.length > TARGET_EVENT_MAX) {
|
|
11
|
+
line = TARGET_EVENT_MARKER + JSON.stringify({ ...event, data: undefined });
|
|
12
|
+
}
|
|
13
|
+
return line.length > TARGET_EVENT_MAX ? line.slice(0, TARGET_EVENT_MAX) : line;
|
|
14
|
+
};
|
|
15
|
+
const parseTargetEventLine = (line) => {
|
|
16
|
+
const at = line.indexOf(TARGET_EVENT_MARKER);
|
|
17
|
+
if (at < 0 || line.length > TARGET_EVENT_MAX + 64) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
let raw;
|
|
21
|
+
try {
|
|
22
|
+
raw = JSON.parse(line.slice(at + TARGET_EVENT_MARKER.length));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
if (raw == null || typeof raw !== 'object') {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const event = raw;
|
|
31
|
+
if (event.v !== TARGET_EVENT_VERSION || (event.kind !== 'error' && event.kind !== 'analytics')
|
|
32
|
+
|| typeof event.ts !== 'number' || !LEVELS.includes(event.level)) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const error = event.error != null && typeof event.error === 'object' ? event.error : undefined;
|
|
36
|
+
return {
|
|
37
|
+
v: TARGET_EVENT_VERSION,
|
|
38
|
+
kind: event.kind,
|
|
39
|
+
ts: event.ts,
|
|
40
|
+
level: event.level,
|
|
41
|
+
scope: text(event.scope, 200) ?? 'target',
|
|
42
|
+
message: text(event.message, 2000) ?? '',
|
|
43
|
+
event: text(event.event, 200),
|
|
44
|
+
data: event.data,
|
|
45
|
+
error: error != null
|
|
46
|
+
? {
|
|
47
|
+
name: text(error.name, 200) ?? 'Error', message: text(error.message, 2000) ?? '',
|
|
48
|
+
stack: text(error.stack, 8000), incidentId: text(error.incidentId, 100),
|
|
49
|
+
}
|
|
50
|
+
: undefined,
|
|
51
|
+
};
|
|
56
52
|
};
|
|
53
|
+
return { targetEventLine, parseTargetEventLine };
|
|
57
54
|
};
|
|
55
|
+
export const targetEventHelper = createTargetEventHelper();
|
|
56
|
+
/** @deprecated compat:factory-refactor — use `targetEventHelper.targetEventLine(…)` */
|
|
57
|
+
export const targetEventLine = (event) => targetEventHelper.targetEventLine(event);
|
|
58
|
+
/** @deprecated compat:factory-refactor — use `targetEventHelper.parseTargetEventLine(…)` */
|
|
59
|
+
export const parseTargetEventLine = (line) => targetEventHelper.parseTargetEventLine(line);
|
|
58
60
|
//# sourceMappingURL=event.js.map
|
package/build/event.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event.js","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"event.js","sourceRoot":"","sources":["../src/event.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
CHANGED
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,mBAAmB,kBAAkB,CAAA;AACrC,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA"}
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA"}
|
package/build/plugins.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PREVIEW_ANALYTICS_TYPE, PREVIEW_REPORTER_FLAG, SLOT_EVENTS_ENV, TARGET_EVENT_VERSION } from './consts.js';
|
|
2
|
-
import {
|
|
2
|
+
import { targetEventHelper } from './event.js';
|
|
3
3
|
/** The reporter's channel of the current document, or `undefined` outside a framed preview. */
|
|
4
4
|
export const previewChannel = () => {
|
|
5
5
|
if (typeof window === 'undefined') {
|
|
@@ -47,7 +47,7 @@ export const viableSlotPlugin = () => {
|
|
|
47
47
|
const emit = (event) => {
|
|
48
48
|
const out = stdout();
|
|
49
49
|
if (out != null && slotEventsEnabled()) {
|
|
50
|
-
out.write(`${targetEventLine(event)}\n`);
|
|
50
|
+
out.write(`${targetEventHelper.targetEventLine(event)}\n`);
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
return {
|
package/build/plugins.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAClH,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAI9C,+FAA+F;AAC/F,MAAM,CAAC,MAAM,cAAc,GAAG,GAA+B,EAAE;IAC7D,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,OAAO,GAAI,MAA6C,CAAC,qBAAqB,CAAwC,CAAA;IAC5H,OAAO,OAAO,IAAI,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU;QACzF,CAAC,CAAC,OAAyB,CAAC,CAAC,CAAC,SAAS,CAAA;AAC3C,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAc,EAAE,CAAC,CAAC;IACnD,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,KAAK,CAAC,EAAE;QACb,cAAc,EAAE,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAA;IACvD,CAAC;CACF,CAAC,CAAA;AAEF,MAAM,MAAM,GAAG,GAAuB,EAAE,CACrC,UAAgD,CAAC,OAAO,EAAE,MAAM,CAAA;AAEnE,MAAM,iBAAiB,GAAG,GAAY,EAAE,CACrC,UAAyE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,GAAG,CAAA;AAEpH,MAAM,OAAO,GAAG,CAAC,MAAiB,EAAwB,EAAE;IAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;IAC1B,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,UAAU,GAAI,KAAkC,CAAC,UAAU,CAAA;IACjE,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK;QAC5D,UAAU,EAAE,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;KACpE,CAAA;AACH,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAc,EAAE;IAC9C,MAAM,IAAI,GAAG,CAAC,KAAkB,EAAQ,EAAE;QACxC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;QACpB,IAAI,GAAG,IAAI,IAAI,IAAI,iBAAiB,EAAE,EAAE,CAAC;YACvC,GAAG,CAAC,KAAK,CAAC,GAAG,iBAAiB,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC5D,CAAC;IACH,CAAC,CAAA;IACD,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,GAAG,EAAE,MAAM,CAAC,EAAE;YACZ,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;gBAC7B,OAAM;YACR,CAAC;YACD,IAAI,CAAC;gBACH,CAAC,EAAE,oBAAoB,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK;gBAC5F,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC;aACxF,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,EAAE,KAAK,CAAC,EAAE;YACb,IAAI,CAAC;gBACH,CAAC,EAAE,oBAAoB,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClG,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI;aAC7D,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|
package/build/types.d.ts
CHANGED
|
@@ -30,5 +30,6 @@ export interface TargetEvent {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
/** The payload of a preview `Analytics` message (browser → manager). */
|
|
33
|
-
export
|
|
33
|
+
export interface PreviewAnalyticsPayload extends AnalyticsEvent {
|
|
34
|
+
}
|
|
34
35
|
//# sourceMappingURL=types.d.ts.map
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,MAAM,CAAA;IACT,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC/C;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,WAAW,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAA;IACT,IAAI,EAAE,eAAe,CAAA;IACrB,0BAA0B;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/E;AAED,wEAAwE;AACxE,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,MAAM,CAAA;IACT,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC/C;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,WAAW,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAA;IACT,IAAI,EAAE,eAAe,CAAA;IACrB,0BAA0B;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/E;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAwB,SAAQ,cAAc;CAAG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/viable-log",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@owlmeans/log": "^0.1.18-rc.
|
|
25
|
+
"@owlmeans/log": "^0.1.18-rc.3"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@owlmeans/dep-config": "workspace:*",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TargetEvent } from '../types.js'
|
|
2
|
+
|
|
3
|
+
/** The stdout line protocol a target's backend speaks to the platform's publisher. */
|
|
4
|
+
export interface TargetEventHelper {
|
|
5
|
+
/** The stdout line of an event. Over-long ones lose their stack, then their data — never their head. */
|
|
6
|
+
targetEventLine: (event: TargetEvent) => string
|
|
7
|
+
/**
|
|
8
|
+
* The event a stdout line carries, or `undefined` when the line is not one — absent marker, broken
|
|
9
|
+
* JSON, unknown version, or a shape the platform does not accept. The input is untrusted: it is
|
|
10
|
+
* written by generated code, and nothing outside this function may read fields of it unchecked.
|
|
11
|
+
*/
|
|
12
|
+
parseTargetEventLine: (line: string) => TargetEvent | undefined
|
|
13
|
+
}
|
package/src/event.ts
CHANGED
|
@@ -1,62 +1,70 @@
|
|
|
1
1
|
import { TARGET_EVENT_MARKER, TARGET_EVENT_MAX, TARGET_EVENT_VERSION } from './consts.js'
|
|
2
2
|
import type { TargetEvent } from './types.js'
|
|
3
|
+
import type { TargetEventHelper } from './event/types.js'
|
|
3
4
|
import { LEVELS } from './consts.local.js'
|
|
4
5
|
|
|
5
|
-
/** The stdout line of an event. Over-long ones lose their stack, then their data — never their head. */
|
|
6
|
-
export const targetEventLine = (event: TargetEvent): string => {
|
|
7
|
-
let line = TARGET_EVENT_MARKER + JSON.stringify(event)
|
|
8
|
-
if (line.length > TARGET_EVENT_MAX && event.error?.stack != null) {
|
|
9
|
-
line = TARGET_EVENT_MARKER + JSON.stringify({ ...event, error: { ...event.error, stack: undefined } })
|
|
10
|
-
}
|
|
11
|
-
if (line.length > TARGET_EVENT_MAX) {
|
|
12
|
-
line = TARGET_EVENT_MARKER + JSON.stringify({ ...event, data: undefined })
|
|
13
|
-
}
|
|
14
|
-
return line.length > TARGET_EVENT_MAX ? line.slice(0, TARGET_EVENT_MAX) : line
|
|
15
|
-
}
|
|
16
|
-
|
|
17
6
|
const text = (value: unknown, max: number): string | undefined =>
|
|
18
7
|
typeof value === 'string' ? value.slice(0, max) : undefined
|
|
19
8
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let raw: unknown
|
|
31
|
-
try {
|
|
32
|
-
raw = JSON.parse(line.slice(at + TARGET_EVENT_MARKER.length))
|
|
33
|
-
} catch {
|
|
34
|
-
return undefined
|
|
35
|
-
}
|
|
36
|
-
if (raw == null || typeof raw !== 'object') {
|
|
37
|
-
return undefined
|
|
38
|
-
}
|
|
39
|
-
const event = raw as Record<string, unknown>
|
|
40
|
-
if (event.v !== TARGET_EVENT_VERSION || (event.kind !== 'error' && event.kind !== 'analytics')
|
|
41
|
-
|| typeof event.ts !== 'number' || !LEVELS.includes(event.level as string)) {
|
|
42
|
-
return undefined
|
|
9
|
+
export const createTargetEventHelper = (): TargetEventHelper => {
|
|
10
|
+
const targetEventLine = (event: TargetEvent): string => {
|
|
11
|
+
let line = TARGET_EVENT_MARKER + JSON.stringify(event)
|
|
12
|
+
if (line.length > TARGET_EVENT_MAX && event.error?.stack != null) {
|
|
13
|
+
line = TARGET_EVENT_MARKER + JSON.stringify({ ...event, error: { ...event.error, stack: undefined } })
|
|
14
|
+
}
|
|
15
|
+
if (line.length > TARGET_EVENT_MAX) {
|
|
16
|
+
line = TARGET_EVENT_MARKER + JSON.stringify({ ...event, data: undefined })
|
|
17
|
+
}
|
|
18
|
+
return line.length > TARGET_EVENT_MAX ? line.slice(0, TARGET_EVENT_MAX) : line
|
|
43
19
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
20
|
+
|
|
21
|
+
const parseTargetEventLine = (line: string): TargetEvent | undefined => {
|
|
22
|
+
const at = line.indexOf(TARGET_EVENT_MARKER)
|
|
23
|
+
if (at < 0 || line.length > TARGET_EVENT_MAX + 64) {
|
|
24
|
+
return undefined
|
|
25
|
+
}
|
|
26
|
+
let raw: unknown
|
|
27
|
+
try {
|
|
28
|
+
raw = JSON.parse(line.slice(at + TARGET_EVENT_MARKER.length))
|
|
29
|
+
} catch {
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
if (raw == null || typeof raw !== 'object') {
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
const event = raw as Record<string, unknown>
|
|
36
|
+
if (event.v !== TARGET_EVENT_VERSION || (event.kind !== 'error' && event.kind !== 'analytics')
|
|
37
|
+
|| typeof event.ts !== 'number' || !LEVELS.includes(event.level as string)) {
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
const error = event.error != null && typeof event.error === 'object' ? event.error as Record<string, unknown> : undefined
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
v: TARGET_EVENT_VERSION,
|
|
44
|
+
kind: event.kind,
|
|
45
|
+
ts: event.ts,
|
|
46
|
+
level: event.level as TargetEvent['level'],
|
|
47
|
+
scope: text(event.scope, 200) ?? 'target',
|
|
48
|
+
message: text(event.message, 2000) ?? '',
|
|
49
|
+
event: text(event.event, 200),
|
|
50
|
+
data: event.data,
|
|
51
|
+
error: error != null
|
|
52
|
+
? {
|
|
53
|
+
name: text(error.name, 200) ?? 'Error', message: text(error.message, 2000) ?? '',
|
|
54
|
+
stack: text(error.stack, 8000), incidentId: text(error.incidentId, 100),
|
|
55
|
+
}
|
|
56
|
+
: undefined,
|
|
57
|
+
}
|
|
61
58
|
}
|
|
59
|
+
|
|
60
|
+
return { targetEventLine, parseTargetEventLine }
|
|
62
61
|
}
|
|
62
|
+
|
|
63
|
+
export const targetEventHelper = createTargetEventHelper()
|
|
64
|
+
|
|
65
|
+
/** @deprecated compat:factory-refactor — use `targetEventHelper.targetEventLine(…)` */
|
|
66
|
+
export const targetEventLine = (event: TargetEvent): string => targetEventHelper.targetEventLine(event)
|
|
67
|
+
|
|
68
|
+
/** @deprecated compat:factory-refactor — use `targetEventHelper.parseTargetEventLine(…)` */
|
|
69
|
+
export const parseTargetEventLine = (line: string): TargetEvent | undefined =>
|
|
70
|
+
targetEventHelper.parseTargetEventLine(line)
|
package/src/index.ts
CHANGED
package/src/plugins.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LogPlugin, LogRecord } from '@owlmeans/log'
|
|
2
2
|
import { PREVIEW_ANALYTICS_TYPE, PREVIEW_REPORTER_FLAG, SLOT_EVENTS_ENV, TARGET_EVENT_VERSION } from './consts.js'
|
|
3
|
-
import {
|
|
3
|
+
import { targetEventHelper } from './event.js'
|
|
4
4
|
import type { PreviewChannel, TargetEvent } from './types.js'
|
|
5
5
|
import type { Stdout } from './types.local.js'
|
|
6
6
|
|
|
@@ -58,7 +58,7 @@ export const viableSlotPlugin = (): LogPlugin => {
|
|
|
58
58
|
const emit = (event: TargetEvent): void => {
|
|
59
59
|
const out = stdout()
|
|
60
60
|
if (out != null && slotEventsEnabled()) {
|
|
61
|
-
out.write(`${targetEventLine(event)}\n`)
|
|
61
|
+
out.write(`${targetEventHelper.targetEventLine(event)}\n`)
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
return {
|
package/src/types.ts
CHANGED
package/tests/viable-log.spec.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
2
|
-
import { addLogPlugin, logger,
|
|
2
|
+
import { addLogPlugin, logger, resetLog, logStateHelper } from '@owlmeans/log'
|
|
3
3
|
import {
|
|
4
4
|
PREVIEW_ANALYTICS_TYPE, PREVIEW_REPORTER_FLAG, SLOT_EVENTS_ENV, TARGET_EVENT_MARKER, TARGET_EVENT_MAX,
|
|
5
|
-
|
|
5
|
+
targetEventHelper, viablePreviewPlugin, viableSlotPlugin,
|
|
6
6
|
} from '@owlmeans/viable-log'
|
|
7
7
|
import type { TargetEvent } from '@owlmeans/viable-log'
|
|
8
8
|
|
|
@@ -13,37 +13,37 @@ const sample = (patch: Partial<TargetEvent> = {}): TargetEvent => ({
|
|
|
13
13
|
describe('target event line', () => {
|
|
14
14
|
test('round-trips through targetEventLine and parseTargetEventLine', () => {
|
|
15
15
|
const event = sample({ data: { id: 1 }, error: { name: 'Error', message: 'x', stack: 's', incidentId: 'i' }, event: 'e' })
|
|
16
|
-
expect(parseTargetEventLine(targetEventLine(event))).toEqual(event)
|
|
16
|
+
expect(targetEventHelper.parseTargetEventLine(targetEventHelper.targetEventLine(event))).toEqual(event)
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
test('finds the marker anywhere in a line (prefixed stdout)', () => {
|
|
20
|
-
expect(parseTargetEventLine(`12:00 ${targetEventLine(sample())}`)?.message).toBe('boom')
|
|
20
|
+
expect(targetEventHelper.parseTargetEventLine(`12:00 ${targetEventHelper.targetEventLine(sample())}`)?.message).toBe('boom')
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
test('rejects a line without a marker, broken JSON, a wrong version or kind, a bad level', () => {
|
|
24
|
-
expect(parseTargetEventLine('plain')).toBeUndefined()
|
|
25
|
-
expect(parseTargetEventLine(`${TARGET_EVENT_MARKER}{nope`)).toBeUndefined()
|
|
26
|
-
expect(parseTargetEventLine(`${TARGET_EVENT_MARKER}${JSON.stringify({ ...sample(), v: 2 })}`)).toBeUndefined()
|
|
27
|
-
expect(parseTargetEventLine(`${TARGET_EVENT_MARKER}${JSON.stringify({ ...sample(), kind: 'x' })}`)).toBeUndefined()
|
|
28
|
-
expect(parseTargetEventLine(`${TARGET_EVENT_MARKER}${JSON.stringify({ ...sample(), level: 'fatal' })}`)).toBeUndefined()
|
|
29
|
-
expect(parseTargetEventLine(`${TARGET_EVENT_MARKER}null`)).toBeUndefined()
|
|
24
|
+
expect(targetEventHelper.parseTargetEventLine('plain')).toBeUndefined()
|
|
25
|
+
expect(targetEventHelper.parseTargetEventLine(`${TARGET_EVENT_MARKER}{nope`)).toBeUndefined()
|
|
26
|
+
expect(targetEventHelper.parseTargetEventLine(`${TARGET_EVENT_MARKER}${JSON.stringify({ ...sample(), v: 2 })}`)).toBeUndefined()
|
|
27
|
+
expect(targetEventHelper.parseTargetEventLine(`${TARGET_EVENT_MARKER}${JSON.stringify({ ...sample(), kind: 'x' })}`)).toBeUndefined()
|
|
28
|
+
expect(targetEventHelper.parseTargetEventLine(`${TARGET_EVENT_MARKER}${JSON.stringify({ ...sample(), level: 'fatal' })}`)).toBeUndefined()
|
|
29
|
+
expect(targetEventHelper.parseTargetEventLine(`${TARGET_EVENT_MARKER}null`)).toBeUndefined()
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
test('clips untrusted text fields and ignores unknown fields', () => {
|
|
33
33
|
const line = `${TARGET_EVENT_MARKER}${JSON.stringify({ ...sample(), message: 'm'.repeat(5000), extra: 'x', scope: 7 })}`
|
|
34
|
-
const parsed = parseTargetEventLine(line)!
|
|
34
|
+
const parsed = targetEventHelper.parseTargetEventLine(line)!
|
|
35
35
|
expect(parsed.message.length).toBe(2000)
|
|
36
36
|
expect(parsed.scope).toBe('target')
|
|
37
37
|
expect('extra' in parsed).toBe(false)
|
|
38
38
|
})
|
|
39
39
|
|
|
40
40
|
test('an over-long event loses its stack first, then its data, and stays under the cap', () => {
|
|
41
|
-
const stackless = targetEventLine(sample({ error: { name: 'E', message: 'm', stack: 's'.repeat(TARGET_EVENT_MAX * 2) } }))
|
|
41
|
+
const stackless = targetEventHelper.targetEventLine(sample({ error: { name: 'E', message: 'm', stack: 's'.repeat(TARGET_EVENT_MAX * 2) } }))
|
|
42
42
|
expect(stackless.length).toBeLessThanOrEqual(TARGET_EVENT_MAX)
|
|
43
|
-
expect(parseTargetEventLine(stackless)?.error?.message).toBe('m')
|
|
44
|
-
const dataless = targetEventLine(sample({ data: 'd'.repeat(TARGET_EVENT_MAX * 2) }))
|
|
43
|
+
expect(targetEventHelper.parseTargetEventLine(stackless)?.error?.message).toBe('m')
|
|
44
|
+
const dataless = targetEventHelper.targetEventLine(sample({ data: 'd'.repeat(TARGET_EVENT_MAX * 2) }))
|
|
45
45
|
expect(dataless.length).toBeLessThanOrEqual(TARGET_EVENT_MAX)
|
|
46
|
-
expect(parseTargetEventLine(dataless)?.message).toBe('boom')
|
|
46
|
+
expect(targetEventHelper.parseTargetEventLine(dataless)?.message).toBe('boom')
|
|
47
47
|
})
|
|
48
48
|
})
|
|
49
49
|
|
|
@@ -74,18 +74,18 @@ describe('viableSlotPlugin', () => {
|
|
|
74
74
|
const written: string[] = []
|
|
75
75
|
const proc = globalThis.process
|
|
76
76
|
const realWrite = proc.stdout.write.bind(proc.stdout)
|
|
77
|
-
let saved: ReturnType<typeof nativeConsole>
|
|
77
|
+
let saved: ReturnType<typeof logStateHelper.nativeConsole>
|
|
78
78
|
|
|
79
79
|
beforeEach(() => {
|
|
80
80
|
resetLog()
|
|
81
81
|
written.length = 0
|
|
82
|
-
saved = { ...nativeConsole() }
|
|
83
|
-
for (const key of Object.keys(nativeConsole()) as (keyof ReturnType<typeof nativeConsole>)[]) nativeConsole()[key] = () => undefined
|
|
82
|
+
saved = { ...logStateHelper.nativeConsole() }
|
|
83
|
+
for (const key of Object.keys(logStateHelper.nativeConsole()) as (keyof ReturnType<typeof logStateHelper.nativeConsole>)[]) logStateHelper.nativeConsole()[key] = () => undefined
|
|
84
84
|
;(proc.stdout as { write: unknown }).write = (chunk: string) => { written.push(String(chunk)); return true }
|
|
85
85
|
})
|
|
86
86
|
afterEach(() => {
|
|
87
87
|
;(proc.stdout as { write: unknown }).write = realWrite
|
|
88
|
-
Object.assign(nativeConsole(), saved)
|
|
88
|
+
Object.assign(logStateHelper.nativeConsole(), saved)
|
|
89
89
|
delete proc.env[SLOT_EVENTS_ENV]
|
|
90
90
|
resetLog()
|
|
91
91
|
})
|
|
@@ -104,7 +104,7 @@ describe('viableSlotPlugin', () => {
|
|
|
104
104
|
logger('api').info('Signed up', { plan: 'free' }, { analytics: 'signup' })
|
|
105
105
|
logger('api').info('plain info')
|
|
106
106
|
logger('api').warn('plain warn')
|
|
107
|
-
const events = written.map(line => parseTargetEventLine(line)!)
|
|
107
|
+
const events = written.map(line => targetEventHelper.parseTargetEventLine(line)!)
|
|
108
108
|
expect(events.map(e => [e.kind, e.event ?? e.message])).toEqual([['error', 'Request failed'], ['analytics', 'signup']])
|
|
109
109
|
expect(events[0].error).toMatchObject({ name: 'Error', message: 'db down', incidentId: 'inc-1' })
|
|
110
110
|
expect(events[1].data).toEqual({ plan: 'free' })
|