Copy
Ask AI
npm install @apollo/server @opentelemetry/api @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions dotenv
.env
Copy
Ask AI
# Required
BRAINTRUST_API_KEY=your-api-key
# Parent identifier for organizing traces
# Format: project_name:experiment_name
BRAINTRUST_PARENT=project_name:apollo-graphql
# Optional: Custom OpenTelemetry endpoint (for self-hosted Braintrust)
# BRAINTRUST_OTEL_ENDPOINT=https://api.braintrust.dev/otel/v1/traces
apollo-graphql-braintrust.ts
Copy
Ask AI
import { trace } from "@opentelemetry/api";
import { SpanStatusCode } from "@opentelemetry/api";
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { BraintrustSpanProcessor } from "braintrust";
const provider = new BasicTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: "graphql-api",
}),
});
// Add Braintrust span processor with configuration
provider.addSpanProcessor(
// @ts-ignore
new BraintrustSpanProcessor(),
);
provider.register();
// Get a tracer for your GraphQL operations
const tracer = trace.getTracer("apollo-graphql", "1.0.0");
// Import Apollo Server
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
// Define your schema
const typeDefs = `#graphql
type Query {
hello(name: String): String!
books: [Book!]!
book(id: ID!): Book
}
type Book {
id: ID!
title: String!
author: String!
year: Int
}
type Mutation {
addBook(title: String!, author: String!, year: Int): Book!
}
`;
// Mock data functions (replace with your actual implementation)
async function fetchBooks() {
return [
{
id: "1",
title: "The Great Gatsby",
author: "F. Scott Fitzgerald",
year: 1925,
},
{ id: "2", title: "1984", author: "George Orwell", year: 1949 },
];
}
async function fetchBookById(id: string) {
const books = await fetchBooks();
return books.find((book) => book.id === id);
}
async function createBook({
title,
author,
year,
}: {
title: string;
author: string;
year?: number;
}) {
return {
id: String(Date.now()),
title,
author,
year: year || new Date().getFullYear(),
};
}
// Implement resolvers with tracing
const resolvers = {
Query: {
hello: (_: any, { name }: { name?: string }) => {
// Create a span for this resolver
const span = tracer.startSpan("query.hello");
span.setAttributes({
"graphql.operation": "query",
"graphql.field": "hello",
"input.name": name || "undefined",
});
try {
const result = `Hello, ${name || "World"}!`;
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.recordException(error as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
},
books: async () => {
const span = tracer.startSpan("query.books");
span.setAttributes({
"graphql.operation": "query",
"graphql.field": "books",
});
try {
const books = await fetchBooks();
span.setAttribute("books.count", books.length);
span.setStatus({ code: SpanStatusCode.OK });
return books;
} catch (error) {
span.recordException(error as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
},
book: async (_: any, { id }: { id: string }) => {
const span = tracer.startSpan("query.book");
span.setAttributes({
"graphql.operation": "query",
"graphql.field": "book",
"book.id": id,
});
try {
const book = await fetchBookById(id);
span.setAttribute("book.found", book ? "true" : "false");
span.setStatus({ code: SpanStatusCode.OK });
return book;
} catch (error) {
span.recordException(error as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
},
},
Mutation: {
addBook: async (
_: any,
{ title, author, year }: { title: string; author: string; year?: number },
) => {
const span = tracer.startSpan("mutation.addBook");
span.setAttributes({
"graphql.operation": "mutation",
"graphql.field": "addBook",
"book.title": title,
"book.author": author,
});
if (year) span.setAttribute("book.year", year);
try {
const newBook = await createBook({ title, author, year });
span.setAttribute("book.id", newBook.id);
span.setStatus({ code: SpanStatusCode.OK });
return newBook;
} catch (error) {
span.recordException(error as Error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
},
},
};
// Create and start Apollo Server
const server = new ApolloServer({ typeDefs, resolvers });
async function main() {
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => {
// Create a root span for each GraphQL request
const rootSpan = tracer.startSpan("graphql.request");
rootSpan.setAttribute("http.method", req.method || "POST");
rootSpan.setAttribute("http.url", req.url || "/graphql");
// The span will be automatically exported when it ends
// BraintrustSpanProcessor handles the batching and sending
setTimeout(() => rootSpan.end(), 100);
return {};
},
});
console.log(`🚀 Server ready at: ${url}`);
}
main().catch(console.error);
apollo-router-braintrust.yaml
Copy
Ask AI
connectors:
preview_connect_v0_3: true
sources:
# OpenAI API configuration
openai:
$config:
apiKey: ${env.OPENAI_API_KEY}
# OpenTelemetry configuration for Braintrust
telemetry:
exporters:
tracing:
# OTLP exporter for Braintrust
otlp:
enabled: true
# Braintrust OTEL endpoint
endpoint: ${env.BRAINTRUST_OTEL_ENDPOINT:-https://api.braintrust.dev/otel}
# Use HTTP protocol for Braintrust
protocol: http
# HTTP configuration with headers for Braintrust
http:
headers:
# Braintrust API authentication
Authorization: Bearer ${env.BRAINTRUST_API_KEY}
# Parent project/experiment for traces
x-bt-parent: ${env.BRAINTRUST_PARENT:-project_name:apollo-graphql-project}
# Batch processor configuration for optimal performance
batch_processor:
scheduled_delay: 5s
max_concurrent_exports: 2
max_export_batch_size: 512
max_export_timeout: 30s
max_queue_size: 2048
metrics:
# Prometheus endpoint for local debugging
prometheus:
enabled: true
listen: 0.0.0.0:9090
path: /metrics
# Optional: OTLP metrics to Braintrust
otlp:
enabled: false
endpoint: ${env.BRAINTRUST_OTEL_ENDPOINT:-https://api.braintrust.dev/otel}
protocol: http
http:
headers:
Authorization: Bearer ${env.BRAINTRUST_API_KEY}
x-bt-parent: ${env.BRAINTRUST_PARENT:-project_name:apollo-graphql-project}
# Include subgraph errors in responses for debugging
include_subgraph_errors:
all: true
# GraphQL query limits
limits:
max_depth: 20
max_height: 200
max_aliases: 30
max_root_fields: 30