import * as Cause from "effect/Cause";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Tracer from "effect/Tracer";import type { HttpClient } from "effect/unstable/http";import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"; export interface RelayClientTracingConfig { readonly tracesUrl: string; readonly tracesDataset: string; readonly tracesToken: string;} export interface RelayClientTracingResource { readonly serviceName: string; readonly serviceVersion?: string; readonly runtime: string; readonly client: string; readonly component?: string;} export class RelayClientTracer extends Context.Reference( "@t3tools/shared/relayTracing/RelayClientTracer", { defaultValue: () => Option.none<Tracer.Tracer>(), },) {} export const withRelayClientTracing = <A, E, R>( effect: Effect.Effect<A, E, R>,): Effect.Effect<A, E, R> => RelayClientTracer.pipe( Effect.flatMap( Option.match({ onNone: () => effect, onSome: (tracer) => effect.pipe(Effect.provideService(Tracer.Tracer, tracer)), }), ), ); function traceSafeError(value: unknown): Error { const message = value instanceof Error ? value.message : typeof value === "object" && value !== null && "message" in value && typeof value.message === "string" ? value.message : String(value); const error = new Error(message); if (value instanceof Error) { error.name = value.name; if (value.stack !== undefined) { error.stack = value.stack; } } else if ( typeof value === "object" && value !== null && "name" in value && typeof value.name === "string" ) { error.name = value.name; } return error;} function traceSafeExit(exit: Exit.Exit<unknown, unknown>): Exit.Exit<unknown, unknown> { if (Exit.isSuccess(exit)) { return exit; } return Exit.failCause( Cause.fromReasons( exit.cause.reasons.map((reason) => { if (Cause.isFailReason(reason)) { return Cause.makeFailReason(traceSafeError(reason.error)); } if (Cause.isDieReason(reason)) { return Cause.makeDieReason(traceSafeError(reason.defect)); } return reason; }), ), );} function nonInterferingTracer(delegate: Tracer.Tracer): Tracer.Tracer { return Tracer.make({ span(options) { const span = delegate.span(options); const end = span.end.bind(span); span.end = (endTime, exit) => { try { end(endTime, traceSafeExit(exit)); } catch { // Telemetry is best-effort and must never change application behavior. } }; return span; }, ...(delegate.context ? { context: delegate.context } : {}), });} export function makeRelayClientTracingLayer( config: RelayClientTracingConfig | null, resource: RelayClientTracingResource,): Layer.Layer<never, never, HttpClient.HttpClient> { if (config === null) { return Layer.succeed(RelayClientTracer, Option.none()); } const tracerLayer = OtlpTracer.layer({ url: config.tracesUrl, headers: { Authorization: `Bearer ${config.tracesToken}`, "X-Axiom-Dataset": config.tracesDataset, }, resource: { serviceName: resource.serviceName, serviceVersion: resource.serviceVersion, attributes: { "service.runtime": resource.runtime, "service.component": resource.component ?? "relay-client", "t3.client.surface": resource.client, }, }, }).pipe(Layer.provide(OtlpSerialization.layerJson)); return Layer.effect( RelayClientTracer, Tracer.Tracer.pipe(Effect.map(nonInterferingTracer), Effect.map(Option.some)), ).pipe(Layer.provide(tracerLayer));}
import * as Cause from "effect/Cause";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Tracer from "effect/Tracer";
import type { HttpClient } from "effect/unstable/http";
import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
export interface RelayClientTracingConfig {
readonly tracesUrl: string;
readonly tracesDataset: string;
readonly tracesToken: string;
}
export interface RelayClientTracingResource {
readonly serviceName: string;
readonly serviceVersion?: string;
readonly runtime: string;
readonly client: string;
readonly component?: string;
}
export class RelayClientTracer extends Context.Reference(
"@t3tools/shared/relayTracing/RelayClientTracer",
{
defaultValue: () => Option.none<Tracer.Tracer>(),
},
) {}
export const withRelayClientTracing = <A, E, R>(
effect: Effect.Effect<A, E, R>,
): Effect.Effect<A, E, R> =>
RelayClientTracer.pipe(
Effect.flatMap(
Option.match({
onNone: () => effect,
onSome: (tracer) => effect.pipe(Effect.provideService(Tracer.Tracer, tracer)),
}),
),
);
function traceSafeError(value: unknown): Error {
const message =
value instanceof Error
? value.message
: typeof value === "object" &&
value !== null &&
"message" in value &&
typeof value.message === "string"
? value.message
: String(value);
const error = new Error(message);
if (value instanceof Error) {
error.name = value.name;
if (value.stack !== undefined) {
error.stack = value.stack;
}
} else if (
typeof value === "object" &&
value !== null &&
"name" in value &&
typeof value.name === "string"
) {
error.name = value.name;
}
return error;
}
function traceSafeExit(exit: Exit.Exit<unknown, unknown>): Exit.Exit<unknown, unknown> {
if (Exit.isSuccess(exit)) {
return exit;
}
return Exit.failCause(
Cause.fromReasons(
exit.cause.reasons.map((reason) => {
if (Cause.isFailReason(reason)) {
return Cause.makeFailReason(traceSafeError(reason.error));
}
if (Cause.isDieReason(reason)) {
return Cause.makeDieReason(traceSafeError(reason.defect));
}
return reason;
}),
),
);
}
function nonInterferingTracer(delegate: Tracer.Tracer): Tracer.Tracer {
return Tracer.make({
span(options) {
const span = delegate.span(options);
const end = span.end.bind(span);
span.end = (endTime, exit) => {
try {
end(endTime, traceSafeExit(exit));
} catch {
// Telemetry is best-effort and must never change application behavior.
}
};
return span;
},
...(delegate.context ? { context: delegate.context } : {}),
});
}
export function makeRelayClientTracingLayer(
config: RelayClientTracingConfig | null,
resource: RelayClientTracingResource,
): Layer.Layer<never, never, HttpClient.HttpClient> {
if (config === null) {
return Layer.succeed(RelayClientTracer, Option.none());
}
const tracerLayer = OtlpTracer.layer({
url: config.tracesUrl,
headers: {
Authorization: `Bearer ${config.tracesToken}`,
"X-Axiom-Dataset": config.tracesDataset,
},
resource: {
serviceName: resource.serviceName,
serviceVersion: resource.serviceVersion,
attributes: {
"service.runtime": resource.runtime,
"service.component": resource.component ?? "relay-client",
"t3.client.surface": resource.client,
},
},
}).pipe(Layer.provide(OtlpSerialization.layerJson));
return Layer.effect(
RelayClientTracer,
Tracer.Tracer.pipe(Effect.map(nonInterferingTracer), Effect.map(Option.some)),
).pipe(Layer.provide(tracerLayer));
}