Skip to content

Commit aa35bec

Browse files
feat: CATALYST-676 add generic otel setup (#2708)
Co-authored-by: Chancellor Clark <[email protected]>
1 parent c3fa141 commit aa35bec

File tree

7 files changed

+320
-45
lines changed

7 files changed

+320
-45
lines changed

.changeset/silent-pillows-sip.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@bigcommerce/catalyst-core": minor
3+
---
4+
5+
Adds OpenTelemetry instrumentation for Catalyst, enabling the collection of spans for Catalyst storefronts.
6+
7+
### Migration
8+
9+
Change is new code only, so just copy over `/core/instrumentation.ts` and `core/lib/otel/tracers.ts`.

core/.env.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,16 @@ TURBO_REMOTE_CACHE_SIGNATURE_KEY=
3030
# https://nextjs.org/docs/app/building-your-application/caching#data-cache
3131
# This sets a sensible revalidation target for cached requests
3232
DEFAULT_REVALIDATE_TARGET=3600
33+
34+
# OpenTelemetry Configuration (Optional)
35+
# See OPENTELEMETRY.md for detailed setup and usage instructions
36+
# See https://nextjs.org/docs/app/guides/open-telemetry for Next.js guide
37+
38+
# Set a custom service name for your traces (defaults to 'next-app')
39+
# OTEL_SERVICE_NAME=catalyst-storefront
40+
41+
# Enable verbose tracing to see all spans (useful for debugging, increases data volume)
42+
# NEXT_OTEL_VERBOSE=1
43+
44+
# Disable automatic fetch instrumentation (not recommended unless using custom instrumentation)
45+
# NEXT_OTEL_FETCH_DISABLED=1

core/instrumentation.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { registerOTel } from '@vercel/otel';
2+
3+
// eslint-disable-next-line valid-jsdoc
4+
/**
5+
* Initializes OpenTelemetry instrumentation for the Next.js application.
6+
*
7+
* This function is automatically called by Next.js during application startup
8+
* to set up distributed tracing and observability.
9+
*
10+
* @see https://nextjs.org/docs/app/guides/instrumentation
11+
* @see https://nextjs.org/docs/app/guides/open-telemetry
12+
*
13+
* Configuration:
14+
* - Uses @vercel/otel for simplified OpenTelemetry setup
15+
* - Automatically instruments Next.js internals (requests, rendering, fetches)
16+
* - Service name is read from OTEL_SERVICE_NAME environment variable
17+
* - If OTEL_SERVICE_NAME is not set, defaults to 'next-app'
18+
*
19+
* Environment Variables:
20+
* - OTEL_SERVICE_NAME: Custom service name (e.g., 'catalyst-storefront')
21+
* - NEXT_OTEL_VERBOSE: Set to '1' to see all spans (default: essential spans only)
22+
* - NEXT_OTEL_FETCH_DISABLED: Set to '1' to disable automatic fetch instrumentation
23+
*
24+
* What's automatically instrumented:
25+
* - All HTTP requests (route, method, status)
26+
* - App Router page and layout rendering
27+
* - API route handler execution
28+
* - fetch() calls to external APIs
29+
* - Metadata generation (generateMetadata)
30+
*
31+
* Custom instrumentation:
32+
* Import the tracer from ~/lib/otel/tracer to create custom spans:
33+
*
34+
* @example
35+
* import { tracer } from '~/lib/otel/tracer';
36+
*
37+
* export async function myFunction() {
38+
* return await tracer.startActiveSpan('myFunction', async (span) => {
39+
* try {
40+
* const result = await doWork();
41+
* span.setAttribute('result.count', result.length);
42+
* return result;
43+
* } finally {
44+
* span.end();
45+
* }
46+
* });
47+
* }
48+
*
49+
* For more information about using OpenTelemetry in Catalyst, see:
50+
* - OPENTELEMETRY.md in this directory
51+
* - Official Next.js guide: https://nextjs.org/docs/app/guides/open-telemetry
52+
*/
53+
export function register() {
54+
// Service name is pulled from the OTEL_SERVICE_NAME env var.
55+
// If you wish to manually set it, you can remove the env var and set it here:
56+
// registerOTel({ serviceName: 'catalyst-storefront' });
57+
registerOTel();
58+
}

core/lib/otel/tracer.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { trace } from '@opentelemetry/api';
2+
3+
/**
4+
* OpenTelemetry tracer for creating custom spans in the Catalyst application.
5+
*
6+
* Use this tracer to instrument important operations and track their performance.
7+
* Spans created with this tracer will appear in your observability dashboard
8+
* nested under the appropriate HTTP request trace.
9+
*
10+
* @see https://nextjs.org/docs/app/guides/open-telemetry#custom-spans
11+
* @see OPENTELEMETRY.md for detailed usage guide
12+
*
13+
* @example Basic usage
14+
* import { tracer } from '~/lib/otel/tracer';
15+
*
16+
* export async function fetchProductRecommendations(productId: string) {
17+
* return await tracer.startActiveSpan('fetchProductRecommendations', async (span) => {
18+
* try {
19+
* // Add attributes for context
20+
* span.setAttribute('product.id', productId);
21+
*
22+
* const recommendations = await fetch(`/api/recommendations/${productId}`);
23+
*
24+
* span.setAttribute('recommendations.count', recommendations.length);
25+
*
26+
* return recommendations;
27+
* } finally {
28+
* // Always end the span, even if an error occurs
29+
* span.end();
30+
* }
31+
* });
32+
* }
33+
*
34+
* @example With error handling
35+
* import { tracer } from '~/lib/otel/tracer';
36+
* import { SpanStatusCode } from '@opentelemetry/api';
37+
*
38+
* export async function processOrder(orderId: string) {
39+
* return await tracer.startActiveSpan('processOrder', async (span) => {
40+
* try {
41+
* span.setAttribute('order.id', orderId);
42+
*
43+
* const result = await submitOrder(orderId);
44+
*
45+
* return result;
46+
* } catch (error) {
47+
* // Record the exception and mark span as error
48+
* span.recordException(error);
49+
* span.setStatus({ code: SpanStatusCode.ERROR });
50+
* throw error;
51+
* } finally {
52+
* span.end();
53+
* }
54+
* });
55+
* }
56+
*
57+
* @example Data transformation
58+
* export async function transformCartData(rawCart: RawCart) {
59+
* return await tracer.startActiveSpan('transformCartData', async (span) => {
60+
* try {
61+
* span.setAttribute('cart.itemCount', rawCart.lineItems.length);
62+
*
63+
* const transformed = {
64+
* items: rawCart.lineItems.map(transformLineItem),
65+
* total: calculateTotal(rawCart),
66+
* };
67+
*
68+
* return transformed;
69+
* } finally {
70+
* span.end();
71+
* }
72+
* });
73+
* }
74+
*
75+
* When to use custom spans:
76+
* - Operations that might be slow (> 100ms)
77+
* - Critical business logic (pricing, inventory, checkout)
78+
* - External API integrations
79+
* - Data transformations with variable performance
80+
* - Any operation you want to monitor and optimize
81+
*
82+
* When NOT to use custom spans:
83+
* - Operations already instrumented by Next.js (fetch calls, route rendering)
84+
* - Simple utility functions
85+
* - Trivial operations (< 10ms)
86+
*
87+
* Best practices:
88+
* - Use descriptive span names (e.g., 'cart.calculateDiscounts')
89+
* - Add meaningful attributes for filtering and debugging
90+
* - Always end spans in finally blocks
91+
* - Use hierarchical naming (e.g., 'cart.validate', 'cart.addItem')
92+
*/
93+
export const tracer = trace.getTracer('default');

core/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
"@conform-to/react": "^1.6.1",
2020
"@conform-to/zod": "^1.6.1",
2121
"@icons-pack/react-simple-icons": "^11.2.0",
22+
"@opentelemetry/api": "^1.9.0",
23+
"@opentelemetry/api-logs": "^0.208.0",
24+
"@opentelemetry/instrumentation": "^0.208.0",
25+
"@opentelemetry/sdk-logs": "^0.208.0",
2226
"@radix-ui/react-accordion": "^1.2.11",
2327
"@radix-ui/react-checkbox": "^1.3.2",
2428
"@radix-ui/react-dialog": "^1.1.14",
@@ -37,6 +41,7 @@
3741
"@upstash/redis": "^1.35.0",
3842
"@vercel/analytics": "^1.5.0",
3943
"@vercel/functions": "^2.2.12",
44+
"@vercel/otel": "^2.1.0",
4045
"@vercel/speed-insights": "^1.2.0",
4146
"clsx": "^2.1.1",
4247
"content-security-policy-builder": "^2.3.0",

0 commit comments

Comments
 (0)