OpenTelemetry Demo Light
A real-world example using the extension with opentelemetry-demo-light — a lightweight microservices demo with 5 services in 5 languages.
The Script
This k6 script simulates realistic user journeys: browsing products, viewing details, adding to cart, and checking out. Every request carries W3C TraceContext and Baggage headers, creating connected distributed traces from the load generator through all backend services.
import { sleep } from "k6";
import otel from "k6/x/otel";
import { randomItem, randomIntBetween } from "https://jslib.k6.io/k6-utils/1.4.0/index.js";
const PRODUCT_IDS = [
"OLJCESPC7Z", "66VCHSJNUP", "1YMWWN1N4O", "L9ECAV7KIM", "2ZYFJ3GM2N",
"0PUK6V6EV0", "LS4PSXUNUM", "9SIQT8TOJO", "6E92ZMYYFZ", "HQTGWGPNH4",
];
const BASE_URL = __ENV.FRONTEND_URL || "http://frontend:8080";
export const options = {
scenarios: {
continuous: {
executor: "constant-vus",
vus: 5,
duration: "5m",
},
},
thresholds: {
http_req_duration: ["p(95)<5000"],
http_req_failed: ["rate<0.1"],
},
};
export default function () {
// Scenario-level baggage — visible to all downstream services
otel.setBaggage("test.scenario", "browse-and-buy");
otel.setAttribute("test.type", "load");
// --- Browse Products ---
otel.step("Browse Products", function () {
const products = otel.request("list-products", "GET", \`\${BASE_URL}/api/products\`);
otel.check("products-loaded", products, {
"status is 200": (r) => r.status === 200,
});
});
sleep(randomIntBetween(1, 3));
// --- View Product Detail ---
const productId = randomItem(PRODUCT_IDS);
otel.step("View Product", function () {
otel.setBaggage("product.id", productId);
const product = otel.request("get-product", "GET", \`\${BASE_URL}/api/product/\${productId}\`);
otel.check("product-found", product, {
"status is 200": (r) => r.status === 200,
});
});
sleep(randomIntBetween(1, 2));
// --- Add to Cart ---
otel.step("Add to Cart", function () {
otel.setBaggage("cart.action", "add");
otel.setAttribute("business.flow", "cart");
otel.request("add-to-cart", "POST", \`\${BASE_URL}/api/cart/add\`,
JSON.stringify({ productId: productId, quantity: randomIntBetween(1, 3) }),
{ headers: { "Content-Type": "application/json" } }
);
});
sleep(randomIntBetween(1, 2));
// --- Checkout (33% of iterations) ---
if (Math.random() < 0.33) {
otel.step("Checkout", function () {
otel.setAttribute("business.flow", "purchase");
otel.setBaggage("checkout.initiated", "true");
const order = otel.request("place-order", "POST", \`\${BASE_URL}/api/checkout\`,
JSON.stringify({
email: "test@example.com",
creditCardNumber: "4111111111111111",
creditCardCvv: "123",
creditCardExpirationYear: "2030",
creditCardExpirationMonth: "12",
}),
{ headers: { "Content-Type": "application/json" } }
);
otel.check("order-placed", order, {
"status is 200": (r) => r.status === 200,
});
});
}
sleep(randomIntBetween(1, 3));
}
What You Get
Traces
Every k6 iteration creates a parent span with child spans per step:
gantt
title k6 Iteration Trace
dateFormat X
axisFormat %s
section Load Generator
iteration :0, 10
Browse Products :0, 2
View Product :3, 5
Add to Cart :6, 7
Checkout :8, 10
section Frontend
GET /api/products :0, 1
GET /api/product/X :3, 4
POST /api/cart/add :6, 7
POST /api/checkout :8, 10
section Backend
Product Catalog :0, 1
Product Catalog :3, 4
Cart + Valkey :6, 7
Checkout → Payment :8, 10
Baggage Propagation
Downstream services see baggage like:
k6.test.name=k6, k6.test.step=Browse Products, test.scenario=browse-and-buy, product.id=OLJCESPC7Z
The Product Catalog service (Go) reads this baggage and attaches it as span attributes.
Run It
# Start the demo
cd opentelemetry-demo-light
docker compose --profile jaeger up -d
# Traces visible at http://localhost:16686