Introduction
Dynatrace OneAgent is an all-in-one monitoring agent that provides automatic instrumentation for Java applications. It delivers full-stack observability, including application performance monitoring (APM), infrastructure monitoring, and digital experience monitoring.
Architecture Overview
OneAgent Components
OneAgent Architecture: ┌─────────────────────────────────────────────────────────────┐ │ Application Code │ ├─────────────────────────────────────────────────────────────┤ │ Java Virtual Machine │ ├─────────────────────────────────────────────────────────────┤ │ OneAgent (Automatic Instrumentation) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ Bytecode │ │ PurePaths │ │ Metric Collection │ │ │ │ Injection │ │ Technology │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ Operating System │ └─────────────────────────────────────────────────────────────┘
Installation and Setup
1. Download and Installation
# Download OneAgent (via Dynatrace UI or API) wget -O Dynatrace-OneAgent-Linux.sh \ "https://{your-environment}.live.dynatrace.com/api/v1/deployment/installer/agent/UnixApi/oneagent/latest?Api-Token={your-token}" # Install OneAgent sudo /bin/sh Dynatrace-OneAgent-Linux.sh # Verify installation sudo /opt/dynatrace/oneagent/agent/lib64/dtanalyzer --version 2. Java Application Integration
Option A: Automatic Injection (Recommended)
# Start application with OneAgent java -jar myapp.jar # OneAgent automatically injects itself via LD_PRELOAD
Option B: Manual Java Agent
# Explicit Java agent configuration java -javaagent:/opt/dynatrace/oneagent/agent/lib64/dtagent.so \ -jar myapp.jar # With additional properties java -javaagent:/opt/dynatrace/oneagent/agent/lib64/dtagent.so=server=https://your-env.live.dynatrace.com,tenant=your-tenant,token=your-token \ -jar myapp.jar
Option C: Docker Container
FROM openjdk:11-jre-slim # Install OneAgent RUN wget -O /tmp/Dynatrace-OneAgent-Linux.sh \ "https://{env}.live.dynatrace.com/api/v1/deployment/installer/agent/UnixApi/oneagent/latest?Api-Token={token}" && \ sh /tmp/Dynatrace-OneAgent-Linux.sh && \ rm /tmp/Dynatrace-OneAgent-Linux.sh # Set OneAgent path ENV LD_PRELOAD /opt/dynatrace/oneagent/agent/lib64/liboneagentproc.so # Your application COPY target/myapp.jar /app/myapp.jar CMD ["java", "-jar", "/app/myapp.jar"] Configuration
1. OneAgent Configuration File
# /opt/dynatrace/oneagent/agent/conf/oneagent.properties # Basic configuration
[oneagent]
tenant = your-tenant-id tenanttoken = your-tenant-token server = https://your-env.live.dynatrace.com # Performance settings
[com.dynatrace.oneagent]
cpu_limit = 10 memory_limit = 256 # Logging configuration
[log]
level = info file = /opt/dynatrace/oneagent/agent/log/oneagent.log max_size = 10 max_files = 3 # Java-specific settings
[java]
session_replay_enabled = true enable_custom_service_ detection = true # HTTP monitoring
[http]
url_include_pattern = * url_exclude_pattern = /health,/metrics
2. Environment Variables
# Required environment variables export DT_TENANT=your-tenant-id export DT_TENANTTOKEN=your-tenant-token export DT_CONNECTION_POINT=https://your-env.live.dynatrace.com # Optional configurations export DT_LOGGING_LEVEL=info export DT_CUSTOM_PROP="Environment=Production,Application=MyApp" export DT_DEPLOYMENT_METADATA="DeploymentVersion=1.0.0"
Automatic Instrumentation
Supported Frameworks and Technologies
Dynatrace OneAgent automatically instruments:
- Web Frameworks: Spring Boot, Spring MVC, JAX-RS, Servlets
- Databases: JDBC, JPA/Hibernate, MongoDB, Redis
- Messaging: JMS, Kafka, RabbitMQ
- HTTP Clients: Apache HttpClient, OkHttp, RestTemplate
- Application Servers: Tomcat, Jetty, WebSphere, WildFly
Example Instrumented Application
@SpringBootApplication @RestController public class InstrumentedApplication { private final UserRepository userRepository; private final RestTemplate restTemplate; public InstrumentedApplication(UserRepository userRepository) { this.userRepository = userRepository; this.restTemplate = new RestTemplate(); } // Automatically monitored HTTP endpoint @GetMapping("/users/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { // Database call automatically monitored User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException(id)); // External HTTP call automatically monitored ResponseEntity<Profile> profile = restTemplate.getForEntity( "https://api.example.com/profiles/" + user.getProfileId(), Profile.class); return ResponseEntity.ok(user); } // Automatically monitored service method @Service @Transactional public User updateUser(Long id, User updatedUser) { // Database transaction automatically monitored User user = userRepository.findById(id) .orElseThrow(() -> new UserNotFoundException(id)); user.setName(updatedUser.getName()); user.setEmail(updatedUser.getEmail()); return userRepository.save(user); } } // Custom exception class UserNotFoundException extends RuntimeException { public UserNotFoundException(Long id) { super("User not found: " + id); } } Custom Instrumentation
1. Custom Service Methods
import com.dynatrace.oneagent.sdk.OneAgentSDKFactory; import com.dynatrace.oneagent.sdk.api.IncomingRemoteCallTracer; import com.dynatrace.oneagent.sdk.api.OutgoingRemoteCallTracer; import com.dynatrace.oneagent.sdk.api.Tracer; import com.dynatrace.oneagent.sdk.api.enums.ChannelType; @Service public class PaymentService { private final OneAgentSDK oneAgent; private final PaymentGatewayClient gatewayClient; public PaymentService(PaymentGatewayClient gatewayClient) { this.oneAgent = OneAgentSDKFactory.createInstance(); this.gatewayClient = gatewayClient; } public Payment processPayment(PaymentRequest request) { // Create custom method tracer Tracer tracer = oneAgent.traceCustomMethod("processPayment"); try { tracer.start(); // Validate payment validatePayment(request); // Trace external service call OutgoingRemoteCallTracer remoteTracer = oneAgent.traceOutgoingRemoteCall( "PaymentGateway", "processPayment", "https://payment-gateway.com/api", ChannelType.TCP_IP); remoteTracer.start(); try { PaymentResponse response = gatewayClient.processPayment(request); remoteTracer.setRemoteServiceName("PaymentGatewayService"); remoteTracer.setRemoteCallMethod("POST"); remoteTracer.setRemoteCallUrl("https://payment-gateway.com/api/payments"); return mapToPayment(response); } finally { remoteTracer.end(); } } catch (Exception e) { tracer.error(e.getMessage()); throw e; } finally { tracer.end(); } } // Custom database operation tracing @Transactional public void updatePaymentStatus(Long paymentId, PaymentStatus status) { Tracer tracer = oneAgent.traceCustomMethod("updatePaymentStatus"); try { tracer.start(); // This database call will be automatically instrumented Payment payment = paymentRepository.findById(paymentId) .orElseThrow(() -> new PaymentNotFoundException(paymentId)); payment.setStatus(status); paymentRepository.save(payment); // Add custom attributes tracer.addTag("paymentId", paymentId.toString()); tracer.addTag("newStatus", status.name()); } catch (Exception e) { tracer.error(e.getMessage()); throw e; } finally { tracer.end(); } } } 2. Custom Web Request Tracing
@RestController @RequestMapping("/api") public class CustomTracedController { private final OneAgentSDK oneAgent; public CustomTracedController() { this.oneAgent = OneAgentSDKFactory.createInstance(); } @PostMapping("/webhook") public ResponseEntity<String> handleWebhook( @RequestHeader Map<String, String> headers, @RequestBody String payload) { // Create incoming web request tracer IncomingRemoteCallTracer tracer = oneAgent.traceIncomingRemoteCall( "CustomWebhook", "handleWebhook", "WebhookEndpoint", ChannelType.TCP_IP); try { tracer.start(); // Set request headers for better context String traceId = headers.get("X-Trace-Id"); if (traceId != null) { tracer.setTraceContext(traceId); } // Process webhook processWebhookPayload(payload); tracer.setRemoteServiceName("WebhookSource"); return ResponseEntity.ok("Webhook processed successfully"); } catch (Exception e) { tracer.error(e.getMessage()); return ResponseEntity.status(500).body("Error processing webhook"); } finally { tracer.end(); } } private void processWebhookPayload(String payload) { // Custom business logic Tracer tracer = oneAgent.traceCustomMethod("processWebhookPayload"); try { tracer.start(); // Processing logic here } finally { tracer.end(); } } } 3. Custom Database Query Monitoring
@Repository public class CustomUserRepository { private final JdbcTemplate jdbcTemplate; private final OneAgentSDK oneAgent; public CustomUserRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; this.oneAgent = OneAgentSDKFactory.createInstance(); } public List<User> findUsersByComplexCriteria(UserCriteria criteria) { Tracer tracer = oneAgent.traceCustomMethod("findUsersByComplexCriteria"); try { tracer.start(); // Custom SQL that might not be automatically instrumented String sql = buildComplexQuery(criteria); tracer.addTag("queryType", "complexSearch"); tracer.addTag("criteriaCount", String.valueOf(criteria.getFieldCount())); return jdbcTemplate.query(sql, this::mapRowToUser); } finally { tracer.end(); } } public void batchInsertUsers(List<User> users) { Tracer tracer = oneAgent.traceCustomMethod("batchInsertUsers"); try { tracer.start(); tracer.addTag("batchSize", String.valueOf(users.size())); // Batch operation jdbcTemplate.batchUpdate( "INSERT INTO users (name, email) VALUES (?, ?)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { User user = users.get(i); ps.setString(1, user.getName()); ps.setString(2, user.getEmail()); } @Override public int getBatchSize() { return users.size(); } }); } finally { tracer.end(); } } } Custom Metrics and Business Analytics
1. Custom Business Metrics
@Service public class OrderMetricsService { private final OneAgentSDK oneAgent; public OrderMetricsService() { this.oneAgent = OneAgentSDKFactory.createInstance(); } public void recordOrderMetrics(Order order) { // Record custom business metrics oneAgent.reportCustomMetric("orders.total", "count", 1); oneAgent.reportCustomMetric("orders.value", "USD", order.getTotalAmount()); oneAgent.reportCustomMetric("orders.items", "count", order.getItemCount()); // Dimension-based metrics Map<String, String> dimensions = new HashMap<>(); dimensions.put("customerType", order.getCustomerType()); dimensions.put("paymentMethod", order.getPaymentMethod()); dimensions.put("region", order.getRegion()); oneAgent.reportCustomMetric("orders.by_type", "count", 1, dimensions); } public void recordProcessingTime(String operation, long durationMs) { oneAgent.reportCustomMetric("processing.time", "milliseconds", durationMs); Map<String, String> dimensions = new HashMap<>(); dimensions.put("operation", operation); dimensions.put("environment", System.getenv().getOrDefault("ENV", "unknown")); oneAgent.reportCustomMetric("operation.duration", "milliseconds", durationMs, dimensions); } } 2. Custom Event Reporting
@Component public class BusinessEventReporter { private final OneAgentSDK oneAgent; public BusinessEventReporter() { this.oneAgent = OneAgentSDKFactory.createInstance(); } public void reportUserRegistration(User user) { Map<String, String> attributes = new HashMap<>(); attributes.put("userId", user.getId().toString()); attributes.put("registrationSource", user.getRegistrationSource()); attributes.put("userType", user.getType()); attributes.put("timestamp", Instant.now().toString()); oneAgent.reportCustomEvent("UserRegistration", attributes); } public void reportPaymentEvent(Payment payment, String eventType) { Map<String, String> attributes = new HashMap<>(); attributes.put("paymentId", payment.getId().toString()); attributes.put("amount", payment.getAmount().toString()); attributes.put("currency", payment.getCurrency()); attributes.put("status", payment.getStatus().name()); attributes.put("eventType", eventType); attributes.put("timestamp", Instant.now().toString()); oneAgent.reportCustomEvent("PaymentEvent", attributes); } public void reportErrorEvent(String component, String errorType, String message) { Map<String, String> attributes = new HashMap<>(); attributes.put("component", component); attributes.put("errorType", errorType); attributes.put("message", message); attributes.put("environment", System.getenv().getOrDefault("ENV", "unknown")); attributes.put("timestamp", Instant.now().toString()); oneAgent.reportCustomEvent("ErrorEvent", attributes); } } Advanced Configuration
1. Custom OneAgent Configuration
@Configuration public class DynatraceConfig { @Bean public OneAgentSDK oneAgentSDK() { OneAgentSDK sdk = OneAgentSDKFactory.createInstance(); // Wait for initialization try { sdk.setLoggingCallback(new LoggingCallback() { @Override public void warn(String message) { System.out.println("Dynatrace WARN: " + message); } @Override public void error(String message) { System.err.println("Dynatrace ERROR: " + message); } }); } catch (Exception e) { System.err.println("Failed to initialize Dynatrace OneAgent SDK: " + e.getMessage()); } return sdk; } } // Custom logging callback @Component public class CustomDynatraceLoggingCallback implements LoggingCallback { private static final Logger logger = LoggerFactory.getLogger(CustomDynatraceLoggingCallback.class); @Override public void warn(String message) { logger.warn("Dynatrace: {}", message); } @Override public void error(String message) { logger.error("Dynatrace: {}", message); } } 2. Request Filter for Additional Context
@Component public class DynatraceContextFilter implements WebFilter { private final OneAgentSDK oneAgent; public DynatraceContextFilter(OneAgentSDK oneAgent) { this.oneAgent = oneAgent; } @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { // Add custom request context oneAgent.addCustomRequestAttribute("requestId", exchange.getRequest().getId()); oneAgent.addCustomRequestAttribute("userAgent", exchange.getRequest().getHeaders().getFirst("User-Agent")); oneAgent.addCustomRequestAttribute("clientIp", getClientIp(exchange.getRequest())); // Add business context if available String userId = getUserIdFromRequest(exchange.getRequest()); if (userId != null) { oneAgent.setUser(userId); } return chain.filter(exchange); } private String getClientIp(ServerHttpRequest request) { // Extract client IP from headers String xForwardedFor = request.getHeaders().getFirst("X-Forwarded-For"); if (xForwardedFor != null && !xForwardedFor.isEmpty()) { return xForwardedFor.split(",")[0].trim(); } return request.getRemoteAddress() != null ? request.getRemoteAddress().getAddress().getHostAddress() : "unknown"; } private String getUserIdFromRequest(ServerHttpRequest request) { // Extract user ID from JWT or session // Implementation depends on your authentication mechanism return null; } } Troubleshooting and Diagnostics
1. Diagnostic Endpoints
@RestController @RequestMapping("/diagnostics") public class DiagnosticsController { private final OneAgentSDK oneAgent; public DiagnosticsController() { this.oneAgent = OneAgentSDKFactory.createInstance(); } @GetMapping("/dynatrace/status") public ResponseEntity<Map<String, Object>> getDynatraceStatus() { Map<String, Object> status = new HashMap<>(); try { status.put("sdkInitialized", true); status.put("agentVersion", getAgentVersion()); status.put("connectionStatus", "ACTIVE"); // This would require custom logic } catch (Exception e) { status.put("sdkInitialized", false); status.put("error", e.getMessage()); } return ResponseEntity.ok(status); } @GetMapping("/dynatrace/logs") public ResponseEntity<List<String>> getRecentLogs() { // Read and return recent Dynatrace logs try { Path logPath = Paths.get("/opt/dynatrace/oneagent/agent/log/oneagent.log"); if (Files.exists(logPath)) { List<String> logs = Files.readAllLines(logPath) .stream() .limit(100) // Last 100 lines .collect(Collectors.toList()); return ResponseEntity.ok(logs); } } catch (IOException e) { // Log error but don't expose details } return ResponseEntity.notFound().build(); } private String getAgentVersion() { // This would require accessing internal OneAgent APIs // or reading from configuration files return "Unknown"; } } 2. Health Check Integration
@Component public class DynatraceHealthIndicator implements HealthIndicator { @Override public Health health() { try { // Check if OneAgent is properly initialized OneAgentSDK sdk = OneAgentSDKFactory.createInstance(); // Add custom health checks here boolean agentActive = checkAgentActive(); if (agentActive) { return Health.up() .withDetail("agent", "active") .withDetail("version", "1.0.0") // Replace with actual version check .build(); } else { return Health.down() .withDetail("agent", "inactive") .withDetail("message", "OneAgent not properly initialized") .build(); } } catch (Exception e) { return Health.down(e).build(); } } private boolean checkAgentActive() { // Implement actual OneAgent health check // This could check for log files, process status, etc. return true; } } Best Practices
1. Performance Optimization
@Service public class OptimizedTracingService { private final OneAgentSDK oneAgent; public OptimizedTracingService() { this.oneAgent = OneAgentSDKFactory.createInstance(); } // Avoid excessive tracing in tight loops public void processBatch(List<Item> items) { // Trace the entire batch instead of individual items Tracer batchTracer = oneAgent.traceCustomMethod("processBatch"); try { batchTracer.start(); batchTracer.addTag("batchSize", String.valueOf(items.size())); for (Item item : items) { processItem(item); // No individual tracing for performance } } finally { batchTracer.end(); } } // Use sampling for high-volume operations public void highVolumeOperation(Data data) { // Sample 1% of operations to reduce overhead if (Math.random() < 0.01) { Tracer tracer = oneAgent.traceCustomMethod("highVolumeOperation"); try { tracer.start(); performOperation(data); } finally { tracer.end(); } } else { performOperation(data); } } private void processItem(Item item) { // Fast processing without tracing } private void performOperation(Data data) { // Operation logic } } 2. Security Considerations
@Component public class SecureTracingService { private final OneAgentSDK oneAgent; public SecureTracingService() { this.oneAgent = OneAgentSDKFactory.createInstance(); } public void processSensitiveData(SensitiveRequest request) { Tracer tracer = oneAgent.traceCustomMethod("processSensitiveData"); try { tracer.start(); // Don't include sensitive data in tags tracer.addTag("operation", "sensitiveProcessing"); tracer.addTag("requestType", request.getType()); // Avoid: tracer.addTag("ssn", request.getSsn()); // DON'T DO THIS // Process data without exposing sensitive information String result = secureProcessing(request); } finally { tracer.end(); } } public void handleUserData(User user) { // Use hashed or masked data for user identification String hashedUserId = hashUserId(user.getId()); oneAgent.setUser(hashedUserId); // Avoid sending PII in custom attributes Map<String, String> safeAttributes = new HashMap<>(); safeAttributes.put("userRole", user.getRole()); safeAttributes.put("accountTier", user.getTier()); // Avoid: safeAttributes.put("email", user.getEmail()); // DON'T DO THIS } private String hashUserId(Long userId) { // Implement secure hashing return String.valueOf(userId.hashCode()); } } Conclusion
Dynatrace OneAgent for Java provides comprehensive automatic instrumentation out-of-the-box while offering extensive customization capabilities through its SDK. Key benefits include:
- Automatic Discovery: No code changes needed for basic monitoring
- Full-Stack Visibility: End-to-end transaction tracing
- Custom Instrumentation: Fine-grained control over what to monitor
- Business Analytics: Custom metrics and events for business insights
- Minimal Performance Impact: Optimized for production use
By following these patterns and best practices, you can effectively monitor your Java applications while maintaining performance and security standards.