Lightstep Java SDK

Introduction to Lightstep Tracing

Lightstep is a distributed tracing platform that provides observability for microservices architectures. The Java SDK enables automatic instrumentation and manual tracing for Java applications.

Maven Configuration

<properties>
<lightstep.version>0.38.0</lightstep.version>
<opentelemetry.version>1.28.0</opentelemetry.version>
</properties>
<dependencies>
<!-- Lightstep SDK -->
<dependency>
<groupId>com.lightstep.tracer</groupId>
<artifactId>lightstep-tracer-jre</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>
<!-- Automatic Instrumentation -->
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-annotations</artifactId>
<version>1.28.0</version>
</dependency>
</dependencies>

Core Configuration

Lightstep Tracer Setup

@Configuration
@EnableConfigurationProperties(LightstepProperties.class)
public class LightstepConfig {
private static final Logger logger = LoggerFactory.getLogger(LightstepConfig.class);
@Bean
@ConditionalOnProperty(name = "lightstep.enabled", havingValue = "true")
public Tracer lightstepTracer(LightstepProperties properties) {
try {
Options options = new Options.OptionsBuilder()
.withAccessToken(properties.getAccessToken())
.withComponentName(properties.getComponentName())
.withCollectorHost(properties.getCollectorHost())
.withCollectorPort(properties.getCollectorPort())
.withCollectorProtocol(properties.getCollectorProtocol())
.withVerbosity(properties.getVerbosity())
.withDeadlineMillis(properties.getDeadlineMillis())
.withMaxBufferedSpans(properties.getMaxBufferedSpans())
.withMaxReportingIntervalMillis(properties.getMaxReportingIntervalMillis())
.withDisableReportingLoop(properties.isDisableReportingLoop())
.build();
Tracer tracer = new LightstepTracer(options);
tracer.setSamplingRate(properties.getSamplingRate());
logger.info("Lightstep tracer initialized for component: {}", 
properties.getComponentName());
return tracer;
} catch (Exception e) {
logger.error("Failed to initialize Lightstep tracer", e);
throw new RuntimeException("Lightstep initialization failed", e);
}
}
@Bean
@ConditionalOnMissingBean(Tracer.class)
public Tracer noopTracer() {
return new NoopTracer();
}
}
@ConfigurationProperties(prefix = "lightstep")
public class LightstepProperties {
private boolean enabled = true;
private String accessToken;
private String componentName = "unknown-service";
private String collectorHost = "ingest.lightstep.com";
private int collectorPort = 443;
private String collectorProtocol = "https";
private int verbosity = 0;
private long deadlineMillis = 30000;
private int maxBufferedSpans = 1000;
private long maxReportingIntervalMillis = 2500;
private boolean disableReportingLoop = false;
private float samplingRate = 1.0f;
private Map<String, String> tags = new HashMap<>();
// Getters and setters
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getAccessToken() { return accessToken; }
public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
public String getComponentName() { return componentName; }
public void setComponentName(String componentName) { this.componentName = componentName; }
public String getCollectorHost() { return collectorHost; }
public void setCollectorHost(String collectorHost) { this.collectorHost = collectorHost; }
public int getCollectorPort() { return collectorPort; }
public void setCollectorPort(int collectorPort) { this.collectorPort = collectorPort; }
public String getCollectorProtocol() { return collectorProtocol; }
public void setCollectorProtocol(String collectorProtocol) { this.collectorProtocol = collectorProtocol; }
public int getVerbosity() { return verbosity; }
public void setVerbosity(int verbosity) { this.verbosity = verbosity; }
public long getDeadlineMillis() { return deadlineMillis; }
public void setDeadlineMillis(long deadlineMillis) { this.deadlineMillis = deadlineMillis; }
public int getMaxBufferedSpans() { return maxBufferedSpans; }
public void setMaxBufferedSpans(int maxBufferedSpans) { this.maxBufferedSpans = maxBufferedSpans; }
public long getMaxReportingIntervalMillis() { return maxReportingIntervalMillis; }
public void setMaxReportingIntervalMillis(long maxReportingIntervalMillis) { 
this.maxReportingIntervalMillis = maxReportingIntervalMillis; 
}
public boolean isDisableReportingLoop() { return disableReportingLoop; }
public void setDisableReportingLoop(boolean disableReportingLoop) { 
this.disableReportingLoop = disableReportingLoop; 
}
public float getSamplingRate() { return samplingRate; }
public void setSamplingRate(float samplingRate) { this.samplingRate = samplingRate; }
public Map<String, String> getTags() { return tags; }
public void setTags(Map<String, String> tags) { this.tags = tags; }
}

OpenTelemetry Integration

OpenTelemetry Configuration

@Component
public class OpenTelemetryConfig {
@Autowired
private LightstepProperties properties;
@Bean
public OpenTelemetrySdk openTelemetry() {
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(
OtlpGrpcSpanExporter.builder()
.setEndpoint(properties.getCollectorProtocol() + "://" + 
properties.getCollectorHost() + ":" + properties.getCollectorPort())
.addHeader("lightstep-access-token", properties.getAccessToken())
.build()
).build())
.setSampler(Sampler.traceIdRatioBased(properties.getSamplingRate()))
.build();
SdkMeterProvider meterProvider = SdkMeterProvider.builder()
.registerMetricReader(PeriodicMetricReader.builder(
OtlpGrpcMetricExporter.builder()
.setEndpoint(properties.getCollectorProtocol() + "://" + 
properties.getCollectorHost() + ":" + properties.getCollectorPort())
.addHeader("lightstep-access-token", properties.getAccessToken())
.build()
).build())
.build();
return OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.setMeterProvider(meterProvider)
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
.build();
}
@Bean
public Tracer tracer(OpenTelemetry openTelemetry) {
return openTelemetry.getTracer(properties.getComponentName());
}
@Bean
public Meter meter(OpenTelemetry openTelemetry) {
return openTelemetry.getMeter(properties.getComponentName());
}
}

Manual Tracing

Basic Span Creation

@Service
public class OrderService {
private final Tracer tracer;
private final Meter meter;
private final Counter orderCounter;
private final Histogram orderDuration;
public OrderService(Tracer tracer, Meter meter) {
this.tracer = tracer;
this.meter = meter;
// Initialize metrics
this.orderCounter = meter.counterBuilder("orders.total")
.setDescription("Total number of orders processed")
.build();
this.orderDuration = meter.histogramBuilder("orders.duration")
.setDescription("Order processing duration")
.setUnit("ms")
.build();
}
public Order processOrder(OrderRequest request) {
// Create a span for the entire operation
Span span = tracer.spanBuilder("processOrder")
.setAttribute("order.id", request.getOrderId())
.setAttribute("customer.id", request.getCustomerId())
.setAttribute("order.amount", request.getAmount())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Record start time for metrics
long startTime = System.currentTimeMillis();
// Business logic with nested spans
validateOrder(request);
processPayment(request);
updateInventory(request);
sendConfirmation(request);
// Record success
span.setStatus(StatusCode.OK);
span.setAttribute("order.status", "completed");
// Update metrics
orderCounter.add(1);
orderDuration.record(System.currentTimeMillis() - startTime);
return createOrderResponse(request);
} catch (Exception e) {
// Record failure
span.setStatus(StatusCode.ERROR, e.getMessage());
span.recordException(e);
span.setAttribute("order.status", "failed");
throw e;
} finally {
span.end();
}
}
private void validateOrder(OrderRequest request) {
Span span = tracer.spanBuilder("validateOrder")
.setAttribute("order.id", request.getOrderId())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Validation logic
if (request.getAmount() <= 0) {
throw new IllegalArgumentException("Invalid order amount");
}
// Simulate validation work
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
span.recordException(e);
throw new RuntimeException("Validation interrupted", e);
} catch (Exception e) {
span.recordException(e);
throw e;
} finally {
span.end();
}
}
private void processPayment(OrderRequest request) {
Span span = tracer.spanBuilder("processPayment")
.setAttribute("order.id", request.getOrderId())
.setAttribute("payment.amount", request.getAmount())
.startSpan();
try (Scope scope = span.makeCurrent()) {
// Payment processing logic
boolean paymentSuccess = paymentGateway.charge(request);
if (!paymentSuccess) {
throw new PaymentException("Payment processing failed");
}
span.setAttribute("payment.status", "success");
} catch (Exception e) {
span.setAttribute("payment.status", "failed");
span.recordException(e);
throw e;
} finally {
span.end();
}
}
}

Annotation-Based Tracing

Custom Tracing Annotations

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Traced {
String value() default "";
String[] tags() default {};
boolean recordMetrics() default true;
}
@Aspect
@Component
public class TracingAspect {
private final Tracer tracer;
private final Meter meter;
public TracingAspect(Tracer tracer, Meter meter) {
this.tracer = tracer;
this.meter = meter;
}
@Around("@annotation(traced)")
public Object traceMethod(ProceedingJoinPoint joinPoint, Traced traced) throws Throwable {
String spanName = traced.value().isEmpty() ? 
joinPoint.getSignature().getName() : traced.value();
Span span = tracer.spanBuilder(spanName)
.setAttribute("class", joinPoint.getTarget().getClass().getSimpleName())
.setAttribute("method", joinPoint.getSignature().getName())
.startSpan();
// Add custom tags from annotation
for (String tag : traced.tags()) {
String[] parts = tag.split("=");
if (parts.length == 2) {
span.setAttribute(parts[0], parts[1]);
}
}
Long startTime = null;
if (traced.recordMetrics()) {
startTime = System.currentTimeMillis();
}
try (Scope scope = span.makeCurrent()) {
return joinPoint.proceed();
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
if (traced.recordMetrics() && startTime != null) {
long duration = System.currentTimeMillis() - startTime;
span.setAttribute("duration.ms", duration);
// Record metric
meter.histogramBuilder(spanName + ".duration")
.setUnit("ms")
.build()
.record(duration);
}
span.end();
}
}
}
// Usage with annotations
@Service
public class InventoryService {
@Traced(value = "updateInventory", tags = {"operation=update", "domain=inventory"})
public void updateInventory(OrderRequest request) {
// Business logic automatically traced
inventoryRepository.updateStock(request.getItems());
}
@Traced("checkAvailability")
public boolean checkAvailability(String productId, int quantity) {
return inventoryRepository.getStock(productId) >= quantity;
}
}

HTTP Instrumentation

Web Filter for HTTP Tracing

@Component
public class TracingFilter implements Filter {
private final Tracer tracer;
public TracingFilter(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Extract context from headers
Context extractedContext = extractContext(httpRequest);
Span span = tracer.spanBuilder(httpRequest.getMethod() + " " + httpRequest.getRequestURI())
.setParent(extractedContext)
.setSpanKind(SpanKind.SERVER)
.setAttribute("http.method", httpRequest.getMethod())
.setAttribute("http.url", httpRequest.getRequestURL().toString())
.setAttribute("http.route", extractRoute(httpRequest))
.setAttribute("http.user_agent", httpRequest.getHeader("User-Agent"))
.setAttribute("http.client_ip", getClientIp(httpRequest))
.startSpan();
try (Scope scope = span.makeCurrent()) {
chain.doFilter(request, response);
// Record response information
span.setAttribute("http.status_code", httpResponse.getStatus());
span.setStatus(getSpanStatus(httpResponse.getStatus()));
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
private Context extractContext(HttpServletRequest request) {
return W3CTraceContextPropagator.getInstance().extract(
Context.current(),
request,
(carrier, key) -> {
String value = carrier.getHeader(key);
return value != null ? Collections.singletonList(value) : Collections.emptyList();
});
}
private String extractRoute(HttpServletRequest request) {
String path = request.getRequestURI();
// Convert path parameters to placeholders
return path.replaceAll("/\\d+", "/{id}")
.replaceAll("/[a-f0-9-]{36}", "/{uuid}");
}
private String getClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddr();
}
private StatusCode getSpanStatus(int statusCode) {
if (statusCode >= 200 && statusCode < 400) {
return StatusCode.OK;
} else {
return StatusCode.ERROR;
}
}
}

HTTP Client Instrumentation

@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 {
Span span = tracer.spanBuilder("http.client")
.setSpanKind(SpanKind.CLIENT)
.setAttribute("http.method", request.getMethod().name())
.setAttribute("http.url", request.getURI().toString())
.startSpan();
// Inject tracing headers
W3CTraceContextPropagator.getInstance().inject(
Context.current().with(span),
request,
(carrier, key, value) -> carrier.getHeaders().set(key, value));
try (Scope scope = span.makeCurrent()) {
ClientHttpResponse response = execution.execute(request, body);
span.setAttribute("http.status_code", response.getRawStatusCode());
span.setStatus(getSpanStatus(response.getRawStatusCode()));
return response;
} catch (IOException e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
}

Database Tracing

JPA/Hibernate Integration

@Component
public class JpaTracingInterceptor implements EmptyInterceptor {
private final Tracer tracer;
public JpaTracingInterceptor(Tracer tracer) {
this.tracer = tracer;
}
@Override
public String onPrepareStatement(String sql) {
Span span = tracer.spanBuilder("database.query")
.setAttribute("db.system", "postgresql")
.setAttribute("db.statement", sql)
.setAttribute("db.operation", extractOperation(sql))
.startSpan();
// Store span in current context
Context.current().with(span);
return sql;
}
private String extractOperation(String sql) {
if (sql == null) return "unknown";
String lowerSql = sql.trim().toLowerCase();
if (lowerSql.startsWith("select")) return "select";
if (lowerSql.startsWith("insert")) return "insert";
if (lowerSql.startsWith("update")) return "update";
if (lowerSql.startsWith("delete")) return "delete";
return "other";
}
}
@Aspect
@Component
public class RepositoryTracingAspect {
private final Tracer tracer;
public RepositoryTracingAspect(Tracer tracer) {
this.tracer = tracer;
}
@Around("execution(* org.springframework.data.repository.Repository+.*(..))")
public Object traceRepositoryMethods(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String repositoryName = joinPoint.getTarget().getClass().getSimpleName();
Span span = tracer.spanBuilder(repositoryName + "." + methodName)
.setAttribute("db.operation", methodName)
.setAttribute("db.repository", repositoryName)
.startSpan();
try (Scope scope = span.makeCurrent()) {
Object result = joinPoint.proceed();
// Add result count if applicable
if (result instanceof Collection) {
span.setAttribute("db.result.count", ((Collection<?>) result).size());
}
return result;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
}

Custom Metrics and Business Observability

Business Metrics

@Service
public class OrderMetricsService {
private final Meter meter;
private final Counter ordersCreated;
private final Counter ordersCompleted;
private final Counter ordersFailed;
private final Histogram orderValue;
private final ObservableGauge inventoryLevel;
private final Map<String, Long> inventoryLevels = new ConcurrentHashMap<>();
public OrderMetricsService(Meter meter) {
this.meter = meter;
// Initialize counters
this.ordersCreated = meter.counterBuilder("orders.created")
.setDescription("Total orders created")
.build();
this.ordersCompleted = meter.counterBuilder("orders.completed")
.setDescription("Total orders completed successfully")
.build();
this.ordersFailed = meter.counterBuilder("orders.failed")
.setDescription("Total orders that failed")
.build();
this.orderValue = meter.histogramBuilder("orders.value")
.setDescription("Distribution of order values")
.setUnit("USD")
.build();
// Initialize gauge for inventory levels
this.inventoryLevel = meter.gaugeBuilder("inventory.level")
.setDescription("Current inventory levels by product")
.setUnit("items")
.buildWithCallback(measurement -> {
inventoryLevels.forEach((productId, level) -> {
measurement.record(level, 
Attributes.of(AttributeKey.stringKey("product.id"), productId));
});
});
}
public void recordOrderCreated(Order order) {
ordersCreated.add(1);
orderValue.record(order.getAmount());
// Record attributes
Attributes attributes = Attributes.of(
AttributeKey.stringKey("customer.tier"), order.getCustomerTier(),
AttributeKey.stringKey("order.type"), order.getType()
);
ordersCreated.add(1, attributes);
}
public void recordOrderCompleted(Order order) {
ordersCompleted.add(1);
}
public void recordOrderFailed(Order order, String reason) {
Attributes attributes = Attributes.of(
AttributeKey.stringKey("failure.reason"), reason
);
ordersFailed.add(1, attributes);
}
public void updateInventoryLevel(String productId, long level) {
inventoryLevels.put(productId, level);
}
}

Distributed Context Propagation

Context Propagation Service

@Service
public class ContextPropagationService {
public <T> T runWithContext(Callable<T> task, Map<String, String> additionalAttributes) {
Span currentSpan = Span.current();
// Add additional attributes to current span
if (additionalAttributes != null) {
additionalAttributes.forEach(currentSpan::setAttribute);
}
try {
return task.call();
} catch (Exception e) {
currentSpan.recordException(e);
throw new RuntimeException(e);
}
}
public Runnable wrapWithContext(Runnable task, Map<String, String> attributes) {
Context currentContext = Context.current();
return () -> {
try (Scope scope = currentContext.makeCurrent()) {
if (attributes != null) {
Span currentSpan = Span.current();
attributes.forEach(currentSpan::setAttribute);
}
task.run();
}
};
}
public Map<String, String> getCurrentContext() {
Map<String, String> context = new HashMap<>();
// Extract trace context
Span currentSpan = Span.current();
if (currentSpan != null) {
context.put("trace_id", currentSpan.getSpanContext().getTraceId());
context.put("span_id", currentSpan.getSpanContext().getSpanId());
}
return context;
}
}

Configuration Management

Environment-based Configuration

# application.yml
lightstep:
enabled: true
access-token: ${LIGHTSTEP_ACCESS_TOKEN:}
component-name: ${SERVICE_NAME:order-service}
collector-host: ${LIGHTSTEP_COLLECTOR_HOST:ingest.lightstep.com}
collector-port: ${LIGHTSTEP_COLLECTOR_PORT:443}
collector-protocol: https
sampling-rate: ${LIGHTSTEP_SAMPLING_RATE:1.0}
tags:
environment: ${ENVIRONMENT:development}
version: ${APP_VERSION:1.0.0}
region: ${AWS_REGION:us-east-1}
management:
endpoints:
web:
exposure:
include: metrics, prometheus
metrics:
export:
lightstep:
enabled: true

Programmatic Configuration

@Configuration
public class LightstepEnvironmentConfig {
@Bean
@Profile("!test")
public Options lightstepOptions() {
return new Options.OptionsBuilder()
.withAccessToken(getAccessToken())
.withComponentName(getComponentName())
.withCollectorHost(getCollectorHost())
.withCollectorPort(getCollectorPort())
.withCollectorProtocol("https")
.withTags(buildTags())
.withMaxBufferedSpans(2000)
.withMaxReportingIntervalMillis(5000)
.build();
}
private String getAccessToken() {
return Optional.ofNullable(System.getenv("LIGHTSTEP_ACCESS_TOKEN"))
.orElseThrow(() -> new IllegalStateException("LIGHTSTEP_ACCESS_TOKEN is required"));
}
private String getComponentName() {
return Optional.ofNullable(System.getenv("SERVICE_NAME"))
.orElse("unknown-service");
}
private String getCollectorHost() {
return Optional.ofNullable(System.getenv("LIGHTSTEP_COLLECTOR_HOST"))
.orElse("ingest.lightstep.com");
}
private int getCollectorPort() {
return Optional.ofNullable(System.getenv("LIGHTSTEP_COLLECTOR_PORT"))
.map(Integer::parseInt)
.orElse(443);
}
private Map<String, String> buildTags() {
Map<String, String> tags = new HashMap<>();
tags.put("environment", System.getenv().getOrDefault("ENVIRONMENT", "development"));
tags.put("version", System.getenv().getOrDefault("APP_VERSION", "1.0.0"));
tags.put("region", System.getenv().getOrDefault("AWS_REGION", "unknown"));
tags.put("hostname", getHostname());
return tags;
}
private String getHostname() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
return "unknown";
}
}
}

Testing Support

Test Configuration

@SpringBootTest
@TestPropertySource(properties = {
"lightstep.enabled=false",
"management.metrics.export.lightstep.enabled=false"
})
public abstract class BaseIntegrationTest {
@MockBean
protected Tracer tracer;
@MockBean
protected Meter meter;
@BeforeEach
void setupMocks() {
SpanContext spanContext = SpanContext.create(
"test-trace-id",
"test-span-id",
TraceFlags.getDefault(),
TraceState.getDefault());
Span mockSpan = mock(Span.class);
when(mockSpan.getSpanContext()).thenReturn(spanContext);
when(tracer.spanBuilder(anyString())).thenReturn(new MockSpanBuilder(mockSpan));
}
}
@Component
@Primary
@Profile("test")
public class MockTracerConfig {
@Bean
public Tracer mockTracer() {
return new NoopTracer();
}
@Bean
public Meter mockMeter() {
return new NoopMeter();
}
}
// Custom span builder for testing
public class MockSpanBuilder implements SpanBuilder {
private final Span span;
public MockSpanBuilder(Span span) {
this.span = span;
}
@Override
public SpanBuilder setParent(Context context) { return this; }
@Override
public SpanBuilder setNoParent() { return this; }
@Override
public SpanBuilder addLink(SpanContext spanContext) { return this; }
@Override
public SpanBuilder setAttribute(String key, String value) { return this; }
@Override
public SpanBuilder setAttribute(String key, long value) { return this; }
@Override
public SpanBuilder setAttribute(String key, double value) { return this; }
@Override
public SpanBuilder setAttribute(String key, boolean value) { return this; }
@Override
public SpanBuilder setSpanKind(SpanKind spanKind) { return this; }
@Override
public SpanBuilder setStartTimestamp(long startTimestamp) { return this; }
@Override
public Span startSpan() { return span; }
}

Error Handling and Resilience

Tracing Error Handler

@Component
public class TracingErrorHandler {
private static final Logger logger = LoggerFactory.getLogger(TracingErrorHandler.class);
public void handleTracingError(String operation, Throwable error) {
Span currentSpan = Span.current();
if (currentSpan != null) {
currentSpan.recordException(error);
currentSpan.setAttribute("error.type", error.getClass().getSimpleName());
currentSpan.setAttribute("error.message", error.getMessage());
currentSpan.setStatus(StatusCode.ERROR, operation + " failed");
}
logger.error("Tracing operation failed: {}", operation, error);
}
public <T> Optional<T> executeWithTracing(String operation, Supplier<T> supplier) {
Span span = Span.current();
if (span != null) {
span.setAttribute("operation", operation);
}
try {
T result = supplier.get();
if (span != null) {
span.setAttribute("operation.success", true);
}
return Optional.of(result);
} catch (Exception e) {
handleTracingError(operation, e);
return Optional.empty();
}
}
}

Conclusion

This Lightstep Java SDK implementation provides:

  1. Comprehensive Tracing - Automatic and manual instrumentation for applications
  2. OpenTelemetry Integration - Standards-based tracing with OpenTelemetry
  3. Spring Boot Support - Auto-configuration and seamless Spring integration
  4. HTTP Instrumentation - Automatic tracing for web requests and REST clients
  5. Database Tracing - JPA/Hibernate and repository-level tracing
  6. Business Metrics - Custom metrics for business observability
  7. Context Propagation - Distributed context across service boundaries
  8. Testing Support - Mock implementations for testing environments

The implementation enables full observability for Java applications with minimal code changes while providing powerful insights into application performance and behavior.

Leave a Reply

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


Macro Nepal Helper