Honeycomb.io Beeline for Java: Complete Guide

Introduction to Honeycomb Beeline

Honeycomb Beeline is a lightweight instrumentation library that makes it easy to collect and send observability data to Honeycomb.io. It provides automatic instrumentation for common Java frameworks and simplifies adding custom telemetry to your applications.


System Architecture Overview

Honeycomb Beeline Pipeline
├── Automatic Instrumentation
│   ├ - HTTP Servers (Servlet, Spring MVC)
│   ├ - HTTP Clients (OkHttp, Apache HttpClient)
│   ├ - Database Calls (JDBC)
│   ├ - Messaging (JMS, Kafka)
│   └ - Async Processing
├── Custom Instrumentation
│   ├ - Business Metrics
│   ├ - Custom Spans
│   ├ - Trace Context Propagation
│   └ - Log Correlation
├── Data Enrichment
│   ├ - Global Fields
│   ├ - Dynamic Sampling
│   └ - Custom Processors
└── Honeycomb Backend
├ - Trace Visualization
├ - Query Interface
├ - Trigger Alerts
└ - Data Analysis

Core Implementation

1. Maven Dependencies

<properties>
<honeycomb.beeline.version>1.4.0</honeycomb.beeline.version>
<opentelemetry.version>1.28.0</opentelemetry.version>
<spring.boot.version>2.7.0</spring.boot.version>
</properties>
<dependencies>
<!-- Honeycomb Beeline -->
<dependency>
<groupId>io.honeycomb.beeline</groupId>
<artifactId>beeline</artifactId>
<version>${honeycomb.beeline.version}</version>
</dependency>
<!-- Beeline Auto-Instrumentation -->
<dependency>
<groupId>io.honeycomb.beeline</groupId>
<artifactId>beeline-spring-boot-starter</artifactId>
<version>${honeycomb.beeline.version}</version>
</dependency>
<dependency>
<groupId>io.honeycomb.beeline</groupId>
<artifactId>beeline-servlet</artifactId>
<version>${honeycomb.beeline.version}</version>
</dependency>
<!-- OpenTelemetry (Beeline uses OTel under the hood) -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>${opentelemetry.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
</dependencies>

2. Configuration Setup

import io.honeycomb.beeline.autoconfigure.BeelineAutoConfiguration;
import io.honeycomb.beeline.tracing.Beeline;
import io.honeycomb.beeline.tracing.Span;
import io.honeycomb.beeline.tracing.Tracer;
import io.honeycomb.beeline.tracing.Tracing;
import io.honeycomb.beeline.tracing.propagation.Propagation;
import io.honeycomb.beeline.tracing.sampling.Sampling;
import io.honeycomb.libhoney.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class HoneycombConfig {
@Value("${honeycomb.api.key:}")
private String honeycombApiKey;
@Value("${honeycomb.dataset:my-java-service}")
private String honeycombDataset;
@Value("${honeycomb.service.name:order-service}")
private String serviceName;
@Bean
public Beeline beeline() {
// Configure Honeycomb client
final HoneyClient honeyClient = LibHoney.create(
Options.builder()
.setWriteKey(honeycombApiKey)
.setDataset(honeycombDataset)
.setApiHost("https://api.honeycomb.io") // or your Honeycomb endpoint
.build()
);
// Configure Beeline with global fields
final Beeline beeline = new Beeline(
honeyClient,
Tracing.createTracer(
Propagation.createFactory(),
Sampling.alwaysSampler(),
serviceName
)
);
// Add global fields to all events
final Map<String, Object> globalFields = new HashMap<>();
globalFields.put("environment", System.getenv().getOrDefault("ENVIRONMENT", "development"));
globalFields.put("service.version", System.getenv().getOrDefault("SERVICE_VERSION", "1.0.0"));
globalFields.put("deployment.region", System.getenv().getOrDefault("DEPLOYMENT_REGION", "us-east-1"));
honeyClient.addGlobalFields(globalFields);
return beeline;
}
@Bean
public Tracer tracer(Beeline beeline) {
return beeline.getTracer();
}
}

3. Application Properties

# application.yml
honeycomb:
api-key: ${HONEYCOMB_API_KEY:your-api-key-here}
dataset: ${HONEYCOMB_DATASET:my-java-service}
service:
name: ${SERVICE_NAME:order-service}
# Beeline Configuration
beeline:
enabled: true
# Sampling configuration
sampler:
enabled: true
sample-rate: 1 # 100% sampling in development
# HTTP instrumentation
http:
include-request-headers: "User-Agent,Content-Type,Accept"
include-response-headers: "Content-Type,Content-Length"
# SQL instrumentation  
sql:
enabled: true
include-query: true
# Logging correlation
logging:
pattern:
level: "%5p [trace_id=%X{trace_id},span_id=%X{span_id}]"
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus

4. Spring Boot Auto-Instrumentation

import io.honeycomb.beeline.autoconfigure.BeelineProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableConfigurationProperties(BeelineProperties.class)
public class BeelineAutoConfig implements WebMvcConfigurer {
private final Beeline beeline;
public BeelineAutoConfig(Beeline beeline) {
this.beeline = beeline;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Beeline automatically adds interceptors for Spring MVC
}
@Bean
public BeelineHttpServletFilter beelineHttpServletFilter() {
return new BeelineHttpServletFilter(beeline);
}
}

5. Custom Instrumentation Service

import io.honeycomb.beeline.tracing.Beeline;
import io.honeycomb.beeline.tracing.Span;
import io.honeycomb.beeline.tracing.Tracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Service
public class HoneycombInstrumentationService {
private static final Logger LOGGER = LoggerFactory.getLogger(HoneycombInstrumentationService.class);
private final Beeline beeline;
private final Tracer tracer;
public HoneycombInstrumentationService(Beeline beeline) {
this.beeline = beeline;
this.tracer = beeline.getTracer();
}
/**
* Start a custom span for business operations
*/
public Span startBusinessSpan(String operationName) {
return tracer.startSpan(operationName)
.addField("span.type", "business_operation")
.addField("service.name", "order-service");
}
/**
* Instrument a runnable operation with tracing
*/
public void instrumentOperation(String operationName, Runnable operation) {
Span span = startBusinessSpan(operationName);
try {
operation.run();
span.addField("operation.success", true);
} catch (Exception e) {
span.addField("operation.success", false);
span.addField("error.message", e.getMessage());
throw e;
} finally {
span.close();
}
}
/**
* Instrument a callable operation with tracing
*/
public <T> T instrumentOperation(String operationName, Callable<T> operation) {
Span span = startBusinessSpan(operationName);
try {
T result = operation.call();
span.addField("operation.success", true);
span.addField("result.type", result.getClass().getSimpleName());
return result;
} catch (Exception e) {
span.addField("operation.success", false);
span.addField("error.message", e.getMessage());
throw new RuntimeException(e);
} finally {
span.close();
}
}
/**
* Add custom fields to current span
*/
public void addField(String key, Object value) {
Span currentSpan = tracer.getActiveSpan();
if (currentSpan != null) {
currentSpan.addField(key, value);
}
}
/**
* Record business metrics
*/
public void recordBusinessMetric(String metricName, double value, Map<String, Object> dimensions) {
Span span = tracer.startSpan("business.metric")
.addField("metric.name", metricName)
.addField("metric.value", value)
.addField("metric.type", "gauge");
if (dimensions != null) {
dimensions.forEach(span::addField);
}
span.close();
}
/**
* Record timing information
*/
public void recordTiming(String operation, long durationMs) {
Span span = tracer.startSpan("timing.measurement")
.addField("operation", operation)
.addField("duration_ms", durationMs)
.addField("measurement.type", "timing");
span.close();
}
/**
* Async operation instrumentation
*/
public <T> CompletableFuture<T> instrumentAsyncOperation(
String operationName, 
Supplier<CompletableFuture<T>> asyncOperation) {
// Capture current trace context
Map<String, String> traceContext = tracer.getActiveSpan().getTraceContext();
Span parentSpan = tracer.startSpan(operationName + ".async")
.addField("operation.type", "async")
.addField("async.trigger", "true");
return asyncOperation.get()
.whenComplete((result, throwable) -> {
// Restore trace context in async thread
try (Span childSpan = tracer.startChildSpan(parentSpan, operationName + ".completion")) {
childSpan.addField("async.completed", true);
if (throwable != null) {
childSpan.addField("async.success", false);
childSpan.addField("error.message", throwable.getMessage());
} else {
childSpan.addField("async.success", true);
if (result != null) {
childSpan.addField("result.present", true);
}
}
}
parentSpan.close();
});
}
/**
* Database query instrumentation
*/
public void instrumentQuery(String queryType, String table, Runnable query) {
Span span = tracer.startSpan("database.query")
.addField("db.system", "postgresql")
.addField("db.operation", queryType)
.addField("db.collection", table)
.addField("span.type", "database");
long startTime = System.currentTimeMillis();
try {
query.run();
long duration = System.currentTimeMillis() - startTime;
span.addField("db.duration_ms", duration);
span.addField("db.success", true);
} catch (Exception e) {
span.addField("db.success", false);
span.addField("db.error", e.getMessage());
throw e;
} finally {
span.close();
}
}
/**
* External service call instrumentation
*/
public <T> T instrumentExternalCall(String serviceName, String endpoint, Supplier<T> call) {
Span span = tracer.startSpan("external.service.call")
.addField("service.name", serviceName)
.addField("service.endpoint", endpoint)
.addField("span.type", "http_client");
long startTime = System.currentTimeMillis();
try {
T result = call.get();
long duration = System.currentTimeMillis() - startTime;
span.addField("http.duration_ms", duration);
span.addField("http.success", true);
span.addField("http.status_code", 200); // Assuming success
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
span.addField("http.duration_ms", duration);
span.addField("http.success", false);
span.addField("http.error", e.getMessage());
throw e;
} finally {
span.close();
}
}
/**
* Add user context to traces
*/
public void addUserContext(String userId, String userRole) {
Span currentSpan = tracer.getActiveSpan();
if (currentSpan != null) {
currentSpan.addField("user.id", userId);
currentSpan.addField("user.role", userRole);
currentSpan.addField("user.authenticated", true);
}
}
/**
* Add request context to traces
*/
public void addRequestContext(String requestId, String clientIp, String userAgent) {
Span currentSpan = tracer.getActiveSpan();
if (currentSpan != null) {
currentSpan.addField("request.id", requestId);
currentSpan.addField("client.ip", clientIp);
currentSpan.addField("user_agent", userAgent);
}
}
}

6. Instrumented REST Controller

import io.honeycomb.beeline.tracing.Span;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);
private final OrderService orderService;
private final PaymentService paymentService;
private final HoneycombInstrumentationService instrumentation;
public OrderController(OrderService orderService, 
PaymentService paymentService,
HoneycombInstrumentationService instrumentation) {
this.orderService = orderService;
this.paymentService = paymentService;
this.instrumentation = instrumentation;
}
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
LOGGER.info("Creating order for customer: {}", request.getCustomerId());
// Add user context to trace
instrumentation.addUserContext(request.getCustomerId(), "customer");
instrumentation.addField("order.amount", request.getAmount());
instrumentation.addField("order.items.count", request.getItems().size());
return instrumentation.instrumentOperation("order.creation", () -> {
Order order = orderService.createOrder(request);
// Instrument payment processing
instrumentation.instrumentExternalCall("payment-service", "/api/payments", 
() -> paymentService.processPayment(order));
LOGGER.info("Order created successfully: {}", order.getId());
return ResponseEntity.ok(order);
});
}
@GetMapping("/{orderId}")
public ResponseEntity<Order> getOrder(@PathVariable String orderId) {
LOGGER.debug("Fetching order: {}", orderId);
instrumentation.addField("order.id", orderId);
return instrumentation.instrumentOperation("order.retrieval", () -> {
Order order = orderService.getOrder(orderId);
if (order == null) {
instrumentation.addField("order.found", false);
return ResponseEntity.notFound().build();
}
instrumentation.addField("order.found", true);
instrumentation.addField("order.status", order.getStatus());
return ResponseEntity.ok(order);
});
}
@GetMapping("/customer/{customerId}")
public ResponseEntity<List<Order>> getCustomerOrders(@PathVariable String customerId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
instrumentation.addUserContext(customerId, "customer");
instrumentation.addField("pagination.page", page);
instrumentation.addField("pagination.size", size);
return instrumentation.instrumentOperation("customer.orders.retrieval", () -> {
List<Order> orders = orderService.getOrdersByCustomer(customerId, page, size);
instrumentation.addField("orders.count", orders.size());
instrumentation.recordBusinessMetric("customer.orders.count", orders.size(), 
Map.of("customer.id", customerId));
return ResponseEntity.ok(orders);
});
}
@PostMapping("/{orderId}/cancel")
public ResponseEntity<Order> cancelOrder(@PathVariable String orderId) {
LOGGER.info("Cancelling order: {}", orderId);
instrumentation.addField("order.id", orderId);
return instrumentation.instrumentOperation("order.cancellation", () -> {
Order order = orderService.cancelOrder(orderId);
// Async notification
instrumentation.instrumentAsyncOperation("order.cancellation.notification",
() -> orderService.notifyCancellationAsync(order));
instrumentation.recordBusinessMetric("order.cancelled", 1, 
Map.of("order.id", orderId, "reason", "user_requested"));
return ResponseEntity.ok(order);
});
}
@GetMapping("/{orderId}/analytics")
public CompletableFuture<ResponseEntity<OrderAnalytics>> getOrderAnalytics(@PathVariable String orderId) {
LOGGER.debug("Fetching analytics for order: {}", orderId);
instrumentation.addField("order.id", orderId);
return instrumentation.instrumentAsyncOperation("order.analytics.computation",
() -> orderService.computeOrderAnalyticsAsync(orderId))
.thenApply(ResponseEntity::ok);
}
@PutMapping("/{orderId}/status")
public ResponseEntity<Order> updateOrderStatus(@PathVariable String orderId,
@RequestBody StatusUpdate update) {
LOGGER.info("Updating order status: {} -> {}", orderId, update.getStatus());
instrumentation.addField("order.id", orderId);
instrumentation.addField("status.old", update.getPreviousStatus());
instrumentation.addField("status.new", update.getNewStatus());
return instrumentation.instrumentOperation("order.status.update", () -> {
long startTime = System.currentTimeMillis();
Order order = orderService.updateOrderStatus(orderId, update);
long duration = System.currentTimeMillis() - startTime;
instrumentation.recordTiming("order.status.update", duration);
// Record status change metric
instrumentation.recordBusinessMetric("order.status.change", 1,
Map.of("from_status", update.getPreviousStatus(),
"to_status", update.getNewStatus(),
"order.id", orderId));
return ResponseEntity.ok(order);
});
}
}

7. Database Instrumentation

import org.springframework.stereotype.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.List;
@Repository
public class InstrumentedOrderRepository {
private final EntityManager entityManager;
private final HoneycombInstrumentationService instrumentation;
public InstrumentedOrderRepository(EntityManager entityManager,
HoneycombInstrumentationService instrumentation) {
this.entityManager = entityManager;
this.instrumentation = instrumentation;
}
public Order findById(String orderId) {
return instrumentation.instrumentOperation("database.order.find_by_id", () -> {
instrumentation.instrumentQuery("SELECT", "orders", 
() -> LOGGER.debug("Executing find by ID query for order: {}", orderId));
return entityManager.find(Order.class, orderId);
});
}
public List<Order> findByCustomerId(String customerId, int page, int size) {
return instrumentation.instrumentOperation("database.order.find_by_customer", () -> {
instrumentation.instrumentQuery("SELECT", "orders", 
() -> LOGGER.debug("Finding orders for customer: {}", customerId));
Query query = entityManager.createQuery(
"SELECT o FROM Order o WHERE o.customerId = :customerId ORDER BY o.createdAt DESC");
query.setParameter("customerId", customerId);
query.setFirstResult(page * size);
query.setMaxResults(size);
return query.getResultList();
});
}
public Order save(Order order) {
return instrumentation.instrumentOperation("database.order.save", () -> {
String operation = order.getId() == null ? "INSERT" : "UPDATE";
instrumentation.instrumentQuery(operation, "orders", 
() -> LOGGER.debug("Saving order: {}", order.getId()));
if (order.getId() == null) {
entityManager.persist(order);
} else {
order = entityManager.merge(order);
}
return order;
});
}
public int updateOrderStatus(String orderId, String status) {
return instrumentation.instrumentOperation("database.order.update_status", () -> {
instrumentation.instrumentQuery("UPDATE", "orders", 
() -> LOGGER.debug("Updating order status: {} -> {}", orderId, status));
Query query = entityManager.createQuery(
"UPDATE Order o SET o.status = :status WHERE o.id = :orderId");
query.setParameter("status", status);
query.setParameter("orderId", orderId);
return query.executeUpdate();
});
}
}

8. HTTP Client Instrumentation

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Service
public class InstrumentedHttpClient {
private final RestTemplate restTemplate;
private final HoneycombInstrumentationService instrumentation;
public InstrumentedHttpClient(RestTemplate restTemplate,
HoneycombInstrumentationService instrumentation) {
this.restTemplate = restTemplate;
this.instrumentation = instrumentation;
}
public <T> ResponseEntity<T> executeWithTracing(String serviceName, 
String url, 
HttpMethod method,
Object requestBody,
Class<T> responseType,
Map<String, String> headers) {
return instrumentation.instrumentExternalCall(serviceName, url, () -> {
HttpHeaders httpHeaders = new HttpHeaders();
if (headers != null) {
headers.forEach(httpHeaders::add);
}
HttpEntity<Object> entity = new HttpEntity<>(requestBody, httpHeaders);
return restTemplate.exchange(url, method, entity, responseType);
});
}
public <T> T getWithTracing(String serviceName, String url, Class<T> responseType) {
return instrumentation.instrumentExternalCall(serviceName, url, 
() -> restTemplate.getForObject(url, responseType));
}
public <T> T postWithTracing(String serviceName, String url, Object request, Class<T> responseType) {
return instrumentation.instrumentExternalCall(serviceName, url,
() -> restTemplate.postForObject(url, request, responseType));
}
}

9. Custom Sampling Strategies

import io.honeycomb.beeline.tracing.sampling.Sampling;
import org.springframework.stereotype.Component;
import java.util.function.Function;
@Component
public class CustomSamplingStrategies {
/**
* Sample important business operations more frequently
*/
public static Function<String, Boolean> businessOperationSampler() {
return (dataset) -> {
// Sample all business operations
if (dataset.contains("business") || dataset.contains("order")) {
return Sampling.alwaysSampler().sample(dataset);
}
// Sample 10% of other operations
return Math.random() < 0.1;
};
}
/**
* Dynamic sampling based on error rates
*/
public Function<String, Boolean> adaptiveSampler() {
return (dataset) -> {
// Increase sampling for error scenarios
if (isHighErrorRate(dataset)) {
return true; // Sample all events during high error rates
}
return Sampling.alwaysSampler().sample(dataset);
};
}
/**
* User-based sampling - sample more for important customers
*/
public Function<String, Boolean> userAwareSampler(String userId) {
return (dataset) -> {
if (isImportantCustomer(userId)) {
return true; // Sample all events for important customers
}
// Sample 20% for regular customers
return Math.random() < 0.2;
};
}
private boolean isHighErrorRate(String dataset) {
// Implement error rate monitoring logic
return false;
}
private boolean isImportantCustomer(String userId) {
// Implement customer importance logic
return userId.startsWith("VIP_") || userId.endsWith("@enterprise.com");
}
}

10. Log Correlation Setup

import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class LogCorrelationFilter implements Filter {
private final Beeline beeline;
public LogCorrelationFilter(Beeline beeline) {
this.beeline = beeline;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Extract trace context and add to MDC for logging
Span activeSpan = beeline.getTracer().getActiveSpan();
if (activeSpan != null) {
Map<String, String> traceContext = activeSpan.getTraceContext();
MDC.put("trace_id", traceContext.get("trace_id"));
MDC.put("span_id", traceContext.get("span_id"));
}
try {
chain.doFilter(request, response);
} finally {
// Clear MDC after request
MDC.clear();
}
}
}

11. Testing Instrumentation

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@SpringBootTest
class HoneycombInstrumentationTest {
@Autowired
private HoneycombInstrumentationService instrumentation;
@MockBean
private Beeline beeline;
@MockBean
private Tracer tracer;
@MockBean
private Span span;
@Test
void testBusinessOperationInstrumentation() {
when(tracer.startSpan(anyString())).thenReturn(span);
when(span.addField(anyString(), any())).thenReturn(span);
instrumentation.instrumentOperation("test.operation", () -> {
// Test operation logic
return "result";
});
verify(span).addField("span.type", "business_operation");
verify(span).addField("operation.success", true);
verify(span).close();
}
@Test
void testExternalCallInstrumentation() {
when(tracer.startSpan(anyString())).thenReturn(span);
when(span.addField(anyString(), any())).thenReturn(span);
String result = instrumentation.instrumentExternalCall("test-service", "/api/test", 
() -> "response");
verify(span).addField("service.name", "test-service");
verify(span).addField("service.endpoint", "/api/test");
verify(span).addField("http.success", true);
}
}

12. Monitoring and Health Checks

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class HoneycombHealthIndicator implements HealthIndicator {
private final Beeline beeline;
public HoneycombHealthIndicator(Beeline beeline) {
this.beeline = beeline;
}
@Override
public Health health() {
try {
// Check if Beeline is properly initialized
if (beeline.getTracer() != null) {
return Health.up()
.withDetail("service", "honeycomb-beeline")
.withDetail("status", "connected")
.withDetail("tracer", beeline.getTracer().getClass().getSimpleName())
.build();
} else {
return Health.down()
.withDetail("service", "honeycomb-beeline")
.withDetail("error", "Tracer not initialized")
.build();
}
} catch (Exception e) {
return Health.down(e)
.withDetail("service", "honeycomb-beeline")
.withDetail("error", "Health check failed")
.build();
}
}
}

Best Practices

1. Field Naming Conventions

public class FieldNamingConventions {
// Use consistent field naming
public static final String SERVICE_NAME = "service.name";
public static final String HTTP_STATUS_CODE = "http.status_code";
public static final String DB_OPERATION = "db.operation";
public static final String ERROR_MESSAGE = "error.message";
// Use dots for hierarchy
public static final String METRIC_BUSINESS_ORDER_COUNT = "metric.business.order.count";
public static final String TIMING_DATABASE_QUERY = "timing.database.query";
}

2. Sensitive Data Handling

@Component
public class DataSanitization {
public Map<String, Object> sanitizeFields(Map<String, Object> fields) {
Map<String, Object> sanitized = new HashMap<>(fields);
// Remove sensitive fields
sanitized.remove("password");
sanitized.remove("credit_card");
sanitized.remove("authorization");
// Hash personal identifiers
if (sanitized.containsKey("user_id")) {
String userId = (String) sanitized.get("user_id");
sanitized.put("user_id", hashUserId(userId));
}
return sanitized;
}
private String hashUserId(String userId) {
return "user_" + Integer.toHexString(userId.hashCode());
}
}

3. Performance Optimization

@Component
public class PerformanceOptimizations {
// Use lazy field evaluation for expensive operations
public void addLazyField(Span span, String fieldName, Supplier<Object> valueSupplier) {
if (span != null && isSampled(span)) {
Object value = valueSupplier.get();
if (value != null) {
span.addField(fieldName, value);
}
}
}
private boolean isSampled(Span span) {
// Check if span is sampled before computing expensive fields
return true; // Implementation depends on your sampling strategy
}
}

Conclusion

This comprehensive Honeycomb Beeline implementation provides:

  • Automatic instrumentation for Spring Boot applications
  • Custom business metrics and tracing
  • Database call instrumentation with timing
  • HTTP client instrumentation for external calls
  • Async operation support with context propagation
  • Custom sampling strategies for cost optimization
  • Log correlation for unified debugging
  • Health monitoring and testing support

Key benefits:

  • Rich observability with detailed traces and metrics
  • Easy integration with Spring Boot auto-configuration
  • Performance optimized with sampling and lazy evaluation
  • Developer friendly with simple instrumentation patterns
  • Production ready with health checks and monitoring

This setup enables comprehensive observability of your Java applications, providing deep insights into performance, errors, and business metrics through Honeycomb.io's powerful analysis platform.

Leave a Reply

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


Macro Nepal Helper