google-site-verification: google61fe8ba583a51912.html
APM Tool Integration in Java: Mastering New Relic and Datadog

Application Performance Monitoring (APM) tools are essential for modern Java applications, providing deep insights into performance, errors, dependencies, and business metrics. This article explores integrating two leading APM solutions—New Relic and Datadog—into Java applications, covering installation, configuration, custom instrumentation, and best practices.


APM Integration Overview

Why APM Integration?

  • Performance Monitoring: Track response times, throughput, and resource usage
  • Error Tracking: Identify and diagnose exceptions and errors
  • Dependency Monitoring: Database queries, external HTTP calls, message queues
  • Business Metrics: Track custom business transactions and KPIs
  • Distributed Tracing: End-to-end request tracing across microservices

New Relic Integration

1. Automatic Installation

1.1 Using the Java Agent (Recommended)

Download the New Relic agent:

wget https://download.newrelic.com/newrelic/java-agent/newrelic-agent/current/newrelic-java.zip unzip newrelic-java.zip

1.2 Configuration File (newrelic.yml)

common: &default_settings # License key license_key: YOUR_LICENSE_KEY_HERE # Application name app_name: My Java Application # Logging configuration log_level: info log_file_path: logs/ # Security settings security: enabled: false # Transaction tracing transaction_tracer: enabled: true transaction_threshold: apdex_f record_sql: obfuscated stack_trace_threshold: 0.5 # Error collection error_collector: enabled: true ignore_errors: "" # Browser monitoring browser_monitoring: auto_instrument: true # Environment-specific settings development: <<: *default_settings app_name: My App (Development) production: <<: *default_settings app_name: My App (Production) log_level: error

1.3 JVM Integration

java -javaagent:/path/to/newrelic/newrelic.jar \ -Dnewrelic.config.file=/path/to/newrelic/newrelic.yml \ -Dnewrelic.environment=production \ -jar myapp.jar

1.4 Programmatic Installation (Spring Boot)

@SpringBootApplication public class MyApplication { public static void main(String[] args) { // Set system properties before Spring Boot starts System.setProperty("newrelic.config.file", "/path/to/newrelic.yml"); System.setProperty("newrelic.environment", "production"); SpringApplication.run(MyApplication.class, args); } }

2. Custom Instrumentation

2.1 Using Annotations

import com.newrelic.api.agent.*; @Service public class OrderService { @Trace(dispatcher = true) public Order createOrder(OrderRequest request) { // This method will be tracked as a transaction validateOrder(request); processPayment(request); return saveOrder(request); } @Trace(metricName = "Order/Validate") private void validateOrder(OrderRequest request) { // Custom segment NewRelic.addCustomParameter("order.amount", request.getAmount()); NewRelic.addCustomParameter("order.currency", request.getCurrency()); if (request.getAmount() <= 0) { NewRelic.noticeError("Invalid order amount"); throw new IllegalArgumentException("Amount must be positive"); } } @Trace(metricName = "Payment/Process") private void processPayment(OrderRequest request) { try { // Business logic NewRelic.recordMetric("Custom/Payment/Processed", 1); } catch (PaymentException e) { NewRelic.noticeError(e, false); throw e; } } }

2.2 Manual Transaction Tracing

import com.newrelic.api.agent.*; @Service public class ManualTracingService { public void complexOperation() { Transaction transaction = NewRelic.getAgent().getTransaction(); // Start a segment Segment segment = transaction.startSegment("ComplexCalculation"); try { // Perform complex operation performCalculation(); // Record custom metrics NewRelic.recordMetric("Custom/ComplexOperation", 1); NewRelic.incrementCounter("Custom/Operations/Completed"); } finally { segment.end(); } } @Trace private void performCalculation() { // This will be part of the transaction trace NewRelic.addCustomParameter("calculation.timestamp", System.currentTimeMillis()); } }

2.3 Custom HTTP Client Instrumentation

import com.newrelic.api.agent.*; @Component public class ExternalServiceClient { @Trace public String callExternalService(String url) { HttpHeaders headers = new HttpHeaders(); // Add distributed tracing headers Transaction transaction = NewRelic.getAgent().getTransaction(); transaction.insertDistributedTraceHeaders(new Headers() { @Override public HeaderType getHeaderType() { return HeaderType.HTTP; } @Override public void setHeader(String name, String value) { headers.add(name, value); } }); // Make HTTP request ResponseEntity<String> response = restTemplate.exchange( url, HttpMethod.GET, new HttpEntity<>(headers), String.class); // Record external call metrics NewRelic.recordResponseTimeMetric("External/Service/" + url, responseTime); return response.getBody(); } }

Datadog Integration

1. Automatic Installation

1.1 Using the Java Agent

Download the Datadog agent:

DD_AGENT_MAJOR_VERSION=7 bash -c "$(curl -L https://s3.amazonaws.com/dd-agent/scripts/install_script.sh)"

1.2 JVM Integration

java -javaagent:/path/to/dd-java-agent.jar \ -Ddd.service=my-java-app \ -Ddd.env=production \ -Ddd.version=1.0.0 \ -Ddd.logs.injection=true \ -jar myapp.jar

1.3 Docker Integration

FROM openjdk:11-jre-slim # Copy Datadog agent COPY dd-java-agent.jar /app/dd-java-agent.jar # Application JAR COPY myapp.jar /app/myapp.jar # Run with Datadog agent CMD ["java", "-javaagent:/app/dd-java-agent.jar", \ "-Ddd.service=my-java-app", \ "-Ddd.env=production", \ "-jar", "/app/myapp.jar"]

2. Configuration

2.1 System Properties

// application.properties or system properties public class DatadogConfig { static { System.setProperty("dd.service", "order-service"); System.setProperty("dd.env", "production"); System.setProperty("dd.version", "1.2.3"); System.setProperty("dd.trace.analytics.enabled", "true"); System.setProperty("dd.trace.methods", "com.example.OrderService[*]"); System.setProperty("dd.trace.db.client.split-by-instance", "true"); System.setProperty("dd.http.server.tag.query-string", "true"); } }

2.2 Environment Variables

export DD_SERVICE=order-service export DD_ENV=production export DD_VERSION=1.0.0 export DD_LOGS_INJECTION=true export DD_TRACE_ANALYTICS_ENABLED=true

3. Custom Instrumentation

3.1 Using Datadog Annotations

import datadog.trace.api.Trace; import datadog.trace.api.Config; @Service public class InventoryService { @Trace(operationName = "inventory.check", resourceName = "InventoryService.checkStock") public boolean checkStock(String productId, int quantity) { // Add custom tags datadog.trace.api.GlobalTracer.get() .activeSpan() .setTag("product.id", productId) .setTag("requested.quantity", quantity); try { boolean available = databaseCheck(productId, quantity); // Record custom metric if (available) { datadog.trace.api.GlobalTracer.get() .activeSpan() .setMetric("inventory.available", 1); } return available; } catch (Exception e) { // Mark span as error datadog.trace.api.GlobalTracer.get() .activeSpan() .setError(true); throw e; } } @Trace(operationName = "database.query", resourceName = "InventoryService.databaseCheck") private boolean databaseCheck(String productId, int quantity) { // Database operation will be automatically traced return inventoryRepository.hasStock(productId, quantity); } }

3.2 Manual Span Creation

import datadog.trace.api.DDTags; import io.opentracing.Span; import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; @Service public class ManualTracingService { private final Tracer tracer = GlobalTracer.get(); public void processBatch(List<Item> items) { Span span = tracer.buildSpan("batch.process") .withTag("batch.size", items.size()) .withTag("processing.type", "async") .start(); try (Scope scope = tracer.activateSpan(span)) { // Business logic for (Item item : items) { processItem(item, span); } span.setTag(DDTags.RESOURCE_NAME, "Processed " + items.size() + " items"); } catch (Exception e) { span.setTag(DDTags.ERROR, true); span.log(Map.of("event", "error", "error.object", e)); throw e; } finally { span.finish(); } } private void processItem(Item item, Span parentSpan) { Span span = tracer.buildSpan("item.process") .asChildOf(parentSpan) .withTag("item.id", item.getId()) .withTag("item.type", item.getType()) .start(); try (Scope scope = tracer.activateSpan(span)) { // Process individual item itemProcessor.process(item); } catch (Exception e) { span.setTag(DDTags.ERROR, true); span.log(Map.of("event", "error", "error.object", e)); } finally { span.finish(); } } }

3.3 Custom HTTP Client Instrumentation

import io.opentracing.contrib.spring.web.client.TracingRestTemplateInterceptor; @Configuration public class TracingConfig { @Bean public RestTemplate tracingRestTemplate() { RestTemplate restTemplate = new RestTemplate(); // Add tracing interceptor restTemplate.getInterceptors().add(new TracingRestTemplateInterceptor()); return restTemplate; } } @Service public class ExternalAPIClient { private final RestTemplate restTemplate; public ExternalAPIClient(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Trace public ApiResponse callExternalAPI(String url) { Tracer tracer = GlobalTracer.get(); Span span = tracer.activeSpan(); if (span != null) { span.setTag("external.api.url", url); span.setTag("http.method", "GET"); } try { ResponseEntity<ApiResponse> response = restTemplate.getForEntity( url, ApiResponse.class); if (span != null) { span.setTag("http.status_code", response.getStatusCodeValue()); } return response.getBody(); } catch (HttpStatusCodeException e) { if (span != null) { span.setTag(DDTags.ERROR, true); span.setTag("http.status_code", e.getRawStatusCode()); } throw e; } } }

Spring Boot Auto-Configuration

1. New Relic Spring Boot Starter

Maven Dependency:

<dependency> <groupId>com.newrelic.agent.java</groupId> <artifactId>newrelic-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency>

Configuration:

# application.yml newrelic: config: license-key: ${NEW_RELIC_LICENSE_KEY} app-name: My Spring Boot App log-level: INFO security: enabled: true

2. Datadog Spring Boot Starter

Maven Dependency:

<dependency> <groupId>com.datadoghq</groupId> <artifactId>dd-trace-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency>

Configuration:

# application.yml dd: service: order-service env: production version: 1.0.0 trace: enabled: true analytics: enabled: true logs: injection: true

Custom Metrics and Business Monitoring

1. New Relic Custom Metrics

import com.newrelic.api.agent.*; @Component public class BusinessMetricsService { public void recordOrderMetrics(Order order) { // Count-based metric NewRelic.incrementCounter("Order/Completed"); // Value-based metric NewRelic.recordMetric("Order/Amount", order.getAmount()); NewRelic.recordMetric("Order/Items", order.getItemCount()); // Custom event Map<String, Object> orderEvent = new HashMap<>(); orderEvent.put("orderId", order.getId()); orderEvent.put("customerId", order.getCustomerId()); orderEvent.put("amount", order.getAmount()); orderEvent.put("currency", order.getCurrency()); orderEvent.put("timestamp", order.getTimestamp()); NewRelic.getAgent().getInsights().recordCustomEvent("OrderCompleted", orderEvent); } public void recordErrorMetrics(Exception error, String context) { NewRelic.incrementCounter("Errors/" + error.getClass().getSimpleName()); Map<String, Object> errorEvent = new HashMap<>(); errorEvent.put("errorType", error.getClass().getName()); errorEvent.put("errorMessage", error.getMessage()); errorEvent.put("context", context); errorEvent.put("timestamp", System.currentTimeMillis()); NewRelic.getAgent().getInsights().recordCustomEvent("ApplicationError", errorEvent); } }

2. Datadog Custom Metrics

import datadog.trace.api.StatsDClient; import org.springframework.beans.factory.annotation.Autowired; @Service public class DatadogMetricsService { @Autowired private StatsDClient statsDClient; public void recordBusinessMetrics(Order order) { // Increment counter statsDClient.incrementCounter("order.completed"); // Record gauge statsDClient.recordGaugeValue("order.amount", order.getAmount()); statsDClient.recordGaugeValue("order.items.count", order.getItemCount()); // Record histogram statsDClient.recordHistogramValue("order.processing.time", order.getProcessingTime()); // Add tags Map<String, String> tags = new HashMap<>(); tags.put("currency", order.getCurrency()); tags.put("customer_tier", order.getCustomerTier()); statsDClient.incrementCounter("order.by_currency", tags); } public void recordErrorRate(String errorType) { statsDClient.incrementCounter("application.errors", "error_type:" + errorType, "environment:production"); } }

Log Integration

1. New Relic Logging

<!-- Logback integration --> <dependency> <groupId>com.newrelic.logging</groupId> <artifactId>logback</artifactId> <version>2.0.0</version> </dependency>

logback.xml:

<configuration> <appender name="NEW_RELIC" class="com.newrelic.logging.logback.NewRelicAppender"/> <root level="INFO"> <appender-ref ref="NEW_RELIC"/> </root> </configuration>

2. Datadog Logging

<!-- Logback integration --> <dependency> <groupId>com.datadoghq</groupId> <artifactId>dd-java-agent</artifactId> <version>1.0.0</version> </dependency>

JSON Layout for Structured Logging:

import lombok.extern.slf4j.Slf4j; @Slf4j @Service public class LoggingService { public void processWithLogging(Order order) { MDC.put("orderId", order.getId()); MDC.put("customerId", order.getCustomerId()); log.info("Processing order", kv("order_amount", order.getAmount()), kv("currency", order.getCurrency())); try { // Business logic processOrder(order); log.info("Order processed successfully"); } catch (Exception e) { log.error("Failed to process order", kv("error_message", e.getMessage()), kv("error_type", e.getClass().getSimpleName())); throw e; } finally { MDC.clear(); } } }

Performance Best Practices

1. Sampling for High-Throughput Applications

@Component public class SamplingStrategy { private static final double SAMPLE_RATE = 0.1; // Sample 10% of requests public boolean shouldTrace(String operation) { // Sample expensive operations less frequently if (operation.startsWith("BATCH_")) { return Math.random() < 0.01; // 1% for batch operations } return Math.random() < SAMPLE_RATE; } }

2. Asynchronous Instrumentation

@Service public class AsyncTracingService { @Async @Trace public CompletableFuture<Result> processAsync(Data data) { // Trace context is automatically propagated return CompletableFuture.supplyAsync(() -> { // Async processing return heavyComputation(data); }); } }

3. Resource Cleanup

@Component public class ResourceMonitoring { @PreDestroy public void cleanup() { // Flush any pending metrics/traces try { NewRelic.getAgent().getTracedMethod().setMetricName("Application/Shutdown"); } catch (Exception e) { // Log but don't throw } } }

Troubleshooting Common Issues

1. Debugging Configuration

public class APMDebugger { public static void checkAPMStatus() { // New Relic try { boolean connected = NewRelic.getAgent().getConfig().getValue("agent_connected"); System.out.println("New Relic connected: " + connected); } catch (Exception e) { System.out.println("New Relic not available"); } // Datadog try { Tracer tracer = GlobalTracer.get(); System.out.println("Datadog tracer active: " + (tracer != null)); } catch (Exception e) { System.out.println("Datadog not available"); } } }

2. Performance Impact Monitoring

@Aspect @Component public class APMPerformanceMonitor { @Around("@annotation(org.springframework.web.bind.annotation.GetMapping)") public Object monitorControllerPerformance(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { long duration = System.currentTimeMillis() - startTime; // Alert if APM adds significant overhead if (duration > 1000) { // 1 second threshold log.warn("APM overhead detected: {}ms for method {}", duration, joinPoint.getSignature().getName()); } } } }

Conclusion

Integrating New Relic and Datadog APM tools into Java applications provides:

  1. Comprehensive Monitoring: End-to-end visibility into application performance
  2. Proactive Alerting: Early detection of performance degradation and errors
  3. Business Insights: Correlation of technical metrics with business outcomes
  4. Distributed Tracing: Understanding complex microservice interactions

Key Success Factors:

  • Proper configuration and environment-specific settings
  • Strategic sampling to balance overhead and insights
  • Custom instrumentation for business-specific metrics
  • Structured logging with trace context propagation
  • Regular monitoring of APM tool performance impact

By following these integration patterns and best practices, you can effectively monitor your Java applications' health, performance, and business impact using industry-leading APM tools.

Leave a Reply

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


Macro Nepal Helper