google-site-verification: google61fe8ba583a51912.html
Thundra APM Integration in Java

Thundra is a comprehensive Application Performance Monitoring (APM) solution for serverless and containerized applications. This guide covers integrating Thundra APM with Java applications for monitoring, tracing, and debugging.

Architecture Overview

Java Application → Thundra Agent → Thundra Backend → Auto-Instrumentation → Custom Spans → Distributed Tracing → Lambda Monitoring → Cold Start Tracking → Performance Metrics → Debugging Support → Security Monitoring

Prerequisites and Setup

Maven Dependencies

<properties> <thundra.version>3.0.0</thundra.version> <aws-lambda.version>1.2.2</aws-lambda.version> </properties> <dependencies> <!-- Thundra Core --> <dependency> <groupId>io.thundra</groupId> <artifactId>thundra-agent</artifactId> <version>${thundra.version}</version> </dependency> <!-- Thundra Lambda --> <dependency> <groupId>io.thundra</groupId> <artifactId>thundra-agent-lambda</artifactId> <version>${thundra.version}</version> </dependency> <!-- AWS Lambda Core --> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>${aws-lambda.version}</version> </dependency> <!-- Spring Boot (optional) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.1.0</version> </dependency> <!-- Database (example) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.1.0</version> </dependency> </dependencies>

Configuration Properties

# Thundra Configuration thundra.apiKey=your-thundra-api-key thundra.agent.lambda.application.name=my-application thundra.agent.lambda.application.stage=production thundra.agent.lambda.application.version=1.0.0 # Tracing Configuration thundra.trace.instrument.sql=true thundra.trace.instrument.aws.sdk=true thundra.trace.instrument.http=true thundra.trace.instrument.redis=true thundra.trace.instrument.mongodb=true # Monitoring Configuration thundra.monitor.cpu=true thundra.monitor.memory=true thundra.monitor.gc=true thundra.monitor.thread=true # Debugging Configuration thundra.debug.enabled=true thundra.debug.sampling.count=100 thundra.debug.sampling.duration=60000 # Security Configuration thundra.security.enabled=true thundra.security.vulnerability.scanning=true

Core Thundra Integration

1. Thundra Configuration Class

package com.yourapp.monitoring.thundra; import io.thundra.agent.core.config.ConfigProvider; import io.thundra.agent.core.plugin.Plugin; import io.thundra.agent.core.plugin.PluginManager; import io.thundra.agent.trace.instrument.config.TraceableConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.Map; @Configuration public class ThundraConfig { @Value("${thundra.apiKey:}") private String thundraApiKey; @Value("${thundra.agent.lambda.application.name:unknown}") private String applicationName; @Value("${thundra.agent.lambda.application.stage:dev}") private String applicationStage; @Value("${thundra.agent.lambda.application.version:1.0.0}") private String applicationVersion; @Value("${thundra.trace.instrument.sql:true}") private boolean traceSQL; @Value("${thundra.trace.instrument.aws.sdk:true}") private boolean traceAWSSDK; @Value("${thundra.trace.instrument.http:true}") private boolean traceHTTP; @Value("${thundra.debug.enabled:false}") private boolean debugEnabled; @PostConstruct public void initializeThundra() { if (thundraApiKey == null || thundraApiKey.isEmpty()) { throw new IllegalStateException("Thundra API key is required"); } // Set Thundra configuration configureThundra(); // Initialize plugins initializePlugins(); // Configure tracing configureTracing(); System.out.println("Thundra APM initialized for application: " + applicationName); } private void configureThundra() { Map<String, String> config = new HashMap<>(); // Basic configuration config.put("thundra.apiKey", thundraApiKey); config.put("thundra.agent.lambda.application.name", applicationName); config.put("thundra.agent.lambda.application.stage", applicationStage); config.put("thundra.agent.lambda.application.version", applicationVersion); // Monitoring configuration config.put("thundra.monitor.cpu", "true"); config.put("thundra.monitor.memory", "true"); config.put("thundra.monitor.gc", "true"); config.put("thundra.monitor.thread", "true"); // Debug configuration config.put("thundra.debug.enabled", String.valueOf(debugEnabled)); config.put("thundra.debug.sampling.count", "100"); config.put("thundra.debug.sampling.duration", "60000"); // Apply configuration ConfigProvider.setConfig(config); } private void initializePlugins() { // Initialize core plugins PluginManager.initialize(); // Register custom plugins if needed registerCustomPlugins(); } private void configureTracing() { TraceableConfig traceableConfig = TraceableConfig.get(); // Configure SQL tracing if (traceSQL) { traceableConfig.setTraceInjectionEnabled(true); traceableConfig.setSqlTraceInjectionEnabled(true); traceableConfig.setSqlPreparedStatementTraceInjectionEnabled(true); } // Configure AWS SDK tracing if (traceAWSSDK) { traceableConfig.setAwsSDKTraceInjectionEnabled(true); } // Configure HTTP tracing if (traceHTTP) { traceableConfig.setHttpConnectionTraceInjectionEnabled(true); traceableConfig.setHttpURLConnectionTraceInjectionEnabled(true); } // Configure Redis tracing traceableConfig.setRedisTraceInjectionEnabled(true); // Configure MongoDB tracing traceableConfig.setMongoDBTraceInjectionEnabled(true); // Configure Elasticsearch tracing traceableConfig.setElasticsearchTraceInjectionEnabled(true); } private void registerCustomPlugins() { // Register custom instrumentation plugins // Example: PluginManager.registerPlugin(new CustomDatabasePlugin()); } public String getApplicationName() { return applicationName; } public String getApplicationStage() { return applicationStage; } public String getApplicationVersion() { return applicationVersion; } }

2. AWS Lambda Handler with Thundra

package com.yourapp.monitoring.thundra; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import io.thundra.agent.lambda.core.handler.ThundraLambdaHandler; import io.thundra.agent.trace.span.Span; import io.thundra.agent.trace.span.SpanFactory; import java.util.HashMap; import java.util.Map; public class ThundraLambdaFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { private final ThundraLambdaHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> thundraHandler; public ThundraLambdaFunction() { this.thundraHandler = new ThundraLambdaHandler<>(this::handleRequest); } @Override public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { return thundraHandler.handleRequest(input, context); } private APIGatewayProxyResponseEvent handleRequestInternal(APIGatewayProxyRequestEvent input, Context context) { // Create custom span for business logic Span businessSpan = SpanFactory.createSpan("BusinessLogic"); try { businessSpan.setTag("http.method", input.getHttpMethod()); businessSpan.setTag("http.path", input.getPath()); businessSpan.setTag("aws.requestId", context.getAwsRequestId()); // Your business logic here String responseBody = processRequest(input, context); businessSpan.setTag("http.status_code", 200); return createResponse(200, responseBody); } catch (Exception e) { businessSpan.setTag("error", true); businessSpan.setTag("error.message", e.getMessage()); businessSpan.setTag("http.status_code", 500); return createResponse(500, "{\"error\": \"Internal Server Error\"}"); } finally { businessSpan.finish(); } } private String processRequest(APIGatewayProxyRequestEvent input, Context context) { // Simulate business logic with multiple operations Span databaseSpan = SpanFactory.createSpan("DatabaseOperation"); try { // Simulate database operation Thread.sleep(50); databaseSpan.setTag("db.operation", "SELECT"); databaseSpan.setTag("db.table", "users"); return "{\"message\": \"Request processed successfully\"}"; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Database operation interrupted", e); } finally { databaseSpan.finish(); } } private APIGatewayProxyResponseEvent createResponse(int statusCode, String body) { APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); response.setStatusCode(statusCode); response.setBody(body); Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); headers.put("X-Powered-By", "Thundra-Monitored-App"); response.setHeaders(headers); return response; } }

Spring Boot Integration

3. Thundra Spring Boot Configuration

package com.yourapp.monitoring.thundra; import io.thundra.agent.core.config.ConfigProvider; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import javax.servlet.Filter; @Configuration public class ThundraSpringConfig { @Bean public FilterRegistrationBean<Filter> thundraFilter() { FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>(); // Create Thundra servlet filter io.thundra.agent.servlet.ThundraServletFilter thundraFilter = new io.thundra.agent.servlet.ThundraServletFilter(); registrationBean.setFilter(thundraFilter); registrationBean.addUrlPatterns("/*"); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); registrationBean.setName("ThundraServletFilter"); return registrationBean; } @Bean public ThundraMonitoringService thundraMonitoringService() { return new ThundraMonitoringService(); } @Bean public ThundraTracingService thundraTracingService() { return new ThundraTracingService(); } }

4. Thundra Monitoring Service

package com.yourapp.monitoring.thundra; import io.thundra.agent.core.metric.Metric; import io.thundra.agent.core.metric.MetricPublisher; import io.thundra.agent.core.monitor.Monitor; import io.thundra.agent.core.monitor.MonitorManager; import io.thundra.agent.core.monitor.SystemMonitor; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Service public class ThundraMonitoringService { private final Map<String, Object> customMetrics = new ConcurrentHashMap<>(); private final SystemMonitor systemMonitor; public ThundraMonitoringService() { this.systemMonitor = MonitorManager.getMonitor(SystemMonitor.class); initializeCustomMetrics(); } /** * Record custom business metric */ public void recordBusinessMetric(String metricName, double value, Map<String, String> tags) { Metric metric = new Metric(metricName, value, System.currentTimeMillis(), tags); MetricPublisher.publish(metric); // Also store in local cache for aggregation customMetrics.put(metricName, value); } /** * Record execution time metric */ public void recordExecutionTime(String operation, long durationMs, boolean success) { Map<String, String> tags = Map.of( "operation", operation, "success", String.valueOf(success), "application", "my-spring-app" ); recordBusinessMetric("execution.time", durationMs, tags); } /** * Record error metric */ public void recordError(String errorType, String operation) { Map<String, String> tags = Map.of( "error_type", errorType, "operation", operation, "application", "my-spring-app" ); recordBusinessMetric("error.count", 1.0, tags); } /** * Get system metrics */ public Map<String, Object> getSystemMetrics() { Map<String, Object> metrics = new ConcurrentHashMap<>(); // CPU metrics metrics.put("cpu.usage", systemMonitor.getCPUUsage()); metrics.put("cpu.system.usage", systemMonitor.getSystemCPUUsage()); metrics.put("cpu.process.usage", systemMonitor.getProcessCPUUsage()); // Memory metrics metrics.put("memory.usage", systemMonitor.getMemoryUsage()); metrics.put("memory.total", systemMonitor.getTotalMemory()); metrics.put("memory.free", systemMonitor.getFreeMemory()); metrics.put("memory.used", systemMonitor.getUsedMemory()); // GC metrics metrics.put("gc.count", systemMonitor.getGCCount()); metrics.put("gc.time", systemMonitor.getGCTime()); // Thread metrics metrics.put("thread.count", systemMonitor.getThreadCount()); metrics.put("thread.daemon.count", systemMonitor.getDaemonThreadCount()); // Custom metrics metrics.putAll(customMetrics); return metrics; } /** * Monitor application health */ public Map<String, Object> getApplicationHealth() { Map<String, Object> health = new ConcurrentHashMap<>(); Map<String, Object> systemMetrics = getSystemMetrics(); // Calculate health score double cpuUsage = (double) systemMetrics.get("cpu.usage"); double memoryUsage = (double) systemMetrics.get("memory.usage"); long threadCount = (long) systemMetrics.get("thread.count"); double healthScore = calculateHealthScore(cpuUsage, memoryUsage, threadCount); health.put("status", healthScore > 0.7 ? "HEALTHY" : "UNHEALTHY"); health.put("score", healthScore); health.put("timestamp", System.currentTimeMillis()); health.put("metrics", systemMetrics); return health; } /** * Scheduled metric collection */ @Scheduled(fixedRate = 60000) // Every minute public void collectAndPublishMetrics() { Map<String, Object> systemMetrics = getSystemMetrics(); // Publish critical metrics to Thundra publishCriticalMetrics(systemMetrics); // Log metrics for debugging System.out.println("Collected metrics: " + systemMetrics); } private void initializeCustomMetrics() { customMetrics.put("request.count", 0L); customMetrics.put("error.count", 0L); customMetrics.put("average.response.time", 0.0); } private double calculateHealthScore(double cpuUsage, double memoryUsage, long threadCount) { double cpuScore = 1.0 - Math.min(cpuUsage / 80.0, 1.0); // 80% threshold double memoryScore = 1.0 - Math.min(memoryUsage / 85.0, 1.0); // 85% threshold double threadScore = 1.0 - Math.min(threadCount / 500.0, 1.0); // 500 threads threshold return (cpuScore + memoryScore + threadScore) / 3.0; } private void publishCriticalMetrics(Map<String, Object> metrics) { // Publish CPU usage MetricPublisher.publish(new Metric( "application.cpu.usage", (double) metrics.get("cpu.usage"), System.currentTimeMillis(), Map.of("application", "my-spring-app") )); // Publish memory usage MetricPublisher.publish(new Metric( "application.memory.usage", (double) metrics.get("memory.usage"), System.currentTimeMillis(), Map.of("application", "my-spring-app") )); // Publish custom business metrics customMetrics.forEach((name, value) -> { if (value instanceof Number) { MetricPublisher.publish(new Metric( "application." + name, ((Number) value).doubleValue(), System.currentTimeMillis(), Map.of("application", "my-spring-app") )); } }); } }

5. Thundra Tracing Service

package com.yourapp.monitoring.thundra; import io.thundra.agent.trace.span.Span; import io.thundra.agent.trace.span.SpanFactory; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; @Service public class ThundraTracingService { /** * Execute operation with custom tracing */ public <T> T traceOperation(String operationName, Callable<T> operation) throws Exception { return traceOperation(operationName, operation, new HashMap<>()); } /** * Execute operation with custom tracing and tags */ public <T> T traceOperation(String operationName, Callable<T> operation, Map<String, Object> tags) throws Exception { Span span = SpanFactory.createSpan(operationName); try { // Set tags tags.forEach((key, value) -> span.setTag(key, String.valueOf(value))); span.setTag("operation.type", "business"); span.setTag("application", "my-spring-app"); // Execute operation return operation.call(); } catch (Exception e) { span.setTag("error", true); span.setTag("error.message", e.getMessage()); span.setTag("error.type", e.getClass().getSimpleName()); throw e; } finally { span.finish(); } } /** * Execute operation without return value */ public void traceOperation(String operationName, Runnable operation, Map<String, Object> tags) { Span span = SpanFactory.createSpan(operationName); try { // Set tags tags.forEach((key, value) -> span.setTag(key, String.valueOf(value))); span.setTag("operation.type", "business"); span.setTag("application", "my-spring-app"); // Execute operation operation.run(); } catch (Exception e) { span.setTag("error", true); span.setTag("error.message", e.getMessage()); span.setTag("error.type", e.getClass().getSimpleName()); throw e; } finally { span.finish(); } } /** * Create database operation span */ public <T> T traceDatabaseOperation(String query, String operation, Callable<T> databaseCall) throws Exception { Map<String, Object> tags = new HashMap<>(); tags.put("db.operation", operation); tags.put("db.query", query); tags.put("span.kind", "database"); return traceOperation("Database." + operation, databaseCall, tags); } /** * Create HTTP operation span */ public <T> T traceHttpOperation(String method, String url, Callable<T> httpCall) throws Exception { Map<String, Object> tags = new HashMap<>(); tags.put("http.method", method); tags.put("http.url", url); tags.put("span.kind", "http"); return traceOperation("HTTP." + method, httpCall, tags); } /** * Create external service call span */ public <T> T traceExternalService(String serviceName, String operation, Callable<T> serviceCall) throws Exception { Map<String, Object> tags = new HashMap<>(); tags.put("service.name", serviceName); tags.put("service.operation", operation); tags.put("span.kind", "external"); return traceOperation(serviceName + "." + operation, serviceCall, tags); } /** * Add custom tag to current span */ public void addTag(String key, Object value) { Span currentSpan = SpanFactory.getCurrentSpan(); if (currentSpan != null) { currentSpan.setTag(key, String.valueOf(value)); } } /** * Log custom event to current span */ public void logEvent(String eventName, Map<String, Object> data) { Span currentSpan = SpanFactory.getCurrentSpan(); if (currentSpan != null) { currentSpan.log(eventName, data); } } /** * Get current trace ID */ public String getCurrentTraceId() { Span currentSpan = SpanFactory.getCurrentSpan(); return currentSpan != null ? currentSpan.getTraceId() : null; } /** * Get current span ID */ public String getCurrentSpanId() { Span currentSpan = SpanFactory.getCurrentSpan(); return currentSpan != null ? currentSpan.getId() : null; } }

Spring Boot Service Integration

6. Example Service with Thundra Integration

package com.yourapp.service; import com.yourapp.monitoring.thundra.ThundraMonitoringService; import com.yourapp.monitoring.thundra.ThundraTracingService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.TimeUnit; @Service public class UserService { @Autowired private ThundraTracingService tracingService; @Autowired private ThundraMonitoringService monitoringService; @Autowired private UserRepository userRepository; /** * Get user by ID with full tracing */ public User getUserById(Long userId) { long startTime = System.nanoTime(); boolean success = false; try { // Trace database operation User user = tracingService.traceDatabaseOperation( "SELECT * FROM users WHERE id = ?", "SELECT", () -> userRepository.findById(userId) ); // Trace additional processing if (user != null) { tracingService.traceOperation("UserDataEnrichment", () -> { enrichUserData(user); return null; }, Map.of("user.id", userId)); } success = true; return user; } finally { // Record execution metrics long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); monitoringService.recordExecutionTime("getUserById", duration, success); if (!success) { monitoringService.recordError("DatabaseError", "getUserById"); } } } /** * Create user with distributed tracing */ public User createUser(User user) { long startTime = System.nanoTime(); boolean success = false; try { // Validate user tracingService.traceOperation("UserValidation", () -> { validateUser(user); return null; }, Map.of("user.email", user.getEmail())); // Trace database insert User createdUser = tracingService.traceDatabaseOperation( "INSERT INTO users (name, email) VALUES (?, ?)", "INSERT", () -> userRepository.save(user) ); // Trace external service call (e.g., send welcome email) tracingService.traceExternalService("EmailService", "sendWelcomeEmail", () -> sendWelcomeEmail(createdUser)); success = true; return createdUser; } finally { // Record metrics long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); monitoringService.recordExecutionTime("createUser", duration, success); if (!success) { monitoringService.recordError("UserCreationError", "createUser"); } else { // Record business metric monitoringService.recordBusinessMetric("user.created", 1.0, Map.of("application", "user-service")); } } } /** * Batch process users with performance monitoring */ public void processUsersBatch() { tracingService.traceOperation("BatchUserProcessing", () -> { long startTime = System.nanoTime(); int processedCount = 0; int errorCount = 0; try { // Get users to process var users = userRepository.findUsersToProcess(); for (User user : users) { try { // Process each user in its own span tracingService.traceOperation("ProcessSingleUser", () -> processSingleUser(user), Map.of("user.id", user.getId()) ); processedCount++; } catch (Exception e) { errorCount++; tracingService.addTag("processing.error", true); tracingService.logEvent("UserProcessingError", Map.of("userId", user.getId(), "error", e.getMessage())); } } // Record batch metrics monitoringService.recordBusinessMetric("users.processed", processedCount, Map.of("batch.size", String.valueOf(users.size()))); monitoringService.recordBusinessMetric("users.processing.errors", errorCount, Map.of("application", "user-service")); } finally { long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); monitoringService.recordExecutionTime("processUsersBatch", duration, errorCount == 0); } return null; }, Map.of("operation.type", "batch")); } private void enrichUserData(User user) { // Simulate data enrichment try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Enrichment interrupted", e); } } private void validateUser(User user) { if (user.getEmail() == null || !user.getEmail().contains("@")) { throw new IllegalArgumentException("Invalid email address"); } } private Void sendWelcomeEmail(User user) { // Simulate external service call try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Email service interrupted", e); } return null; } private Void processSingleUser(User user) { // Simulate user processing try { Thread.sleep(5); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("User processing interrupted", e); } return null; } }

7. Spring REST Controller with Thundra

package com.yourapp.controller; import com.yourapp.monitoring.thundra.ThundraMonitoringService; import com.yourapp.monitoring.thundra.ThundraTracingService; import com.yourapp.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Map; import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @Autowired private ThundraTracingService tracingService; @Autowired private ThundraMonitoringService monitoringService; @GetMapping("/{id}") public ResponseEntity<?> getUser(@PathVariable Long id) { long startTime = System.nanoTime(); boolean success = false; try { tracingService.addTag("http.route", "/api/users/{id}"); tracingService.addTag("http.method", "GET"); tracingService.addTag("user.id", id); var user = userService.getUserById(id); if (user == null) { tracingService.addTag("http.status_code", 404); return ResponseEntity.notFound().build(); } tracingService.addTag("http.status_code", 200); success = true; return ResponseEntity.ok(user); } catch (Exception e) { tracingService.addTag("http.status_code", 500); tracingService.addTag("error", true); tracingService.logEvent("ControllerError", Map.of("endpoint", "/api/users/{id}", "error", e.getMessage())); return ResponseEntity.internalServerError().build(); } finally { long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); monitoringService.recordExecutionTime("GET /api/users/{id}", duration, success); } } @PostMapping public ResponseEntity<?> createUser(@RequestBody Map<String, Object> userData) { long startTime = System.nanoTime(); boolean success = false; try { tracingService.addTag("http.route", "/api/users"); tracingService.addTag("http.method", "POST"); // Convert and validate input var user = convertToUser(userData); var createdUser = userService.createUser(user); tracingService.addTag("http.status_code", 201); success = true; return ResponseEntity.status(201).body(createdUser); } catch (IllegalArgumentException e) { tracingService.addTag("http.status_code", 400); tracingService.addTag("error", true); return ResponseEntity.badRequest().body(Map.of("error", e.getMessage())); } catch (Exception e) { tracingService.addTag("http.status_code", 500); tracingService.addTag("error", true); tracingService.logEvent("ControllerError", Map.of("endpoint", "/api/users", "error", e.getMessage())); return ResponseEntity.internalServerError().build(); } finally { long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); monitoringService.recordExecutionTime("POST /api/users", duration, success); } } @PostMapping("/batch-process") public ResponseEntity<?> processUsersBatch() { long startTime = System.nanoTime(); boolean success = false; try { tracingService.addTag("http.route", "/api/users/batch-process"); tracingService.addTag("http.method", "POST"); userService.processUsersBatch(); tracingService.addTag("http.status_code", 202); success = true; return ResponseEntity.accepted().body(Map.of("message", "Batch processing started")); } catch (Exception e) { tracingService.addTag("http.status_code", 500); tracingService.addTag("error", true); tracingService.logEvent("ControllerError", Map.of("endpoint", "/api/users/batch-process", "error", e.getMessage())); return ResponseEntity.internalServerError().build(); } finally { long duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime); monitoringService.recordExecutionTime("POST /api/users/batch-process", duration, success); } } @GetMapping("/health") public ResponseEntity<?> healthCheck() { var health = monitoringService.getApplicationHealth(); return ResponseEntity.ok(health); } private User convertToUser(Map<String, Object> data) { // Simple conversion logic User user = new User(); user.setName((String) data.get("name")); user.setEmail((String) data.get("email")); return user; } }

Custom Instrumentation

8. Custom Thundra Plugin

package com.yourapp.monitoring.thundra; import io.thundra.agent.core.instrument.InstrumentationContext; import io.thundra.agent.core.instrument.config.InstrumentConfig; import io.thundra.agent.core.plugin.Plugin; import io.thundra.agent.core.plugin.PluginContext; public class CustomDatabasePlugin implements Plugin { @Override public void initialize(PluginContext context) { System.out.println("Initializing CustomDatabasePlugin"); } @Override public void onPreDestroy() { System.out.println("Destroying CustomDatabasePlugin"); } @Override public InstrumentConfig getInstrumentConfig() { return new InstrumentConfig.Builder() .addClass("com.yourapp.repository.UserRepository") .addMethod("findById") .addMethod("save") .addMethod("delete") .build(); } public static class UserRepositoryInterceptor { public static void onMethodEnter(InstrumentationContext context) { String methodName = context.getMethodName(); Object[] args = context.getArgs(); // Create span for database operation io.thundra.agent.trace.span.Span span = io.thundra.agent.trace.span.SpanFactory.createSpan("CustomDatabase." + methodName); span.setTag("db.operation", methodName); span.setTag("db.class", "UserRepository"); if (args != null && args.length > 0) { span.setTag("db.parameters.count", args.length); } context.setLocal("customSpan", span); } public static void onMethodExit(InstrumentationContext context) { io.thundra.agent.trace.span.Span span = (io.thundra.agent.trace.span.Span) context.getLocal("customSpan"); if (span != null) { Object result = context.getReturnValue(); if (result != null) { span.setTag("db.result.type", result.getClass().getSimpleName()); } Throwable throwable = context.getThrowable(); if (throwable != null) { span.setTag("db.error", true); span.setTag("db.error.message", throwable.getMessage()); } span.finish(); } } } }

9. Thundra Aspect for Custom Monitoring

```java
package com.yourapp.monitoring.thundra;

import io.thundra.agent.trace.span.Span;
import io.thundra.agent.trace.span.SpanFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class ThundraMonitoringAspect {

@Around("@annotation(com.yourapp.monitoring.thundra.Monitored)") public Object monitorMethod(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String methodName = signature.getMethod().getDeclaringClass().getSimpleName() + "." + signature.getMethod().getName(); Span span = SpanFactory.createSpan("Aspect." + methodName); long startTime = System.nanoTime(); boolean success = false; try { // Set method context span.setTag("method.name", methodName); span.setTag("class.name", signature.getDeclaringTypeName()); span.setTag("aspect.monitored", true); // Log method arguments if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) { Map<String, Object> args = new HashMap<>(); for (int i = 0; i < joinPoint.getArgs().length; i++) { args.put("arg" + i, String.valueOf(joinPoint.getArgs()[i])); } span.log("MethodArguments", args); } // Execute method Object result = joinPoint.proceed(); success = true; return result; } catch (Throwable throwable) { span.setTag("error", true); span.setTag("error.type", throwable.getClass().getSimpleName()); span.setTag("error.message", throwable.getMessage()); throw throwable;

Leave a Reply

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


Macro Nepal Helper