Dynatrace OneAgent for Java

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.

Leave a Reply

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


Macro Nepal Helper