rian 0.3.0-next.8 → 0.3.0
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/async.d.ts +56 -3
- package/async.js +1 -1
- package/async.mjs +1 -1
- package/index.d.ts +76 -22
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +1 -1
- package/readme.md +129 -77
- package/utils.d.ts +10 -51
- package/utils.js +1 -1
- package/utils.mjs +1 -1
package/async.d.ts
CHANGED
@@ -1,6 +1,59 @@
|
|
1
|
-
import type { CallableScope,
|
1
|
+
import type { CallableScope, Options, Scope } from 'rian';
|
2
2
|
|
3
|
-
export
|
3
|
+
export { report, configure } from 'rian';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Returns the current span in the current execution context.
|
7
|
+
*
|
8
|
+
* This will throw an error if there is no current span.
|
9
|
+
*
|
10
|
+
* @example
|
11
|
+
*
|
12
|
+
* ```ts
|
13
|
+
* function doWork() {
|
14
|
+
* const span = currentSpan();
|
15
|
+
* span.set_context({ foo: 'bar' });
|
16
|
+
* }
|
17
|
+
*
|
18
|
+
* span('some-name')(() => {
|
19
|
+
* doWork(); // will guarantee `currentSpan` returns this span
|
20
|
+
* });
|
21
|
+
* ```
|
22
|
+
*/
|
23
|
+
export function currentSpan(): Scope;
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Creates a new span for the currently active tracer.
|
27
|
+
*
|
28
|
+
* @example
|
29
|
+
*
|
30
|
+
* ```ts
|
31
|
+
* tracer('some-name')(() => {
|
32
|
+
* // some deeply nested moments later
|
33
|
+
* const s = span('my-span');
|
34
|
+
* });
|
35
|
+
* ```
|
36
|
+
*/
|
4
37
|
export function span(name: string): CallableScope;
|
5
38
|
|
6
|
-
export
|
39
|
+
export type Tracer<T> = (cb: T) => ReturnType<T>;
|
40
|
+
|
41
|
+
/**
|
42
|
+
* A tracer is a logical unit in your application. This alleviates the need to pass around a tracer instance.
|
43
|
+
*
|
44
|
+
* All spans produced by a tracer will all collect into a single span collection that is given to {@link report}.
|
45
|
+
*
|
46
|
+
* @example
|
47
|
+
*
|
48
|
+
* ```ts
|
49
|
+
* const trace = tracer('server');
|
50
|
+
*
|
51
|
+
* trace(() => {
|
52
|
+
* // application logic
|
53
|
+
* });
|
54
|
+
* ```
|
55
|
+
*/
|
56
|
+
export function tracer<T extends () => any>(
|
57
|
+
name: string,
|
58
|
+
options?: Options,
|
59
|
+
): Tracer<T>;
|
package/async.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
const
|
1
|
+
const e = require('node:async_hooks');const { measure:t } = require('rian/utils');const { make:n, parse:r, SAMPLED_FLAG:o } = require('tctx');const { is_sampled:a } = require('tctx');var s={};function c(e,t={}){s={...t,"service.name":e,"telemetry.sdk.name":"rian","telemetry.sdk.version":"0.3.0"}}var l=new Set,i=new WeakMap;async function p(e){let t=[],n=new Map;for(let[e,r]of l){let o;n.has(r)?o=n.get(r).spans:n.set(r,{scope:r,spans:o=[]}),o.push(e),i.has(r)&&(t.push(...i.get(r)),i.delete(r))}return l.clear(),t.length&&await Promise.all(t),e({resource:s,scopeSpans:n.values()})}function u(e,t){return a(t)}var d=new e.AsyncLocalStorage;function m(){let e=d.getStore()?.[1];if(null==e)throw new Error("no current span");return e}function f(e){let r=d.getStore();if(!r)throw Error("TODO");let o=r[0],a=o.scope,s=r[1],c=o.sampler,p=s?.traceparent??o.root_id,u=p?p.child():n(),m="boolean"!=typeof c?c(e,u,a):c;u.flags;let w={id:u,parent:p,start:o.clock.now(),name:e,events:[],context:{}};m&&l.add([w,a]);let g=e=>d.run([o,g],t,g,e);g.traceparent=u,g.span=f,g.set_context=e=>{"function"!=typeof e?Object.assign(w.context,e):w.context=e(w.context)},g.add_event=(e,t)=>{w.events.push({name:e,timestamp:o.clock.now(),attributes:t||{}})},g.end=()=>{null==w.end&&(w.end=o.clock.now())};let h=i.get(a);return g.__add_promise=e=>{h.add(e),e.then((()=>h.delete(e)))},g}function w(e,t){let n=t?.sampler??u,o={name:e},a={root_id:"string"==typeof t?.traceparent?r(t.traceparent):void 0,scope:o,sampler:n,clock:t?.clock??Date};return i.set(o,new Set),function(e){let t=d.getStore();return a.root_id||(a.root_id=t?.[0].root_id),d.run([a,t?.[1]||null],e)}}exports.configure=c;exports.currentSpan=m;exports.report=p;exports.span=f;exports.tracer=w;
|
package/async.mjs
CHANGED
@@ -1 +1 @@
|
|
1
|
-
import
|
1
|
+
import*as e from"node:async_hooks";import{measure as t}from"rian/utils";import{make as n,parse as r,SAMPLED_FLAG as o}from"tctx";import{is_sampled as a}from"tctx";var s={};function c(e,t={}){s={...t,"service.name":e,"telemetry.sdk.name":"rian","telemetry.sdk.version":"0.3.0"}}var l=new Set,i=new WeakMap;async function p(e){let t=[],n=new Map;for(let[e,r]of l){let o;n.has(r)?o=n.get(r).spans:n.set(r,{scope:r,spans:o=[]}),o.push(e),i.has(r)&&(t.push(...i.get(r)),i.delete(r))}return l.clear(),t.length&&await Promise.all(t),e({resource:s,scopeSpans:n.values()})}function u(e,t){return a(t)}var d=new e.AsyncLocalStorage;function m(){let e=d.getStore()?.[1];if(null==e)throw new Error("no current span");return e}function f(e){let r=d.getStore();if(!r)throw Error("TODO");let o=r[0],a=o.scope,s=r[1],c=o.sampler,p=s?.traceparent??o.root_id,u=p?p.child():n(),m="boolean"!=typeof c?c(e,u,a):c;u.flags;let w={id:u,parent:p,start:o.clock.now(),name:e,events:[],context:{}};m&&l.add([w,a]);let g=e=>d.run([o,g],t,g,e);g.traceparent=u,g.span=f,g.set_context=e=>{"function"!=typeof e?Object.assign(w.context,e):w.context=e(w.context)},g.add_event=(e,t)=>{w.events.push({name:e,timestamp:o.clock.now(),attributes:t||{}})},g.end=()=>{null==w.end&&(w.end=o.clock.now())};let h=i.get(a);return g.__add_promise=e=>{h.add(e),e.then((()=>h.delete(e)))},g}function w(e,t){let n=t?.sampler??u,o={name:e},a={root_id:"string"==typeof t?.traceparent?r(t.traceparent):void 0,scope:o,sampler:n,clock:t?.clock??Date};return i.set(o,new Set),function(e){let t=d.getStore();return a.root_id||(a.root_id=t?.[0].root_id),d.run([a,t?.[1]||null],e)}}export{c as configure,m as currentSpan,p as report,f as span,w as tracer};
|
package/index.d.ts
CHANGED
@@ -3,8 +3,7 @@ import type { Traceparent } from 'tctx';
|
|
3
3
|
// --- tracer
|
4
4
|
|
5
5
|
/**
|
6
|
-
*
|
7
|
-
* during this execution.
|
6
|
+
* The exporter is called when the {@link report} method is called.
|
8
7
|
*/
|
9
8
|
export type Exporter = (trace: {
|
10
9
|
resource: Context;
|
@@ -23,12 +22,14 @@ export type Options = {
|
|
23
22
|
sampler?: Sampler | boolean;
|
24
23
|
|
25
24
|
/**
|
26
|
-
* A root, or extracted w3c traceparent
|
25
|
+
* A root, or extracted w3c traceparent string header.
|
27
26
|
*
|
28
27
|
* If the id is malformed, the {@link create} method will throw an exception. If no root is
|
29
|
-
* provided then one will be created obeying the {@link Options.sampler|sampling} rules.
|
28
|
+
* provided then one will be created obeying the {@link Options.sampler|sampling} rules on each span.
|
30
29
|
*/
|
31
30
|
traceparent?: string | null;
|
31
|
+
|
32
|
+
clock?: ClockLike;
|
32
33
|
};
|
33
34
|
|
34
35
|
export type Tracer = Pick<Scope, 'span'>;
|
@@ -41,17 +42,24 @@ export type Context = {
|
|
41
42
|
};
|
42
43
|
|
43
44
|
/**
|
44
|
-
*
|
45
|
-
* decisions is made preemptively.
|
46
|
-
*
|
47
|
-
* Returning false will not include this span in the {@link Exporter}.
|
45
|
+
* Allows a sampling decision to be made. This method will influence the {@link Span.id|traceparent} sampling flag.
|
48
46
|
*
|
49
|
-
*
|
47
|
+
* Return true if the span should be sampled, and reported to the {@link Exporter}.
|
48
|
+
* Return false if the span should not be sampled, and not reported to the {@link Exporter}.
|
50
49
|
*/
|
51
50
|
export type Sampler = (
|
51
|
+
/**
|
52
|
+
* The name of the span.
|
53
|
+
*/
|
52
54
|
readonly name: string,
|
55
|
+
/**
|
56
|
+
* The traceparent id of the span.
|
57
|
+
*/
|
53
58
|
readonly id: Traceparent,
|
54
|
-
|
59
|
+
/**
|
60
|
+
* The tracer this span belongs to.
|
61
|
+
*/
|
62
|
+
readonly tracer: { readonly name: string },
|
55
63
|
) => boolean;
|
56
64
|
|
57
65
|
// --- spans
|
@@ -61,20 +69,15 @@ export type Sampler = (
|
|
61
69
|
* {@link Span.name|name}, and a {@link Span.start|start} and {@link Span.end|end} time.
|
62
70
|
*
|
63
71
|
* Each span should be named, not too vague, and not too precise. For example, "resolve_user_ids"
|
64
|
-
* and not "resolver_user_ids[1,2,3]" nor "
|
72
|
+
* and not "resolver_user_ids[1,2,3]" nor "resolver".
|
65
73
|
*
|
66
74
|
* A span forms part of a wider trace, and can be visualized like:
|
67
75
|
*
|
68
76
|
* ```plain
|
69
77
|
* [Span A················································(2ms)]
|
70
78
|
* [Span B·········································(1.7ms)]
|
71
|
-
* [Span D···············(0.8ms)]
|
79
|
+
* [Span D···············(0.8ms)] [Span C......(0.6ms)]
|
72
80
|
* ```
|
73
|
-
*
|
74
|
-
* ---
|
75
|
-
*
|
76
|
-
* Spans are aimed to interoperate with
|
77
|
-
* {@link https://github.com/opentracing/specification/blob/master/specification.md|OpenTracing's Spans}, albeit not entirely api compatible — they do share principles.
|
78
81
|
*/
|
79
82
|
export type Span = {
|
80
83
|
/**
|
@@ -82,6 +85,7 @@ export type Span = {
|
|
82
85
|
* or stage of the larger stack.
|
83
86
|
*
|
84
87
|
* @example
|
88
|
+
*
|
85
89
|
* "resolve_user_ids"
|
86
90
|
* "[POST] /api"
|
87
91
|
*/
|
@@ -105,7 +109,7 @@ export type Span = {
|
|
105
109
|
/**
|
106
110
|
* The time represented as a UNIX epoch timestamp in milliseconds when this span was created.
|
107
111
|
* Typically, via
|
108
|
-
* {@link Scope.
|
112
|
+
* {@link Scope.span|scope.span()}.
|
109
113
|
*/
|
110
114
|
start: number;
|
111
115
|
|
@@ -119,7 +123,7 @@ export type Span = {
|
|
119
123
|
* An arbitrary context object useful for storing information during a trace.
|
120
124
|
*
|
121
125
|
* Usually following a convention such as `tag.*`, `http.*` or any of the
|
122
|
-
* {@link https://
|
126
|
+
* {@link https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/|OpenTelemetry Trace Semantic Conventions}.
|
123
127
|
*
|
124
128
|
* ### Note!
|
125
129
|
*
|
@@ -153,7 +157,12 @@ export type Scope = {
|
|
153
157
|
/**
|
154
158
|
* Forks the span into a new child span.
|
155
159
|
*/
|
156
|
-
span(
|
160
|
+
span(
|
161
|
+
/**
|
162
|
+
* @borrows {@link Span.name}
|
163
|
+
*/
|
164
|
+
name: string,
|
165
|
+
): CallableScope;
|
157
166
|
|
158
167
|
/**
|
159
168
|
* Allows the span's context to be set. Passing an object will be `Object.assign`ed into the
|
@@ -177,13 +186,33 @@ export type Scope = {
|
|
177
186
|
};
|
178
187
|
|
179
188
|
export type CallableScope = Scope & {
|
180
|
-
|
189
|
+
<Fn extends (scope: Omit<Scope, 'end'>) => any>(cb: Fn): ReturnType<Fn>;
|
181
190
|
};
|
182
191
|
|
183
192
|
// --- main api
|
184
193
|
|
194
|
+
/**
|
195
|
+
* A tracer is a logical unit in your application. This alleviates the need to pass around a tracer instance.
|
196
|
+
*
|
197
|
+
* All spans produced by a tracer will all collect into a single span collection that is given to {@link report}.
|
198
|
+
*
|
199
|
+
* @example
|
200
|
+
*
|
201
|
+
* ```ts
|
202
|
+
* // file: server.ts
|
203
|
+
* const trace = tracer('server');
|
204
|
+
*
|
205
|
+
* // file: orm.ts
|
206
|
+
* const trace = tracer('orm');
|
207
|
+
*
|
208
|
+
* // file: api.ts
|
209
|
+
* const trace = tracer('api');
|
210
|
+
* ```
|
211
|
+
*/
|
185
212
|
export function tracer(name: string, options?: Options): Tracer;
|
186
213
|
|
214
|
+
// -- general api
|
215
|
+
|
187
216
|
/**
|
188
217
|
* Awaits all active promises, and then calls the {@link Options.exporter|exporter}. Passing all collected spans.
|
189
218
|
*/
|
@@ -191,5 +220,30 @@ export async function report<T extends Exporter>(
|
|
191
220
|
exporter: T,
|
192
221
|
): Promise<ReturnType<T>>;
|
193
222
|
|
194
|
-
|
223
|
+
/**
|
224
|
+
* Calling this method will set the resource attributes for this runtime. This is useful for things like:
|
225
|
+
* - setting the deployment environment of the application
|
226
|
+
* - setting the k8s namespace
|
227
|
+
* - ...
|
228
|
+
*
|
229
|
+
* The `name` argument will set the `service.name` attribute. And is required.
|
230
|
+
*
|
231
|
+
* The fields can be whatever you want, but it is recommended to follow the {@link https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/|OpenTelemetry Resource Semantic Conventions}.
|
232
|
+
*
|
233
|
+
* @example
|
234
|
+
*
|
235
|
+
* ```ts
|
236
|
+
* configure('my-service', { 'deployment.environment': 'production', 'k8s.namespace.name': 'default' });
|
237
|
+
* ```
|
238
|
+
*/
|
195
239
|
export function configure(name: string, attributes: Context = {}): void;
|
240
|
+
|
241
|
+
/**
|
242
|
+
* Provinding a clock allows you to control the time of the span.
|
243
|
+
*/
|
244
|
+
export type ClockLike = {
|
245
|
+
/**
|
246
|
+
* Must return the number of milliseconds since the epoch.
|
247
|
+
*/
|
248
|
+
now(): number;
|
249
|
+
};
|
package/index.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
const {
|
1
|
+
const { measure:e } = require('rian/utils');const { make:t, parse:n, SAMPLED_FLAG:a } = require('tctx');const { is_sampled:r } = require('tctx');var o={};function s(e,t={}){o={...t,"service.name":e,"telemetry.sdk.name":"rian","telemetry.sdk.version":"0.3.0"}}var p=new Set,c=new WeakMap;async function i(e){let t=[],n=new Map;for(let[e,a]of p){let r;n.has(a)?r=n.get(a).spans:n.set(a,{scope:a,spans:r=[]}),r.push(e),c.has(a)&&(t.push(...c.get(a)),c.delete(a))}return p.clear(),t.length&&await Promise.all(t),e({resource:o,scopeSpans:n.values()})}function l(e,t){return r(t)}function d(a,r){let o=r?.sampler??l,s=r?.clock??Date,i={name:a},d=new Set;c.set(i,d);let m="string"==typeof r?.traceparent?n(r.traceparent):void 0,u=(n,a)=>{let r=a?a.child():t(),c="boolean"!=typeof o?o(n,r,i):o;r.flags;let l={id:r,parent:a,start:s.now(),name:n,events:[],context:{}};c&&p.add([l,i]);let m=t=>e(m,t);return m.traceparent=r,m.span=e=>u(e,r),m.set_context=e=>"function"==typeof e?void(l.context=e(l.context)):void Object.assign(l.context,e),m.add_event=(e,t)=>{l.events.push({name:e,timestamp:s.now(),attributes:t||{}})},m.end=()=>{null==l.end&&(l.end=s.now())},m.__add_promise=e=>{d.add(e),e.then((()=>d.delete(e)))},m};return{span:e=>u(e,m)}}exports.configure=s;exports.report=i;exports.tracer=d;
|
package/index.mjs
CHANGED
@@ -1 +1 @@
|
|
1
|
-
import{
|
1
|
+
import{measure as e}from"rian/utils";import{make as t,parse as n,SAMPLED_FLAG as a}from"tctx";import{is_sampled as r}from"tctx";var o={};function s(e,t={}){o={...t,"service.name":e,"telemetry.sdk.name":"rian","telemetry.sdk.version":"0.3.0"}}var p=new Set,c=new WeakMap;async function i(e){let t=[],n=new Map;for(let[e,a]of p){let r;n.has(a)?r=n.get(a).spans:n.set(a,{scope:a,spans:r=[]}),r.push(e),c.has(a)&&(t.push(...c.get(a)),c.delete(a))}return p.clear(),t.length&&await Promise.all(t),e({resource:o,scopeSpans:n.values()})}function l(e,t){return r(t)}function d(a,r){let o=r?.sampler??l,s=r?.clock??Date,i={name:a},d=new Set;c.set(i,d);let m="string"==typeof r?.traceparent?n(r.traceparent):void 0,u=(n,a)=>{let r=a?a.child():t(),c="boolean"!=typeof o?o(n,r,i):o;r.flags;let l={id:r,parent:a,start:s.now(),name:n,events:[],context:{}};c&&p.add([l,i]);let m=t=>e(m,t);return m.traceparent=r,m.span=e=>u(e,r),m.set_context=e=>"function"==typeof e?void(l.context=e(l.context)):void Object.assign(l.context,e),m.add_event=(e,t)=>{l.events.push({name:e,timestamp:s.now(),attributes:t||{}})},m.end=()=>{null==l.end&&(l.end=s.now())},m.__add_promise=e=>{d.add(e),e.then((()=>d.delete(e)))},m};return{span:e=>u(e,m)}}export{s as configure,i as report,d as tracer};
|
package/package.json
CHANGED
package/readme.md
CHANGED
@@ -6,9 +6,6 @@
|
|
6
6
|
|
7
7
|
<p><code>npm add rian</code> doesn't overcomplicate tracing</p>
|
8
8
|
<span>
|
9
|
-
<a href="https://github.com/maraisr/rian/actions/workflows/ci.yml">
|
10
|
-
<img src="https://github.com/maraisr/rian/actions/workflows/ci.yml/badge.svg"/>
|
11
|
-
</a>
|
12
9
|
<a href="https://npm-stat.com/charts.html?package=rian">
|
13
10
|
<img src="https://badgen.net/npm/dw/rian?labelColor=black&color=black&cache=600" alt="downloads"/>
|
14
11
|
</a>
|
@@ -26,78 +23,136 @@
|
|
26
23
|
|
27
24
|
## ⚡ Features
|
28
25
|
|
29
|
-
- 🤔 **Familiar** — looks very much like
|
26
|
+
- 🤔 **Familiar** — looks very much like opentelemetry.
|
30
27
|
|
31
|
-
- ✅ **Simple** — `
|
28
|
+
- ✅ **Simple** — `configure()` an environment, create a `tracer()`, `report()` and done.
|
32
29
|
|
33
30
|
- 🏎 **Performant** — check the [benchmarks](#-benchmark).
|
34
31
|
|
35
|
-
- 🪶 **Lightweight** — a mere
|
32
|
+
- 🪶 **Lightweight** — a mere 1KB and next to no [dependencies](https://npm.anvaka.com/#/view/2d/rian/).
|
36
33
|
|
37
34
|
## 🚀 Usage
|
38
35
|
|
39
|
-
> Visit [/examples](/examples) for more
|
36
|
+
> Visit [/examples](/examples) for more!
|
40
37
|
|
41
38
|
```ts
|
42
|
-
import {
|
43
|
-
import { measure } from 'rian/utils';
|
39
|
+
import { configure, tracer, report } from 'rian';
|
44
40
|
import { exporter } from 'rian/exporter.otel.http';
|
45
41
|
|
46
|
-
// ~>
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
body: JSON.stringify(payload),
|
51
|
-
}),
|
52
|
-
);
|
42
|
+
// ~> configure the environment, all tracers will inherit this
|
43
|
+
configure('my-service' {
|
44
|
+
'service.version': 'DEV'
|
45
|
+
});
|
53
46
|
|
54
|
-
// ~>
|
55
|
-
const
|
47
|
+
// ~> create a tracer — typically "per request" or "per operation".
|
48
|
+
const trace = tracer('request');
|
56
49
|
|
57
|
-
|
50
|
+
function handler(req) {
|
51
|
+
// ~> start a span
|
52
|
+
return trace.span(`${req.method} ${req.path}`)(async (s) => {
|
53
|
+
// set some fields on this span's context
|
54
|
+
s.set_context({ user_id: req.params.user_id });
|
58
55
|
|
59
|
-
|
56
|
+
// ~> span again for `db::read`
|
57
|
+
const data = await s.span('db::read')(() => db_execute('SELECT * FROM users'));
|
60
58
|
|
61
|
-
|
62
|
-
|
63
|
-
});
|
59
|
+
// ~> maybe have some manual spanning
|
60
|
+
const processing_span = s.span('process records');
|
64
61
|
|
65
|
-
|
66
|
-
|
62
|
+
for (let row of data) {
|
63
|
+
processing_span.add_event('doing stuff', { id: row.id });
|
64
|
+
do_stuff(row);
|
65
|
+
}
|
67
66
|
|
68
|
-
//
|
69
|
-
|
67
|
+
// don't forget to end
|
68
|
+
processing_span.end();
|
70
69
|
|
71
|
-
|
72
|
-
|
73
|
-
do_stuff(row);
|
70
|
+
return reply(200, { data });
|
71
|
+
});
|
74
72
|
}
|
75
73
|
|
76
|
-
|
77
|
-
|
78
|
-
|
74
|
+
const otel_exporter = exporter((payload) =>
|
75
|
+
fetch('/traces/otlp', {
|
76
|
+
method: 'POST',
|
77
|
+
body: JSON.stringify(payload),
|
78
|
+
}),
|
79
|
+
);
|
79
80
|
|
80
|
-
|
81
|
-
|
81
|
+
http.listen((req, executionCtx) => {
|
82
|
+
// ~> report all the spans once the response is sent
|
83
|
+
executionCtx.defer(() => report(otel_exporter));
|
84
|
+
return handler(req);
|
85
|
+
});
|
82
86
|
|
83
87
|
/*
|
84
88
|
And we end up with something like this in our reporting tool:
|
85
89
|
|
86
|
-
[ GET
|
87
|
-
[ db::read .... (0.5ms) ]
|
88
|
-
|
90
|
+
[ GET /data .................,,...................... (1.2ms) ] { request }
|
91
|
+
[ db::read .... (0.5ms) ] [ process records .... (0.5ms) ]
|
92
|
+
^ ^ ^ ^
|
93
|
+
{ user_id } ev { id: 1 } | |
|
94
|
+
ev { id: 2 } |
|
95
|
+
ev { id: 3 }
|
89
96
|
*/
|
90
97
|
```
|
91
98
|
|
99
|
+
You only need to `report` in your application once somewhere. All spans are collected into the same "bucket".
|
100
|
+
|
92
101
|
## 🔎 API
|
93
102
|
|
94
103
|
#### Module: [`rian`](./packages/rian/src/index.ts)
|
95
104
|
|
96
105
|
The main and _default_ module responsible for creating and provisioning spans.
|
97
106
|
|
98
|
-
> 💡 Note ~> when providing span context values,
|
99
|
-
> [Semantic Conventions](https://
|
100
|
-
> enforced.
|
107
|
+
> 💡 Note ~> when providing span context values, you can use
|
108
|
+
> [Semantic Conventions](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/), but won't
|
109
|
+
> be enforced.
|
110
|
+
|
111
|
+
#### Module: [`rian/async`](./packages/rian/src/async.ts)
|
112
|
+
|
113
|
+
A module that utilizes the `async_hooks` API to provide a `tracer` and `spans` that can be used where the current span
|
114
|
+
isn't accessible.
|
115
|
+
|
116
|
+
> 💡 Note ~> this module should be used mutually exclusively with the main `rian` module.
|
117
|
+
|
118
|
+
<detials>
|
119
|
+
|
120
|
+
<summary>Example</summary>
|
121
|
+
|
122
|
+
```ts
|
123
|
+
import { configure, tracer, span, currentSpan, report } from 'rian/async';
|
124
|
+
import { exporter } from 'rian/exporter.otel.http';
|
125
|
+
|
126
|
+
function handler(req) {
|
127
|
+
return span(`${req.method} ${req.path}`)(async () => {
|
128
|
+
const s = currentSpan();
|
129
|
+
|
130
|
+
s.set_context({ user_id: req.params.user_id });
|
131
|
+
|
132
|
+
const data = await s.span('db::read')(() => db_execute('SELECT * FROM users'));
|
133
|
+
|
134
|
+
const processing_span = s.span('process records');
|
135
|
+
|
136
|
+
for (let row of data) {
|
137
|
+
processing_span.add_event('doing stuff', { id: row.id });
|
138
|
+
do_stuff(row);
|
139
|
+
}
|
140
|
+
|
141
|
+
processing_span.end();
|
142
|
+
|
143
|
+
return reply(200, { data });
|
144
|
+
});
|
145
|
+
}
|
146
|
+
|
147
|
+
const httpTrace = tracer('http');
|
148
|
+
|
149
|
+
http.listen((req, executionCtx) => {
|
150
|
+
executionCtx.defer(() => report(exporter));
|
151
|
+
return httpTrace(() => handler(req));
|
152
|
+
});
|
153
|
+
```
|
154
|
+
|
155
|
+
</details>
|
101
156
|
|
102
157
|
#### Module: [`rian/exporter.zipkin`](./packages/rian/src/exporter.zipkin.ts)
|
103
158
|
|
@@ -112,7 +167,7 @@ Implements the OpenTelemetry protocol for use with http transports.
|
|
112
167
|
<details><summary>NewRelic</summary>
|
113
168
|
|
114
169
|
```ts
|
115
|
-
import {
|
170
|
+
import { configure, tracer, report } from 'rian';
|
116
171
|
import { exporter } from 'rian/exporter.zipkin';
|
117
172
|
|
118
173
|
const newrelic = exporter((payload) =>
|
@@ -128,7 +183,9 @@ const newrelic = exporter((payload) =>
|
|
128
183
|
}),
|
129
184
|
);
|
130
185
|
|
131
|
-
|
186
|
+
configure('my-service');
|
187
|
+
|
188
|
+
const tracer = tracer('app');
|
132
189
|
|
133
190
|
await report(newrelic);
|
134
191
|
```
|
@@ -137,14 +194,14 @@ await report(newrelic);
|
|
137
194
|
|
138
195
|
</details>
|
139
196
|
|
140
|
-
<details><summary>
|
197
|
+
<details><summary>Lightstep</summary>
|
141
198
|
|
142
199
|
```ts
|
143
|
-
import {
|
200
|
+
import { configure, tracer, report } from 'rian';
|
144
201
|
import { exporter } from 'rian/exporter.otel.http';
|
145
202
|
|
146
203
|
const lightstep = exporter((payload) =>
|
147
|
-
fetch('https://ingest.lightstep.com/traces/otlp/v0.
|
204
|
+
fetch('https://ingest.lightstep.com/traces/otlp/v0.9', {
|
148
205
|
method: 'POST',
|
149
206
|
headers: {
|
150
207
|
'lightstep-access-token': '<your api key>',
|
@@ -154,7 +211,9 @@ const lightstep = exporter((payload) =>
|
|
154
211
|
}),
|
155
212
|
);
|
156
213
|
|
157
|
-
|
214
|
+
configure('my-service');
|
215
|
+
|
216
|
+
const tracer = tracer('app');
|
158
217
|
|
159
218
|
await report(lightstep);
|
160
219
|
```
|
@@ -165,34 +224,23 @@ await report(lightstep);
|
|
165
224
|
|
166
225
|
## 🤔 Motivation
|
167
226
|
|
168
|
-
|
169
|
-
|
170
|
-
In efforts to be better observant citizens, we generally reach for the — NewRelic, LightStep, DataDog's. Which, and in
|
171
|
-
no offence to them, is bloated and slow! Where they more often than not do way too much or and relatively speaking, ship
|
172
|
-
useless traces. Which ramp up your bill — see... every span you trace, costs.
|
173
|
-
|
174
|
-
And here we are, introducing **rian** — a lightweight, fast effective tracer. Inspired by the giants in the industry,
|
175
|
-
OpenTracing and OpenTelemetry.
|
176
|
-
|
177
|
-
You might have not heard of those before — and that is okay. It means the design goals from OpenTelemetry or OpenTracing
|
178
|
-
has been met. They are frameworks built to abstract the telemetry part from vendors. So folk like NewRelic can wrap
|
179
|
-
their layers on top of open telemetry — and have libraries instrument theirs without knowing about the vendor. Which
|
180
|
-
allows consumers to ship those spans to the vendor of their choosing. OpenTracing has a very similar design goal, so
|
181
|
-
please do go checkout their documentation's, to help decide.
|
227
|
+
To clarify, `rian` is the Irish word for "trace".
|
182
228
|
|
183
|
-
|
184
|
-
|
185
|
-
MongoDB ran", or how many network calls your ORM made. Cardinality will destroy you. Although rian can scale to support
|
186
|
-
those as well. But the reality is; there are profiler tools far more capable — "right tool for the job".
|
229
|
+
In our efforts to be observant citizens, we often rely on tools such as NewRelic, Lightstep, and Datadog. However, these
|
230
|
+
tools can be bloated and slow, often performing too many unnecessary tasks and driving up costs, as every span costs.
|
187
231
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
have that forwarded onto all sub-services.
|
232
|
+
This is where rian comes in as a lightweight, fast, and effective tracer inspired by industry giants OpenTracing and
|
233
|
+
OpenTelemetry. These frameworks were designed to abstract the telemetry part from vendors, allowing libraries to be
|
234
|
+
instrumented without needing to know about the vendor.
|
192
235
|
|
193
|
-
|
236
|
+
Rian does not intend to align or compete with them, slightly different goals. Rian aims to be used exclusively for
|
237
|
+
instrumenting your application, particularly critical business paths. While rian can scale to support more complex
|
238
|
+
constructs, there are profiler tools that are better suited for those jobs. Rian's primary design goal is to provide
|
239
|
+
better insights into your application's behavior, particularly for edge or service workers where a lean tracer is
|
240
|
+
favored.
|
194
241
|
|
195
|
-
Rian
|
242
|
+
Rian does not by design handle injecting [`w3c trace-context`](https://www.w3.org/TR/trace-context/), or
|
243
|
+
[propagating baggage](https://www.w3.org/TR/baggage/). But we do expose api's for achieving this.
|
196
244
|
|
197
245
|
## 💨 Benchmark
|
198
246
|
|
@@ -201,19 +249,23 @@ Rian is still in active development, but ready for production!
|
|
201
249
|
```
|
202
250
|
Validation :: single span
|
203
251
|
✔ rian
|
252
|
+
✔ rian/async
|
204
253
|
✔ opentelemetry
|
205
254
|
|
206
255
|
Benchmark :: single span
|
207
|
-
rian x
|
208
|
-
|
256
|
+
rian x 277,283 ops/sec ±3.57% (90 runs sampled)
|
257
|
+
rian/async x 279,525 ops/sec ±2.33% (91 runs sampled)
|
258
|
+
opentelemetry x 155,019 ops/sec ±13.13% (70 runs sampled)
|
209
259
|
|
210
260
|
Validation :: child span
|
211
261
|
✔ rian
|
262
|
+
✔ rian/async
|
212
263
|
✔ opentelemetry
|
213
264
|
|
214
265
|
Benchmark :: child span
|
215
|
-
rian x
|
216
|
-
|
266
|
+
rian x 146,793 ops/sec ±3.38% (87 runs sampled)
|
267
|
+
rian/async x 180,488 ops/sec ±1.64% (92 runs sampled)
|
268
|
+
opentelemetry x 102,541 ops/sec ±9.77% (73 runs sampled)
|
217
269
|
```
|
218
270
|
|
219
271
|
> And please... I know these results are anything but the full story. But it's a number and point on comparison.
|
@@ -225,5 +277,5 @@ MIT © [Marais Rossouw](https://marais.io)
|
|
225
277
|
##### Disclaimer
|
226
278
|
|
227
279
|
<sup>- NewRelic is a registered trademark of https://newrelic.com/ and not affiliated with this project.</sup><br />
|
228
|
-
<sup>-
|
229
|
-
<sup>-
|
280
|
+
<sup>- Datadog is a registered trademark of https://www.datadoghq.com/ and not affiliated with this project.</sup><br />
|
281
|
+
<sup>- Lightstep is a registered trademark of https://lightstep.com/ and not affiliated with this project.</sup>
|
package/utils.d.ts
CHANGED
@@ -1,66 +1,25 @@
|
|
1
1
|
import type { Scope } from 'rian';
|
2
2
|
|
3
|
-
export type MeasureFn =
|
4
|
-
| ((...args: [...args: any[]]) => any)
|
5
|
-
| ((...args: [...args: any[], scope: Scope]) => any);
|
6
|
-
|
7
3
|
/**
|
8
|
-
* With a passed function
|
9
|
-
* the span finishes.
|
4
|
+
* With a passed function, `measure` will run the function and once finishes, will end the span.
|
10
5
|
*
|
11
6
|
* The measure method will return whatever the function is, so if it's a promise, it returns a
|
12
7
|
* promise and so on. Any error is caught and re thrown, and automatically tracked in the
|
13
8
|
* context under the `error` property.
|
14
9
|
*
|
15
|
-
* All promises are tracked, and awaited on a `
|
10
|
+
* All promises are tracked, and awaited on a `report`.
|
16
11
|
*
|
17
|
-
*
|
18
|
-
*
|
19
|
-
* ```text
|
20
|
-
* const data = await measure(scope, get_data, 'user_id_123');
|
21
|
-
* ^ ^ ^ ^
|
22
|
-
* | | | |
|
23
|
-
* | | | the first argument to get_data
|
24
|
-
* | | |
|
25
|
-
* | | function to be called
|
26
|
-
* | |
|
27
|
-
* | the parent scope
|
28
|
-
* return value from get_data
|
29
|
-
* ```
|
30
|
-
*/
|
31
|
-
export const measure: <Fn extends MeasureFn>(
|
32
|
-
scope: Scope,
|
33
|
-
fn: Fn, // TODO: fn doesnt see scope correctly
|
34
|
-
...args: RealMeasureFnParams<Parameters<Fn>>
|
35
|
-
) => ReturnType<Fn>;
|
36
|
-
|
37
|
-
/**
|
38
|
-
* Wraps any function with a measured scoped function. Useful for when defer function execution
|
39
|
-
* till a later time.
|
12
|
+
* This is a utility method, but is functionally equivalent to `scope.span('name')(fn)`.
|
40
13
|
*
|
41
14
|
* @example
|
42
15
|
*
|
43
|
-
* ```
|
44
|
-
* const
|
45
|
-
*
|
46
|
-
*
|
47
|
-
*
|
48
|
-
* wrapped();
|
16
|
+
* ```text
|
17
|
+
* const data = await measure(scope, get_data);
|
18
|
+
* // or with arguments:
|
19
|
+
* const data = await measure(scope, () => get_data('foo', 'bar'));
|
49
20
|
* ```
|
50
21
|
*/
|
51
|
-
export
|
22
|
+
export function measure<Fn extends (scope: Scope) => any>(
|
52
23
|
scope: Scope,
|
53
|
-
fn: Fn,
|
54
|
-
)
|
55
|
-
|
56
|
-
// ==> internals
|
57
|
-
|
58
|
-
/** @internal */
|
59
|
-
export type RealMeasureFnParams<T extends unknown[]> = T extends []
|
60
|
-
? []
|
61
|
-
: T extends [...rest: infer U, scope: Scope]
|
62
|
-
? U
|
63
|
-
: T;
|
64
|
-
|
65
|
-
/** @internal */
|
66
|
-
export const measureFn: (scope: Scope, fn: any, ...args: any[]) => any;
|
24
|
+
fn: Fn,
|
25
|
+
): ReturnType<Fn>;
|
package/utils.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
function r(r,t){try{var e=t(r),n=e instanceof Promise;return n&&r.__add_promise(e.catch((t=>{r.set_context({error:t})})).finally((()=>r.end()))),e}catch(t){throw t instanceof Error&&r.set_context({error:t}),t}finally{!0!==n&&r.end()}}exports.measure=r;
|
package/utils.mjs
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
function r(r,t){try{var e=t(r),n=e instanceof Promise;return n&&r.__add_promise(e.catch((t=>{r.set_context({error:t})})).finally((()=>r.end()))),e}catch(t){throw t instanceof Error&&r.set_context({error:t}),t}finally{!0!==n&&r.end()}}export{r as measure};
|