Lightstep Java SDK: Distributed Tracing and Observability Implementation

Lightstep is a powerful distributed tracing system that helps you understand and optimize your microservices architecture. This guide covers comprehensive integration of Lightstep's Java SDK for end-to-end observability.


Dependencies and Setup

Maven Dependencies
<properties>
<lightstep.version>0.21.0</lightstep.version>
<opentelemetry.version>1.28.0</opentelemetry.version>
<grpc.version>1.56.1</grpc.version>
</properties>
<dependencies>
<!-- Lightstep OpenTelemetry SDK -->
<dependency>
<groupId>com.lightstep</groupId>
<artifactId>lightstep-opentelemetry-java</artifactId>
<version>${lightstep.version}</version>
</dependency>
<!-- OpenTelemetry API -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<!-- OpenTelemetry SDK -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<!-- OpenTelemetry Exporter -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<!-- gRPC for OTLP export -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- Spring Boot Starter (if using Spring) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>

Core Configuration

1. Lightstep Tracer Configuration
package com.example.tracing.config;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.ResourceAttributes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
public class LightstepConfig {
@Value("${lightstep.access.token:}")
private String lightstepAccessToken;
@Value("${lightstep.service.name:unknown-service}")
private String serviceName;
@Value("${lightstep.collector.host:ingest.lightstep.com}")
private String collectorHost;
@Value("${lightstep.collector.port:443}")
private int collectorPort;
@Bean
public OpenTelemetry openTelemetry() {
// Create resource with service name
Resource resource = Resource.getDefault()
.merge(Resource.builder()
.put(ResourceAttributes.SERVICE_NAME, serviceName)
.put(ResourceAttributes.SERVICE_VERSION, "1.0.0")
.put(ResourceAttributes.DEPLOYMENT_ENVIRONMENT, getEnvironment())
.build());
// Configure OTLP exporter for Lightstep
OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint(String.format("https://%s:%d", collectorHost, collectorPort))
.addHeader("lightstep-access-token", lightstepAccessToken)
.setTimeout(30, TimeUnit.SECONDS)
.build();
// Configure SDK
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.setResource(resource)
.build();
OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
.build();
// Register as global instance
GlobalOpenTelemetry.set(openTelemetrySdk);
return openTelemetrySdk;
}
@Bean
public Tracer tracer(OpenTelemetry openTelemetry) {
return openTelemetry.getTracer(serviceName, "1.0.0");
}
private String getEnvironment() {
return System.getenv().getOrDefault("ENVIRONMENT", "development");
}
}
2. Environment-Specific Configuration
# application.yml
lightstep:
access:
token: ${LIGHTSTEP_ACCESS_TOKEN:}
service:
name: user-service
collector:
host: ingest.lightstep.com
port: 443
management:
tracing:
sampling:
probability: 1.0
// Alternative Programmatic Configuration
public class LightstepTracerFactory {
public static OpenTelemetry createLightstepTracer(String serviceName, 
String accessToken, 
String environment) {
// Using Lightstep's OpenTelemetry distribution
return LightstepOpenTelemetry
.newBuilder(accessToken)
.setServiceName(serviceName)
.setServiceVersion("1.0.0")
.setCollectorHost("ingest.lightstep.com")
.setCollectorPort(443)
.setCollectorProtocol("https")
.setResourceAttributes(ResourceAttributes.builder()
.put("deployment.environment", environment)
.put("team", "backend")
.build())
.build()
.getOpenTelemetry();
}
}

Instrumentation Implementation

1. HTTP Request Filter for Incoming Requests
package com.example.tracing.filter;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class TracingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(TracingFilter.class);
private final Tracer tracer;
public TracingFilter(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Extract context from incoming headers
Context extractedContext = GlobalOpenTelemetry.getPropagators().getTextMapPropagator()
.extract(Context.current(), httpRequest, HttpServletRequestGetter.INSTANCE);
// Start span
Span span = tracer.spanBuilder(httpRequest.getMethod() + " " + httpRequest.getRequestURI())
.setSpanKind(SpanKind.SERVER)
.setParent(extractedContext)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Add attributes to span
span.setAttribute("http.method", httpRequest.getMethod());
span.setAttribute("http.route", httpRequest.getRequestURI());
span.setAttribute("http.url", httpRequest.getRequestURL().toString());
span.setAttribute("http.user_agent", httpRequest.getHeader("User-Agent"));
span.setAttribute("http.client_ip", getClientIp(httpRequest));
// Set span as current
chain.doFilter(request, response);
// Record response status
span.setAttribute("http.status_code", httpResponse.getStatus());
if (httpResponse.getStatus() >= 400) {
span.setStatus(StatusCode.ERROR, "HTTP " + httpResponse.getStatus());
} else {
span.setStatus(StatusCode.OK);
}
} catch (Exception e) {
// Record exception
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
private String getClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0];
}
return request.getRemoteAddr();
}
private enum HttpServletRequestGetter implements TextMapGetter<HttpServletRequest> {
INSTANCE;
@Override
public Iterable<String> keys(HttpServletRequest carrier) {
return () -> carrier.getHeaderNames().asIterator();
}
@Override
public String get(HttpServletRequest carrier, String key) {
return carrier.getHeader(key);
}
}
}
2. RestTemplate Interceptor for Outgoing HTTP Calls
package com.example.tracing.interceptor;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class TracingRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final Tracer tracer;
public TracingRestTemplateInterceptor(Tracer tracer) {
this.tracer = tracer;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
ClientHttpRequestExecution execution) throws IOException {
// Create child span for outgoing request
Span span = tracer.spanBuilder("HTTP " + request.getMethod() + " " + request.getURI().getHost())
.setSpanKind(SpanKind.CLIENT)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Add attributes
span.setAttribute("http.method", request.getMethod().name());
span.setAttribute("http.url", request.getURI().toString());
span.setAttribute("http.target", request.getURI().getPath());
span.setAttribute("net.peer.name", request.getURI().getHost());
span.setAttribute("net.peer.port", request.getURI().getPort());
// Inject tracing headers
GlobalOpenTelemetry.getPropagators().getTextMapPropagator()
.inject(Context.current(), request, HttpRequestSetter.INSTANCE);
// Execute request
ClientHttpResponse response = execution.execute(request, body);
// Record response
span.setAttribute("http.status_code", response.getStatusCode().value());
if (response.getStatusCode().isError()) {
span.setStatus(StatusCode.ERROR, "HTTP " + response.getStatusCode().value());
}
return response;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
private enum HttpRequestSetter implements TextMapSetter<HttpRequest> {
INSTANCE;
@Override
public void set(HttpRequest carrier, String key, String value) {
carrier.getHeaders().add(key, value);
}
}
}
3. Database Tracing Aspect
package com.example.tracing.aspect;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class DatabaseTracingAspect {
private static final Logger logger = LoggerFactory.getLogger(DatabaseTracingAspect.class);
private final Tracer tracer;
public DatabaseTracingAspect(Tracer tracer) {
this.tracer = tracer;
}
@Around("execution(* com.example.repository.*.*(..))")
public Object traceRepositoryMethods(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
Span span = tracer.spanBuilder(className + "." + methodName)
.setSpanKind(SpanKind.INTERNAL)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Add database operation attributes
span.setAttribute("db.operation", methodName);
span.setAttribute("db.component", "JDBC");
span.setAttribute("db.instance", "users-db");
// Log parameters (be careful with sensitive data)
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
span.setAttribute("db.parameters.count", joinPoint.getArgs().length);
}
logger.debug("Executing database operation: {}.{}", className, methodName);
Object result = joinPoint.proceed();
span.setStatus(StatusCode.OK);
return result;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
logger.error("Database operation failed: {}.{}", className, methodName, e);
throw e;
} finally {
span.end();
}
}
@Around("@annotation(com.example.tracing.annotation.TracedQuery)")
public Object traceCustomQueries(ProceedingJoinPoint joinPoint) throws Throwable {
String queryName = getQueryName(joinPoint);
Span span = tracer.spanBuilder("DB Query: " + queryName)
.setSpanKind(SpanKind.INTERNAL)
.startSpan();
long startTime = System.currentTimeMillis();
try (Scope scope = span.makeCurrent()) {
span.setAttribute("db.operation", "query");
span.setAttribute("db.query.name", queryName);
span.setAttribute("db.system", "postgresql");
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
span.setAttribute("db.duration_ms", duration);
span.setStatus(StatusCode.OK);
return result;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
private String getQueryName(ProceedingJoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}
}
4. Custom Business Operation Tracing
package com.example.tracing.service;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class BusinessOperationTracer {
private static final Logger logger = LoggerFactory.getLogger(BusinessOperationTracer.class);
private final Tracer tracer;
public BusinessOperationTracer(Tracer tracer) {
this.tracer = tracer;
}
public <T> T traceOperation(String operationName, Map<String, Object> attributes, 
BusinessOperation<T> operation) {
Span span = tracer.spanBuilder(operationName)
.setSpanKind(io.opentelemetry.api.trace.SpanKind.INTERNAL)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Set operation attributes
if (attributes != null) {
attributes.forEach((key, value) -> 
span.setAttribute(key, String.valueOf(value)));
}
span.setAttribute("business.operation", operationName);
span.setAttribute("component", "business-logic");
logger.info("Starting business operation: {}", operationName);
T result = operation.execute();
span.setStatus(StatusCode.OK);
logger.info("Business operation completed: {}", operationName);
return result;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
logger.error("Business operation failed: {}", operationName, e);
throw e;
} finally {
span.end();
}
}
public void traceAsyncOperation(String operationName, Map<String, Object> attributes,
Runnable operation) {
// Capture current context for async execution
Context context = Context.current();
new Thread(() -> {
try (Scope scope = context.makeCurrent()) {
traceOperation(operationName, attributes, () -> {
operation.run();
return null;
});
}
}).start();
}
@FunctionalInterface
public interface BusinessOperation<T> {
T execute();
}
}

Service Implementation Examples

1. User Service with Comprehensive Tracing
package com.example.service;
import com.example.tracing.service.BusinessOperationTracer;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
private final Tracer tracer;
private final BusinessOperationTracer businessTracer;
private final UserRepository userRepository;
public UserService(Tracer tracer, BusinessOperationTracer businessTracer, 
UserRepository userRepository) {
this.tracer = tracer;
this.businessTracer = businessTracer;
this.userRepository = userRepository;
}
public User createUser(CreateUserRequest request) {
return businessTracer.traceOperation("user.create", 
Map.of(
"user.email", request.getEmail(),
"user.role", request.getRole(),
"user.source", request.getSource()
), 
() -> {
// Validate user
validateUserRequest(request);
// Check for existing user
if (userRepository.existsByEmail(request.getEmail())) {
throw new UserAlreadyExistsException("User already exists");
}
// Create user entity
User user = new User(request);
User savedUser = userRepository.save(user);
// Add event to current span
Span.current().addEvent("user.created", 
io.opentelemetry.api.common.Attributes.of(
io.opentelemetry.api.common.AttributeKey.stringKey("user.id"), 
savedUser.getId()
));
logger.info("User created successfully: {}", savedUser.getId());
return savedUser;
});
}
public User findUserById(String userId) {
Span span = tracer.spanBuilder("user.findById")
.setAttribute("user.id", userId)
.startSpan();
try (Scope scope = span.makeCurrent()) {
logger.debug("Searching for user: {}", userId);
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("User not found"));
span.setAttribute("user.found", true);
span.setAttribute("user.email", user.getEmail());
return user;
} catch (Exception e) {
span.recordException(e);
span.setAttribute("user.found", false);
throw e;
} finally {
span.end();
}
}
public void processUserBatch(List<String> userIds) {
businessTracer.traceAsyncOperation("user.batchProcessing", 
Map.of("batch.size", userIds.size()), 
() -> {
// Process users in batch
userIds.forEach(this::processSingleUser);
});
}
private void processSingleUser(String userId) {
// This will be traced as part of the parent async operation
User user = findUserById(userId);
// Process user...
}
private void validateUserRequest(CreateUserRequest request) {
if (request.getEmail() == null || !request.getEmail().contains("@")) {
throw new IllegalArgumentException("Invalid email address");
}
}
}
2. Order Service with Distributed Tracing
package com.example.service;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Service
public class OrderService {
private final Tracer tracer;
private final RestTemplate restTemplate;
private final BusinessOperationTracer businessTracer;
public OrderService(Tracer tracer, RestTemplate restTemplate, 
BusinessOperationTracer businessTracer) {
this.tracer = tracer;
this.restTemplate = restTemplate;
this.businessTracer = businessTracer;
}
public Order processOrder(OrderRequest request) {
return businessTracer.traceOperation("order.process", 
Map.of(
"order.amount", request.getAmount(),
"order.currency", request.getCurrency(),
"order.customer_id", request.getCustomerId()
), 
() -> {
// Create order span
Span orderSpan = Span.current();
// Step 1: Validate inventory
boolean inventoryAvailable = checkInventory(request.getItems());
orderSpan.setAttribute("order.inventory_available", inventoryAvailable);
if (!inventoryAvailable) {
throw new InventoryUnavailableException("Insufficient inventory");
}
// Step 2: Process payment
PaymentResponse paymentResponse = processPayment(request);
orderSpan.setAttribute("order.payment_id", paymentResponse.getPaymentId());
orderSpan.addEvent("payment.processed");
// Step 3: Create order
Order order = createOrderEntity(request, paymentResponse);
Order savedOrder = orderRepository.save(order);
// Step 4: Update inventory
updateInventory(request.getItems());
orderSpan.addEvent("inventory.updated");
// Step 5: Send notification
sendOrderConfirmation(savedOrder);
orderSpan.addEvent("notification.sent");
logger.info("Order processed successfully: {}", savedOrder.getId());
return savedOrder;
});
}
private boolean checkInventory(List<OrderItem> items) {
Span span = tracer.spanBuilder("inventory.check")
.setAttribute("inventory.items_count", items.size())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Call inventory service
InventoryCheckRequest checkRequest = new InventoryCheckRequest(items);
ResponseEntity<InventoryCheckResponse> response = restTemplate.postForEntity(
"http://inventory-service/api/inventory/check",
checkRequest,
InventoryCheckResponse.class
);
boolean available = response.getBody().isAvailable();
span.setAttribute("inventory.available", available);
return available;
} finally {
span.end();
}
}
private PaymentResponse processPayment(OrderRequest request) {
return businessTracer.traceOperation("payment.process", 
Map.of(
"payment.amount", request.getAmount(),
"payment.currency", request.getCurrency()
), 
() -> {
// Call payment service
PaymentRequest paymentRequest = new PaymentRequest(request);
ResponseEntity<PaymentResponse> response = restTemplate.postForEntity(
"http://payment-service/api/payments/process",
paymentRequest,
PaymentResponse.class
);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new PaymentProcessingException("Payment processing failed");
}
return response.getBody();
});
}
}

Configuration Management

1. Spring Boot Auto-Configuration
package com.example.tracing.config;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.spring.webmvc.v6_0.SpringWebMvcTelemetry;
import jakarta.servlet.Filter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(name = "lightstep.enabled", havingValue = "true", matchIfMissing = true)
public class TracingAutoConfiguration {
@Bean
public Filter tracingFilter(OpenTelemetry openTelemetry) {
return SpringWebMvcTelemetry.create(openTelemetry).createServletFilter();
}
}
2. Environment-Specific Configuration
# application-dev.yml
lightstep:
enabled: true
access:
token: ${LIGHTSTEP_DEV_TOKEN}
service:
name: user-service-dev
collector:
host: ingest.lightstep.com
port: 443
opentelemetry:
tracer:
config:
sampler: always_on
export:
interval: 5000
logging:
level:
io.opentelemetry: INFO
# application-prod.yml
lightstep:
enabled: true
access:
token: ${LIGHTSTEP_PROD_TOKEN}
service:
name: user-service
collector:
host: ingest.lightstep.com
port: 443
opentelemetry:
tracer:
config:
sampler: parentbased_always_on
export:
interval: 1000
management:
endpoints:
web:
exposure:
include: health,info,metrics

Testing and Verification

1. Unit Test for Tracing
package com.example.tracing.test;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
import io.opentelemetry.sdk.trace.data.SpanData;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserServiceTracingTest {
@RegisterExtension
static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create();
@Autowired
private UserService userService;
@Test
void shouldCreateSpanForUserCreation() {
// Given
CreateUserRequest request = new CreateUserRequest("[email protected]", "USER");
// When
User user = userService.createUser(request);
// Then
List<SpanData> spans = otelTesting.getSpans();
assertEquals(1, spans.size());
SpanData span = spans.get(0);
assertEquals("user.create", span.getName());
assertEquals("[email protected]", 
span.getAttributes().get(AttributeKey.stringKey("user.email")));
}
}
2. Integration Test
package com.example.tracing.test;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TracingIntegrationTest {
@LocalServerPort
private int port;
private TestRestTemplate restTemplate = new TestRestTemplate();
@Test
void shouldPropagateTraceHeaders() {
// When
ResponseEntity<String> response = restTemplate.getForEntity(
"http://localhost:" + port + "/api/users/123", String.class);
// Then
assertEquals(200, response.getStatusCodeValue());
// Verify trace headers are present in response
assertNotNull(response.getHeaders().get("traceparent"));
}
}

Best Practices

  1. Consistent Naming: Use consistent span names and attribute keys
  2. Reasonable Sampling: Adjust sampling rate based on traffic
  3. Meaningful Attributes: Include business context in spans
  4. Error Handling: Always record exceptions and set appropriate status
  5. Performance: Use async operations for non-critical tracing
  6. Security: Never include sensitive data in span attributes
// Good practice - meaningful attributes
span.setAttribute("business.customer_tier", customer.getTier());
span.setAttribute("business.order_priority", order.getPriority());
// Bad practice - unclear attributes
span.setAttribute("data", customerData);

Conclusion

Lightstep Java SDK provides:

  • Distributed tracing across microservices
  • Performance monitoring with detailed span timing
  • Error analysis with stack traces and context
  • Dependency mapping through span relationships
  • Business context in operational data

By implementing comprehensive tracing with Lightstep, you gain deep visibility into your application's behavior, enabling faster debugging, performance optimization, and better understanding of system dependencies.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper