1import * as Alchemy from "alchemy";
2import * as Axiom from "alchemy/Axiom";
3import * as Cloudflare from "alchemy/Cloudflare";
4import * as GitHub from "alchemy/GitHub";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7
8import { CliOverviewDashboard } from "./otel/Dashboard.ts";
9import { Logs, Metrics, Traces } from "./otel/Datasets.ts";
10import Ingester from "./otel/Ingester.ts";
11import {
12 ActiveUsersByCi,
13 ActiveUsersByVersion,
14 ActiveUsersHourly,
15 CliInvocations,
16 DeployDestroyLatency,
17 ResourceErrorRate,
18 ResourceLatency,
19 ResourcesUsed,
20} from "./otel/Views.ts";
21
22/**
23 * Provisions an Axiom OTEL ingestion pipeline:
24 *
25 * - Three datasets (`{stage}-traces`, `{stage}-logs`, `{stage}-metrics`),
26 * each with the matching `otel:*:v1` `kind`.
27 * - One ingest-only API token scoped to those three datasets.
28 * - Outputs the OTLP endpoints + `Authorization` header value so callers can
29 * wire them straight into a Worker / Lambda's env vars.
30 * - Optionally syncs the same values to the GitHub repo's Actions secrets
31 * (set `SYNC_GITHUB_SECRETS=1` when deploying).
32 */
33export default Alchemy.Stack(
34 "AlchemyOtel",
35 {
36 providers: Layer.mergeAll(
37 Axiom.providers(),
38 Cloudflare.providers(),
39 GitHub.providers(),
40 ),
41 state: Cloudflare.state(),
42 },
43 Effect.gen(function* () {
44 const traces = yield* Traces;
45 const logs = yield* Logs;
46 const metrics = yield* Metrics;
47
48 // Public ingest relay. Bound to `otel.alchemy.run` and
49 // `analytics.alchemy.run` only in prod so dev stages exercise the same
50 // code path under a `*.workers.dev` URL. The same Worker also reverse-
51 // proxies PostHog Cloud for first-party browser analytics.
52 const relay = yield* Ingester;
53
54 // const posthogProjectKey = yield* Config.String("POSTHOG_PROJECT_KEY")
55 // .pipe(Config.option)
56 //
57 // .pipe(Effect.orDie);
58
59 // Saved APL queries — one per insight surfaced by the dashboard.
60 yield* ActiveUsersHourly;
61 yield* ActiveUsersByVersion;
62 yield* ActiveUsersByCi;
63 yield* ResourcesUsed;
64 yield* DeployDestroyLatency;
65 yield* ResourceLatency;
66 yield* CliInvocations;
67 yield* ResourceErrorRate;
68
69 // Composed dashboard combining the same signals on a 12-col grid.
70 yield* CliOverviewDashboard;
71
72 const env = {
73 OTEL_EXPORTER_OTLP_ENDPOINT: traces.otelEndpoint,
74 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: traces.otelTracesEndpoint,
75 OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: logs.otelLogsEndpoint,
76 OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: metrics.otelMetricsEndpoint,
77 AXIOM_DATASET_TRACES: traces.name,
78 AXIOM_DATASET_LOGS: logs.name,
79 AXIOM_DATASET_METRICS: metrics.name,
80 RELAY_URL: relay.url,
81 };
82
83 if (process.env.SYNC_GITHUB_SECRETS === "1") {
84 yield* GitHub.Secrets({
85 owner: "alchemy-run",
86 repository: "alchemy",
87 secrets: {
88 AXIOM_DATASET_TRACES: traces.name,
89 AXIOM_DATASET_LOGS: logs.name,
90 AXIOM_DATASET_METRICS: metrics.name,
91 OTEL_EXPORTER_OTLP_ENDPOINT: traces.otelEndpoint,
92 },
93 });
94 }
95
96 return env;
97 }),
98);