JFR Event Correlation with Logs in Java

Java Flight Recorder (JFR) provides detailed runtime information, but correlating JFR events with application logs creates a powerful observability story. This comprehensive guide covers techniques for correlating JFR events with logging frameworks to create unified diagnostics.

Understanding JFR Event and Log Correlation

Key Concepts:

  • Event Correlation IDs: Unique identifiers linking logs and JFR events
  • Structured Logging: JSON-formatted logs with correlation metadata
  • JFR Custom Events: Application-specific events with correlation context
  • MDC (Mapped Diagnostic Context): Thread-local storage for correlation IDs
  • Unified Diagnostics: Combined JFR and log analysis

Project Setup and Dependencies

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- JFR API -->
<dependency>
<groupId>jdk.jfr</groupId>
<artifactId>jdk.jfr</artifactId>
<version>${java.version}</version>
</dependency>
<!-- Logback with JSON support -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
</dependency>
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-json-classic</artifactId>
<version>0.1.5</version>
</dependency>
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-jackson</artifactId>
<version>0.1.5</version>
</dependency>
<!-- SLF4J MDC -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>

Core Correlation Framework

package com.example.jfr.correlation;
import jdk.jfr.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Core framework for correlating JFR events with application logs
*/
public class JFRLogCorrelation {
private static final Logger logger = LoggerFactory.getLogger(JFRLogCorrelation.class);
private static final String CORRELATION_ID_KEY = "correlationId";
private static final String TRACE_ID_KEY = "traceId";
private static final String SPAN_ID_KEY = "spanId";
private static final AtomicLong correlationIdCounter = new AtomicLong();
private static final ThreadLocal<CorrelationContext> currentContext = 
new ThreadLocal<>();
/**
* Correlation context holding tracing information
*/
public static class CorrelationContext {
private final String correlationId;
private final String traceId;
private final String spanId;
private final long startTime;
private final Map<String, String> customAttributes;
public CorrelationContext(String correlationId, String traceId, String spanId) {
this.correlationId = correlationId;
this.traceId = traceId;
this.spanId = spanId;
this.startTime = System.currentTimeMillis();
this.customAttributes = new HashMap<>();
}
public void setAttribute(String key, String value) {
customAttributes.put(key, value);
}
public String getAttribute(String key) {
return customAttributes.get(key);
}
// Getters
public String getCorrelationId() { return correlationId; }
public String getTraceId() { return traceId; }
public String getSpanId() { return spanId; }
public long getStartTime() { return startTime; }
public Map<String, String> getCustomAttributes() { return customAttributes; }
@Override
public String toString() {
return String.format("CorrelationContext[correlationId=%s, traceId=%s, spanId=%s]", 
correlationId, traceId, spanId);
}
}
/**
* Starts a new correlation context
*/
public static CorrelationContext startContext() {
String correlationId = generateCorrelationId();
String traceId = generateTraceId();
String spanId = generateSpanId();
return startContext(correlationId, traceId, spanId);
}
/**
* Starts a correlation context with provided IDs
*/
public static CorrelationContext startContext(String correlationId, String traceId, String spanId) {
CorrelationContext context = new CorrelationContext(correlationId, traceId, spanId);
currentContext.set(context);
// Set MDC for logging
MDC.put(CORRELATION_ID_KEY, correlationId);
MDC.put(TRACE_ID_KEY, traceId);
MDC.put(SPAN_ID_KEY, spanId);
// Emit JFR event for context start
CorrelationStartEvent event = new CorrelationStartEvent();
event.correlationId = correlationId;
event.traceId = traceId;
event.spanId = spanId;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.info("Started correlation context: correlationId={}, traceId={}, spanId={}", 
correlationId, traceId, spanId);
return context;
}
/**
* Ends the current correlation context
*/
public static void endContext() {
CorrelationContext context = currentContext.get();
if (context != null) {
endContext(context);
}
}
/**
* Ends a specific correlation context
*/
public static void endContext(CorrelationContext context) {
long duration = System.currentTimeMillis() - context.getStartTime();
// Emit JFR event for context end
CorrelationEndEvent event = new CorrelationEndEvent();
event.correlationId = context.getCorrelationId();
event.traceId = context.getTraceId();
event.spanId = context.getSpanId();
event.durationMs = duration;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.info("Ended correlation context: correlationId={}, duration={}ms", 
context.getCorrelationId(), duration);
// Clear MDC
MDC.remove(CORRELATION_ID_KEY);
MDC.remove(TRACE_ID_KEY);
MDC.remove(SPAN_ID_KEY);
currentContext.remove();
}
/**
* Gets the current correlation context
*/
public static CorrelationContext getCurrentContext() {
return currentContext.get();
}
/**
* Runs a task within a correlation context
*/
public static <T> T runWithContext(RunnableWithReturn<T> task) {
CorrelationContext context = startContext();
try {
return task.run();
} finally {
endContext(context);
}
}
/**
* Runs a task within a correlation context
*/
public static void runWithContext(Runnable task) {
CorrelationContext context = startContext();
try {
task.run();
} finally {
endContext(context);
}
}
/**
* Records a business event with correlation context
*/
public static void recordBusinessEvent(String eventType, String description, 
Map<String, String> attributes) {
CorrelationContext context = getCurrentContext();
if (context != null) {
BusinessEvent event = new BusinessEvent();
event.eventType = eventType;
event.description = description;
event.correlationId = context.getCorrelationId();
event.traceId = context.getTraceId();
event.spanId = context.getSpanId();
event.timestamp = System.currentTimeMillis();
if (attributes != null) {
event.attributes = String.join(";", 
attributes.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.toArray(String[]::new)
);
}
event.commit();
// Also log the business event
logger.info("Business event: type={}, description={}, attributes={}", 
eventType, description, attributes);
}
}
/**
* Records a method execution with timing
*/
public static void recordMethodExecution(String className, String methodName, 
long durationNanos, String outcome) {
CorrelationContext context = getCurrentContext();
if (context != null) {
MethodExecutionEvent event = new MethodExecutionEvent();
event.className = className;
event.methodName = methodName;
event.durationNanos = durationNanos;
event.outcome = outcome;
event.correlationId = context.getCorrelationId();
event.traceId = context.getTraceId();
event.commit();
logger.debug("Method execution: {}.{}, duration={}ns, outcome={}", 
className, methodName, durationNanos, outcome);
}
}
// ID generation utilities
private static String generateCorrelationId() {
return "corr-" + System.currentTimeMillis() + "-" + correlationIdCounter.incrementAndGet();
}
private static String generateTraceId() {
return UUID.randomUUID().toString();
}
private static String generateSpanId() {
return Long.toHexString(System.nanoTime());
}
@FunctionalInterface
public interface RunnableWithReturn<T> {
T run();
}
// JFR Event Definitions
@Name("com.example.CorrelationStart")
@Label("Correlation Context Start")
@Category("Correlation")
@Description("Records the start of a correlation context")
public static class CorrelationStartEvent extends Event {
@Label("Correlation ID")
public String correlationId;
@Label("Trace ID")
public String traceId;
@Label("Span ID")
public String spanId;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.CorrelationEnd")
@Label("Correlation Context End")
@Category("Correlation")
@Description("Records the end of a correlation context")
public static class CorrelationEndEvent extends Event {
@Label("Correlation ID")
public String correlationId;
@Label("Trace ID")
public String traceId;
@Label("Span ID")
public String spanId;
@Label("Duration Milliseconds")
public long durationMs;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.BusinessEvent")
@Label("Business Event")
@Category("Business")
@Description("Records business-level events")
public static class BusinessEvent extends Event {
@Label("Event Type")
public String eventType;
@Label("Description")
public String description;
@Label("Correlation ID")
public String correlationId;
@Label("Trace ID")
public String traceId;
@Label("Span ID")
public String spanId;
@Label("Attributes")
public String attributes;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.MethodExecution")
@Label("Method Execution")
@Category("Performance")
@Description("Records method execution details")
public static class MethodExecutionEvent extends Event {
@Label("Class Name")
public String className;
@Label("Method Name")
public String methodName;
@Label("Duration Nanoseconds")
public long durationNanos;
@Label("Outcome")
public String outcome;
@Label("Correlation ID")
public String correlationId;
@Label("Trace ID")
public String traceId;
}
}

Structured Logging Configuration

<!-- src/main/resources/logback.xml -->
<configuration>
<!-- JSON Layout for structured logging -->
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
<jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
<prettyPrint>true</prettyPrint>
</jsonFormatter>
<timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat>
<appendLineSeparator>true</appendLineSeparator>
<!-- Include MDC in JSON output -->
<includeMdc>true</includeMdc>
<!-- Custom fields -->
<customFields>{"application":"jfr-correlation-demo","version":"1.0.0"}</customFields>
</layout>
</encoder>
</appender>
<!-- Pattern layout for human-readable logs -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [correlationId=%mdc{correlationId}, traceId=%mdc{traceId}] - %msg%n</pattern>
</encoder>
</appender>
<!-- Async appender for better performance -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT" />
<queueSize>1000</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>false</includeCallerData>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC" />
<!-- Uncomment for JSON logging -->
<!-- <appender-ref ref="JSON" /> -->
</root>
<!-- Specific configuration for correlation package -->
<logger name="com.example.jfr.correlation" level="DEBUG" additivity="false">
<appender-ref ref="ASYNC" />
</logger>
</configuration>

JFR Event Monitoring and Log Integration

package com.example.jfr.correlation;
import jdk.jfr.*;
import jdk.jfr.consumer.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* Monitors JFR events and correlates them with application logs
*/
public class JFRLogMonitor {
private static final Logger logger = LoggerFactory.getLogger(JFRLogMonitor.class);
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
private final Map<String, CorrelationSession> activeSessions = new ConcurrentHashMap<>();
private final JFRLogCorrelator correlator = new JFRLogCorrelator();
private volatile boolean monitoring = false;
/**
* Starts JFR event monitoring
*/
public void startMonitoring() {
if (monitoring) return;
monitoring = true;
// Start JFR recording
startJFRRecording();
// Start event processing
scheduler.scheduleAtFixedRate(this::processJFREvents, 1, 1, TimeUnit.SECONDS);
scheduler.scheduleAtFixedRate(this::cleanupOldSessions, 5, 5, TimeUnit.MINUTES);
logger.info("Started JFR event monitoring and log correlation");
}
/**
* Stops JFR event monitoring
*/
public void stopMonitoring() {
if (!monitoring) return;
monitoring = false;
scheduler.shutdown();
stopJFRRecording();
logger.info("Stopped JFR event monitoring");
}
private void startJFRRecording() {
try {
// Configure JFR recording
Configuration config = Configuration.getConfiguration("default");
Recording recording = new Recording(config);
// Enable custom events
recording.enable("com.example.CorrelationStart");
recording.enable("com.example.CorrelationEnd");
recording.enable("com.example.BusinessEvent");
recording.enable("com.example.MethodExecution");
// Enable standard JVM events
recording.enable("jdk.GCHeapSummary");
recording.enable("jdk.JavaMonitorEnter");
recording.enable("jdk.ThreadPark");
recording.enable("jdk.CPULoad");
// Start recording to memory
recording.start();
logger.info("Started JFR recording");
} catch (Exception e) {
logger.error("Failed to start JFR recording", e);
}
}
private void stopJFRRecording() {
// In production, you'd manage recording lifecycle properly
}
private void processJFREvents() {
try {
// Process events from active recordings
// This is a simplified example - real implementation would use RecordingStream
correlator.correlateEvents();
} catch (Exception e) {
logger.error("Error processing JFR events", e);
}
}
private void cleanupOldSessions() {
long cutoffTime = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(30);
activeSessions.entrySet().removeIf(entry -> 
entry.getValue().getLastActivity() < cutoffTime
);
logger.debug("Cleaned up old correlation sessions, remaining: {}", activeSessions.size());
}
/**
* Gets statistics about monitored sessions
*/
public MonitoringStats getStats() {
return new MonitoringStats(activeSessions.size(), correlator.getProcessedEventCount());
}
/**
* JFR event correlator
*/
public static class JFRLogCorrelator {
private final AtomicLong processedEvents = new AtomicLong();
private final Map<String, List<CorrelatedEvent>> eventStore = new ConcurrentHashMap<>();
public void correlateEvents() {
// In real implementation, this would:
// 1. Read JFR events from recording
// 2. Extract correlation IDs
// 3. Match with log entries
// 4. Store correlated events
processedEvents.incrementAndGet();
}
public void addEvent(String correlationId, CorrelatedEvent event) {
eventStore.computeIfAbsent(correlationId, k -> new CopyOnWriteArrayList<>())
.add(event);
}
public List<CorrelatedEvent> getEventsForCorrelation(String correlationId) {
return eventStore.getOrDefault(correlationId, Collections.emptyList());
}
public long getProcessedEventCount() {
return processedEvents.get();
}
public Map<String, List<CorrelatedEvent>> getAllEvents() {
return new HashMap<>(eventStore);
}
}
/**
* Represents a correlation session
*/
public static class CorrelationSession {
private final String correlationId;
private final long startTime;
private volatile long lastActivity;
private final List<CorrelatedEvent> events = new CopyOnWriteArrayList<>();
public CorrelationSession(String correlationId) {
this.correlationId = correlationId;
this.startTime = System.currentTimeMillis();
this.lastActivity = startTime;
}
public void addEvent(CorrelatedEvent event) {
events.add(event);
lastActivity = System.currentTimeMillis();
}
public List<CorrelatedEvent> getEvents() {
return new ArrayList<>(events);
}
public String getCorrelationId() { return correlationId; }
public long getStartTime() { return startTime; }
public long getLastActivity() { return lastActivity; }
public int getEventCount() { return events.size(); }
}
/**
* Represents a correlated event (JFR event + log entry)
*/
public static class CorrelatedEvent {
private final String eventId;
private final String type;
private final long timestamp;
private final String correlationId;
private final Map<String, Object> data;
private final String logMessage;
private final String logLevel;
public CorrelatedEvent(String eventId, String type, long timestamp, 
String correlationId, Map<String, Object> data, 
String logMessage, String logLevel) {
this.eventId = eventId;
this.type = type;
this.timestamp = timestamp;
this.correlationId = correlationId;
this.data = new HashMap<>(data);
this.logMessage = logMessage;
this.logLevel = logLevel;
}
// Getters
public String getEventId() { return eventId; }
public String getType() { return type; }
public long getTimestamp() { return timestamp; }
public String getCorrelationId() { return correlationId; }
public Map<String, Object> getData() { return data; }
public String getLogMessage() { return logMessage; }
public String getLogLevel() { return logLevel; }
@Override
public String toString() {
return String.format("CorrelatedEvent[type=%s, correlationId=%s, timestamp=%d]", 
type, correlationId, timestamp);
}
}
/**
* Monitoring statistics
*/
public static class MonitoringStats {
private final int activeSessions;
private final long processedEvents;
private final long timestamp;
public MonitoringStats(int activeSessions, long processedEvents) {
this.activeSessions = activeSessions;
this.processedEvents = processedEvents;
this.timestamp = System.currentTimeMillis();
}
// Getters
public int getActiveSessions() { return activeSessions; }
public long getProcessedEvents() { return processedEvents; }
public long getTimestamp() { return timestamp; }
@Override
public String toString() {
return String.format(
"MonitoringStats[activeSessions=%d, processedEvents=%d, timestamp=%d]", 
activeSessions, processedEvents, timestamp);
}
}
}

Advanced Correlation with Distributed Tracing

package com.example.jfr.correlation;
import jdk.jfr.Event;
import jdk.jfr.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Advanced correlation with distributed tracing support
*/
public class DistributedTraceCorrelation {
private static final Logger logger = LoggerFactory.getLogger(DistributedTraceCorrelation.class);
/**
* Distributed trace context
*/
public static class TraceContext {
private final String traceId;
private final String parentSpanId;
private final String spanId;
private final Map<String, String> baggage;
private final boolean isSampled;
private final long startTime;
public TraceContext(String traceId, String parentSpanId, String spanId, 
boolean isSampled) {
this.traceId = traceId;
this.parentSpanId = parentSpanId;
this.spanId = spanId;
this.isSampled = isSampled;
this.baggage = new HashMap<>();
this.startTime = System.currentTimeMillis();
}
public void addBaggage(String key, String value) {
baggage.put(key, value);
}
public String getBaggage(String key) {
return baggage.get(key);
}
public TraceContext createChild() {
String childSpanId = generateSpanId();
TraceContext child = new TraceContext(traceId, spanId, childSpanId, isSampled);
child.baggage.putAll(this.baggage);
return child;
}
// Getters
public String getTraceId() { return traceId; }
public String getParentSpanId() { return parentSpanId; }
public String getSpanId() { return spanId; }
public boolean isSampled() { return isSampled; }
public long getStartTime() { return startTime; }
public Map<String, String> getBaggage() { return baggage; }
@Override
public String toString() {
return String.format("TraceContext[traceId=%s, spanId=%s, sampled=%s]", 
traceId, spanId, isSampled);
}
}
private static final ThreadLocal<TraceContext> currentTrace = new ThreadLocal<>();
/**
* Starts a new distributed trace
*/
public static TraceContext startTrace(boolean sampled) {
String traceId = generateTraceId();
String spanId = generateSpanId();
TraceContext context = new TraceContext(traceId, null, spanId, sampled);
currentTrace.set(context);
// Set MDC for logging
MDC.put("traceId", traceId);
MDC.put("spanId", spanId);
MDC.put("sampled", String.valueOf(sampled));
// Emit JFR event
TraceStartEvent event = new TraceStartEvent();
event.traceId = traceId;
event.spanId = spanId;
event.sampled = sampled;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.info("Started distributed trace: {}", context);
return context;
}
/**
* Continues an existing trace from incoming context
*/
public static TraceContext continueTrace(String traceId, String parentSpanId, 
boolean sampled, Map<String, String> baggage) {
String spanId = generateSpanId();
TraceContext context = new TraceContext(traceId, parentSpanId, spanId, sampled);
if (baggage != null) {
context.baggage.putAll(baggage);
}
currentTrace.set(context);
// Set MDC
MDC.put("traceId", traceId);
MDC.put("spanId", spanId);
MDC.put("parentSpanId", parentSpanId);
MDC.put("sampled", String.valueOf(sampled));
// Emit JFR event
TraceContinueEvent event = new TraceContinueEvent();
event.traceId = traceId;
event.parentSpanId = parentSpanId;
event.spanId = spanId;
event.sampled = sampled;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.info("Continued distributed trace: {}", context);
return context;
}
/**
* Ends the current trace
*/
public static void endTrace() {
TraceContext context = currentTrace.get();
if (context != null) {
endTrace(context);
}
}
/**
* Ends a specific trace
*/
public static void endTrace(TraceContext context) {
long duration = System.currentTimeMillis() - context.getStartTime();
// Emit JFR event
TraceEndEvent event = new TraceEndEvent();
event.traceId = context.getTraceId();
event.spanId = context.getSpanId();
event.durationMs = duration;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.info("Ended distributed trace: {}, duration={}ms", context, duration);
// Clear MDC
MDC.remove("traceId");
MDC.remove("spanId");
MDC.remove("parentSpanId");
MDC.remove("sampled");
currentTrace.remove();
}
/**
* Gets the current trace context
*/
public static TraceContext getCurrentTrace() {
return currentTrace.get();
}
/**
* Records a span within the current trace
*/
public static Span startSpan(String name, String kind) {
TraceContext trace = getCurrentTrace();
if (trace != null && trace.isSampled()) {
return new Span(trace, name, kind);
}
return null; // Return no-op span if not sampled
}
/**
* Represents a span in distributed tracing
*/
public static class Span implements AutoCloseable {
private final TraceContext traceContext;
private final String name;
private final String kind;
private final long startTime;
private final Map<String, String> tags;
public Span(TraceContext traceContext, String name, String kind) {
this.traceContext = traceContext.createChild();
this.name = name;
this.kind = kind;
this.startTime = System.nanoTime();
this.tags = new HashMap<>();
// Emit JFR event
SpanStartEvent event = new SpanStartEvent();
event.traceId = traceContext.getTraceId();
event.spanId = this.traceContext.getSpanId();
event.parentSpanId = traceContext.getSpanId();
event.name = name;
event.kind = kind;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.debug("Started span: {} (kind: {})", name, kind);
}
public void setTag(String key, String value) {
tags.put(key, value);
}
public void recordEvent(String eventName, Map<String, String> attributes) {
SpanEvent jfrEvent = new SpanEvent();
jfrEvent.traceId = traceContext.getTraceId();
jfrEvent.spanId = traceContext.getSpanId();
jfrEvent.eventName = eventName;
if (attributes != null) {
jfrEvent.attributes = String.join(";", 
attributes.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.toArray(String[]::new)
);
}
jfrEvent.timestamp = System.currentTimeMillis();
jfrEvent.commit();
logger.debug("Span event: {} - {}", eventName, attributes);
}
@Override
public void close() {
long duration = System.nanoTime() - startTime;
// Emit JFR event
SpanEndEvent event = new SpanEndEvent();
event.traceId = traceContext.getTraceId();
event.spanId = traceContext.getSpanId();
event.name = name;
event.durationNanos = duration;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.debug("Ended span: {}, duration={}ns", name, duration);
}
public TraceContext getTraceContext() {
return traceContext;
}
}
// ID generation
private static String generateTraceId() {
return UUID.randomUUID().toString();
}
private static String generateSpanId() {
return Long.toHexString(System.nanoTime());
}
// JFR Event Definitions for Distributed Tracing
@Name("com.example.TraceStart")
@Label("Distributed Trace Start")
@Category("Tracing")
@Description("Records the start of a distributed trace")
public static class TraceStartEvent extends Event {
@Label("Trace ID")
public String traceId;
@Label("Span ID")
public String spanId;
@Label("Sampled")
public boolean sampled;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.TraceContinue")
@Label("Distributed Trace Continue")
@Category("Tracing")
@Description("Records continuation of a distributed trace")
public static class TraceContinueEvent extends Event {
@Label("Trace ID")
public String traceId;
@Label("Parent Span ID")
public String parentSpanId;
@Label("Span ID")
public String spanId;
@Label("Sampled")
public boolean sampled;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.TraceEnd")
@Label("Distributed Trace End")
@Category("Tracing")
@Description("Records the end of a distributed trace")
public static class TraceEndEvent extends Event {
@Label("Trace ID")
public String traceId;
@Label("Span ID")
public String spanId;
@Label("Duration Milliseconds")
public long durationMs;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.SpanStart")
@Label("Span Start")
@Category("Tracing")
@Description("Records the start of a span")
public static class SpanStartEvent extends Event {
@Label("Trace ID")
public String traceId;
@Label("Span ID")
public String spanId;
@Label("Parent Span ID")
public String parentSpanId;
@Label("Span Name")
public String name;
@Label("Span Kind")
public String kind;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.SpanEnd")
@Label("Span End")
@Category("Tracing")
@Description("Records the end of a span")
public static class SpanEndEvent extends Event {
@Label("Trace ID")
public String traceId;
@Label("Span ID")
public String spanId;
@Label("Span Name")
public String name;
@Label("Duration Nanoseconds")
public long durationNanos;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.SpanEvent")
@Label("Span Event")
@Category("Tracing")
@Description("Records an event within a span")
public static class SpanEvent extends Event {
@Label("Trace ID")
public String traceId;
@Label("Span ID")
public String spanId;
@Label("Event Name")
public String eventName;
@Label("Attributes")
public String attributes;
@Label("Timestamp")
public long timestamp;
}
}

Performance Monitoring Integration

package com.example.jfr.correlation;
import jdk.jfr.Event;
import jdk.jfr.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* Integrates performance monitoring with JFR events and logs
*/
public class PerformanceCorrelation {
private static final Logger logger = LoggerFactory.getLogger(PerformanceCorrelation.class);
private static final ConcurrentHashMap<String, RequestMetrics> requestMetrics = 
new ConcurrentHashMap<>();
private static final ScheduledExecutorService metricsReporter = 
Executors.newSingleThreadScheduledExecutor();
static {
// Start metrics reporting
metricsReporter.scheduleAtFixedRate(
PerformanceCorrelation::reportMetrics, 30, 30, TimeUnit.SECONDS);
}
/**
* Records performance metrics for a request
*/
public static class RequestMetrics {
private final String correlationId;
private final long startTime;
private final AtomicLong databaseQueries = new AtomicLong();
private final AtomicLong externalCalls = new AtomicLong();
private final AtomicLong cacheHits = new AtomicLong();
private final AtomicLong cacheMisses = new AtomicLong();
private final AtomicLong totalDuration = new AtomicLong();
private volatile long endTime;
public RequestMetrics(String correlationId) {
this.correlationId = correlationId;
this.startTime = System.currentTimeMillis();
}
public void recordDatabaseQuery(long durationMs) {
databaseQueries.incrementAndGet();
totalDuration.addAndGet(durationMs);
DatabaseQueryEvent event = new DatabaseQueryEvent();
event.correlationId = correlationId;
event.durationMs = durationMs;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.debug("Database query: correlationId={}, duration={}ms", 
correlationId, durationMs);
}
public void recordExternalCall(String service, long durationMs, boolean success) {
externalCalls.incrementAndGet();
totalDuration.addAndGet(durationMs);
ExternalCallEvent event = new ExternalCallEvent();
event.correlationId = correlationId;
event.service = service;
event.durationMs = durationMs;
event.success = success;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.debug("External call: correlationId={}, service={}, duration={}ms, success={}", 
correlationId, service, durationMs, success);
}
public void recordCacheAccess(boolean hit, long durationMs) {
if (hit) {
cacheHits.incrementAndGet();
} else {
cacheMisses.incrementAndGet();
}
totalDuration.addAndGet(durationMs);
CacheAccessEvent event = new CacheAccessEvent();
event.correlationId = correlationId;
event.hit = hit;
event.durationMs = durationMs;
event.timestamp = System.currentTimeMillis();
event.commit();
}
public void complete() {
this.endTime = System.currentTimeMillis();
RequestCompleteEvent event = new RequestCompleteEvent();
event.correlationId = correlationId;
event.totalDurationMs = endTime - startTime;
event.databaseQueries = databaseQueries.get();
event.externalCalls = externalCalls.get();
event.cacheHits = cacheHits.get();
event.cacheMisses = cacheMisses.get();
event.timestamp = System.currentTimeMillis();
event.commit();
logger.info("Request completed: correlationId={}, totalDuration={}ms, dbQueries={}, extCalls={}", 
correlationId, event.totalDurationMs, databaseQueries.get(), externalCalls.get());
}
// Getters
public String getCorrelationId() { return correlationId; }
public long getStartTime() { return startTime; }
public long getDatabaseQueries() { return databaseQueries.get(); }
public long getExternalCalls() { return externalCalls.get(); }
public long getCacheHits() { return cacheHits.get(); }
public long getCacheMisses() { return cacheMisses.get(); }
public long getTotalDuration() { return totalDuration.get(); }
}
/**
* Starts tracking metrics for a request
*/
public static RequestMetrics startRequestTracking(String correlationId) {
RequestMetrics metrics = new RequestMetrics(correlationId);
requestMetrics.put(correlationId, metrics);
return metrics;
}
/**
* Gets metrics for a correlation ID
*/
public static RequestMetrics getRequestMetrics(String correlationId) {
return requestMetrics.get(correlationId);
}
/**
* Completes request tracking
*/
public static void completeRequestTracking(String correlationId) {
RequestMetrics metrics = requestMetrics.remove(correlationId);
if (metrics != null) {
metrics.complete();
}
}
/**
* Records garbage collection correlation
*/
public static void recordGCCorrelation(String correlationId, long pauseTimeMs, 
String gcType, long memoryFreed) {
GCCorrelationEvent event = new GCCorrelationEvent();
event.correlationId = correlationId;
event.pauseTimeMs = pauseTimeMs;
event.gcType = gcType;
event.memoryFreed = memoryFreed;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.warn("GC during request: correlationId={}, pause={}ms, type={}, memoryFreed={}", 
correlationId, pauseTimeMs, gcType, memoryFreed);
}
/**
* Records thread pool usage
*/
public static void recordThreadPoolUsage(String poolName, int activeThreads, 
int queueSize, int poolSize) {
ThreadPoolEvent event = new ThreadPoolEvent();
event.poolName = poolName;
event.activeThreads = activeThreads;
event.queueSize = queueSize;
event.poolSize = poolSize;
event.timestamp = System.currentTimeMillis();
event.commit();
if (queueSize > 100) {
logger.warn("Thread pool queue growing: pool={}, queueSize={}, activeThreads={}", 
poolName, queueSize, activeThreads);
}
}
private static void reportMetrics() {
int activeRequests = requestMetrics.size();
long totalDbQueries = requestMetrics.values().stream()
.mapToLong(RequestMetrics::getDatabaseQueries)
.sum();
long totalExtCalls = requestMetrics.values().stream()
.mapToLong(RequestMetrics::getExternalCalls)
.sum();
MetricsReportEvent event = new MetricsReportEvent();
event.activeRequests = activeRequests;
event.totalDbQueries = totalDbQueries;
event.totalExtCalls = totalExtCalls;
event.timestamp = System.currentTimeMillis();
event.commit();
logger.info("Performance metrics: activeRequests={}, totalDbQueries={}, totalExtCalls={}", 
activeRequests, totalDbQueries, totalExtCalls);
}
// JFR Event Definitions for Performance Monitoring
@Name("com.example.DatabaseQuery")
@Label("Database Query")
@Category("Performance")
@Description("Records database query execution")
public static class DatabaseQueryEvent extends Event {
@Label("Correlation ID")
public String correlationId;
@Label("Duration Milliseconds")
public long durationMs;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.ExternalCall")
@Label("External Service Call")
@Category("Performance")
@Description("Records external service calls")
public static class ExternalCallEvent extends Event {
@Label("Correlation ID")
public String correlationId;
@Label("Service Name")
public String service;
@Label("Duration Milliseconds")
public long durationMs;
@Label("Success")
public boolean success;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.CacheAccess")
@Label("Cache Access")
@Category("Performance")
@Description("Records cache access patterns")
public static class CacheAccessEvent extends Event {
@Label("Correlation ID")
public String correlationId;
@Label("Cache Hit")
public boolean hit;
@Label("Duration Milliseconds")
public long durationMs;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.RequestComplete")
@Label("Request Completion")
@Category("Performance")
@Description("Records request completion with metrics")
public static class RequestCompleteEvent extends Event {
@Label("Correlation ID")
public String correlationId;
@Label("Total Duration Milliseconds")
public long totalDurationMs;
@Label("Database Queries")
public long databaseQueries;
@Label("External Calls")
public long externalCalls;
@Label("Cache Hits")
public long cacheHits;
@Label("Cache Misses")
public long cacheMisses;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.GCCorrelation")
@Label("GC Correlation")
@Category("Performance")
@Description("Correlates GC events with requests")
public static class GCCorrelationEvent extends Event {
@Label("Correlation ID")
public String correlationId;
@Label("Pause Time Milliseconds")
public long pauseTimeMs;
@Label("GC Type")
public String gcType;
@Label("Memory Freed")
public long memoryFreed;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.ThreadPool")
@Label("Thread Pool Usage")
@Category("Performance")
@Description("Records thread pool utilization")
public static class ThreadPoolEvent extends Event {
@Label("Pool Name")
public String poolName;
@Label("Active Threads")
public int activeThreads;
@Label("Queue Size")
public int queueSize;
@Label("Pool Size")
public int poolSize;
@Label("Timestamp")
public long timestamp;
}
@Name("com.example.MetricsReport")
@Label("Metrics Report")
@Category("Performance")
@Description("Periodic metrics reporting")
public static class MetricsReportEvent extends Event {
@Label("Active Requests")
public int activeRequests;
@Label("Total Database Queries")
public long totalDbQueries;
@Label("Total External Calls")
public long totalExtCalls;
@Label("Timestamp")
public long timestamp;
}
}

Complete Usage Example

package com.example.jfr.correlation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Complete example demonstrating JFR event and log correlation
*/
public class JFRCorrelationExample {
private static final Logger logger = LoggerFactory.getLogger(JFRCorrelationExample.class);
private final JFRLogMonitor monitor = new JFRLogMonitor();
private final PerformanceCorrelation.RequestMetrics metrics = null; // Will be initialized per request
public static void main(String[] args) throws Exception {
JFRCorrelationExample example = new JFRCorrelationExample();
// Start monitoring
example.monitor.startMonitoring();
try {
// Simulate multiple requests
for (int i = 0; i < 5; i++) {
example.processRequest("user-" + i, "request-" + i);
Thread.sleep(1000);
}
// Demonstrate distributed tracing
example.demonstrateDistributedTracing();
} finally {
example.monitor.stopMonitoring();
}
}
/**
* Processes a sample request with full correlation
*/
public void processRequest(String userId, String requestId) {
// Start correlation context
JFRLogCorrelation.CorrelationContext context = JFRLogCorrelation.startContext();
try {
// Start performance tracking
PerformanceCorrelation.RequestMetrics metrics = 
PerformanceCorrelation.startRequestTracking(context.getCorrelationId());
logger.info("Processing request: userId={}, requestId={}", userId, requestId);
// Record business event
Map<String, String> attributes = new HashMap<>();
attributes.put("userId", userId);
attributes.put("requestId", requestId);
JFRLogCorrelation.recordBusinessEvent("REQUEST_START", 
"Started processing user request", attributes);
// Simulate business logic
validateUser(userId);
processOrder(requestId);
updateInventory(requestId);
// Record completion
JFRLogCorrelation.recordBusinessEvent("REQUEST_COMPLETE", 
"Completed processing user request", attributes);
logger.info("Request completed successfully: userId={}", userId);
// Complete performance tracking
PerformanceCorrelation.completeRequestTracking(context.getCorrelationId());
} catch (Exception e) {
logger.error("Request processing failed: userId={}, error={}", 
userId, e.getMessage(), e);
// Record failure event
Map<String, String> errorAttributes = new HashMap<>();
errorAttributes.put("userId", userId);
errorAttributes.put("error", e.getMessage());
JFRLogCorrelation.recordBusinessEvent("REQUEST_FAILED", 
"Request processing failed", errorAttributes);
} finally {
// Always end correlation context
JFRLogCorrelation.endContext();
}
}
private void validateUser(String userId) {
long startTime = System.nanoTime();
try {
// Simulate user validation
logger.debug("Validating user: {}", userId);
Thread.sleep(50); // Simulate work
// Simulate database query
PerformanceCorrelation.getRequestMetrics(
JFRLogCorrelation.getCurrentContext().getCorrelationId()
).recordDatabaseQuery(25);
JFRLogCorrelation.recordMethodExecution(
getClass().getName(), "validateUser", 
System.nanoTime() - startTime, "SUCCESS"
);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Validation interrupted", e);
} catch (Exception e) {
JFRLogCorrelation.recordMethodExecution(
getClass().getName(), "validateUser", 
System.nanoTime() - startTime, "FAILED"
);
throw e;
}
}
private void processOrder(String requestId) {
long startTime = System.nanoTime();
try (DistributedTraceCorrelation.Span span = 
DistributedTraceCorrelation.startSpan("processOrder", "INTERNAL")) {
if (span != null) {
span.setTag("requestId", requestId);
}
logger.debug("Processing order: {}", requestId);
Thread.sleep(100); // Simulate work
// Simulate external service call
PerformanceCorrelation.getRequestMetrics(
JFRLogCorrelation.getCurrentContext().getCorrelationId()
).recordExternalCall("PaymentService", 75, true);
// Record span event
if (span != null) {
Map<String, String> eventAttributes = new HashMap<>();
eventAttributes.put("amount", "150.00");
eventAttributes.put("currency", "USD");
span.recordEvent("payment_processed", eventAttributes);
}
JFRLogCorrelation.recordMethodExecution(
getClass().getName(), "processOrder", 
System.nanoTime() - startTime, "SUCCESS"
);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Order processing interrupted", e);
} catch (Exception e) {
JFRLogCorrelation.recordMethodExecution(
getClass().getName(), "processOrder", 
System.nanoTime() - startTime, "FAILED"
);
throw e;
}
}
private void updateInventory(String requestId) {
long startTime = System.nanoTime();
try {
logger.debug("Updating inventory for request: {}", requestId);
Thread.sleep(30); // Simulate work
// Simulate cache access
PerformanceCorrelation.getRequestMetrics(
JFRLogCorrelation.getCurrentContext().getCorrelationId()
).recordCacheAccess(true, 2); // Cache hit
JFRLogCorrelation.recordMethodExecution(
getClass().getName(), "updateInventory", 
System.nanoTime() - startTime, "SUCCESS"
);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Inventory update interrupted", e);
} catch (Exception e) {
JFRLogCorrelation.recordMethodExecution(
getClass().getName(), "updateInventory", 
System.nanoTime() - startTime, "FAILED"
);
throw e;
}
}
/**
* Demonstrates distributed tracing across service boundaries
*/
public void demonstrateDistributedTracing() {
logger.info("Starting distributed tracing demonstration");
// Start a new trace
DistributedTraceCorrelation.TraceContext trace = 
DistributedTraceCorrelation.startTrace(true);
try {
// Simulate processing that involves multiple services
processWithDistributedTracing(trace);
} finally {
DistributedTraceCorrelation.endTrace();
}
logger.info("Completed distributed tracing demonstration");
}
private void processWithDistributedTracing(DistributedTraceCorrelation.TraceContext parentTrace) {
// Simulate calling another service with trace context propagation
Map<String, String> headers = new HashMap<>();
headers.put("traceId", parentTrace.getTraceId());
headers.put("parentSpanId", parentTrace.getSpanId());
headers.put("sampled", String.valueOf(parentTrace.isSampled()));
logger.info("Propagating trace context to downstream service: traceId={}", 
parentTrace.getTraceId());
// Simulate receiving trace context in another service
DistributedTraceCorrelation.TraceContext childTrace = 
DistributedTraceCorrelation.continueTrace(
headers.get("traceId"),
headers.get("parentSpanId"),
Boolean.parseBoolean(headers.get("sampled")),
null
);
try {
// Process in the "downstream service"
try (DistributedTraceCorrelation.Span span = 
DistributedTraceCorrelation.startSpan("downstreamOperation", "SERVER")) {
if (span != null) {
span.setTag("operation", "dataProcessing");
}
logger.info("Processing in downstream service with trace: {}", childTrace);
Thread.sleep(200);
logger.info("Downstream service completed processing");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
DistributedTraceCorrelation.endTrace();
}
}
}

Analysis and Reporting Tools

package com.example.jfr.correlation;
import jdk.jfr.consumer.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* Tools for analyzing correlated JFR events and logs
*/
public class CorrelationAnalyzer {
private static final Logger logger = LoggerFactory.getLogger(CorrelationAnalyzer.class);
/**
* Analyzes JFR recording files for correlation patterns
*/
public static class JFRRecordingAnalyzer {
private final Path recordingFile;
public JFRRecordingAnalyzer(Path recordingFile) {
this.recordingFile = recordingFile;
}
/**
* Analyzes correlation events in the recording
*/
public CorrelationAnalysis analyze() throws IOException {
CorrelationAnalysis analysis = new CorrelationAnalysis();
try (RecordingFile file = new RecordingFile(recordingFile)) {
while (file.hasMoreEvents()) {
RecordedEvent event = file.readEvent();
processEvent(event, analysis);
}
}
return analysis;
}
private void processEvent(RecordedEvent event, CorrelationAnalysis analysis) {
String eventName = event.getEventType().getName();
// Process correlation events
if (eventName.contains("Correlation")) {
processCorrelationEvent(event, analysis);
}
// Process business events
if (eventName.contains("BusinessEvent")) {
processBusinessEvent(event, analysis);
}
// Process performance events
if (eventName.contains("MethodExecution") || eventName.contains("DatabaseQuery")) {
processPerformanceEvent(event, analysis);
}
}
private void processCorrelationEvent(RecordedEvent event, CorrelationAnalysis analysis) {
String correlationId = event.getString("correlationId");
if (correlationId != null) {
analysis.recordCorrelationEvent(correlationId, event);
}
}
private void processBusinessEvent(RecordedEvent event, CorrelationAnalysis analysis) {
String correlationId = event.getString("correlationId");
if (correlationId != null) {
analysis.recordBusinessEvent(correlationId, event);
}
}
private void processPerformanceEvent(RecordedEvent event, CorrelationAnalysis analysis) {
String correlationId = event.getString("correlationId");
if (correlationId != null) {
analysis.recordPerformanceEvent(correlationId, event);
}
}
}
/**
* Comprehensive correlation analysis results
*/
public static class CorrelationAnalysis {
private final Map<String, List<RecordedEvent>> eventsByCorrelation = new HashMap<>();
private final Map<String, CorrelationStats> correlationStats = new HashMap<>();
private long totalEvents = 0;
public void recordCorrelationEvent(String correlationId, RecordedEvent event) {
eventsByCorrelation.computeIfAbsent(correlationId, k -> new ArrayList<>())
.add(event);
totalEvents++;
}
public void recordBusinessEvent(String correlationId, RecordedEvent event) {
eventsByCorrelation.computeIfAbsent(correlationId, k -> new ArrayList<>())
.add(event);
totalEvents++;
// Update statistics
CorrelationStats stats = correlationStats.computeIfAbsent(correlationId, 
k -> new CorrelationStats(correlationId));
stats.recordBusinessEvent(event);
}
public void recordPerformanceEvent(String correlationId, RecordedEvent event) {
eventsByCorrelation.computeIfAbsent(correlationId, k -> new ArrayList<>())
.add(event);
totalEvents++;
// Update statistics
CorrelationStats stats = correlationStats.computeIfAbsent(correlationId, 
k -> new CorrelationStats(correlationId));
stats.recordPerformanceEvent(event);
}
public List<String> getCorrelationIds() {
return new ArrayList<>(eventsByCorrelation.keySet());
}
public List<RecordedEvent> getEventsForCorrelation(String correlationId) {
return eventsByCorrelation.getOrDefault(correlationId, Collections.emptyList());
}
public CorrelationStats getStatsForCorrelation(String correlationId) {
return correlationStats.get(correlationId);
}
public void printSummary() {
System.out.println("\n=== Correlation Analysis Summary ===");
System.out.printf("Total Events: %,d%n", totalEvents);
System.out.printf("Unique Correlation IDs: %,d%n", eventsByCorrelation.size());
System.out.println("\nTop Correlations by Event Count:");
correlationStats.values().stream()
.sorted((a, b) -> Long.compare(b.getTotalEvents(), a.getTotalEvents()))
.limit(10)
.forEach(stats -> System.out.printf("  %s: %,d events%n", 
stats.getCorrelationId(), stats.getTotalEvents()));
System.out.println("\nPerformance Statistics:");
long slowRequests = correlationStats.values().stream()
.filter(CorrelationStats::isSlowRequest)
.count();
System.out.printf("  Slow Requests (>100ms): %,d%n", slowRequests);
long highDbQueries = correlationStats.values().stream()
.filter(stats -> stats.getDatabaseQueryCount() > 10)
.count();
System.out.printf("  High Database Query Count (>10): %,d%n", highDbQueries);
}
}
/**
* Statistics for a single correlation ID
*/
public static class CorrelationStats {
private final String correlationId;
private long businessEventCount = 0;
private long performanceEventCount = 0;
private long databaseQueryCount = 0;
private long totalDuration = 0;
private long startTime = Long.MAX_VALUE;
private long endTime = Long.MIN_VALUE;
public CorrelationStats(String correlationId) {
this.correlationId = correlationId;
}
public void recordBusinessEvent(RecordedEvent event) {
businessEventCount++;
updateTiming(event);
}
public void recordPerformanceEvent(RecordedEvent event) {
performanceEventCount++;
if (event.getEventType().getName().contains("DatabaseQuery")) {
databaseQueryCount++;
}
if (event.hasField("durationMs")) {
totalDuration += event.getLong("durationMs");
}
updateTiming(event);
}
private void updateTiming(RecordedEvent event) {
long eventTime = event.getStartTime().toEpochMilli();
startTime = Math.min(startTime, eventTime);
endTime = Math.max(endTime, eventTime);
}
public boolean isSlowRequest() {
return getDuration() > 100; // 100ms threshold
}
public long getDuration() {
return endTime - startTime;
}
// Getters
public String getCorrelationId() { return correlationId; }
public long getTotalEvents() { return businessEventCount + performanceEventCount; }
public long getBusinessEventCount() { return businessEventCount; }
public long getPerformanceEventCount() { return performanceEventCount; }
public long getDatabaseQueryCount() { return databaseQueryCount; }
public long getTotalDuration() { return totalDuration; }
public long getStartTime() { return startTime; }
public long getEndTime() { return endTime; }
}
}

Best Practices for JFR-Log Correlation

  1. Performance Considerations:
  • Use sampling for high-frequency events
  • Enable JFR events selectively based on needs
  • Use asynchronous logging to minimize impact
  1. Storage and Retention:
  • Implement log rotation with correlation ID preservation
  • Store JFR recordings with appropriate retention policies
  • Consider centralized logging for distributed systems
  1. Analysis and Debugging:
  • Create dashboards showing correlation between JFR events and logs
  • Set up alerts for abnormal patterns
  • Use correlation IDs in error reporting
  1. Production Readiness:
  • Test correlation in staging environments
  • Monitor the overhead of JFR and structured logging
  • Implement graceful degradation when JFR is unavailable

Conclusion

JFR event correlation with logs provides a powerful observability framework for Java applications. Key benefits include:

  • Unified Diagnostics: Combine low-level JVM metrics with application-level logs
  • Performance Analysis: Correlate business events with system performance
  • Distributed Tracing: Track requests across service boundaries
  • Root Cause Analysis: Quickly identify performance bottlenecks and errors

By implementing the patterns shown in this guide, you can create comprehensive monitoring solutions that provide deep insights into application behavior while maintaining performance and scalability.

Leave a Reply

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


Macro Nepal Helper