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