Back to packages list

Vals using @opentelemetry/api

Description from the NPM package:
Public API for OpenTelemetry

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,
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
import {
init,
traced,
traced_handler,
} from "https://esm.town/v/saolsen/tracing?v=126";
import { trace } from "npm:@opentelemetry/api";
init("traced_http_val");
const tracer = trace.getTracer("traced_http_val");
const sleep = (delay: number) =>
new Promise((resolve) => setTimeout(resolve, delay));
function _foo(x: number) {
console.log(x);
}
const foo = traced("foo", _foo);
function _throw() {
throw new Error("errr");
}
const thro = traced("throw", _throw);
const doSomething = traced("do-something", async () => {
await sleep(100);
});
async function afn(x: number): Promise<number> {
return x;
}
const tafn = traced("afn", afn);
async function handler(request: Request): Promise<Response> {
await tracer.startActiveSpan("sub-span", async (span) => {
await doSomething();
await foo();
try {
await thro();
} catch (e) {
console.log("nah");
}
await sleep(100);
span.addEvent("sub event ok");
await tracer.startActiveSpan("sub-span-2", async (span2) => {
await sleep(100);
span2.end();
});
await tracer.startActiveSpan("sub-span-3", async (span3) => {
await sleep(100);
span3.end();
});
span.end();
});
return Response.json({ hello: "world" });
}
export default traced_handler(handler);

Connect4 agent that uses Monte-Carlo tree search to simulate 10,000 random games from each possible action and pick the one with the highest win rate. Ported from a version I made in rust.

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
import { init, traced_handler } from "https://esm.town/v/saolsen/tracing?v=109";
import { trace } from "npm:@opentelemetry/api";
init("connect4_mcts_agent");
const tracer = trace.getTracer("connect4_mcts_agent");
import * as connect4 from "https://esm.town/v/saolsen/connect4";
import { connect4_agent } from "https://esm.town/v/saolsen/connect4_agent";
const SIMULATIONS = 10000;
function rand_action(state: connect4.State): connect4.Action {
const player = state.next_player;
while (true) {
let column = Math.floor(Math.random() * connect4.COLS);
let action = { player, column };
if (connect4.check_action(state, action) === "ok") {
return action;
}
}
}
function score_action(
current_state: connect4.State,
action: connect4.Action,
): number {
const player = current_state.next_player;
// Create a new match with the action applied.
const next_state = JSON.parse(JSON.stringify(current_state));
connect4.apply_action(next_state, action);
// Simulate random games from this state.
let score = 0;
for (let i = 0; i < SIMULATIONS; i++) {
let sim_state = JSON.parse(JSON.stringify(next_state));
// Play out the rest of the game randomly.
let status = connect4.status(sim_state);
while (status.status === "in_progress") {
let sim_action = rand_action(sim_state);
status = connect4.apply_action(sim_state, sim_action);
}
if (status.result.kind === "winner") {
if (status.result.winner === player) {
score += 1;
} else {
score -= 1;
}
}
}
return score / SIMULATIONS;
}
function agent(state: connect4.State): connect4.Action {
// For each action we could take, simulate multiple random games from the resulting state.
// Keep track of the number of wins for each action.
// Pick the action with the highest win rate.
let max_score = Number.MIN_VALUE;
let best_action = { player: state.next_player, column: 0 };
for (let col = 0; col < connect4.COLS; col++) {
let action = { player: state.next_player, column: col };
let check = connect4.check_action(state, action);
if (check === "ok") {
const score = score_action(state, action);
if (score > max_score) {
max_score = score;
best_action = action;
}
}
}
return best_action;
}
const handler = connect4_agent(agent);
export default traced_handler(handler);

Play connect4.

  • Write agents that play connect4.
  • Battle your agents against other agents.
  • It's fun.
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 https://esm.sh/hono/jsx */
import { track } from "https://esm.town/v/saolsen/plausible?v=3";
import { init, traced_fetch, traced_handler, traced, get_tracer } from "https://esm.town/v/saolsen/tracing?v=135";
import { trace } from "npm:@opentelemetry/api";
init("connect4_site");
const tracer = get_tracer("connect4_site");
import { blob } from "https://esm.town/v/std/blob";
import { customAlphabet } from "npm:nanoid";
import { z } from "npm:zod";
import { Hono } from "npm:hono";
import { html } from "npm:hono/html";
import { jsxRenderer, useRequestContext } from "npm:hono/jsx-renderer";
import * as connect4 from "https://esm.town/v/saolsen/connect4";
// TODO:
// * htmx loading spinners
const _nanoid = customAlphabet("123456789abcdefghijklmnopqrstuvwxyz", 10);
const MatchId = z.string().startsWith("m_");
type MatchId = z.infer<typeof MatchId>;
function matchid(): MatchId {
return `m_${_nanoid()}`;
}
type MePlayer = {
kind: "me";
};
type AgentPlayer = {
kind: "agent";
name: string;
url: string;
};
type Player = MePlayer | AgentPlayer;
type AgentFetchError = {
kind: "agent_fetch";
error_message: string;
};
type AgentHTTPResponseError = {
kind: "agent_http_response";
status: number;
body: string;
};
type AgentInvalidActionJsonError = {
kind: "agent_invalid_json";
body: string;
parse_error: string;
};
type AgentInvalidActionError = {
kind: "agent_invalid_action";
action: connect4.Action;
error: string;
};
type Error =
| AgentFetchError
| AgentHTTPResponseError
| AgentInvalidActionJsonError
| AgentInvalidActionError;
type ErrorStatus = {
status: "error";
error: Error;
};
type StatusOrError = connect4.Status | ErrorStatus;
type Turn = {
number: number;
status: StatusOrError;
player: number | null;
column: number | null;
state: connect4.State;
};
type Match = {
id: MatchId;
players: [Player, Player];
turns: Turn[];
};
async function get_match(id: MatchId): Promise<Match | null> {
const span = tracer.startSpan("get_match");
span.setAttribute("match_id", id);
const match = await blob.getJSON(`connect4_matches/${id}`);
span.end();
if (match === undefined) {
return null;
}
return match;
}
1
Next