Readme

Telemetry For Vals.

Telemetry is a library that lets you trace val town executions with opentelemetry. All traces are stored in val.town sqlite and there is an integrated trace viewer to see them.

image.png

Quickstart

Instrument an http val like this.

import {
  init,
  tracedHandler,
} from "https://esm.town/v/saolsen/telemetry";

// Set up tracing by passing in `import.meta.url`.
// be sure to await it!!!
await init(import.meta.url);

async function handler(req: Request): Promise<Response> {
  // whatever else you do.
  return 
  
}

export default tracedHandler(handler);

This will instrument the http val and trace every request. Too add additional traces see this widgets example.

Then, too see your traces create another http val like this.

import { traceViewer } from "https://esm.town/v/saolsen/telemetry";
export default traceViewer;

This val will serve a UI that lets you browse traces. For example, you can see my UI here.

Tracing

By wrapping your http handler in tracedHandler all your val executions will be traced. You can add additional traces by using the helpers.

  • trace lets you trace a block of syncronous code.
import { trace } from "https://esm.town/v/saolsen/telemetry";

trace("traced block", () => {
    // do something
});
  • traceAsync lets you trace a block of async code.
import { traceAsync } from "https://esm.town/v/saolsen/telemetry";

await traceAsync("traced block", await () => {
    // await doSomething();
});
  • traced wraps an async function in tracing.
import { traceAsync } from "https://esm.town/v/saolsen/telemetry";

const myTracedFunction: () => Promise<string> = traced(
  "myTracedFunction",
  async () => {
    // await sleep(100);
    return "something";
  },
);
  • fetch is a traced version of the builtin fetch function that traces the request. Just import it and use it like you would use fetch.

  • sqlite is a traced version of the val town sqlite client. Just import it and use it like you would use https://www.val.town/v/std/sqlite

  • attribute adds an attribute to the current span, which you can see in the UI.

  • event adds an event to the current span, which you can see in the UI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource npm:hono@4.2.2/jsx */
import "https://deno.land/x/xhr@0.1.0/mod.ts";
import "node:async_hooks";
import {
AttributeValue,
context,
propagation,
Span,
SpanStatusCode,
trace as otelTrace,
Tracer,
} from "npm:@opentelemetry/api";
import { ReadableSpan } from "npm:@opentelemetry/sdk-trace-base";
import {
ExportResult,
ExportResultCode,
hrTimeToMicroseconds,
} from "npm:@opentelemetry/core";
import { B3Propagator } from "npm:@opentelemetry/propagator-b3";
import { Resource } from "npm:@opentelemetry/resources";
import {
SimpleSpanProcessor,
WebTracerProvider,
} from "npm:@opentelemetry/sdk-trace-web";
import { SEMRESATTRS_SERVICE_NAME } from "npm:@opentelemetry/semantic-conventions";
import { AsyncLocalStorageContextManager } from "npm:@opentelemetry/context-async-hooks";
import { and, desc, eq, isNull, sql } from "npm:drizzle-orm@0.30.7";
import {
index,
integer,
primaryKey,
sqliteTable,
text,
} from "npm:drizzle-orm@0.30.7/sqlite-core";
import { drizzle } from "npm:drizzle-orm@0.30.7/libsql";
import { BatchItem } from "npm:drizzle-orm@0.30.7/batch";
import { Hono } from "npm:hono@4.2.2";
import { Child, FC } from "npm:hono@4.2.2/jsx";
import { jsxRenderer, useRequestContext } from "npm:hono@4.2.2/jsx-renderer";
import { html } from "npm:hono@4.2.2/html";
import { type TransactionMode } from "npm:@libsql/client";
import {
InStatement,
ResultSet,
sqlite as std_sqlite,
} from "https://esm.town/v/std/sqlite";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
const tracing_spans = sqliteTable(
"tracing_spans",
{
span_id: text("span_id").notNull().primaryKey(),
trace_id: text("trace_id").notNull(),
parent_span_id: text("parent_span_id"),
service: text("service").notNull(),
name: text("name").notNull(),
timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(),
duration: integer("duration").notNull(),
status_code: integer("status_code"),
},
(table) => {
return {
spansTraceIdIdx: index("tracing_spans_trace_id").on(
table.trace_id,
),
spansServiceIdx: index("tracing_spans_service").on(
table.service,
),
spansTimestampIdx: index("tracing_spans_timestamp").on(
table.timestamp,
),
};
},
);
type InsertSpan = typeof tracing_spans.$inferInsert;
type SelectSpan = typeof tracing_spans.$inferSelect;
const tracing_span_attributes = sqliteTable(
"tracing_span_attributes",
{
span_id: text("span_id").notNull().references(
() => tracing_spans.span_id,
{ onDelete: "cascade" },
),
name: text("name").notNull(),
value: text("value", { mode: "json" }).notNull(),
},
(table) => {
return {
pk: primaryKey({ columns: [table.span_id, table.name] }),
spanAttributesSpanIdIdx: index("tracing_span_attributes_span_id").on(
table.span_id,
),
spanAttributesNameIdx: index("tracing_span_attributes_name").on(
table.name,
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.