google-site-verification: google61fe8ba583a51912.html
New Relic Java Agent: Complete Implementation Guide

New Relic provides comprehensive application performance monitoring (APM) for Java applications. This guide demonstrates how to integrate and leverage the New Relic Java Agent for monitoring, tracing, and performance analysis.

Why Use New Relic Java Agent?

  • Application Performance Monitoring: Real-time performance insights
  • Distributed Tracing: End-to-end transaction tracing
  • Error Analytics: Detailed error tracking and analysis
  • Custom Metrics: Business-specific performance metrics
  • Infrastructure Monitoring: Server and container metrics
  • Alerting: Proactive performance alerts

Prerequisites

  • New Relic Account with license key
  • Java 8+ application
  • Maven/Gradle for dependency management

Step 1: Installation and Dependencies

Maven (pom.xml):

<dependencies> <!-- New Relic Agent (provided scope for agent jar) --> <dependency> <groupId>com.newrelic.agent.java</groupId> <artifactId>newrelic-java</artifactId> <version>8.0.0</version> <scope>provided</scope> </dependency> <!-- New Relic API for custom instrumentation --> <dependency> <groupId>com.newrelic.agent.java</groupId> <artifactId>newrelic-api</artifactId> <version>8.0.0</version> </dependency> <!-- Spring Boot Starter (Optional) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.0</version> </dependency> <!-- Micrometer for custom metrics --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> <version>1.10.5</version> </dependency> </dependencies> <build> <plugins> <!-- Agent setup plugin --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.4.0</version> <executions> <execution> <id>copy-newrelic</id> <phase>package</phase> <goals> <goal>copy</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>com.newrelic.agent.java</groupId> <artifactId>newrelic-java</artifactId> <version>8.0.0</version> <type>jar</type> <overWrite>true</overWrite> <outputDirectory>${project.build.directory}</outputDirectory> <destFileName>newrelic.jar</destFileName> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> </plugins> </build>

Step 2: New Relic Configuration

newrelic.yml:

# New Relic Configuration File common: &default_settings # License key license_key: ${NEW_RELIC_LICENSE_KEY} # Application name app_name: ${NEW_RELIC_APP_NAME:My Java Application} # Logging configuration log_level: info log_file_path: logs log_file_name: newrelic.log # Security settings security: enabled: false agent_token: record_sql: obfuscated attributes: include_enabled: true exclude: - request.headers.cookie - request.headers.authorization - response.headers.set-cookie # Transaction tracing transaction_tracer: enabled: true transaction_threshold: apdex_f record_sql: obfuscated stack_trace_threshold: 0.5 explain_enabled: true explain_threshold: 0.5 # Error collection error_collector: enabled: true ignore_errors: # Browser monitoring browser_monitoring: enabled: true auto_instrument: true # Thread profiling thread_profiler: enabled: true # Slow queries slow_sql: enabled: true max_samples: 10 # Development environment development: <<: *default_settings app_name: My App (Development) transaction_tracer: enabled: true record_sql: raw # Production environment production: <<: *default_settings app_name: My App (Production) log_level: info security: enabled: true

newrelic.env:

# Environment variables for New Relic NEW_RELIC_APP_NAME=My Java Application NEW_RELIC_LICENSE_KEY=your_license_key_here NEW_RELIC_LOG_LEVEL=info NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=true NEW_RELIC_SPAN_EVENTS_ENABLED=true NEW_RELIC_LOG_FORWARDING_ENABLED=true

Step 3: Application Configuration

application.yml:

# Spring Boot Application Configuration spring: application: name: order-service # New Relic specific properties newrelic: enabled: true config: file: classpath:newrelic.yml environment: ${NEW_RELIC_ENVIRONMENT:development} # Custom New Relic properties newrelic: app-name: ${spring.application.name} license-key: ${NEW_RELIC_LICENSE_KEY:} log-level: info distributed-tracing: enabled: true custom-insights-events: enabled: true labels: environment: ${spring.profiles.active:local} version: 1.0.0 team: backend-services # Logging configuration for New Relic logging: level: com.newrelic: INFO NewRelic: INFO file: path: ./logs

Step 4: New Relic Service Classes

@Service @Slf4j public class NewRelicMonitoringService { private final NewRelicApiClient newRelicApiClient; public NewRelicMonitoringService(NewRelicApiClient newRelicApiClient) { this.newRelicApiClient = newRelicApiClient; } // Custom metric recording public void recordCustomMetric(String metricName, double value) { NewRelic.recordMetric(metricName, value); log.debug("Recorded custom metric: {} = {}", metricName, value); } public void recordResponseTime(String operation, long durationMs) { String metricName = "Custom/ResponseTime/" + operation; NewRelic.recordMetric(metricName, durationMs); // Also record as response time metric NewRelic.recordResponseTimeMetric(operation, durationMs); } // Business transaction monitoring public void recordBusinessTransaction(String transactionName, long duration, boolean success) { String metricName = "BusinessTransaction/" + transactionName; NewRelic.recordMetric(metricName, duration); if (!success) { NewRelic.recordMetric(metricName + "/Errors", 1); } // Add custom attributes NewRelic.addCustomParameter("business_transaction", transactionName); NewRelic.addCustomParameter("transaction_duration", duration); NewRelic.addCustomParameter("transaction_success", success); } // Error tracking public void recordError(Throwable error, Map<String, String> context) { NewRelic.noticeError(error); // Add context as custom parameters if (context != null) { context.forEach(NewRelic::addCustomParameter); } log.error("Error recorded in New Relic: {}", error.getMessage(), error); } public void recordError(String errorMessage, Map<String, String> context) { NewRelic.noticeError(errorMessage); if (context != null) { context.forEach(NewRelic::addCustomParameter); } log.error("Error message recorded in New Relic: {}", errorMessage); } // Custom event tracking public void recordCustomEvent(String eventType, Map<String, Object> attributes) { NewRelic.recordCustomEvent(eventType, attributes); log.debug("Recorded custom event: {} with {} attributes", eventType, attributes.size()); } // Distributed tracing public void addTracingAttributes(Map<String, String> attributes) { if (attributes != null) { attributes.forEach(NewRelic::addCustomParameter); } } public String getTraceId() { try { return NewRelic.getAgent().getTransaction().getTraceId(); } catch (Exception e) { log.warn("Failed to get trace ID from New Relic", e); return "unknown"; } } public boolean isAgentConnected() { try { return NewRelic.getAgent().getConnected(); } catch (Exception e) { return false; } } // Performance monitoring public <T> T monitorPerformance(String operationName, Supplier<T> operation) { long startTime = System.currentTimeMillis(); boolean success = false; try { T result = operation.get(); success = true; return result; } catch (Exception e) { recordError(e, Map.of("operation", operationName)); throw e; } finally { long duration = System.currentTimeMillis() - startTime; recordResponseTime(operationName, duration); recordBusinessTransaction(operationName, duration, success); } } public void monitorPerformance(String operationName, Runnable operation) { long startTime = System.currentTimeMillis(); boolean success = false; try { operation.run(); success = true; } catch (Exception e) { recordError(e, Map.of("operation", operationName)); throw e; } finally { long duration = System.currentTimeMillis() - startTime; recordResponseTime(operationName, duration); recordBusinessTransaction(operationName, duration, success); } } } @Component @Slf4j public class NewRelicApiClient { private final RestTemplate restTemplate; private final String apiKey; private final String accountId; @Value("${newrelic.api.base-url:https://api.newrelic.com}") private String baseUrl; public NewRelicApiClient( @Value("${newrelic.api.key}") String apiKey, @Value("${newrelic.account.id}") String accountId, RestTemplateBuilder restTemplateBuilder) { this.apiKey = apiKey; this.accountId = accountId; this.restTemplate = restTemplateBuilder.build(); } // Query New Relic Insights public Map<String, Object> queryInsights(String nrql) { try { String url = baseUrl + "/v2/query?nrql=" + URLEncoder.encode(nrql, StandardCharsets.UTF_8); HttpHeaders headers = new HttpHeaders(); headers.set("X-Query-Key", apiKey); headers.set("Content-Type", "application/json"); HttpEntity<String> entity = new HttpEntity<>(headers); ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class); return response.getBody(); } catch (Exception e) { log.error("Failed to query New Relic Insights", e); throw new RuntimeException("New Relic API call failed", e); } } // Get application summary public Map<String, Object> getApplicationSummary(String applicationId) { String nrql = String.format( "SELECT average(duration), percentage(count(*), WHERE error IS true) as errorRate " + "FROM Transaction WHERE appId = '%s' SINCE 1 hour ago", applicationId); return queryInsights(nrql); } // Get error rates public double getErrorRate(String applicationId, Duration since) { String nrql = String.format( "SELECT percentage(count(*), WHERE error IS true) as errorRate " + "FROM Transaction WHERE appId = '%s' SINCE %d minutes ago", applicationId, since.toMinutes()); Map<String, Object> result = queryInsights(nrql); // Parse result and return error rate return parseErrorRate(result); } // Get slow transactions public List<Map<String, Object>> getSlowTransactions(String applicationId, int limit) { String nrql = String.format( "SELECT * FROM Transaction WHERE appId = '%s' " + "WHERE duration > 1.0 LIMIT %d SINCE 1 hour ago", applicationId, limit); Map<String, Object> result = queryInsights(nrql); return parseTransactions(result); } private double parseErrorRate(Map<String, Object> result) { // Implementation to parse error rate from NRQL result return 0.0; } private List<Map<String, Object>> parseTransactions(Map<String, Object> result) { // Implementation to parse transactions from NRQL result return Collections.emptyList(); } }

Step 5: Custom Instrumentation

// Custom method annotation for New Relic tracing @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Trace { String metricName() default ""; String category() default "Custom"; boolean dispatcher() default false; } // Aspect for @Trace annotation @Aspect @Component @Slf4j public class TraceAspect { private final NewRelicMonitoringService monitoringService; public TraceAspect(NewRelicMonitoringService monitoringService) { this.monitoringService = monitoringService; } @Around("@annotation(trace)") public Object traceMethod(ProceedingJoinPoint joinPoint, Trace trace) throws Throwable { String metricName = getMetricName(joinPoint, trace); return monitoringService.monitorPerformance(metricName, () -> { try { // Add method parameters as custom attributes addMethodParametersToNewRelic(joinPoint); return joinPoint.proceed(); } catch (Throwable throwable) { if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } else { throw new RuntimeException(throwable); } } }); } private String getMetricName(ProceedingJoinPoint joinPoint, Trace trace) { if (!trace.metricName().isEmpty()) { return trace.metricName(); } String className = joinPoint.getTarget().getClass().getSimpleName(); String methodName = joinPoint.getSignature().getName(); return trace.category() + "/" + className + "/" + methodName; } private void addMethodParametersToNewRelic(ProceedingJoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); String[] parameterNames = getParameterNames(joinPoint); for (int i = 0; i < args.length; i++) { String paramName = parameterNames != null && i < parameterNames.length ? parameterNames[i] : "param" + i; Object paramValue = args[i]; if (paramValue != null) { NewRelic.addCustomParameter("method_param_" + paramName, paramValue.toString()); } } } private String[] getParameterNames(ProceedingJoinPoint joinPoint) { // Implementation to get parameter names (using reflection or Spring's ParameterNameDiscoverer) return null; } } // Custom WebClient instrumentation @Component @Slf4j public class NewRelicWebClientFilter { public ExchangeFilterFunction newRelicTracingFilter() { return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { // Add distributed tracing headers ClientRequest tracedRequest = ClientRequest.from(clientRequest) .headers(headers -> addDistributedTracingHeaders(headers)) .build(); return Mono.just(tracedRequest); }); } private void addDistributedTracingHeaders(HttpHeaders headers) { try { // Add New Relic distributed tracing headers headers.add("newrelic", NewRelic.getAgent().getTransaction().getRequestMetadata()); } catch (Exception e) { log.warn("Failed to add New Relic tracing headers", e); } } } // Custom database instrumentation @Component @Slf4j public class NewRelicJdbcInterceptor { @Autowired private NewRelicMonitoringService monitoringService; public void interceptQuery(String sql, long duration, boolean success) { String metricName = "Database/Query"; monitoringService.recordResponseTime(metricName, duration); if (!success) { monitoringService.recordError("Database query failed", Map.of("sql", sql, "duration", String.valueOf(duration))); } // Add SQL as custom parameter (obfuscated for security) NewRelic.addCustomParameter("sql_query", obfuscateSql(sql)); NewRelic.addCustomParameter("query_duration_ms", duration); } private String obfuscateSql(String sql) { // Basic SQL obfuscation for security return sql.replaceAll("\\b\\d+\\b", "?") .replaceAll("'.*?'", "?"); } }

Step 6: Spring Boot Integration

@Configuration @Slf4j public class NewRelicAutoConfiguration { @Bean @ConditionalOnProperty(name = "newrelic.enabled", havingValue = "true") public NewRelicMonitoringService newRelicMonitoringService(NewRelicApiClient apiClient) { log.info("Initializing New Relic Monitoring Service"); return new NewRelicMonitoringService(apiClient); } @Bean @ConditionalOnProperty(name = "newrelic.api.key") public NewRelicApiClient newRelicApiClient( @Value("${newrelic.api.key}") String apiKey, @Value("${newrelic.account.id}") String accountId, RestTemplateBuilder restTemplateBuilder) { return new NewRelicApiClient(apiKey, accountId, restTemplateBuilder); } @Bean public TraceAspect traceAspect(NewRelicMonitoringService monitoringService) { return new TraceAspect(monitoringService); } @Bean public NewRelicWebClientFilter newRelicWebClientFilter() { return new NewRelicWebClientFilter(); } @Bean public FilterRegistrationBean<NewRelicFilter> newRelicFilter() { FilterRegistrationBean<NewRelicFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new NewRelicFilter()); registrationBean.addUrlPatterns("/*"); registrationBean.setOrder(1); return registrationBean; } } // Custom servlet filter for New Relic @Slf4j public class NewRelicFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; long startTime = System.currentTimeMillis(); try { // Set transaction name based on request String transactionName = getTransactionName(httpRequest); NewRelic.setTransactionName("Custom", transactionName); // Add request attributes to New Relic addRequestAttributes(httpRequest); chain.doFilter(request, response); } finally { long duration = System.currentTimeMillis() - startTime; // Record response metrics NewRelic.recordMetric("Http/ResponseTime", duration); NewRelic.addCustomParameter("http_response_code", httpResponse.getStatus()); NewRelic.addCustomParameter("http_duration_ms", duration); } } private String getTransactionName(HttpServletRequest request) { String method = request.getMethod(); String path = request.getRequestURI(); return method + " " + path; } private void addRequestAttributes(HttpServletRequest request) { NewRelic.addCustomParameter("http_method", request.getMethod()); NewRelic.addCustomParameter("http_path", request.getRequestURI()); NewRelic.addCustomParameter("http_query_string", request.getQueryString()); NewRelic.addCustomParameter("http_user_agent", request.getHeader("User-Agent")); NewRelic.addCustomParameter("http_referer", request.getHeader("Referer")); // Client IP String clientIp = getClientIp(request); NewRelic.addCustomParameter("client_ip", clientIp); } 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(); } }

Step 7: Usage Examples

@Service @Slf4j public class OrderService { private final NewRelicMonitoringService monitoringService; private final OrderRepository orderRepository; public OrderService(NewRelicMonitoringService monitoringService, OrderRepository orderRepository) { this.monitoringService = monitoringService; this.orderRepository = orderRepository; } @Trace(metricName = "OrderService/processOrder", category = "Business") public Order processOrder(Order order) { return monitoringService.monitorPerformance("processOrder", () -> { // Add business context NewRelic.addCustomParameter("order_id", order.getId()); NewRelic.addCustomParameter("customer_id", order.getCustomerId()); NewRelic.addCustomParameter("order_amount", order.getAmount()); try { // Validate order validateOrder(order); // Process payment processPayment(order); // Update inventory updateInventory(order); // Record success event Map<String, Object> eventAttributes = new HashMap<>(); eventAttributes.put("order_id", order.getId()); eventAttributes.put("amount", order.getAmount()); eventAttributes.put("currency", order.getCurrency()); monitoringService.recordCustomEvent("OrderProcessed", eventAttributes); return order; } catch (Exception e) { // Record error with context Map<String, String> errorContext = Map.of( "order_id", order.getId(), "customer_id", order.getCustomerId(), "operation", "processOrder" ); monitoringService.recordError(e, errorContext); throw e; } }); } @Trace public List<Order> getOrdersByCustomer(String customerId, int page, int size) { return monitoringService.monitorPerformance("getOrdersByCustomer", () -> { NewRelic.addCustomParameter("customer_id", customerId); NewRelic.addCustomParameter("page", page); NewRelic.addCustomParameter("size", size); try { List<Order> orders = orderRepository.findByCustomerId(customerId, page, size); // Record custom metric monitoringService.recordCustomMetric("Orders/Count", orders.size()); return orders; } catch (Exception e) { monitoringService.recordError(e, Map.of("customer_id", customerId)); throw e; } }); } public void batchProcessOrders(List<Order> orders) { monitoringService.monitorPerformance("batchProcessOrders", () -> { NewRelic.addCustomParameter("batch_size", orders.size()); int successCount = 0; int errorCount = 0; for (Order order : orders) { try { processOrder(order); successCount++; } catch (Exception e) { errorCount++; log.error("Failed to process order: {}", order.getId(), e); } } // Record batch metrics monitoringService.recordCustomMetric("BatchProcessing/SuccessCount", successCount); monitoringService.recordCustomMetric("BatchProcessing/ErrorCount", errorCount); monitoringService.recordCustomMetric("BatchProcessing/TotalCount", orders.size()); // Record custom event for batch completion Map<String, Object> batchEvent = new HashMap<>(); batchEvent.put("total_orders", orders.size()); batchEvent.put("successful_orders", successCount); batchEvent.put("failed_orders", errorCount); batchEvent.put("success_rate", (double) successCount / orders.size()); monitoringService.recordCustomEvent("BatchOrderProcessing", batchEvent); }); } private void validateOrder(Order order) { // Validation logic if (order.getAmount() <= 0) { throw new IllegalArgumentException("Invalid order amount"); } } private void processPayment(Order order) { // Payment processing logic monitoringService.monitorPerformance("processPayment", () -> { // Simulate payment processing try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Payment processing interrupted", e); } }); } private void updateInventory(Order order) { // Inventory update logic monitoringService.monitorPerformance("updateInventory", () -> { // Simulate inventory update try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Inventory update interrupted", e); } }); } } @RestController @Slf4j public class OrderController { private final OrderService orderService; private final NewRelicMonitoringService monitoringService; public OrderController(OrderService orderService, NewRelicMonitoringService monitoringService) { this.orderService = orderService; this.monitoringService = monitoringService; } @PostMapping("/orders") public ResponseEntity<Order> createOrder(@RequestBody Order order) { // Add request context to New Relic monitoringService.addTracingAttributes(Map.of( "http_method", "POST", "http_path", "/orders", "customer_id", order.getCustomerId() )); try { Order processedOrder = orderService.processOrder(order); return ResponseEntity.ok(processedOrder); } catch (Exception e) { monitoringService.recordError(e, Map.of( "endpoint", "/orders", "http_method", "POST" )); return ResponseEntity.status(500).build(); } } @GetMapping("/orders/customer/{customerId}") public ResponseEntity<List<Order>> getCustomerOrders( @PathVariable String customerId, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { monitoringService.addTracingAttributes(Map.of( "http_method", "GET", "http_path", "/orders/customer/{customerId}", "customer_id", customerId, "page", String.valueOf(page), "size", String.valueOf(size) )); try { List<Order> orders = orderService.getOrdersByCustomer(customerId, page, size); return ResponseEntity.ok(orders); } catch (Exception e) { monitoringService.recordError(e, Map.of( "customer_id", customerId, "endpoint", "/orders/customer/{customerId}" )); return ResponseEntity.status(500).build(); } } }

Step 8: Custom Metrics and Dashboards

@Component @Slf4j public class BusinessMetricsService { private final NewRelicMonitoringService monitoringService; private final MeterRegistry meterRegistry; public BusinessMetricsService(NewRelicMonitoringService monitoringService, MeterRegistry meterRegistry) { this.monitoringService = monitoringService; this.meterRegistry = meterRegistry; } // Business KPIs public void recordOrderKpis(Order order, boolean success, long processingTime) { // Custom metrics for New Relic monitoringService.recordCustomMetric("Business/Orders/Total", 1); monitoringService.recordCustomMetric("Business/Orders/ProcessingTime", processingTime); if (success) { monitoringService.recordCustomMetric("Business/Orders/Successful", 1); monitoringService.recordCustomMetric("Business/Revenue", order.getAmount()); } else { monitoringService.recordCustomMetric("Business/Orders/Failed", 1); } // Micrometer metrics (will be picked up by New Relic agent) Counter.builder("business.orders.total") .tag("status", success ? "success" : "failed") .register(meterRegistry) .increment(); Timer.builder("business.orders.processing.time") .register(meterRegistry) .record(processingTime, TimeUnit.MILLISECONDS); } // User behavior metrics public void recordUserActivity(String userId, String activity, Map<String, String> context) { Map<String, Object> eventAttributes = new HashMap<>(); eventAttributes.put("user_id", userId); eventAttributes.put("activity_type", activity); eventAttributes.putAll(context); monitoringService.recordCustomEvent("UserActivity", eventAttributes); monitoringService.recordCustomMetric("Business/UserActivities/" + activity, 1); } // System health metrics public void recordSystemHealth(Map<String, Object> healthMetrics) { healthMetrics.forEach((key, value) -> { if (value instanceof Number) { monitoringService.recordCustomMetric("System/Health/" + key, ((Number) value).doubleValue()); } }); monitoringService.recordCustomEvent("SystemHealthCheck", healthMetrics); } // Performance metrics by tier public void recordTierPerformance(String tier, String operation, long duration, boolean success) { String metricName = String.format("Performance/%s/%s", tier, operation); monitoringService.recordCustomMetric(metricName, duration); if (!success) { monitoringService.recordCustomMetric(metricName + "/Errors", 1); } } } @Component @Slf4j public class NewRelicDashboardService { private final NewRelicApiClient apiClient; public NewRelicDashboardService(NewRelicApiClient apiClient) { this.apiClient = apiClient; } // Monitor application health public void checkApplicationHealth(String applicationId) { Map<String, Object> summary = apiClient.getApplicationSummary(applicationId); double errorRate = apiClient.getErrorRate(applicationId, Duration.ofMinutes(30)); if (errorRate > 0.05) { // 5% error rate threshold log.warn("High error rate detected: {}% for application {}", errorRate * 100, applicationId); // Trigger alert or notification } log.info("Application {} health - Error rate: {}%", applicationId, errorRate * 100); } // Get performance insights public void analyzePerformance(String applicationId) { List<Map<String, Object>> slowTransactions = apiClient.getSlowTransactions(applicationId, 10); if (!slowTransactions.isEmpty()) { log.info("Found {} slow transactions for application {}", slowTransactions.size(), applicationId); slowTransactions.forEach(transaction -> log.info("Slow transaction: {}", transaction) ); } } }

Step 9: Testing and Verification

@SpringBootTest @Slf4j class NewRelicIntegrationTest { @Autowired private NewRelicMonitoringService monitoringService; @Autowired private OrderService orderService; @Test void testCustomMetrics() { monitoringService.recordCustomMetric("Test/Metric", 42.0); monitoringService.recordResponseTime("TestOperation", 150L); // Verify no exceptions are thrown assertTrue(monitoringService.isAgentConnected()); } @Test void testErrorTracking() { Exception testException = new RuntimeException("Test error"); Map<String, String> context = Map.of("test_id", "123", "component", "test"); monitoringService.recordError(testException, context); // Error should be recorded without throwing exception assertTrue(true); } @Test void testPerformanceMonitoring() { String result = monitoringService.monitorPerformance("testOperation", () -> { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return "success"; }); assertEquals("success", result); } @Test void testTraceAnnotation() { Order testOrder = new Order("test-order", "test-customer", 100.0); // This should be traced by New Relic Order result = orderService.processOrder(testOrder); assertNotNull(result); } } // Test configuration @TestConfiguration class NewRelicTestConfig { @Bean @Primary public NewRelicMonitoringService testNewRelicMonitoringService() { return new NewRelicMonitoringService(mock(NewRelicApiClient.class)); } }

Step 10: Deployment and Runtime Configuration

Dockerfile:

FROM openjdk:11-jre-slim # Copy New Relic agent COPY target/newrelic.jar /app/newrelic.jar COPY newrelic.yml /app/newrelic.yml # Copy application COPY target/application.jar /app/application.jar # Set New Relic environment variables ENV NEW_RELIC_APP_NAME=my-java-app ENV NEW_RELIC_LICENSE_KEY=your_license_key ENV NEW_RELIC_LOG_LEVEL=info # Java agent configuration ENV JAVA_OPTS="-javaagent:/app/newrelic.jar" WORKDIR /app CMD java $JAVA_OPTS -jar application.jar

Kubernetes Deployment:

apiVersion: apps/v1 kind: Deployment metadata: name: java-app spec: replicas: 3 selector: matchLabels: app: java-app template: metadata: labels: app: java-app annotations: newrelic.io/enabled: "true" newrelic.io/app-name: "my-java-app" spec: containers: - name: java-app image: my-java-app:latest env: - name: NEW_RELIC_APP_NAME value: "my-java-app" - name: NEW_RELIC_LICENSE_KEY valueFrom: secretKeyRef: name: newrelic-secret key: license-key - name: NEW_RELIC_DISTRIBUTED_TRACING_ENABLED value: "true" - name: JAVA_OPTS value: "-javaagent:/newrelic/newrelic.jar" volumeMounts: - name: newrelic-agent mountPath: /newrelic resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" volumes: - name: newrelic-agent configMap: name: newrelic-agent

Key Features Implemented

  1. APM Integration: Full application performance monitoring
  2. Custom Metrics: Business-specific performance indicators
  3. Distributed Tracing: End-to-end transaction tracking
  4. Error Analytics: Comprehensive error tracking and analysis
  5. Custom Instrumentation: Method-level tracing and monitoring
  6. Business Metrics: Revenue, order processing, user activity tracking
  7. Dashboard Integration: Custom New Relic dashboards and alerts
  8. Microservices Support: Distributed tracing across services

Best Practices

  1. Meaningful Metric Names: Use consistent, descriptive metric names
  2. Context Enrichment: Add relevant business context to transactions
  3. Security: Obfuscate sensitive data in traces and logs
  4. Performance: Use async operations for metric recording where possible
  5. Alerting: Set up meaningful alerts based on business KPIs
  6. Monitoring: Regularly review performance metrics and traces
  7. Documentation: Document custom metrics and their business significance

This comprehensive New Relic Java Agent implementation provides robust application monitoring, performance insights, and business metrics tracking for Java applications in production environments.

Leave a Reply

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


Macro Nepal Helper