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 CHANGED
@@ -1,6 +1,59 @@
1
- import type { CallableScope, Scope, Span } from 'rian';
1
+ import type { CallableScope, Options, Scope } from 'rian';
2
2
 
3
- export function currentSpan(): Scope | null;
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 { tracer, report, configure } from 'rian';
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 { default:e } = require('node:async_hooks');const { measure:t } = require('rian/utils');const { make:n, parse:r, SAMPLED_FLAG:a } = require('tctx');const { is_sampled:o } = require('tctx');var s={};function l(e,t={}){s={...t,"service.name":e,"telemetry.sdk.name":"rian","telemetry.sdk.version":"0.3.0-next.8"}}var p=new Set,c=new WeakMap;async function i(e){let t=[],n=new Map;for(let[e,r]of p){let a;n.has(r)?a=n.get(r).spans:n.set(r,{scope:r,spans:a=[]}),a.push(e),c.has(r)&&(t.push(...c.get(r)),c.delete(r))}return p.clear(),t.length&&await Promise.all(t),e({resource:s,scopeSpans:n.values()})}function u(e,t){return o(t)}var d=new e.AsyncLocalStorage;function m(){return d.getStore()?.[1]||null}function f(e){let r=d.getStore();if(!r)throw Error("TODO");let a=r[0],o=a.scope,s=r[1],l=a.sampler,i=s?.traceparent??a.root_id,u=i?i.child():n(),m="boolean"!=typeof l?l(e,u,o):l;u.flags;let g={id:u,parent:i,start:Date.now(),name:e,events:[],context:{}};m&&p.add([g,o]);let h=e=>d.run([a,h],t,h,e);h.traceparent=u,h.span=f,h.set_context=e=>{"function"!=typeof e?Object.assign(g.context,e):g.context=e(g.context)},h.add_event=(e,t)=>{g.events.push({name:e,timestamp:Date.now(),attributes:t||{}})},h.end=()=>{null==g.end&&(g.end=Date.now())};let w=c.get(o);return h.__add_promise=e=>{w.add(e),e.then((()=>w.delete(e)))},d.run([a,h],(()=>h))}function g(e,t){let n=t?.sampler??u,a={name:e},o={root_id:"string"==typeof t?.traceparent?r(t.traceparent):d.getStore()?.[0].root_id,scope:a,sampler:n};return c.set(a,new Set),{span:e=>d.run([o,null],f,e)}}exports.configure=l;exports.currentSpan=m;exports.report=i;exports.span=f;exports.tracer=g;
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{default as e}from"node:async_hooks";import{measure as t}from"rian/utils";import{make as n,parse as r,SAMPLED_FLAG as a}from"tctx";import{is_sampled as o}from"tctx";var s={};function l(e,t={}){s={...t,"service.name":e,"telemetry.sdk.name":"rian","telemetry.sdk.version":"0.3.0-next.8"}}var p=new Set,c=new WeakMap;async function i(e){let t=[],n=new Map;for(let[e,r]of p){let a;n.has(r)?a=n.get(r).spans:n.set(r,{scope:r,spans:a=[]}),a.push(e),c.has(r)&&(t.push(...c.get(r)),c.delete(r))}return p.clear(),t.length&&await Promise.all(t),e({resource:s,scopeSpans:n.values()})}function u(e,t){return o(t)}var d=new e.AsyncLocalStorage;function m(){return d.getStore()?.[1]||null}function f(e){let r=d.getStore();if(!r)throw Error("TODO");let a=r[0],o=a.scope,s=r[1],l=a.sampler,i=s?.traceparent??a.root_id,u=i?i.child():n(),m="boolean"!=typeof l?l(e,u,o):l;u.flags;let g={id:u,parent:i,start:Date.now(),name:e,events:[],context:{}};m&&p.add([g,o]);let h=e=>d.run([a,h],t,h,e);h.traceparent=u,h.span=f,h.set_context=e=>{"function"!=typeof e?Object.assign(g.context,e):g.context=e(g.context)},h.add_event=(e,t)=>{g.events.push({name:e,timestamp:Date.now(),attributes:t||{}})},h.end=()=>{null==g.end&&(g.end=Date.now())};let w=c.get(o);return h.__add_promise=e=>{w.add(e),e.then((()=>w.delete(e)))},d.run([a,h],(()=>h))}function g(e,t){let n=t?.sampler??u,a={name:e},o={root_id:"string"==typeof t?.traceparent?r(t.traceparent):d.getStore()?.[0].root_id,scope:a,sampler:n};return c.set(a,new Set),{span:e=>d.run([o,null],f,e)}}export{l as configure,m as currentSpan,i as report,f as span,g as tracer};
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
- * An exporter is a method called when the parent scope ends, gets given a Set of all spans traced
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 stringed header.
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
- * Should return true when you want to sample the span, this is ran before the span is traced — so
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
- * Sampling does impact the traceparent, for injection and is encoded there.
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
- readonly scope: { readonly name: string },
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 "resolve".
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)] [Span C......(0.6ms)]
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.fork|tracer.fork()}.
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://github.com/opentracing/specification/blob/master/semantic_conventions.md|Semantic Conventions outlined by OpenTracing}.
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(name: string): CallableScope;
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
- (cb: (scope: Omit<Scope, 'end'>) => void): ReturnType<typeof cb>;
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
- // TODO
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 { measureFn:e } = require('rian/utils');const { make:t, parse:n, SAMPLED_FLAG:a } = require('tctx');const { is_sampled:r } = require('tctx');var s={};function o(e,t={}){s={...t,"service.name":e,"telemetry.sdk.name":"rian","telemetry.sdk.version":"0.3.0-next.8"}}var p=new Set,i=new WeakMap;async function l(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),i.has(a)&&(t.push(...i.get(a)),i.delete(a))}return p.clear(),t.length&&await Promise.all(t),e({resource:s,scopeSpans:n.values()})}function c(e,t){return r(t)}function d(a,r){let s=r?.sampler??c,o={name:a},l=new Set;i.set(o,l);let d="string"==typeof r?.traceparent?n(r.traceparent):void 0,m=(n,a)=>{let r=a?a.child():t(),i="boolean"!=typeof s?s(n,r,o):s;r.flags;let c={id:r,parent:a,start:Date.now(),name:n,events:[],context:{}};i&&p.add([c,o]);let d=t=>e(d,t);return d.traceparent=r,d.span=e=>m(e,r),d.set_context=e=>"function"==typeof e?void(c.context=e(c.context)):void Object.assign(c.context,e),d.add_event=(e,t)=>{c.events.push({name:e,timestamp:Date.now(),attributes:t||{}})},d.end=()=>{null==c.end&&(c.end=Date.now())},d.__add_promise=e=>{l.add(e),e.then((()=>l.delete(e)))},d};return{span:e=>m(e,d)}}exports.configure=o;exports.report=l;exports.tracer=d;
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{measureFn 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 s={};function o(e,t={}){s={...t,"service.name":e,"telemetry.sdk.name":"rian","telemetry.sdk.version":"0.3.0-next.8"}}var p=new Set,i=new WeakMap;async function l(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),i.has(a)&&(t.push(...i.get(a)),i.delete(a))}return p.clear(),t.length&&await Promise.all(t),e({resource:s,scopeSpans:n.values()})}function c(e,t){return r(t)}function d(a,r){let s=r?.sampler??c,o={name:a},l=new Set;i.set(o,l);let d="string"==typeof r?.traceparent?n(r.traceparent):void 0,m=(n,a)=>{let r=a?a.child():t(),i="boolean"!=typeof s?s(n,r,o):s;r.flags;let c={id:r,parent:a,start:Date.now(),name:n,events:[],context:{}};i&&p.add([c,o]);let d=t=>e(d,t);return d.traceparent=r,d.span=e=>m(e,r),d.set_context=e=>"function"==typeof e?void(c.context=e(c.context)):void Object.assign(c.context,e),d.add_event=(e,t)=>{c.events.push({name:e,timestamp:Date.now(),attributes:t||{}})},d.end=()=>{null==c.end&&(c.end=Date.now())},d.__add_promise=e=>{l.add(e),e.then((()=>l.delete(e)))},d};return{span:e=>m(e,d)}}export{o as configure,l as report,d as tracer};
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rian",
3
- "version": "0.3.0-next.8",
3
+ "version": "0.3.0",
4
4
  "description": "Effective tracing for the edge and origins",
5
5
  "keywords": [
6
6
  "opentelemetry",
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 OpenTracing.
26
+ - 🤔 **Familiar** — looks very much like opentelemetry.
30
27
 
31
- - ✅ **Simple** — `create` a tracer, and `report()`, done.
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 1Kb and next to no [dependencies](https://npm.anvaka.com/#/view/2d/rian/).
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 info!
36
+ > Visit [/examples](/examples) for more!
40
37
 
41
38
  ```ts
42
- import { create, report } from 'rian';
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
- // ~> Where to send the spans.
47
- const otel_endpoint = exporter((payload) =>
48
- fetch('/traces/otlp', {
49
- method: 'POST',
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
- // ~> Create a tracer — typically "per request" or "per operation"
55
- const tracer = create('my-service');
47
+ // ~> create a tracer — typically "per request" or "per operation".
48
+ const trace = tracer('request');
56
49
 
57
- // Let us trace
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
- const req = tracer.span('GET ~> /data');
56
+ // ~> span again for `db::read`
57
+ const data = await s.span('db::read')(() => db_execute('SELECT * FROM users'));
60
58
 
61
- req.set_context({
62
- user: request_context.user_id,
63
- });
59
+ // ~> maybe have some manual spanning
60
+ const processing_span = s.span('process records');
64
61
 
65
- // ~> Wrap any method and be timed 🕺🏻
66
- const data = await measure(req.span('db::read'), get_data);
62
+ for (let row of data) {
63
+ processing_span.add_event('doing stuff', { id: row.id });
64
+ do_stuff(row);
65
+ }
67
66
 
68
- // ~> Maybe have some in-flow spanning
69
- const span = req.span('process records');
67
+ // don't forget to end
68
+ processing_span.end();
70
69
 
71
- for (let row of data) {
72
- span.add_event('doing stuff', { id: row.id });
73
- do_stuff(row);
70
+ return reply(200, { data });
71
+ });
74
72
  }
75
73
 
76
- span.end();
77
-
78
- req.end();
74
+ const otel_exporter = exporter((payload) =>
75
+ fetch('/traces/otlp', {
76
+ method: 'POST',
77
+ body: JSON.stringify(payload),
78
+ }),
79
+ );
79
80
 
80
- // ~> And finally let's export — will also end the root span.
81
- await report(otel_endpoint);
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 ~> /data .................................... (1.2ms) ]
87
- [ db::read .... (0.5ms) ]
88
- [ process records .... (0.5ms) ]
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, please stick to
99
- > [Semantic Conventions](https://github.com/opentracing/specification/blob/master/semantic_conventions.md), but won't be
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 { create, report } from 'rian';
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
- const tracer = create('my-service');
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>LightStep</summary>
197
+ <details><summary>Lightstep</summary>
141
198
 
142
199
  ```ts
143
- import { create, report } from 'rian';
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.6', {
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
- const tracer = create('my-service');
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
- Firstly, what is `rian`? _trace_ in Irish is `rian`.
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
- Rian does not intend to align or compete with them. rian's intent is to be used to instrument your application and
184
- **only** your application. Rian is primed in that critical business paths where you don't care " which handlers
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
- Rian is simply a tracer you can use to see what your application is doing, have better insight into why something failed
189
- and stitch it with your logs. It starts by capturing a [`w3c trace-context`](https://www.w3.org/TR/trace-context/),
190
- tracing some business steps. "inbound request /data", "getting data", "sending email", or as granular as you'd like. And
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
- You see, the primary design goal is targeted at edge or service workers where lean quick tracers is favoured.
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 is still in active development, but ready for production!
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 385,085 ops/sec ±4.26% (85 runs sampled)
208
- opentelemetry x 205,004 ops/sec ±11.99% (65 runs sampled)
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 206,736 ops/sec ±6.37% (86 runs sampled)
216
- opentelemetry x 128,298 ops/sec ±14.82% (68 runs sampled)
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>- DataDog is a registered trademark of https://www.datadoghq.com/ and not affiliated with this project.</sup><br />
229
- <sup>- LightStep is a registered trademark of https://lightstep.com/ and not affiliated with this project.</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 will start a span, and run the function, when the function finishes
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 `tracer.end`.
10
+ * All promises are tracked, and awaited on a `report`.
16
11
  *
17
- * @example
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
- * ```js
44
- * const wrapped = wrap(scope, "run something", my_function);
45
- *
46
- * // ... lots of things, where the access to `scope` is lost.
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 const wrap: <Fn extends MeasureFn>(
22
+ export function measure<Fn extends (scope: Scope) => any>(
52
23
  scope: Scope,
53
- fn: Fn, // TODO: fn doesnt see scope correctly
54
- ) => Fn;
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
- var r=(r,t,...e)=>{try{var n=t(...e,r),o=n instanceof Promise;return o&&r.__add_promise(n.catch((t=>{r.set_context({error:t})})).finally((()=>r.end()))),n}catch(t){throw t instanceof Error&&r.set_context({error:t}),t}finally{!0!==o&&r.end()}},t=(t,e,...n)=>r(t,e,...n),e=(t,e)=>function(){return r(t,e,...arguments)};exports.measure=t;exports.measureFn=r;exports.wrap=e;
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
- var r=(r,t,...e)=>{try{var n=t(...e,r),o=n instanceof Promise;return o&&r.__add_promise(n.catch((t=>{r.set_context({error:t})})).finally((()=>r.end()))),n}catch(t){throw t instanceof Error&&r.set_context({error:t}),t}finally{!0!==o&&r.end()}},t=(t,e,...n)=>r(t,e,...n),e=(t,e)=>function(){return r(t,e,...arguments)};export{t as measure,r as measureFn,e as wrap};
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};