Dynatrace OneAgent in Java: Full-Stack Application Monitoring

Dynatrace OneAgent provides automatic, deep monitoring for Java applications with minimal configuration. It offers code-level visibility, distributed tracing, and AI-powered problem detection. While OneAgent operates largely automatically, Java developers can enhance monitoring through custom configuration, API integrations, and code-level instrumentation.

Understanding Dynatrace OneAgent Architecture

Key Components:

  • Automatic Instrumentation of JVM, frameworks, and technologies
  • PurePath Technology for distributed tracing
  • Smartscape for dependency mapping
  • Davis AI for problem detection and root cause analysis
  • OneAgent SDK for custom metrics and business events

Core Implementation Patterns

1. Project Setup and Dependencies

Configure Dynatrace dependencies and monitoring.

Maven Configuration:

<dependencies> <!-- Dynatrace OneAgent API --> <dependency> <groupId>com.dynatrace.oneagent</groupId> <artifactId>oneagent-sdk</artifactId> <version>1.9.0</version> </dependency> <!-- Micrometer for custom metrics --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> <version>1.11.5</version> </dependency> <!-- Spring Boot Actuator --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>3.1.0</version> </dependency> <!-- Web Framework --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.1.0</version> </dependency> <!-- Database --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.1.0</version> </dependency> </dependencies>

2. Dynatrace Configuration Models

Create configuration models for Dynatrace monitoring.

Configuration Models:

@Data @ConfigurationProperties(prefix = "dynatrace") public class DynatraceConfig { private boolean enabled = true; private String environmentId; private String apiToken; private String endpoint; private CustomMonitoringConfig customMonitoring = new CustomMonitoringConfig(); private BusinessEventConfig businessEvents = new BusinessEventConfig(); private LogMonitoringConfig logMonitoring = new LogMonitoringConfig(); public boolean isFullyConfigured() { return environmentId != null && !environmentId.trim().isEmpty() && apiToken != null && !apiToken.trim().isEmpty(); } } @Data public class CustomMonitoringConfig { private boolean enabled = true; private double samplingRate = 1.0; private List<String> excludedPackages = Arrays.asList( "org.springframework", "ch.qos.logback" ); private Map<String, String> customDimensions = new HashMap<>(); } @Data public class BusinessEventConfig { private boolean enabled = true; private List<String> capturedEvents = Arrays.asList( "user.login", "payment.processed", "order.completed" ); private Map<String, String> eventMappings = new HashMap<>(); } @Data public class LogMonitoringConfig { private boolean enabled = true; private List<String> monitoredLevels = Arrays.asList("ERROR", "WARN"); private boolean captureStackTraces = true; private int maxLogLength = 4096; } @Data public class CustomServiceRequest { private String serviceName; private String serviceMethod; private String serviceEndpoint; private Map<String, String> tags; private Map<String, Object> metrics; private Duration duration; public boolean isValid() { return serviceName != null && !serviceName.trim().isEmpty() && serviceMethod != null && !serviceMethod.trim().isEmpty(); } } @Data public class BusinessEvent { private String eventType; private Map<String, String> properties; private Map<String, Double> metrics; private Instant timestamp; public BusinessEvent(String eventType) { this.eventType = eventType; this.properties = new HashMap<>(); this.metrics = new HashMap<>(); this.timestamp = Instant.now(); } public BusinessEvent addProperty(String key, String value) { this.properties.put(key, value); return this; } public BusinessEvent addMetric(String key, double value) { this.metrics.put(key, value); return this; } }

3. OneAgent SDK Service

Implement core Dynatrace OneAgent SDK integration.

OneAgent Service:

@Service @Slf4j public class OneAgentService { private final OneAgentSDK oneAgent; private final DynatraceConfig config; private final boolean sdkAvailable; public OneAgentService(DynatraceConfig config) { this.config = config; this.oneAgent = createOneAgentSDK(); this.sdkAvailable = oneAgent != null; if (sdkAvailable) { log.info("Dynatrace OneAgent SDK initialized successfully"); } else { log.warn("Dynatrace OneAgent SDK not available - running in standalone mode"); } } private OneAgentSDK createOneAgentSDK() { try { return OneAgentSDKFactory.createInstance(); } catch (Exception e) { log.warn("Failed to initialize Dynatrace OneAgent SDK", e); return null; } } public void trackCustomService(CustomServiceRequest request) { if (!sdkAvailable || !config.isEnabled()) { return; } try { InProcessLink inProcessLink = oneAgent.createInProcessLink(); Tracer tracer = oneAgent.traceCustomService( request.getServiceName(), request.getServiceMethod(), OneAgentSDK.ChannelType.TCP_IP, request.getServiceEndpoint() ); tracer.start(); try { // Add custom dimensions if (request.getTags() != null) { request.getTags().forEach(tracer::setTag); } // Add custom metrics if (request.getMetrics() != null) { request.getMetrics().forEach((key, value) -> { if (value instanceof Number) { tracer.reportValue(key, ((Number) value).doubleValue()); } }); } // Simulate work duration if (request.getDuration() != null) { Thread.sleep(request.getDuration().toMillis()); } tracer.end(); } catch (Exception e) { tracer.error(e.getMessage()); tracer.end(); throw e; } } catch (Exception e) { log.warn("Failed to track custom service: {}", request.getServiceName(), e); } } public void recordBusinessEvent(BusinessEvent event) { if (!sdkAvailable || !config.getBusinessEvents().isEnabled()) { return; } try { oneAgent.identifyUser(event.getProperties().get("user.id")); // Report business event Map<String, String> stringProperties = new HashMap<>(event.getProperties()); oneAgent.reportEvent( event.getEventType(), event.getEventType(), // Using event type as name stringProperties, event.getMetrics() ); log.debug("Recorded business event: {}", event.getEventType()); } catch (Exception e) { log.warn("Failed to record business event: {}", event.getEventType(), e); } } public void addCustomRequestAttribute(String key, String value) { if (!sdkAvailable) return; try { oneAgent.addCustomRequestAttribute(key, value); } catch (Exception e) { log.warn("Failed to add custom request attribute: {}", key, e); } } public void setWebRequestContext(String context) { if (!sdkAvailable) return; try { oneAgent.setWebRequestContext(context); } catch (Exception e) { log.warn("Failed to set web request context: {}", context, e); } } public void traceSQLDatabaseRequest(String query, String database, String instance) { if (!sdkAvailable) return; try { DatabaseInfo dbInfo = oneAgent.createDatabaseInfo( database, OneAgentSDK.DatabaseVendor.MYSQL, // Adjust as needed instance ); Tracer tracer = oneAgent.traceSQLDatabaseRequest(dbInfo, query); tracer.start(); // The actual database call would happen here // tracer.end() would be called after database operation completes } catch (Exception e) { log.warn("Failed to trace SQL database request", e); } } public boolean isSdkAvailable() { return sdkAvailable; } public String getSDKState() { if (!sdkAvailable) return "UNAVAILABLE"; try { return oneAgent.getCurrentState().toString(); } catch (Exception e) { return "ERROR"; } } } @Component @Slf4j public class DynatraceContextManager { private final OneAgentService oneAgentService; private final ThreadLocal<Map<String, Object>> contextHolder = new ThreadLocal<>(); public DynatraceContextManager(OneAgentService oneAgentService) { this.oneAgentService = oneAgentService; } public void startRequestContext(String requestId, String userId, String sessionId) { Map<String, Object> context = new HashMap<>(); context.put("requestId", requestId); context.put("userId", userId); context.put("sessionId", sessionId); context.put("startTime", Instant.now()); contextHolder.set(context); // Set Dynatrace context oneAgentService.setWebRequestContext(requestId); oneAgentService.addCustomRequestAttribute("request.id", requestId); oneAgentService.addCustomRequestAttribute("user.id", userId); oneAgentService.addCustomRequestAttribute("session.id", sessionId); } public void endRequestContext() { Map<String, Object> context = contextHolder.get(); if (context != null) { Instant startTime = (Instant) context.get("startTime"); Duration duration = Duration.between(startTime, Instant.now()); oneAgentService.addCustomRequestAttribute("request.duration.ms", String.valueOf(duration.toMillis())); } contextHolder.remove(); } public void addRequestAttribute(String key, String value) { oneAgentService.addCustomRequestAttribute(key, value); Map<String, Object> context = contextHolder.get(); if (context != null) { context.put(key, value); } } public Optional<String> getRequestAttribute(String key) { Map<String, Object> context = contextHolder.get(); if (context != null && context.containsKey(key)) { return Optional.of(context.get(key).toString()); } return Optional.empty(); } }

4. Custom Metrics and Monitoring

Implement custom metrics and monitoring points.

Custom Metrics Service:

@Service @Slf4j public class DynatraceMetricsService { private final OneAgentService oneAgentService; private final MeterRegistry meterRegistry; private final Map<String, Counter> customCounters = new ConcurrentHashMap<>(); private final Map<String, Gauge> customGauges = new ConcurrentHashMap<>(); public DynatraceMetricsService(OneAgentService oneAgentService, MeterRegistry meterRegistry) { this.oneAgentService = oneAgentService; this.meterRegistry = meterRegistry; initializeDefaultMetrics(); } public void recordCustomMetric(String metricName, double value, Map<String, String> dimensions) { // Record to Micrometer (for applications without OneAgent) Timer.builder(metricName) .tags(convertDimensionsToTags(dimensions)) .register(meterRegistry) .record(Duration.ofMillis((long) value)); // Record to Dynatrace if SDK is available if (oneAgentService.isSdkAvailable()) { try { // Use OneAgent SDK to report custom metric // This would typically be done through the OneAgent SDK metrics API log.debug("Recorded custom metric: {} = {}", metricName, value); } catch (Exception e) { log.warn("Failed to record custom metric to Dynatrace: {}", metricName, e); } } } public void incrementCounter(String counterName, Map<String, String> dimensions) { Counter counter = customCounters.computeIfAbsent(counterName, name -> Counter.builder(name) .tags(convertDimensionsToTags(dimensions)) .register(meterRegistry) ); counter.increment(); } public void setGaugeValue(String gaugeName, double value, Map<String, String> dimensions) { // Gauges are more complex as they need to be updated // This is a simplified implementation AtomicDouble gaugeValue = new AtomicDouble(value); Gauge gauge = Gauge.builder(gaugeName, gaugeValue::get) .tags(convertDimensionsToTags(dimensions)) .register(meterRegistry); customGauges.put(gaugeName, gauge); } public void recordBusinessTransaction(String transactionName, boolean success, Duration duration) { Map<String, String> dimensions = new HashMap<>(); dimensions.put("transaction.name", transactionName); dimensions.put("success", String.valueOf(success)); recordCustomMetric("business.transaction.duration", duration.toMillis(), dimensions); incrementCounter("business.transaction.count", dimensions); // Record to Dynatrace as custom service CustomServiceRequest request = new CustomServiceRequest(); request.setServiceName("BusinessTransaction"); request.setServiceMethod(transactionName); request.setServiceEndpoint("internal"); request.setTags(dimensions); request.setMetrics(Map.of("duration", duration.toMillis())); oneAgentService.trackCustomService(request); } public void recordDatabaseMetrics(String operation, String table, Duration duration, boolean success) { Map<String, String> dimensions = new HashMap<>(); dimensions.put("db.operation", operation); dimensions.put("db.table", table); dimensions.put("success", String.valueOf(success)); recordCustomMetric("database.operation.duration", duration.toMillis(), dimensions); incrementCounter("database.operation.count", dimensions); } public void recordExternalServiceCall(String serviceName, String endpoint, Duration duration, int statusCode) { Map<String, String> dimensions = new HashMap<>(); dimensions.put("service.name", serviceName); dimensions.put("endpoint", endpoint); dimensions.put("status.code", String.valueOf(statusCode)); dimensions.put("success", String.valueOf(statusCode < 400)); recordCustomMetric("external.service.duration", duration.toMillis(), dimensions); incrementCounter("external.service.call.count", dimensions); } public void recordError(String errorType, String source, Map<String, String> context) { Map<String, String> dimensions = new HashMap<>(); dimensions.put("error.type", errorType); dimensions.put("source", source); if (context != null) { dimensions.putAll(context); } incrementCounter("application.errors", dimensions); // Report as custom event to Dynatrace BusinessEvent errorEvent = new BusinessEvent("application.error"); errorEvent.addProperty("errorType", errorType); errorEvent.addProperty("source", source); errorEvent.addMetric("error.count", 1); oneAgentService.recordBusinessEvent(errorEvent); } private Iterable<Tag> convertDimensionsToTags(Map<String, String> dimensions) { return dimensions.entrySet().stream() .map(entry -> Tag.of(entry.getKey(), entry.getValue())) .collect(Collectors.toList()); } private void initializeDefaultMetrics() { // JVM metrics new ClassLoaderMetrics().bindTo(meterRegistry); new JvmMemoryMetrics().bindTo(meterRegistry); new JvmGcMetrics().bindTo(meterRegistry); new ProcessorMetrics().bindTo(meterRegistry); new JvmThreadMetrics().bindTo(meterRegistry); // Application metrics Gauge.builder("application.uptime") .description("Application uptime in seconds") .tag("component", "application") .register(meterRegistry, new AtomicLong(0)); } @Scheduled(fixedRate = 60000) // Every minute public void updateUptimeMetric() { // This would update the uptime gauge // Implementation depends on how uptime is tracked } } @Component public class DynatraceWebConfiguration implements WebMvcConfigurer { private final DynatraceContextManager contextManager; private final DynatraceMetricsService metricsService; public DynatraceWebConfiguration(DynatraceContextManager contextManager, DynatraceMetricsService metricsService) { this.contextManager = contextManager; this.metricsService = metricsService; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new DynatraceWebInterceptor(contextManager, metricsService)); } } @Slf4j public class DynatraceWebInterceptor implements HandlerInterceptor { private final DynatraceContextManager contextManager; private final DynatraceMetricsService metricsService; public DynatraceWebInterceptor(DynatraceContextManager contextManager, DynatraceMetricsService metricsService) { this.contextManager = contextManager; this.metricsService = metricsService; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestId = UUID.randomUUID().toString(); String userId = extractUserId(request); String sessionId = request.getSession(false) != null ? request.getSession().getId() : "no-session"; contextManager.startRequestContext(requestId, userId, sessionId); // Add request details to context contextManager.addRequestAttribute("http.method", request.getMethod()); contextManager.addRequestAttribute("http.url", request.getRequestURL().toString()); contextManager.addRequestAttribute("http.query", request.getQueryString()); contextManager.addRequestAttribute("user.agent", request.getHeader("User-Agent")); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { try { // Record request metrics long startTime = (Long) request.getAttribute("requestStartTime"); long duration = System.currentTimeMillis() - startTime; boolean success = ex == null && response.getStatus() < 400; Map<String, String> dimensions = new HashMap<>(); dimensions.put("http.method", request.getMethod()); dimensions.put("http.path", request.getRequestURI()); dimensions.put("http.status", String.valueOf(response.getStatus())); dimensions.put("success", String.valueOf(success)); metricsService.recordCustomMetric("http.request.duration", duration, dimensions); metricsService.incrementCounter("http.request.count", dimensions); if (ex != null) { metricsService.recordError(ex.getClass().getSimpleName(), "web.request", dimensions); } } finally { contextManager.endRequestContext(); } } private String extractUserId(HttpServletRequest request) { // Extract user ID from authentication context // This is application-specific Principal principal = request.getUserPrincipal(); return principal != null ? principal.getName() : "anonymous"; } }

5. Database Monitoring Integration

Integrate database operations with Dynatrace monitoring.

Database Monitoring:

@Aspect @Component @Slf4j public class DatabaseMonitoringAspect { private final DynatraceMetricsService metricsService; private final OneAgentService oneAgentService; public DatabaseMonitoringAspect(DynatraceMetricsService metricsService, OneAgentService oneAgentService) { this.metricsService = metricsService; this.oneAgentService = oneAgentService; } @Around("execution(* org.springframework.data.repository.Repository+.*(..))") public Object monitorRepositoryMethods(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); String repositoryName = joinPoint.getTarget().getClass().getSimpleName(); long startTime = System.currentTimeMillis(); boolean success = false; try { Object result = joinPoint.proceed(); success = true; return result; } catch (Exception e) { success = false; throw e; } finally { long duration = System.currentTimeMillis() - startTime; Map<String, String> dimensions = new HashMap<>(); dimensions.put("repository", repositoryName); dimensions.put("method", methodName); dimensions.put("success", String.valueOf(success)); metricsService.recordDatabaseMetrics(methodName, repositoryName, Duration.ofMillis(duration), success); } } @Around("execution(* javax.persistence.EntityManager.*(..))") public Object monitorEntityManager(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); // Trace SQL operations through OneAgent if (oneAgentService.isSdkAvailable() && isSqlOperation(methodName)) { return traceSqlOperation(joinPoint, methodName); } return joinPoint.proceed(); } private Object traceSqlOperation(ProceedingJoinPoint joinPoint, String methodName) throws Throwable { Object[] args = joinPoint.getArgs(); String query = extractQuery(args, methodName); if (query != null) { oneAgentService.traceSQLDatabaseRequest(query, "application_db", "main"); } return joinPoint.proceed(); } private boolean isSqlOperation(String methodName) { return methodName.contains("createQuery") || methodName.contains("createNativeQuery") || methodName.contains("persist") || methodName.contains("merge") || methodName.contains("remove"); } private String extractQuery(Object[] args, String methodName) { if (args.length > 0 && args[0] instanceof String) { return (String) args[0]; } return methodName; // Fallback to method name } } @Component public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor { private final DynatraceMetricsService metricsService; public DatasourceProxyBeanPostProcessor(DynatraceMetricsService metricsService) { this.metricsService = metricsService; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof DataSource) { // Wrap datasource with monitoring proxy return createMonitoringDataSourceProxy((DataSource) bean); } return bean; } private DataSource createMonitoringDataSourceProxy(DataSource dataSource) { return (DataSource) Proxy.newProxyInstance( dataSource.getClass().getClassLoader(), new Class[]{DataSource.class}, new MonitoringDataSourceInvocationHandler(dataSource, metricsService) ); } private static class MonitoringDataSourceInvocationHandler implements InvocationHandler { private final DataSource delegate; private final DynatraceMetricsService metricsService; public MonitoringDataSourceInvocationHandler(DataSource delegate, DynatraceMetricsService metricsService) { this.delegate = delegate; this.metricsService = metricsService; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("getConnection".equals(method.getName())) { return monitorConnectionCreation(method, args); } return method.invoke(delegate, args); } private Object monitorConnectionCreation(Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); boolean success = false; try { Object result = method.invoke(delegate, args); success = true; return result; } finally { long duration = System.currentTimeMillis() - startTime; Map<String, String> dimensions = new HashMap<>(); dimensions.put("operation", "getConnection"); dimensions.put("success", String.valueOf(success)); metricsService.recordCustomMetric("datasource.connection.duration", duration, dimensions); } } } }

6. External Service Monitoring

Monitor external service calls and dependencies.

External Service Monitoring:

@Component @Slf4j public class ExternalServiceMonitor { private final DynatraceMetricsService metricsService; private final OneAgentService oneAgentService; public ExternalServiceMonitor(DynatraceMetricsService metricsService, OneAgentService oneAgentService) { this.metricsService = metricsService; this.oneAgentService = oneAgentService; } public <T> T executeWithMonitoring(String serviceName, String endpoint, ExternalServiceCall<T> call) { long startTime = System.currentTimeMillis(); boolean success = false; int statusCode = 0; try { T result = call.execute(); success = true; statusCode = 200; // Assuming success return result; } catch (Exception e) { success = false; statusCode = extractStatusCode(e); throw e; } finally { long duration = System.currentTimeMillis() - startTime; metricsService.recordExternalServiceCall(serviceName, endpoint, Duration.ofMillis(duration), statusCode); // Track as custom service in Dynatrace if (oneAgentService.isSdkAvailable()) { CustomServiceRequest request = new CustomServiceRequest(); request.setServiceName(serviceName); request.setServiceMethod(endpoint); request.setServiceEndpoint(serviceName + ":443"); // Assuming HTTPS request.setTags(Map.of( "http.status", String.valueOf(statusCode), "success", String.valueOf(success) )); request.setMetrics(Map.of("duration", (double) duration)); oneAgentService.trackCustomService(request); } } } public void monitorHttpCall(RestTemplate restTemplate, HttpRequest request, byte[] body, ClientHttpResponse response) { String serviceName = extractServiceName(request); String endpoint = request.getURI().getPath(); int statusCode = getStatusCode(response); boolean success = statusCode < 400; // This would be called from an interceptor Map<String, String> dimensions = new HashMap<>(); dimensions.put("service.name", serviceName); dimensions.put("endpoint", endpoint); dimensions.put("http.method", request.getMethod().name()); dimensions.put("http.status", String.valueOf(statusCode)); dimensions.put("success", String.valueOf(success)); metricsService.incrementCounter("http.client.call.count", dimensions); } private int extractStatusCode(Exception e) { if (e instanceof HttpStatusCodeException) { return ((HttpStatusCodeException) e).getStatusCode().value(); } return 500; // Internal server error } private String extractServiceName(HttpRequest request) { String host = request.getURI().getHost(); return host != null ? host : "unknown"; } private int getStatusCode(ClientHttpResponse response) { try { return response.getStatusCode().value(); } catch (Exception e) { return 0; } } @FunctionalInterface public interface ExternalServiceCall<T> { T execute() throws Exception; } } @Component public class MonitoringRestTemplateCustomizer implements RestTemplateCustomizer { private final ExternalServiceMonitor serviceMonitor; public MonitoringRestTemplateCustomizer(ExternalServiceMonitor serviceMonitor) { this.serviceMonitor = serviceMonitor; } @Override public void customize(RestTemplate restTemplate) { restTemplate.getInterceptors().add(new MonitoringClientHttpRequestInterceptor(serviceMonitor)); } private static class MonitoringClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { private final ExternalServiceMonitor serviceMonitor; public MonitoringClientHttpRequestInterceptor(ExternalServiceMonitor serviceMonitor) { this.serviceMonitor = serviceMonitor; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { long startTime = System.currentTimeMillis(); ClientHttpResponse response = null; boolean success = false; try { response = execution.execute(request, body); success = true; return response; } finally { long duration = System.currentTimeMillis() - startTime; int statusCode = response != null ? response.getStatusCode().value() : 0; serviceMonitor.monitorHttpCall(null, request, body, response); } } } }

7. Business Transaction Monitoring

Monitor business transactions and user journeys.

Business Transaction Service:

@Service @Slf4j public class BusinessTransactionService { private final DynatraceMetricsService metricsService; private final OneAgentService oneAgentService; private final ThreadLocal<BusinessTransaction> currentTransaction = new ThreadLocal<>(); public BusinessTransactionService(DynatraceMetricsService metricsService, OneAgentService oneAgentService) { this.metricsService = metricsService; this.oneAgentService = oneAgentService; } public BusinessTransaction startTransaction(String name, String type, String userId) { BusinessTransaction transaction = new BusinessTransaction(name, type, userId); currentTransaction.set(transaction); // Add to Dynatrace context oneAgentService.addCustomRequestAttribute("business.transaction", name); oneAgentService.addCustomRequestAttribute("business.transaction.type", type); oneAgentService.addCustomRequestAttribute("business.transaction.user", userId); log.info("Started business transaction: {} [{}] for user {}", name, type, userId); return transaction; } public void endTransaction(boolean success) { BusinessTransaction transaction = currentTransaction.get(); if (transaction != null) { transaction.end(success); recordTransactionMetrics(transaction); currentTransaction.remove(); log.info("Ended business transaction: {} - Success: {}", transaction.getName(), success); } } public void recordTransactionStep(String stepName, boolean success) { BusinessTransaction transaction = currentTransaction.get(); if (transaction != null) { transaction.addStep(stepName, success); // Record step as custom service CustomServiceRequest request = new CustomServiceRequest(); request.setServiceName("BusinessStep"); request.setServiceMethod(stepName); request.setTags(Map.of( "transaction", transaction.getName(), "step", stepName, "success", String.valueOf(success) )); oneAgentService.trackCustomService(request); } } public void recordTransactionError(String errorType, String errorMessage) { BusinessTransaction transaction = currentTransaction.get(); if (transaction != null) { transaction.recordError(errorType, errorMessage); Map<String, String> context = new HashMap<>(); context.put("transaction", transaction.getName()); context.put("user", transaction.getUserId()); metricsService.recordError(errorType, "business.transaction", context); } } private void recordTransactionMetrics(BusinessTransaction transaction) { metricsService.recordBusinessTransaction( transaction.getName(), transaction.isSuccess(), transaction.getDuration() ); // Record business event to Dynatrace BusinessEvent event = new BusinessEvent("business.transaction.completed"); event.addProperty("transaction.name", transaction.getName()); event.addProperty("transaction.type", transaction.getType()); event.addProperty("user.id", transaction.getUserId()); event.addProperty("success", String.valueOf(transaction.isSuccess())); event.addMetric("duration", transaction.getDuration().toMillis()); event.addMetric("step.count", transaction.getSteps().size()); event.addMetric("error.count", transaction.getErrors().size()); oneAgentService.recordBusinessEvent(event); } public Optional<BusinessTransaction> getCurrentTransaction() { return Optional.ofNullable(currentTransaction.get()); } } @Data public class BusinessTransaction { private final String name; private final String type; private final String userId; private final Instant startTime; private Instant endTime; private boolean success; private final List<TransactionStep> steps = new ArrayList<>(); private final List<TransactionError> errors = new ArrayList<>(); public BusinessTransaction(String name, String type, String userId) { this.name = name; this.type = type; this.userId = userId; this.startTime = Instant.now(); } public void end(boolean success) { this.endTime = Instant.now(); this.success = success; } public void addStep(String stepName, boolean success) { steps.add(new TransactionStep(stepName, success, Instant.now())); } public void recordError(String errorType, String errorMessage) { errors.add(new TransactionError(errorType, errorMessage, Instant.now())); } public Duration getDuration() { Instant end = endTime != null ? endTime : Instant.now(); return Duration.between(startTime, end); } @Data public static class TransactionStep { private final String name; private final boolean success; private final Instant timestamp; public TransactionStep(String name, boolean success, Instant timestamp) { this.name = name; this.success = success; this.timestamp = timestamp; } } @Data public static class TransactionError { private final String type; private final String message; private final Instant timestamp; public TransactionError(String type, String message, Instant timestamp) { this.type = type; this.message = message; this.timestamp = timestamp; } } } @Aspect @Component @Slf4j public class BusinessTransactionAspect { private final BusinessTransactionService transactionService; public BusinessTransactionAspect(BusinessTransactionService transactionService) { this.transactionService = transactionService; } @Around("@annotation(MonitorBusinessTransaction)") public Object monitorBusinessTransaction(ProceedingJoinPoint joinPoint) throws Throwable { MonitorBusinessTransaction annotation = getAnnotation(joinPoint); String transactionName = annotation.value(); String transactionType = annotation.type(); String userId = extractUserId(joinPoint); BusinessTransaction transaction = transactionService .startTransaction(transactionName, transactionType, userId); boolean success = false; try { Object result = joinPoint.proceed(); success = true; return result; } catch (Exception e) { transactionService.recordTransactionError( e.getClass().getSimpleName(), e.getMessage()); throw e; } finally { transactionService.endTransaction(success); } } private MonitorBusinessTransaction getAnnotation(ProceedingJoinPoint joinPoint) { Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); return method.getAnnotation(MonitorBusinessTransaction.class); } private String extractUserId(ProceedingJoinPoint joinPoint) { // Extract user ID from method arguments or security context // This is application-specific Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof String) { // Simple heuristic - adjust based on your application String str = (String) arg; if (str.length() == 36 && str.contains("-")) { // Looks like UUID return str; } } } return "unknown"; } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MonitorBusinessTransaction { String value(); String type() default "GENERAL"; }

8. Configuration and Management

Manage Dynatrace configuration and provide operational endpoints.

Configuration Controller:

@RestController @RequestMapping("/api/dynatrace") @Slf4j public class DynatraceConfigurationController { private final OneAgentService oneAgentService; private final DynatraceMetricsService metricsService; private final BusinessTransactionService transactionService; private final DynatraceConfig config; public DynatraceConfigurationController(OneAgentService oneAgentService, DynatraceMetricsService metricsService, BusinessTransactionService transactionService, DynatraceConfig config) { this.oneAgentService = oneAgentService; this.metricsService = metricsService; this.transactionService = transactionService; this.config = config; } @GetMapping("/status") public ResponseEntity<Map<String, Object>> getStatus() { Map<String, Object> status = new HashMap<>(); status.put("enabled", config.isEnabled()); status.put("sdkAvailable", oneAgentService.isSdkAvailable()); status.put("sdkState", oneAgentService.getSDKState()); status.put("environmentId", config.getEnvironmentId()); status.put("fullyConfigured", config.isFullyConfigured()); return ResponseEntity.ok(status); } @PostMapping("/custom-service") public ResponseEntity<String> recordCustomService(@RequestBody CustomServiceRequest request) { if (!request.isValid()) { return ResponseEntity.badRequest().body("Invalid custom service request"); } oneAgentService.trackCustomService(request); return ResponseEntity.ok("Custom service recorded"); } @PostMapping("/business-event") public ResponseEntity<String> recordBusinessEvent(@RequestBody BusinessEventRequest eventRequest) { BusinessEvent event = new BusinessEvent(eventRequest.getEventType()); if (eventRequest.getProperties() != null) { eventRequest.getProperties().forEach(event::addProperty); } if (eventRequest.getMetrics() != null) { eventRequest.getMetrics().forEach(event::addMetric); } oneAgentService.recordBusinessEvent(event); return ResponseEntity.ok("Business event recorded"); } @PostMapping("/custom-metric") public ResponseEntity<String> recordCustomMetric(@RequestBody CustomMetricRequest metricRequest) { metricsService.recordCustomMetric( metricRequest.getMetricName(), metricRequest.getValue(), metricRequest.getDimensions() ); return ResponseEntity.ok("Custom metric recorded"); } @GetMapping("/metrics/summary") public ResponseEntity<Map<String, Object>> getMetricsSummary() { // This would provide a summary of custom metrics // Implementation depends on metrics storage Map<String, Object> summary = new HashMap<>(); summary.put("timestamp", Instant.now()); summary.put("status", "metrics_summary_not_implemented"); return ResponseEntity.ok(summary); } @PostMapping("/test-transaction") public ResponseEntity<String> testBusinessTransaction() { try { BusinessTransaction transaction = transactionService.startTransaction( "test-transaction", "TEST", "test-user"); // Simulate some work transactionService.recordTransactionStep("step-1", true); Thread.sleep(100); transactionService.recordTransactionStep("step-2", true); transactionService.endTransaction(true); return ResponseEntity.ok("Test transaction completed"); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Test transaction failed: " + e.getMessage()); } } } @Data class BusinessEventRequest { private String eventType; private Map<String, String> properties; private Map<String, Double> metrics; } @Data class CustomMetricRequest { private String metricName; private double value; private Map<String, String> dimensions; }

Best Practices for Dynatrace OneAgent in Java

  1. Automatic vs Custom: Leverage automatic instrumentation first, add custom monitoring for business context
  2. Context Propagation: Ensure transaction context flows across service boundaries
  3. Metric Design: Design meaningful custom metrics with proper dimensions
  4. Performance Impact: Monitor OneAgent performance overhead
  5. Security: Secure Dynatrace API tokens and sensitive data in custom attributes
  6. Documentation: Document custom monitoring points and business transactions
  7. Testing: Test monitoring in non-production environments first

Conclusion: Comprehensive Application Observability

Dynatrace OneAgent in Java provides unparalleled visibility into application performance, user experience, and business impact. By combining automatic instrumentation with strategic custom monitoring, organizations can achieve full-stack observability that drives operational excellence and business value.

This implementation demonstrates that effective Dynatrace integration goes beyond basic setup—it's about creating a cohesive monitoring strategy that connects technical metrics to business outcomes, enabling proactive problem detection and data-driven decision making.

Leave a Reply

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


Macro Nepal Helper