rian 0.3.0-next.8 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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};