1/**
2 * @title Setting up tracing with Otlp modules
3 *
4 * Configure Otlp tracing + log export with a reusable observability layer.
5 */
6import { NodeRuntime } from "@effect/platform-node"
7import { Context, Effect, Layer } from "effect"
8import { FetchHttpClient } from "effect/http"
9import { OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/observability"
10
11// Configure OTLP span export.
12export const OtlpTracingLayer = OtlpTracer.layer({
13 url: "http://localhost:4318/v1/traces",
14 resource: {
15 serviceName: "checkout-api",
16 serviceVersion: "1.0.0",
17 attributes: {
18 "deployment.environment": "staging"
19 }
20 }
21})
22
23// Configure OTLP log export.
24export const OtlpLoggingLayer = OtlpLogger.layer({
25 url: "http://localhost:4318/v1/logs",
26 resource: {
27 serviceName: "checkout-api",
28 serviceVersion: "1.0.0"
29 }
30})
31
32// Reusable app-wide observability layer.
33//
34// - OtlpTracer/OtlpLogger require an OTLP serializer and an HttpClient.
35// - FetchHttpClient.layer provides the HttpClient used by the exporter.
36export const ObservabilityLayer = Layer.merge(OtlpTracingLayer, OtlpLoggingLayer).pipe(
37 Layer.provide(OtlpSerialization.layerJson),
38 Layer.provide(FetchHttpClient.layer)
39)
40
41export class Checkout extends Context.Service<Checkout, {
42 processCheckout(orderId: string): Effect.Effect<void>
43}>()("acme/Checkout") {
44 static readonly layer = Layer.effect(
45 Checkout,
46 Effect.gen(function*() {
47 yield* Effect.logInfo("setting up checkout service")
48
49 return Checkout.of({
50 processCheckout: Effect.fn("Checkout.processCheckout")(function*(orderId: string) {
51 yield* Effect.logInfo("starting checkout", { orderId })
52
53 yield* Effect.sleep("50 millis").pipe(
54 Effect.withSpan("checkout.charge-card"),
55 Effect.annotateSpans({
56 "checkout.order_id": orderId,
57 "checkout.provider": "acme-pay"
58 })
59 )
60
61 yield* Effect.sleep("20 millis").pipe(
62 Effect.withSpan("checkout.persist-order")
63 )
64
65 yield* Effect.logInfo("checkout completed", { orderId })
66 })
67 })
68 })
69 )
70}
71
72// Example usage of the Checkout service.
73const CheckoutTest = Layer.effectDiscard(
74 Effect.gen(function*() {
75 const checkout = yield* Checkout
76 yield* checkout.processCheckout("ord_123")
77 }).pipe(
78 Effect.withSpan("checkout-test-run")
79 )
80).pipe(
81 // You can also attach spans to Layers
82 Layer.withSpan("checkout-test"),
83 Layer.provide(Checkout.layer)
84)
85
86const Main = CheckoutTest.pipe(
87 // Provide the observability layer at the very end, so that all spans created
88 // by the app are exported.
89 Layer.provide(ObservabilityLayer)
90)
91
92// Launch the app
93Layer.launch(Main).pipe(
94 NodeRuntime.runMain
95)